[483] | 1 | define([ |
---|
| 2 | "doh/_browserRunner", "require", |
---|
| 3 | "dojo/aspect", "dojo/Deferred", "dojo/dom-class", "dojo/dom-construct", "dojo/dom-geometry", "dojo/_base/lang", "dojo/ready", |
---|
| 4 | "dojo/_base/unload", "dojo/when", "dojo/_base/window", "dojo/sniff", "dojo/has", "dojo/has!android?doh/plugins/android-webdriver-robot" |
---|
| 5 | ], function(doh, require, aspect, Deferred, domClass, construct, geom, lang, ready, unload, when, win, sniff, has, webdriver){ |
---|
| 6 | |
---|
| 7 | // loading state |
---|
| 8 | var _robot = null; |
---|
| 9 | |
---|
| 10 | var isSecure = (function(){ |
---|
| 11 | var key = Math.random(); |
---|
| 12 | return function(fcn){ |
---|
| 13 | return key; |
---|
| 14 | }; |
---|
| 15 | })(); |
---|
| 16 | |
---|
| 17 | var _keyPress = function(/*Number*/ charCode, /*Number*/ keyCode, /*Boolean*/ alt, /*Boolean*/ ctrl, /*Boolean*/ shift, /*Boolean*/ meta, /*Integer?*/ delay, /*Boolean*/ async){ |
---|
| 18 | // internal function to type one non-modifier key |
---|
| 19 | |
---|
| 20 | // typecasting Numbers helps Sun's IE plugin lookup methods that take int arguments |
---|
| 21 | |
---|
| 22 | // otherwise JS will send a double and Sun will complain |
---|
| 23 | _robot.typeKey(isSecure(), Number(charCode), Number(keyCode), Boolean(alt), Boolean(ctrl), Boolean(shift), Boolean(meta), Number(delay||0), Boolean(async||false)); |
---|
| 24 | }; |
---|
| 25 | |
---|
| 26 | // Queue of pending actions plus the currently executing action registered via sequence(). |
---|
| 27 | // Each action is a function that either: |
---|
| 28 | // 1. does a setTimeout() |
---|
| 29 | // 2. calls java Robot (mouse movement, typing a single letter, etc.) |
---|
| 30 | // 3. executes user defined function (for when app called sequence() directly). |
---|
| 31 | // Each function can return a Promise, or just a plain value if it executes synchronously. |
---|
| 32 | var seqPromise; |
---|
| 33 | aspect.before(doh, "_runFixture", function(){ |
---|
| 34 | // At the start of each new test fixture, clear any leftover queued actions from the previous test fixture. |
---|
| 35 | // This will happen when the previous test throws an error, or times out. |
---|
| 36 | var _seqPromise = seqPromise; |
---|
| 37 | // need setTimeout to avoid false error; seqPromise from passing test is not fulfilled until after this execution trace finishes! |
---|
| 38 | // really we should not have both `seqPromise` here and `var d = new doh.Deferred()` in the test |
---|
| 39 | setTimeout(function(){ |
---|
| 40 | if(_seqPromise && !_seqPromise.isFulfilled()){ |
---|
| 41 | _seqPromise.cancel(new Error("new test starting, cancelling pending & in-progress queued events from previous test")); |
---|
| 42 | } |
---|
| 43 | },0); |
---|
| 44 | seqPromise = new Deferred(); |
---|
| 45 | seqPromise.resolve(true); |
---|
| 46 | }); |
---|
| 47 | |
---|
| 48 | // Previous mouse position (from most recent mouseMoveTo() command) |
---|
| 49 | var lastMouse = {x: 5, y: 5}; |
---|
| 50 | |
---|
| 51 | // For 2.0, remove code to set doh.robot global. |
---|
| 52 | var robot = doh.robot = { |
---|
| 53 | _robotLoaded: true, |
---|
| 54 | _robotInitialized: false, |
---|
| 55 | // prime the event pump for fast browsers like Google Chrome - it's so fast, it doesn't stop to listen for keypresses! |
---|
| 56 | _spaceReceived: false, |
---|
| 57 | _primePump: false, |
---|
| 58 | |
---|
| 59 | _killApplet: function(){}, // overridden by Robot.html |
---|
| 60 | |
---|
| 61 | killRobot: function(){ |
---|
| 62 | if(robot._robotLoaded){ |
---|
| 63 | robot._robotLoaded = false; |
---|
| 64 | domClass.remove(document.documentElement, "dohRobot"); |
---|
| 65 | robot._killApplet(); |
---|
| 66 | } |
---|
| 67 | }, |
---|
| 68 | |
---|
| 69 | // Robot init methods |
---|
| 70 | |
---|
| 71 | // controls access to doh.run |
---|
| 72 | // basically, doh.run takes two calls to start the robot: |
---|
| 73 | // one (or more after the robot loads) from the test page |
---|
| 74 | // one from either the applet or an error condition |
---|
| 75 | _runsemaphore: { |
---|
| 76 | lock: ["lock"], |
---|
| 77 | unlock: function(){ |
---|
| 78 | try{ |
---|
| 79 | return this.lock.shift(); |
---|
| 80 | }catch(e){ |
---|
| 81 | return null; |
---|
| 82 | } |
---|
| 83 | } |
---|
| 84 | }, |
---|
| 85 | |
---|
| 86 | startRobot: function(){ |
---|
| 87 | //startRobot should be called to initialize the robot (after the java applet is loaded). |
---|
| 88 | //one good place to do this is in a dojo.addOnLoad handler. This function will be called |
---|
| 89 | //automatically if it is not already called when doh.run() is invoked. |
---|
| 90 | if(!this._robotInitialized){ |
---|
| 91 | this._robotInitialized = true; |
---|
| 92 | // if the iframe requested the applet and got a 404, then _robot is obviously unavailable |
---|
| 93 | // at least run the non-robot tests! |
---|
| 94 | if(robot._appletDead){ |
---|
| 95 | robot._onKeyboard(); |
---|
| 96 | }else{ |
---|
| 97 | _robot._callLoaded(isSecure()); |
---|
| 98 | } |
---|
| 99 | } |
---|
| 100 | |
---|
| 101 | // When robot finishes initializing it types a key, firing the _onKeyboard() listener, which calls _run(), |
---|
| 102 | // which resolves this Deferred. |
---|
| 103 | return this._started; |
---|
| 104 | }, |
---|
| 105 | |
---|
| 106 | // _loaded: Deferred |
---|
| 107 | // Deferred that resolves when the _initRobot() has been called. |
---|
| 108 | // Note to be confused with dojo/robotx.js, which defines initRobot() without an underscore |
---|
| 109 | _loaded: new doh.Deferred(), |
---|
| 110 | |
---|
| 111 | _initRobot: function(r){ |
---|
| 112 | // called from Robot |
---|
| 113 | // Robot calls _initRobot in its startup sequence |
---|
| 114 | |
---|
| 115 | // Prevent rerunning the whole test (see #8958 for details) |
---|
| 116 | if(doh._initRobotCalled){ return; } |
---|
| 117 | doh._initRobotCalled = true; |
---|
| 118 | |
---|
| 119 | // add dohRobot class to HTML element so tests can use that in CSS rules if desired |
---|
| 120 | domClass.add(document.documentElement, "dohRobot"); |
---|
| 121 | window.scrollTo(0, 0); |
---|
| 122 | // document.documentElement.scrollTop = document.documentElement.scrollLeft = 0; |
---|
| 123 | _robot = r; |
---|
| 124 | _robot._setKey(isSecure()); |
---|
| 125 | this._loaded.resolve(true); |
---|
| 126 | }, |
---|
| 127 | |
---|
| 128 | // _started: Deferred |
---|
| 129 | // Deferred that resolves when startRobot() has signaled completing by typing on the keyboard, |
---|
| 130 | // which in turn calls _run(). |
---|
| 131 | _started: new doh.Deferred(), |
---|
| 132 | |
---|
| 133 | // some utility functions to help the iframe use private variables |
---|
| 134 | _run: function(frame){ |
---|
| 135 | // called after the robot has been able to type on the keyboard, indicating that it's started |
---|
| 136 | frame.style.visibility = "hidden"; |
---|
| 137 | this._started.resolve(true); |
---|
| 138 | }, |
---|
| 139 | |
---|
| 140 | _initKeyboard: function(){ |
---|
| 141 | _robot._initKeyboard(isSecure()); |
---|
| 142 | }, |
---|
| 143 | |
---|
| 144 | _onKeyboard: function(){ |
---|
| 145 | // replaced by iframe when applet present. |
---|
| 146 | // remote robots don't have frames so pass a mock frame. |
---|
| 147 | this._run({style:{visibility:""}}); |
---|
| 148 | }, |
---|
| 149 | |
---|
| 150 | _initWheel: function(){ |
---|
| 151 | _robot._initWheel(isSecure()); |
---|
| 152 | }, |
---|
| 153 | |
---|
| 154 | _setDocumentBounds: function(docScreenX, docScreenY){ |
---|
| 155 | var robotView = document.getElementById("dohrobotview"); |
---|
| 156 | _robot.setDocumentBounds(isSecure(), Number(docScreenX), Number(docScreenY), Number(robotView.offsetLeft), Number(robotView.offsetTop)); |
---|
| 157 | }, |
---|
| 158 | |
---|
| 159 | _notified: function(keystring){ |
---|
| 160 | _robot._notified(isSecure(), keystring); |
---|
| 161 | }, |
---|
| 162 | |
---|
| 163 | // if the applet is 404 or cert is denied, this becomes true and kills tests |
---|
| 164 | _appletDead: false, |
---|
| 165 | |
---|
| 166 | _assertRobot: function(){ |
---|
| 167 | // make sure the applet is there and cert accepted |
---|
| 168 | // otherwise, skip the test requesting the robot action |
---|
| 169 | if(robot._appletDead){ throw new Error('robot not available; skipping test.'); } |
---|
| 170 | }, |
---|
| 171 | |
---|
| 172 | _mouseMove: function(/*Number*/ x, /*Number*/ y, /*Boolean*/ absolute, /*Integer?*/ duration){ |
---|
| 173 | // This function is no longer used, but left for back-compat |
---|
| 174 | if(absolute){ |
---|
| 175 | var scroll = {y: (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0), |
---|
| 176 | x: (window.pageXOffset || geom.fixIeBiDiScrollLeft(document.documentElement.scrollLeft) || document.body.scrollLeft || 0)}; |
---|
| 177 | y -= scroll.y; |
---|
| 178 | x -= scroll.x; |
---|
| 179 | } |
---|
| 180 | _robot.moveMouse(isSecure(), Number(x), Number(y), Number(0), Number(duration||100)); |
---|
| 181 | }, |
---|
| 182 | |
---|
| 183 | // Main robot API |
---|
| 184 | sequence: function(/*Function*/ f, /*Integer?*/ delay, /*Integer?*/ duration){ |
---|
| 185 | // summary: |
---|
| 186 | // Defer an action by adding it to the robot's incrementally delayed queue of actions to execute. |
---|
| 187 | // f: |
---|
| 188 | // A function containing actions you want to defer. It can return a Promise |
---|
| 189 | // to delay further actions. |
---|
| 190 | // delay: |
---|
| 191 | // Delay, in milliseconds, to wait before firing. |
---|
| 192 | // The delay is a delta with respect to the previous automation call. |
---|
| 193 | // For example, the following code ends after 600ms: |
---|
| 194 | // | robot.mouseClick({left: true}, 100) // first call; wait 100ms |
---|
| 195 | // | robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all |
---|
| 196 | // duration: |
---|
| 197 | // Delay to wait after firing. |
---|
| 198 | |
---|
| 199 | function waitFunc(ms){ |
---|
| 200 | // Returns a function that returns a Promise that fires after ms milliseconds. |
---|
| 201 | return function(){ |
---|
| 202 | var timer, d; |
---|
| 203 | d = new Deferred(function(){ clearTimeout(timer); }); |
---|
| 204 | timer = setTimeout(function(){ d.resolve(true); }, ms); |
---|
| 205 | return d; |
---|
| 206 | }; |
---|
| 207 | } |
---|
| 208 | |
---|
| 209 | // Queue action to run specified function, plus optional "wait" actions for delay and duration. |
---|
| 210 | if(delay){ seqPromise = seqPromise.then(waitFunc(delay)); } |
---|
| 211 | seqPromise = seqPromise.then(f); |
---|
| 212 | if(duration){ seqPromise = seqPromise.then(waitFunc(duration)); } |
---|
| 213 | }, |
---|
| 214 | |
---|
| 215 | typeKeys: function(/*String|Number*/ chars, /*Integer?*/ delay, /*Integer?*/ duration){ |
---|
| 216 | // summary: |
---|
| 217 | // Types a string of characters in order, or types a dojo.keys.* constant. |
---|
| 218 | // description: |
---|
| 219 | // Types a string of characters in order, or types a dojo.keys.* constant. |
---|
| 220 | // example: |
---|
| 221 | // | robot.typeKeys("dijit.ed", 500); |
---|
| 222 | // chars: |
---|
| 223 | // String of characters to type, or a dojo.keys.* constant |
---|
| 224 | // delay: |
---|
| 225 | // Delay, in milliseconds, to wait before firing. |
---|
| 226 | // The delay is a delta with respect to the previous automation call. |
---|
| 227 | // For example, the following code ends after 600ms: |
---|
| 228 | // | robot.mouseClick({left: true}, 100) // first call; wait 100ms |
---|
| 229 | // | robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all |
---|
| 230 | // duration: |
---|
| 231 | // Time, in milliseconds, to spend pressing all of the keys. |
---|
| 232 | // The default is (string length)*50 ms. |
---|
| 233 | |
---|
| 234 | this._assertRobot(); |
---|
| 235 | var isNum = typeof(chars) == Number; |
---|
| 236 | duration = duration||(isNum?50: chars.length*50); |
---|
| 237 | if(isNum){ |
---|
| 238 | this.sequence(lang.partial(_keyPress, chars, chars, false, false, false, false, 0, 0), |
---|
| 239 | delay, duration); |
---|
| 240 | }else{ |
---|
| 241 | for(var i = 0; i < chars.length; i++){ |
---|
| 242 | this.sequence(lang.partial(_keyPress, chars.charCodeAt(i), 0, false, false, false, false, 0, 0), |
---|
| 243 | i == 0 ? delay : 0, Math.max(Math.ceil(duration/chars.length), 0)); |
---|
| 244 | } |
---|
| 245 | } |
---|
| 246 | }, |
---|
| 247 | |
---|
| 248 | keyPress: function(/*Integer*/ charOrCode, /*Integer?*/ delay, /*Object*/ modifiers, /*Boolean*/ asynchronous){ |
---|
| 249 | // summary: |
---|
| 250 | // Types a key combination, like SHIFT-TAB. |
---|
| 251 | // description: |
---|
| 252 | // Types a key combination, like SHIFT-TAB. |
---|
| 253 | // example: |
---|
| 254 | // to press shift-tab immediately, call robot.keyPress(dojo.keys.TAB, 0, {shift: true}) |
---|
| 255 | // charOrCode: |
---|
| 256 | // char/JS keyCode/dojo.keys.* constant for the key you want to press |
---|
| 257 | // delay: |
---|
| 258 | // Delay, in milliseconds, to wait before firing. |
---|
| 259 | // The delay is a delta with respect to the previous automation call. |
---|
| 260 | // For example, the following code ends after 600ms: |
---|
| 261 | // | robot.mouseClick({left: true}, 100) // first call; wait 100ms |
---|
| 262 | // | robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all |
---|
| 263 | // modifiers: |
---|
| 264 | // JSON object that represents all of the modifier keys being pressed. |
---|
| 265 | // It takes the following Boolean attributes: |
---|
| 266 | // |
---|
| 267 | // - shift |
---|
| 268 | // - alt |
---|
| 269 | // - ctrl |
---|
| 270 | // - meta |
---|
| 271 | // asynchronous: |
---|
| 272 | // If true, the delay happens asynchronously and immediately, outside of the browser's JavaScript thread and any previous calls. |
---|
| 273 | // This is useful for interacting with the browser's modal dialogs. |
---|
| 274 | |
---|
| 275 | this._assertRobot(); |
---|
| 276 | if(!modifiers){ |
---|
| 277 | modifiers = {alt:false, ctrl:false, shift:false, meta:false}; |
---|
| 278 | }else{ |
---|
| 279 | // normalize modifiers |
---|
| 280 | var attrs = ["alt", "ctrl", "shift", "meta"]; |
---|
| 281 | for(var i = 0; i<attrs.length; i++){ |
---|
| 282 | if(!modifiers[attrs[i]]){ |
---|
| 283 | modifiers[attrs[i]] = false; |
---|
| 284 | } |
---|
| 285 | } |
---|
| 286 | } |
---|
| 287 | var isChar = typeof(charOrCode)=="string"; |
---|
| 288 | if(asynchronous){ |
---|
| 289 | _keyPress(isChar?charOrCode.charCodeAt(0):0, isChar?0:charOrCode, modifiers.alt, modifiers.ctrl, modifiers.shift, modifiers.meta, delay, true); |
---|
| 290 | return; |
---|
| 291 | } |
---|
| 292 | this.sequence(function(){ |
---|
| 293 | _keyPress(isChar?charOrCode.charCodeAt(0):0, isChar?0:charOrCode, modifiers.alt, modifiers.ctrl, modifiers.shift, modifiers.meta, 0); |
---|
| 294 | }, delay); |
---|
| 295 | }, |
---|
| 296 | |
---|
| 297 | keyDown: function(/*Integer*/ charOrCode, /*Integer?*/ delay){ |
---|
| 298 | // summary: |
---|
| 299 | // Holds down a single key, like SHIFT or 'a'. |
---|
| 300 | // description: |
---|
| 301 | // Holds down a single key, like SHIFT or 'a'. |
---|
| 302 | // example: |
---|
| 303 | // to hold down the 'a' key immediately, call robot.keyDown('a') |
---|
| 304 | // charOrCode: |
---|
| 305 | // char/JS keyCode/dojo.keys.* constant for the key you want to hold down |
---|
| 306 | // Warning: holding down a shifted key, like 'A', can have unpredictable results. |
---|
| 307 | // delay: |
---|
| 308 | // Delay, in milliseconds, to wait before firing. |
---|
| 309 | // The delay is a delta with respect to the previous automation call. |
---|
| 310 | // For example, the following code ends after 600ms: |
---|
| 311 | // | robot.mouseClick({left: true}, 100) // first call; wait 100ms |
---|
| 312 | // | robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all |
---|
| 313 | |
---|
| 314 | this._assertRobot(); |
---|
| 315 | this.sequence(function(){ |
---|
| 316 | var isChar = typeof(charOrCode)=="string"; |
---|
| 317 | _robot.downKey(isSecure(), isChar?charOrCode:0, isChar?0:charOrCode, 0); |
---|
| 318 | }, delay); |
---|
| 319 | }, |
---|
| 320 | |
---|
| 321 | keyUp: function(/*Integer*/ charOrCode, /*Integer?*/ delay){ |
---|
| 322 | // summary: |
---|
| 323 | // Releases a single key, like SHIFT or 'a'. |
---|
| 324 | // description: |
---|
| 325 | // Releases a single key, like SHIFT or 'a'. |
---|
| 326 | // example: |
---|
| 327 | // to release the 'a' key immediately, call robot.keyUp('a') |
---|
| 328 | // charOrCode: |
---|
| 329 | // char/JS keyCode/dojo.keys.* constant for the key you want to release |
---|
| 330 | // Warning: releasing a shifted key, like 'A', can have unpredictable results. |
---|
| 331 | // delay: |
---|
| 332 | // Delay, in milliseconds, to wait before firing. |
---|
| 333 | // The delay is a delta with respect to the previous automation call. |
---|
| 334 | // For example, the following code ends after 600ms: |
---|
| 335 | // | robot.mouseClick({left: true}, 100) // first call; wait 100ms |
---|
| 336 | // | robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all |
---|
| 337 | |
---|
| 338 | this._assertRobot(); |
---|
| 339 | this.sequence(function(){ |
---|
| 340 | var isChar=typeof(charOrCode)=="string"; |
---|
| 341 | _robot.upKey(isSecure(), isChar?charOrCode:0, isChar?0:charOrCode, 0); |
---|
| 342 | }, delay); |
---|
| 343 | }, |
---|
| 344 | |
---|
| 345 | |
---|
| 346 | mouseClick: function(/*Object*/ buttons, /*Integer?*/ delay){ |
---|
| 347 | // summary: |
---|
| 348 | // Convenience function to do a press/release. |
---|
| 349 | // See robot.mousePress for more info. |
---|
| 350 | // description: |
---|
| 351 | // Convenience function to do a press/release. |
---|
| 352 | // See robot.mousePress for more info. |
---|
| 353 | |
---|
| 354 | this._assertRobot(); |
---|
| 355 | robot.mousePress(buttons, delay); |
---|
| 356 | robot.mouseRelease(buttons, 1); |
---|
| 357 | }, |
---|
| 358 | |
---|
| 359 | mousePress: function(/*Object*/ buttons, /*Integer?*/ delay){ |
---|
| 360 | // summary: |
---|
| 361 | // Presses mouse buttons. |
---|
| 362 | // description: |
---|
| 363 | // Presses the mouse buttons you pass as true. |
---|
| 364 | // Example: to press the left mouse button, pass {left: true}. |
---|
| 365 | // Mouse buttons you don't specify keep their previous pressed state. |
---|
| 366 | // buttons: |
---|
| 367 | // JSON object that represents all of the mouse buttons being pressed. |
---|
| 368 | // It takes the following Boolean attributes: |
---|
| 369 | // |
---|
| 370 | // - left |
---|
| 371 | // - middle |
---|
| 372 | // - right |
---|
| 373 | // delay: |
---|
| 374 | // Delay, in milliseconds, to wait before firing. |
---|
| 375 | // The delay is a delta with respect to the previous automation call. |
---|
| 376 | // For example, the following code ends after 600ms: |
---|
| 377 | // | robot.mouseClick({left: true}, 100) // first call; wait 100ms |
---|
| 378 | // | robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all |
---|
| 379 | |
---|
| 380 | this._assertRobot(); |
---|
| 381 | if(!buttons){ return; } |
---|
| 382 | this.sequence(function(){ |
---|
| 383 | var attrs = ["left", "middle", "right"]; |
---|
| 384 | for(var i = 0; i<attrs.length; i++){ |
---|
| 385 | if(!buttons[attrs[i]]){ |
---|
| 386 | buttons[attrs[i]] = false; |
---|
| 387 | } |
---|
| 388 | } |
---|
| 389 | _robot.pressMouse(isSecure(), Boolean(buttons.left), Boolean(buttons.middle), Boolean(buttons.right), Number(0)); |
---|
| 390 | }, delay); |
---|
| 391 | }, |
---|
| 392 | |
---|
| 393 | mouseMoveTo: function(/*Object*/ point, /*Integer?*/ delay, /*Integer?*/ duration, /*Boolean*/ absolute){ |
---|
| 394 | // summary: |
---|
| 395 | // Move the mouse from the current position to the specified point. |
---|
| 396 | // Delays reading contents point until queued command starts running. |
---|
| 397 | // See mouseMove() for details. |
---|
| 398 | // point: Object |
---|
| 399 | // x, y position relative to viewport, or if absolute == true, to document |
---|
| 400 | |
---|
| 401 | this._assertRobot(); |
---|
| 402 | duration = duration||100; |
---|
| 403 | |
---|
| 404 | // Calculate number of mouse movements we will do, based on specified duration. |
---|
| 405 | // IE6-8 timers have a granularity of 15ms, so only do one mouse move every 15ms |
---|
| 406 | var steps = duration<=1 ? 1 : // duration==1 -> user wants to jump the mouse |
---|
| 407 | (duration/15)|1; // |1 to ensure an odd # of intermediate steps for sensible interpolation |
---|
| 408 | var stepDuration = Math.floor(duration/steps); |
---|
| 409 | |
---|
| 410 | // Starting and ending points of the mouse movement. |
---|
| 411 | var start, end; |
---|
| 412 | |
---|
| 413 | this.sequence(function(){ |
---|
| 414 | // This runs right before we start moving the mouse. At this time (but not before), point is guaranteed |
---|
| 415 | // to be filled w/the correct data. So set start and end points for the movement of the mouse. |
---|
| 416 | start = lastMouse; |
---|
| 417 | if(absolute){ |
---|
| 418 | // Adjust end to be relative to viewport |
---|
| 419 | var scroll = {y: (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0), |
---|
| 420 | x: (window.pageXOffset || geom.fixIeBiDiScrollLeft(document.documentElement.scrollLeft) || document.body.scrollLeft || 0)}; |
---|
| 421 | end = { y: point.y - scroll.y, x: point.x - scroll.x }; |
---|
| 422 | }else{ |
---|
| 423 | end = point; |
---|
| 424 | } |
---|
| 425 | //console.log("mouseMoveTo() start, going from (", lastMouse.x, lastMouse.y, "), (", end.x, end.y, "), delay = " + |
---|
| 426 | // delay + ", duration = " + duration); |
---|
| 427 | }, delay || 0); |
---|
| 428 | |
---|
| 429 | // Function to positions the mouse along the line from start to end at the idx'th position (from 0 .. steps) |
---|
| 430 | function step(idx){ |
---|
| 431 | function easeInOutQuad(/*Number*/ t, /*Number*/ b, /*Number*/ c, /*Number*/ d){ |
---|
| 432 | t /= d / 2; |
---|
| 433 | if(t < 1) |
---|
| 434 | return Math.round(c / 2 * t * t + b); |
---|
| 435 | t--; |
---|
| 436 | return Math.round(-c / 2 * (t * (t - 2) - 1) + b); |
---|
| 437 | } |
---|
| 438 | |
---|
| 439 | var x = idx == steps ? end.x : easeInOutQuad(idx, start.x, end.x - start.x, steps), |
---|
| 440 | y = idx == steps ? end.y : easeInOutQuad(idx, start.y, end.y - start.y, steps); |
---|
| 441 | |
---|
| 442 | // If same position as the last time, don't bother calling java robot. |
---|
| 443 | if(x == lastMouse.x && y == lastMouse.y){ return true; } |
---|
| 444 | |
---|
| 445 | _robot.moveMouse(isSecure(), Number(x), Number(y), Number(0), Number(1)); |
---|
| 446 | lastMouse = {x: x, y: y}; |
---|
| 447 | } |
---|
| 448 | |
---|
| 449 | // Schedule mouse moves from beginning to end of line. |
---|
| 450 | // Start from t=1 because there's no need to move the mouse to where it already is |
---|
| 451 | for (var t = 1; t <= steps; t++){ |
---|
| 452 | // Use lang.partial() to lock in value of t before the t++ |
---|
| 453 | this.sequence(lang.partial(step, t), 0, stepDuration); |
---|
| 454 | } |
---|
| 455 | }, |
---|
| 456 | |
---|
| 457 | mouseMove: function(/*Number*/ x, /*Number*/ y, /*Integer?*/ delay, /*Integer?*/ duration, /*Boolean*/ absolute){ |
---|
| 458 | // summary: |
---|
| 459 | // Moves the mouse to the specified x,y offset relative to the viewport. |
---|
| 460 | // x: |
---|
| 461 | // x offset relative to the viewport, in pixels, to move the mouse. |
---|
| 462 | // y: |
---|
| 463 | // y offset relative to the viewport, in pixels, to move the mouse. |
---|
| 464 | // delay: |
---|
| 465 | // Delay, in milliseconds, to wait before firing. |
---|
| 466 | // The delay is a delta with respect to the previous automation call. |
---|
| 467 | // For example, the following code ends after 600ms: |
---|
| 468 | // | robot.mouseClick({left: true}, 100) // first call; wait 100ms |
---|
| 469 | // | robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all |
---|
| 470 | // duration: |
---|
| 471 | // Approximate time Robot will spend moving the mouse |
---|
| 472 | // The default is 100ms. This also affects how many mousemove events will |
---|
| 473 | // be generated, which is the log of the duration. |
---|
| 474 | // absolute: |
---|
| 475 | // Boolean indicating whether the x and y values are absolute coordinates. |
---|
| 476 | // If false, then mouseMove expects that the x,y will be relative to the window. (clientX/Y) |
---|
| 477 | // If true, then mouseMove expects that the x,y will be absolute. (pageX/Y) |
---|
| 478 | |
---|
| 479 | this.mouseMoveTo({x: x, y: y}, delay, duration, absolute); |
---|
| 480 | }, |
---|
| 481 | |
---|
| 482 | mouseRelease: function(/*Object*/ buttons, /*Integer?*/ delay){ |
---|
| 483 | // summary: |
---|
| 484 | // Releases mouse buttons. |
---|
| 485 | // description: |
---|
| 486 | // Releases the mouse buttons you pass as true. |
---|
| 487 | // Example: to release the left mouse button, pass {left: true}. |
---|
| 488 | // Mouse buttons you don't specify keep their previous pressed state. |
---|
| 489 | // See robot.mousePress for more info. |
---|
| 490 | |
---|
| 491 | this._assertRobot(); |
---|
| 492 | if(!buttons){ return; } |
---|
| 493 | this.sequence(function(){ |
---|
| 494 | var attrs = ["left", "middle", "right"]; |
---|
| 495 | for(var i = 0; i<attrs.length; i++){ |
---|
| 496 | if(!buttons[attrs[i]]){ |
---|
| 497 | buttons[attrs[i]] = false; |
---|
| 498 | } |
---|
| 499 | } |
---|
| 500 | _robot.releaseMouse(isSecure(), Boolean(buttons.left), Boolean(buttons.middle), Boolean(buttons.right), Number(0)); |
---|
| 501 | }, delay); |
---|
| 502 | }, |
---|
| 503 | |
---|
| 504 | // mouseWheelSize: Integer value that determines the amount of wheel motion per unit |
---|
| 505 | mouseWheelSize: 1, |
---|
| 506 | |
---|
| 507 | mouseWheel: function(/*Number*/ wheelAmt, /*Integer?*/ delay, /*Integer?*/ duration){ |
---|
| 508 | // summary: |
---|
| 509 | // Spins the mouse wheel. |
---|
| 510 | // description: |
---|
| 511 | // Spins the wheel wheelAmt "notches." |
---|
| 512 | // Negative wheelAmt scrolls up/away from the user. |
---|
| 513 | // Positive wheelAmt scrolls down/toward the user. |
---|
| 514 | // Note: this will all happen in one event. |
---|
| 515 | // Warning: the size of one mouse wheel notch is an OS setting. |
---|
| 516 | // You can access this size from robot.mouseWheelSize |
---|
| 517 | // wheelAmt: |
---|
| 518 | // Number of notches to spin the wheel. |
---|
| 519 | // Negative wheelAmt scrolls up/away from the user. |
---|
| 520 | // Positive wheelAmt scrolls down/toward the user. |
---|
| 521 | // delay: |
---|
| 522 | // Delay, in milliseconds, to wait before firing. |
---|
| 523 | // The delay is a delta with respect to the previous automation call. |
---|
| 524 | // For example, the following code ends after 600ms: |
---|
| 525 | // robot.mouseClick({left: true}, 100) // first call; wait 100ms |
---|
| 526 | // robot.typeKeys("dij", 500) // 500ms AFTER previous call; 600ms in all |
---|
| 527 | // duration: |
---|
| 528 | // Approximate time Robot will spend moving the mouse |
---|
| 529 | // By default, the Robot will wheel the mouse as fast as possible. |
---|
| 530 | |
---|
| 531 | this._assertRobot(); |
---|
| 532 | if(!wheelAmt){ return; } |
---|
| 533 | this.sequence(function(){ |
---|
| 534 | _robot.wheelMouse(isSecure(), Number(wheelAmt), Number(0), Number(duration||0)); |
---|
| 535 | }, delay, duration); |
---|
| 536 | }, |
---|
| 537 | |
---|
| 538 | setClipboard: function(/*String*/ data,/*String?*/ format){ |
---|
| 539 | // summary: |
---|
| 540 | // Set clipboard content. |
---|
| 541 | // description: |
---|
| 542 | // Set data as clipboard content, overriding anything already there. The |
---|
| 543 | // data will be put to the clipboard using the given format. |
---|
| 544 | // data: |
---|
| 545 | // New clipboard content to set |
---|
| 546 | // format: |
---|
| 547 | // Set this to "text/html" to put richtext to the clipboard. |
---|
| 548 | // Otherwise, data is treated as plaintext. By default, plaintext |
---|
| 549 | // is used. |
---|
| 550 | if(format==='text/html'){ |
---|
| 551 | _robot.setClipboardHtml(isSecure(), data); |
---|
| 552 | }else{ |
---|
| 553 | _robot.setClipboardText(isSecure(), data); |
---|
| 554 | } |
---|
| 555 | } |
---|
| 556 | }; |
---|
| 557 | |
---|
| 558 | // After page has finished loading, create the applet iframe. |
---|
| 559 | // Note: could eliminate dojo/ready dependency by tying this code to startRobot() call, but then users |
---|
| 560 | // are required to put doh.run() inside of a dojo/ready. Probably they are already doing that though. |
---|
| 561 | ready(function(){ |
---|
| 562 | // console.log("creating applet iframe"); |
---|
| 563 | var iframesrc; |
---|
| 564 | var scripts = document.getElementsByTagName("script"); |
---|
| 565 | for(var x = 0; x<scripts.length; x++){ |
---|
| 566 | var s = scripts[x].getAttribute('src'); |
---|
| 567 | if(s && (s.substr(s.length-9) == "runner.js")){ |
---|
| 568 | iframesrc = s.substr(0, s.length-9)+'Robot.html'; |
---|
| 569 | break; |
---|
| 570 | } |
---|
| 571 | } |
---|
| 572 | |
---|
| 573 | if(!iframesrc){ |
---|
| 574 | // if user set document.domain to something else, send it to the Robot too |
---|
| 575 | iframesrc = require.toUrl("./Robot.html") + "?domain=" + escape(document.domain); |
---|
| 576 | } |
---|
| 577 | construct.place('<div id="dohrobotview" style="border:0px none; margin:0px; padding:0px; position:absolute; bottom:0px; right:0px; width:1px; height:1px; overflow:hidden; visibility:hidden; background-color:red;"></div>', |
---|
| 578 | win.body()); |
---|
| 579 | |
---|
| 580 | if(!has("doh-custom-robot")){ |
---|
| 581 | // load default robot when not custom def given |
---|
| 582 | construct.place('<iframe application="true" style="border:0px none; z-index:32767; padding:0px; margin:0px; position:absolute; left:0px; top:0px; height:100px; width:200px; overflow:hidden; background-color:transparent;" tabIndex="-1" src="'+iframesrc+'" ALLOWTRANSPARENCY="true"></iframe>', |
---|
| 583 | win.body()); |
---|
| 584 | }else{ |
---|
| 585 | // custom def given |
---|
| 586 | console.log("using custom robot"); |
---|
| 587 | _robot = webdriver; |
---|
| 588 | // mix in exports |
---|
| 589 | for(var i in _robot){ |
---|
| 590 | if(robot[i]&&_robot[i]){ |
---|
| 591 | robot[i]=_robot[i]; |
---|
| 592 | } |
---|
| 593 | } |
---|
| 594 | // continue init instead of waiting on frame |
---|
| 595 | robot._initRobot(_robot); |
---|
| 596 | } |
---|
| 597 | }); |
---|
| 598 | |
---|
| 599 | // Start the robot as the first "test" when DOH runs. |
---|
| 600 | doh.registerGroup("initialize robot", [ |
---|
| 601 | { |
---|
| 602 | name: "load robot", |
---|
| 603 | timeout: 20000, |
---|
| 604 | runTest: function(){ |
---|
| 605 | // first wait for robot to tell us it's loaded, i.e. that _initRobot() has been called |
---|
| 606 | return robot._loaded; |
---|
| 607 | } |
---|
| 608 | }, |
---|
| 609 | { |
---|
| 610 | name: "start robot", |
---|
| 611 | timeout: 20000, |
---|
| 612 | runTest: function(){ |
---|
| 613 | // then we call startRobot(), and wait it to asynchronously complete |
---|
| 614 | return robot.startRobot(); |
---|
| 615 | } |
---|
| 616 | } |
---|
| 617 | ]); |
---|
| 618 | |
---|
| 619 | // Register the killRobot() command as the last "test" to run. |
---|
| 620 | // There's no good API to do this, so instead call doh.registerGroup() when the app first calls doh.run(), |
---|
| 621 | // since presumably all the real tests have already been registered. Note that doh.run() is called multiple times, |
---|
| 622 | // so make sure to only call registerGroup() once. |
---|
| 623 | var _oldRun = doh.run; |
---|
| 624 | doh.run = function(){ |
---|
| 625 | doh.registerGroup("kill robot", { |
---|
| 626 | name: "killRobot", |
---|
| 627 | timeout: 10000, |
---|
| 628 | runTest: function(){ |
---|
| 629 | robot.killRobot(); |
---|
| 630 | } |
---|
| 631 | }); |
---|
| 632 | doh.run = _oldRun; |
---|
| 633 | doh.run(); |
---|
| 634 | }; |
---|
| 635 | |
---|
| 636 | |
---|
| 637 | return robot; |
---|
| 638 | }); |
---|