1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/services/sync/tps/extensions/mozmill/resource/driver/controller.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1150 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +var EXPORTED_SYMBOLS = ["MozMillController", "globalEventRegistry", 1.9 + "sleep", "windowMap"]; 1.10 + 1.11 +const Cc = Components.classes; 1.12 +const Ci = Components.interfaces; 1.13 +const Cu = Components.utils; 1.14 + 1.15 +var EventUtils = {}; Cu.import('resource://mozmill/stdlib/EventUtils.js', EventUtils); 1.16 + 1.17 +var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions); 1.18 +var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker); 1.19 +var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib); 1.20 +var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors); 1.21 +var mozelement = {}; Cu.import('resource://mozmill/driver/mozelement.js', mozelement); 1.22 +var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils); 1.23 +var windows = {}; Cu.import('resource://mozmill/modules/windows.js', windows); 1.24 + 1.25 +// Declare most used utils functions in the controller namespace 1.26 +var assert = new assertions.Assert(); 1.27 +var waitFor = assert.waitFor; 1.28 + 1.29 +var sleep = utils.sleep; 1.30 + 1.31 +// For Mozmill 1.5 backward compatibility 1.32 +var windowMap = windows.map; 1.33 + 1.34 +waitForEvents = function () { 1.35 +} 1.36 + 1.37 +waitForEvents.prototype = { 1.38 + /** 1.39 + * Initialize list of events for given node 1.40 + */ 1.41 + init: function waitForEvents_init(node, events) { 1.42 + if (node.getNode != undefined) 1.43 + node = node.getNode(); 1.44 + 1.45 + this.events = events; 1.46 + this.node = node; 1.47 + node.firedEvents = {}; 1.48 + this.registry = {}; 1.49 + 1.50 + for each (var e in events) { 1.51 + var listener = function (event) { 1.52 + this.firedEvents[event.type] = true; 1.53 + } 1.54 + 1.55 + this.registry[e] = listener; 1.56 + this.registry[e].result = false; 1.57 + this.node.addEventListener(e, this.registry[e], true); 1.58 + } 1.59 + }, 1.60 + 1.61 + /** 1.62 + * Wait until all assigned events have been fired 1.63 + */ 1.64 + wait: function waitForEvents_wait(timeout, interval) { 1.65 + for (var e in this.registry) { 1.66 + assert.waitFor(function () { 1.67 + return this.node.firedEvents[e] == true; 1.68 + }, "waitForEvents.wait(): Event '" + ex + "' has been fired.", timeout, interval); 1.69 + 1.70 + this.node.removeEventListener(e, this.registry[e], true); 1.71 + } 1.72 + } 1.73 +} 1.74 + 1.75 +/** 1.76 + * Class to handle menus and context menus 1.77 + * 1.78 + * @constructor 1.79 + * @param {MozMillController} controller 1.80 + * Mozmill controller of the window under test 1.81 + * @param {string} menuSelector 1.82 + * jQuery like selector string of the element 1.83 + * @param {object} document 1.84 + * Document to use for finding the menu 1.85 + * [optional - default: aController.window.document] 1.86 + */ 1.87 +var Menu = function (controller, menuSelector, document) { 1.88 + this._controller = controller; 1.89 + this._menu = null; 1.90 + 1.91 + document = document || controller.window.document; 1.92 + var node = document.querySelector(menuSelector); 1.93 + if (node) { 1.94 + // We don't unwrap nodes automatically yet (Bug 573185) 1.95 + node = node.wrappedJSObject || node; 1.96 + this._menu = new mozelement.Elem(node); 1.97 + } else { 1.98 + throw new Error("Menu element '" + menuSelector + "' not found."); 1.99 + } 1.100 +} 1.101 + 1.102 +Menu.prototype = { 1.103 + 1.104 + /** 1.105 + * Open and populate the menu 1.106 + * 1.107 + * @param {ElemBase} contextElement 1.108 + * Element whose context menu has to be opened 1.109 + * @returns {Menu} The Menu instance 1.110 + */ 1.111 + open: function Menu_open(contextElement) { 1.112 + // We have to open the context menu 1.113 + var menu = this._menu.getNode(); 1.114 + if ((menu.localName == "popup" || menu.localName == "menupopup") && 1.115 + contextElement && contextElement.exists()) { 1.116 + this._controller.rightClick(contextElement); 1.117 + assert.waitFor(function () { 1.118 + return menu.state == "open"; 1.119 + }, "Context menu has been opened."); 1.120 + } 1.121 + 1.122 + // Run through the entire menu and populate with dynamic entries 1.123 + this._buildMenu(menu); 1.124 + 1.125 + return this; 1.126 + }, 1.127 + 1.128 + /** 1.129 + * Close the menu 1.130 + * 1.131 + * @returns {Menu} The Menu instance 1.132 + */ 1.133 + close: function Menu_close() { 1.134 + var menu = this._menu.getNode(); 1.135 + 1.136 + this._controller.keypress(this._menu, "VK_ESCAPE", {}); 1.137 + assert.waitFor(function () { 1.138 + return menu.state == "closed"; 1.139 + }, "Context menu has been closed."); 1.140 + 1.141 + return this; 1.142 + }, 1.143 + 1.144 + /** 1.145 + * Retrieve the specified menu entry 1.146 + * 1.147 + * @param {string} itemSelector 1.148 + * jQuery like selector string of the menu item 1.149 + * @returns {ElemBase} Menu element 1.150 + * @throws Error If menu element has not been found 1.151 + */ 1.152 + getItem: function Menu_getItem(itemSelector) { 1.153 + // Run through the entire menu and populate with dynamic entries 1.154 + this._buildMenu(this._menu.getNode()); 1.155 + 1.156 + var node = this._menu.getNode().querySelector(itemSelector); 1.157 + 1.158 + if (!node) { 1.159 + throw new Error("Menu entry '" + itemSelector + "' not found."); 1.160 + } 1.161 + 1.162 + return new mozelement.Elem(node); 1.163 + }, 1.164 + 1.165 + /** 1.166 + * Click the specified menu entry 1.167 + * 1.168 + * @param {string} itemSelector 1.169 + * jQuery like selector string of the menu item 1.170 + * 1.171 + * @returns {Menu} The Menu instance 1.172 + */ 1.173 + click: function Menu_click(itemSelector) { 1.174 + this._controller.click(this.getItem(itemSelector)); 1.175 + 1.176 + return this; 1.177 + }, 1.178 + 1.179 + /** 1.180 + * Synthesize a keypress against the menu 1.181 + * 1.182 + * @param {string} key 1.183 + * Key to press 1.184 + * @param {object} modifier 1.185 + * Key modifiers 1.186 + * @see MozMillController#keypress 1.187 + * 1.188 + * @returns {Menu} The Menu instance 1.189 + */ 1.190 + keypress: function Menu_keypress(key, modifier) { 1.191 + this._controller.keypress(this._menu, key, modifier); 1.192 + 1.193 + return this; 1.194 + }, 1.195 + 1.196 + /** 1.197 + * Opens the context menu, click the specified entry and 1.198 + * make sure that the menu has been closed. 1.199 + * 1.200 + * @param {string} itemSelector 1.201 + * jQuery like selector string of the element 1.202 + * @param {ElemBase} contextElement 1.203 + * Element whose context menu has to be opened 1.204 + * 1.205 + * @returns {Menu} The Menu instance 1.206 + */ 1.207 + select: function Menu_select(itemSelector, contextElement) { 1.208 + this.open(contextElement); 1.209 + this.click(itemSelector); 1.210 + this.close(); 1.211 + }, 1.212 + 1.213 + /** 1.214 + * Recursive function which iterates through all menu elements and 1.215 + * populates the menus with dynamic menu entries. 1.216 + * 1.217 + * @param {node} menu 1.218 + * Top menu node whose elements have to be populated 1.219 + */ 1.220 + _buildMenu: function Menu__buildMenu(menu) { 1.221 + var items = menu ? menu.childNodes : null; 1.222 + 1.223 + Array.forEach(items, function (item) { 1.224 + // When we have a menu node, fake a click onto it to populate 1.225 + // the sub menu with dynamic entries 1.226 + if (item.tagName == "menu") { 1.227 + var popup = item.querySelector("menupopup"); 1.228 + 1.229 + if (popup) { 1.230 + var popupEvent = this._controller.window.document.createEvent("MouseEvent"); 1.231 + popupEvent.initMouseEvent("popupshowing", true, true, 1.232 + this._controller.window, 0, 0, 0, 0, 0, 1.233 + false, false, false, false, 0, null); 1.234 + popup.dispatchEvent(popupEvent); 1.235 + 1.236 + this._buildMenu(popup); 1.237 + } 1.238 + } 1.239 + }, this); 1.240 + } 1.241 +}; 1.242 + 1.243 +var MozMillController = function (window) { 1.244 + this.window = window; 1.245 + 1.246 + this.mozmillModule = {}; 1.247 + Cu.import('resource://mozmill/driver/mozmill.js', this.mozmillModule); 1.248 + 1.249 + var self = this; 1.250 + assert.waitFor(function () { 1.251 + return window != null && self.isLoaded(); 1.252 + }, "controller(): Window has been initialized."); 1.253 + 1.254 + // Ensure to focus the window which will move it virtually into the foreground 1.255 + // when focusmanager.testmode is set enabled. 1.256 + this.window.focus(); 1.257 + 1.258 + var windowType = window.document.documentElement.getAttribute('windowtype'); 1.259 + if (controllerAdditions[windowType] != undefined ) { 1.260 + this.prototype = new utils.Copy(this.prototype); 1.261 + controllerAdditions[windowType](this); 1.262 + this.windowtype = windowType; 1.263 + } 1.264 +} 1.265 + 1.266 +/** 1.267 + * Returns the global browser object of the window 1.268 + * 1.269 + * @returns {Object} The browser object 1.270 + */ 1.271 +MozMillController.prototype.__defineGetter__("browserObject", function () { 1.272 + return utils.getBrowserObject(this.window); 1.273 +}); 1.274 + 1.275 +// constructs a MozMillElement from the controller's window 1.276 +MozMillController.prototype.__defineGetter__("rootElement", function () { 1.277 + if (this._rootElement == undefined) { 1.278 + let docElement = this.window.document.documentElement; 1.279 + this._rootElement = new mozelement.MozMillElement("Elem", docElement); 1.280 + } 1.281 + 1.282 + return this._rootElement; 1.283 +}); 1.284 + 1.285 +MozMillController.prototype.sleep = utils.sleep; 1.286 +MozMillController.prototype.waitFor = assert.waitFor; 1.287 + 1.288 +// Open the specified url in the current tab 1.289 +MozMillController.prototype.open = function (url) { 1.290 + switch (this.mozmillModule.Application) { 1.291 + case "Firefox": 1.292 + case "MetroFirefox": 1.293 + // Stop a running page load to not overlap requests 1.294 + if (this.browserObject.selectedBrowser) { 1.295 + this.browserObject.selectedBrowser.stop(); 1.296 + } 1.297 + 1.298 + this.browserObject.loadURI(url); 1.299 + break; 1.300 + 1.301 + default: 1.302 + throw new Error("MozMillController.open not supported."); 1.303 + } 1.304 + 1.305 + broker.pass({'function':'Controller.open()'}); 1.306 +} 1.307 + 1.308 +/** 1.309 + * Take a screenshot of specified node 1.310 + * 1.311 + * @param {Element} node 1.312 + * The window or DOM element to capture 1.313 + * @param {String} name 1.314 + * The name of the screenshot used in reporting and as filename 1.315 + * @param {Boolean} save 1.316 + * If true saves the screenshot as 'name.jpg' in tempdir, 1.317 + * otherwise returns a dataURL 1.318 + * @param {Element[]} highlights 1.319 + * A list of DOM elements to highlight by drawing a red rectangle around them 1.320 + * 1.321 + * @returns {Object} Object which contains properties like filename, dataURL, 1.322 + * name and timestamp of the screenshot 1.323 + */ 1.324 +MozMillController.prototype.screenshot = function (node, name, save, highlights) { 1.325 + if (!node) { 1.326 + throw new Error("node is undefined"); 1.327 + } 1.328 + 1.329 + // Unwrap the node and highlights 1.330 + if ("getNode" in node) { 1.331 + node = node.getNode(); 1.332 + } 1.333 + 1.334 + if (highlights) { 1.335 + for (var i = 0; i < highlights.length; ++i) { 1.336 + if ("getNode" in highlights[i]) { 1.337 + highlights[i] = highlights[i].getNode(); 1.338 + } 1.339 + } 1.340 + } 1.341 + 1.342 + // If save is false, a dataURL is used 1.343 + // Include both in the report anyway to avoid confusion and make the report easier to parse 1.344 + var screenshot = {"filename": undefined, 1.345 + "dataURL": utils.takeScreenshot(node, highlights), 1.346 + "name": name, 1.347 + "timestamp": new Date().toLocaleString()}; 1.348 + 1.349 + if (!save) { 1.350 + return screenshot; 1.351 + } 1.352 + 1.353 + // Save the screenshot to disk 1.354 + 1.355 + let {filename, failure} = utils.saveDataURL(screenshot.dataURL, name); 1.356 + screenshot.filename = filename; 1.357 + screenshot.failure = failure; 1.358 + 1.359 + if (failure) { 1.360 + broker.log({'function': 'controller.screenshot()', 1.361 + 'message': 'Error writing to file: ' + screenshot.filename}); 1.362 + } else { 1.363 + // Send the screenshot object to python over jsbridge 1.364 + broker.sendMessage("screenshot", screenshot); 1.365 + broker.pass({'function': 'controller.screenshot()'}); 1.366 + } 1.367 + 1.368 + return screenshot; 1.369 +} 1.370 + 1.371 +/** 1.372 + * Checks if the specified window has been loaded 1.373 + * 1.374 + * @param {DOMWindow} [aWindow=this.window] Window object to check for loaded state 1.375 + */ 1.376 +MozMillController.prototype.isLoaded = function (aWindow) { 1.377 + var win = aWindow || this.window; 1.378 + 1.379 + return windows.map.getValue(utils.getWindowId(win), "loaded") || false; 1.380 +}; 1.381 + 1.382 +MozMillController.prototype.__defineGetter__("waitForEvents", function () { 1.383 + if (this._waitForEvents == undefined) { 1.384 + this._waitForEvents = new waitForEvents(); 1.385 + } 1.386 + 1.387 + return this._waitForEvents; 1.388 +}); 1.389 + 1.390 +/** 1.391 + * Wrapper function to create a new instance of a menu 1.392 + * @see Menu 1.393 + */ 1.394 +MozMillController.prototype.getMenu = function (menuSelector, document) { 1.395 + return new Menu(this, menuSelector, document); 1.396 +}; 1.397 + 1.398 +MozMillController.prototype.__defineGetter__("mainMenu", function () { 1.399 + return this.getMenu("menubar"); 1.400 +}); 1.401 + 1.402 +MozMillController.prototype.__defineGetter__("menus", function () { 1.403 + logDeprecated('controller.menus', 'Use controller.mainMenu instead'); 1.404 +}); 1.405 + 1.406 +MozMillController.prototype.waitForImage = function (aElement, timeout, interval) { 1.407 + this.waitFor(function () { 1.408 + return aElement.getNode().complete == true; 1.409 + }, "timeout exceeded for waitForImage " + aElement.getInfo(), timeout, interval); 1.410 + 1.411 + broker.pass({'function':'Controller.waitForImage()'}); 1.412 +} 1.413 + 1.414 +MozMillController.prototype.startUserShutdown = function (timeout, restart, next, resetProfile) { 1.415 + if (restart && resetProfile) { 1.416 + throw new Error("You can't have a user-restart and reset the profile; there is a race condition"); 1.417 + } 1.418 + 1.419 + let shutdownObj = { 1.420 + 'user': true, 1.421 + 'restart': Boolean(restart), 1.422 + 'next': next, 1.423 + 'resetProfile': Boolean(resetProfile), 1.424 + 'timeout': timeout 1.425 + }; 1.426 + 1.427 + broker.sendMessage('shutdown', shutdownObj); 1.428 +} 1.429 + 1.430 +/** 1.431 + * Restart the application 1.432 + * 1.433 + * @param {string} aNext 1.434 + * Name of the next test function to run after restart 1.435 + * @param {boolean} [aFlags=undefined] 1.436 + * Additional flags how to handle the shutdown or restart. The attributes 1.437 + * eRestarti386 (0x20) and eRestartx86_64 (0x30) have not been documented yet. 1.438 + * @see https://developer.mozilla.org/nsIAppStartup#Attributes 1.439 + */ 1.440 +MozMillController.prototype.restartApplication = function (aNext, aFlags) { 1.441 + var flags = Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart; 1.442 + 1.443 + if (aFlags) { 1.444 + flags |= aFlags; 1.445 + } 1.446 + 1.447 + broker.sendMessage('shutdown', {'user': false, 1.448 + 'restart': true, 1.449 + 'flags': flags, 1.450 + 'next': aNext, 1.451 + 'timeout': 0 }); 1.452 + 1.453 + // We have to ensure to stop the test from continuing until the application is 1.454 + // shutting down. The only way to do that is by throwing an exception. 1.455 + throw new errors.ApplicationQuitError(); 1.456 +} 1.457 + 1.458 +/** 1.459 + * Stop the application 1.460 + * 1.461 + * @param {boolean} [aResetProfile=false] 1.462 + * Whether to reset the profile during restart 1.463 + * @param {boolean} [aFlags=undefined] 1.464 + * Additional flags how to handle the shutdown or restart. The attributes 1.465 + * eRestarti386 and eRestartx86_64 have not been documented yet. 1.466 + * @see https://developer.mozilla.org/nsIAppStartup#Attributes 1.467 + */ 1.468 +MozMillController.prototype.stopApplication = function (aResetProfile, aFlags) { 1.469 + var flags = Ci.nsIAppStartup.eAttemptQuit; 1.470 + 1.471 + if (aFlags) { 1.472 + flags |= aFlags; 1.473 + } 1.474 + 1.475 + broker.sendMessage('shutdown', {'user': false, 1.476 + 'restart': false, 1.477 + 'flags': flags, 1.478 + 'resetProfile': aResetProfile, 1.479 + 'timeout': 0 }); 1.480 + 1.481 + // We have to ensure to stop the test from continuing until the application is 1.482 + // shutting down. The only way to do that is by throwing an exception. 1.483 + throw new errors.ApplicationQuitError(); 1.484 +} 1.485 + 1.486 +//Browser navigation functions 1.487 +MozMillController.prototype.goBack = function () { 1.488 + this.window.content.history.back(); 1.489 + broker.pass({'function':'Controller.goBack()'}); 1.490 + 1.491 + return true; 1.492 +} 1.493 + 1.494 +MozMillController.prototype.goForward = function () { 1.495 + this.window.content.history.forward(); 1.496 + broker.pass({'function':'Controller.goForward()'}); 1.497 + 1.498 + return true; 1.499 +} 1.500 + 1.501 +MozMillController.prototype.refresh = function () { 1.502 + this.window.content.location.reload(true); 1.503 + broker.pass({'function':'Controller.refresh()'}); 1.504 + 1.505 + return true; 1.506 +} 1.507 + 1.508 +function logDeprecated(funcName, message) { 1.509 + broker.log({'function': funcName + '() - DEPRECATED', 1.510 + 'message': funcName + '() is deprecated. ' + message}); 1.511 +} 1.512 + 1.513 +function logDeprecatedAssert(funcName) { 1.514 + logDeprecated('controller.' + funcName, 1.515 + '. Use the generic `assertion` module instead.'); 1.516 +} 1.517 + 1.518 +MozMillController.prototype.assertText = function (el, text) { 1.519 + logDeprecatedAssert("assertText"); 1.520 + 1.521 + var n = el.getNode(); 1.522 + 1.523 + if (n && n.innerHTML == text) { 1.524 + broker.pass({'function': 'Controller.assertText()'}); 1.525 + } else { 1.526 + throw new Error("could not validate element " + el.getInfo() + 1.527 + " with text "+ text); 1.528 + } 1.529 + 1.530 + return true; 1.531 +}; 1.532 + 1.533 +/** 1.534 + * Assert that a specified node exists 1.535 + */ 1.536 +MozMillController.prototype.assertNode = function (el) { 1.537 + logDeprecatedAssert("assertNode"); 1.538 + 1.539 + //this.window.focus(); 1.540 + var element = el.getNode(); 1.541 + if (!element) { 1.542 + throw new Error("could not find element " + el.getInfo()); 1.543 + } 1.544 + 1.545 + broker.pass({'function': 'Controller.assertNode()'}); 1.546 + return true; 1.547 +}; 1.548 + 1.549 +/** 1.550 + * Assert that a specified node doesn't exist 1.551 + */ 1.552 +MozMillController.prototype.assertNodeNotExist = function (el) { 1.553 + logDeprecatedAssert("assertNodeNotExist"); 1.554 + 1.555 + try { 1.556 + var element = el.getNode(); 1.557 + } catch (e) { 1.558 + broker.pass({'function': 'Controller.assertNodeNotExist()'}); 1.559 + } 1.560 + 1.561 + if (element) { 1.562 + throw new Error("Unexpectedly found element " + el.getInfo()); 1.563 + } else { 1.564 + broker.pass({'function':'Controller.assertNodeNotExist()'}); 1.565 + } 1.566 + 1.567 + return true; 1.568 +}; 1.569 + 1.570 +/** 1.571 + * Assert that a form element contains the expected value 1.572 + */ 1.573 +MozMillController.prototype.assertValue = function (el, value) { 1.574 + logDeprecatedAssert("assertValue"); 1.575 + 1.576 + var n = el.getNode(); 1.577 + 1.578 + if (n && n.value == value) { 1.579 + broker.pass({'function': 'Controller.assertValue()'}); 1.580 + } else { 1.581 + throw new Error("could not validate element " + el.getInfo() + 1.582 + " with value " + value); 1.583 + } 1.584 + 1.585 + return false; 1.586 +}; 1.587 + 1.588 +/** 1.589 + * Check if the callback function evaluates to true 1.590 + */ 1.591 +MozMillController.prototype.assert = function (callback, message, thisObject) { 1.592 + logDeprecatedAssert("assert"); 1.593 + 1.594 + utils.assert(callback, message, thisObject); 1.595 + broker.pass({'function': ": controller.assert('" + callback + "')"}); 1.596 + 1.597 + return true; 1.598 +} 1.599 + 1.600 +/** 1.601 + * Assert that a provided value is selected in a select element 1.602 + */ 1.603 +MozMillController.prototype.assertSelected = function (el, value) { 1.604 + logDeprecatedAssert("assertSelected"); 1.605 + 1.606 + var n = el.getNode(); 1.607 + var validator = value; 1.608 + 1.609 + if (n && n.options[n.selectedIndex].value == validator) { 1.610 + broker.pass({'function':'Controller.assertSelected()'}); 1.611 + } else { 1.612 + throw new Error("could not assert value for element " + el.getInfo() + 1.613 + " with value " + value); 1.614 + } 1.615 + 1.616 + return true; 1.617 +}; 1.618 + 1.619 +/** 1.620 + * Assert that a provided checkbox is checked 1.621 + */ 1.622 +MozMillController.prototype.assertChecked = function (el) { 1.623 + logDeprecatedAssert("assertChecked"); 1.624 + 1.625 + var element = el.getNode(); 1.626 + 1.627 + if (element && element.checked == true) { 1.628 + broker.pass({'function':'Controller.assertChecked()'}); 1.629 + } else { 1.630 + throw new Error("assert failed for checked element " + el.getInfo()); 1.631 + } 1.632 + 1.633 + return true; 1.634 +}; 1.635 + 1.636 +/** 1.637 + * Assert that a provided checkbox is not checked 1.638 + */ 1.639 +MozMillController.prototype.assertNotChecked = function (el) { 1.640 + logDeprecatedAssert("assertNotChecked"); 1.641 + 1.642 + var element = el.getNode(); 1.643 + 1.644 + if (!element) { 1.645 + throw new Error("Could not find element" + el.getInfo()); 1.646 + } 1.647 + 1.648 + if (!element.hasAttribute("checked") || element.checked != true) { 1.649 + broker.pass({'function': 'Controller.assertNotChecked()'}); 1.650 + } else { 1.651 + throw new Error("assert failed for not checked element " + el.getInfo()); 1.652 + } 1.653 + 1.654 + return true; 1.655 +}; 1.656 + 1.657 +/** 1.658 + * Assert that an element's javascript property exists or has a particular value 1.659 + * 1.660 + * if val is undefined, will return true if the property exists. 1.661 + * if val is specified, will return true if the property exists and has the correct value 1.662 + */ 1.663 +MozMillController.prototype.assertJSProperty = function (el, attrib, val) { 1.664 + logDeprecatedAssert("assertJSProperty"); 1.665 + 1.666 + var element = el.getNode(); 1.667 + 1.668 + if (!element){ 1.669 + throw new Error("could not find element " + el.getInfo()); 1.670 + } 1.671 + 1.672 + var value = element[attrib]; 1.673 + var res = (value !== undefined && (val === undefined ? true : 1.674 + String(value) == String(val))); 1.675 + if (res) { 1.676 + broker.pass({'function':'Controller.assertJSProperty("' + el.getInfo() + '") : ' + val}); 1.677 + } else { 1.678 + throw new Error("Controller.assertJSProperty(" + el.getInfo() + ") : " + 1.679 + (val === undefined ? "property '" + attrib + 1.680 + "' doesn't exist" : val + " == " + value)); 1.681 + } 1.682 + 1.683 + return true; 1.684 +}; 1.685 + 1.686 +/** 1.687 + * Assert that an element's javascript property doesn't exist or doesn't have a particular value 1.688 + * 1.689 + * if val is undefined, will return true if the property doesn't exist. 1.690 + * if val is specified, will return true if the property doesn't exist or doesn't have the specified value 1.691 + */ 1.692 +MozMillController.prototype.assertNotJSProperty = function (el, attrib, val) { 1.693 + logDeprecatedAssert("assertNotJSProperty"); 1.694 + 1.695 + var element = el.getNode(); 1.696 + 1.697 + if (!element){ 1.698 + throw new Error("could not find element " + el.getInfo()); 1.699 + } 1.700 + 1.701 + var value = element[attrib]; 1.702 + var res = (val === undefined ? value === undefined : String(value) != String(val)); 1.703 + if (res) { 1.704 + broker.pass({'function':'Controller.assertNotProperty("' + el.getInfo() + '") : ' + val}); 1.705 + } else { 1.706 + throw new Error("Controller.assertNotJSProperty(" + el.getInfo() + ") : " + 1.707 + (val === undefined ? "property '" + attrib + 1.708 + "' exists" : val + " != " + value)); 1.709 + } 1.710 + 1.711 + return true; 1.712 +}; 1.713 + 1.714 +/** 1.715 + * Assert that an element's dom property exists or has a particular value 1.716 + * 1.717 + * if val is undefined, will return true if the property exists. 1.718 + * if val is specified, will return true if the property exists and has the correct value 1.719 + */ 1.720 +MozMillController.prototype.assertDOMProperty = function (el, attrib, val) { 1.721 + logDeprecatedAssert("assertDOMProperty"); 1.722 + 1.723 + var element = el.getNode(); 1.724 + 1.725 + if (!element){ 1.726 + throw new Error("could not find element " + el.getInfo()); 1.727 + } 1.728 + 1.729 + var value, res = element.hasAttribute(attrib); 1.730 + if (res && val !== undefined) { 1.731 + value = element.getAttribute(attrib); 1.732 + res = (String(value) == String(val)); 1.733 + } 1.734 + 1.735 + if (res) { 1.736 + broker.pass({'function':'Controller.assertDOMProperty("' + el.getInfo() + '") : ' + val}); 1.737 + } else { 1.738 + throw new Error("Controller.assertDOMProperty(" + el.getInfo() + ") : " + 1.739 + (val === undefined ? "property '" + attrib + 1.740 + "' doesn't exist" : val + " == " + value)); 1.741 + } 1.742 + 1.743 + return true; 1.744 +}; 1.745 + 1.746 +/** 1.747 + * Assert that an element's dom property doesn't exist or doesn't have a particular value 1.748 + * 1.749 + * if val is undefined, will return true if the property doesn't exist. 1.750 + * if val is specified, will return true if the property doesn't exist or doesn't have the specified value 1.751 + */ 1.752 +MozMillController.prototype.assertNotDOMProperty = function (el, attrib, val) { 1.753 + logDeprecatedAssert("assertNotDOMProperty"); 1.754 + 1.755 + var element = el.getNode(); 1.756 + 1.757 + if (!element) { 1.758 + throw new Error("could not find element " + el.getInfo()); 1.759 + } 1.760 + 1.761 + var value, res = element.hasAttribute(attrib); 1.762 + if (res && val !== undefined) { 1.763 + value = element.getAttribute(attrib); 1.764 + res = (String(value) == String(val)); 1.765 + } 1.766 + 1.767 + if (!res) { 1.768 + broker.pass({'function':'Controller.assertNotDOMProperty("' + el.getInfo() + '") : ' + val}); 1.769 + } else { 1.770 + throw new Error("Controller.assertNotDOMProperty(" + el.getInfo() + ") : " + 1.771 + (val == undefined ? "property '" + attrib + 1.772 + "' exists" : val + " == " + value)); 1.773 + } 1.774 + 1.775 + return true; 1.776 +}; 1.777 + 1.778 +/** 1.779 + * Assert that a specified image has actually loaded. The Safari workaround results 1.780 + * in additional requests for broken images (in Safari only) but works reliably 1.781 + */ 1.782 +MozMillController.prototype.assertImageLoaded = function (el) { 1.783 + logDeprecatedAssert("assertImageLoaded"); 1.784 + 1.785 + var img = el.getNode(); 1.786 + 1.787 + if (!img || img.tagName != 'IMG') { 1.788 + throw new Error('Controller.assertImageLoaded() failed.') 1.789 + return false; 1.790 + } 1.791 + 1.792 + var comp = img.complete; 1.793 + var ret = null; // Return value 1.794 + 1.795 + // Workaround for Safari -- it only supports the 1.796 + // complete attrib on script-created images 1.797 + if (typeof comp == 'undefined') { 1.798 + test = new Image(); 1.799 + // If the original image was successfully loaded, 1.800 + // src for new one should be pulled from cache 1.801 + test.src = img.src; 1.802 + comp = test.complete; 1.803 + } 1.804 + 1.805 + // Check the complete attrib. Note the strict 1.806 + // equality check -- we don't want undefined, null, etc. 1.807 + // -------------------------- 1.808 + if (comp === false) { 1.809 + // False -- Img failed to load in IE/Safari, or is 1.810 + // still trying to load in FF 1.811 + ret = false; 1.812 + } else if (comp === true && img.naturalWidth == 0) { 1.813 + // True, but image has no size -- image failed to 1.814 + // load in FF 1.815 + ret = false; 1.816 + } else { 1.817 + // Otherwise all we can do is assume everything's 1.818 + // hunky-dory 1.819 + ret = true; 1.820 + } 1.821 + 1.822 + if (ret) { 1.823 + broker.pass({'function':'Controller.assertImageLoaded'}); 1.824 + } else { 1.825 + throw new Error('Controller.assertImageLoaded() failed.') 1.826 + } 1.827 + 1.828 + return true; 1.829 +}; 1.830 + 1.831 +/** 1.832 + * Drag one element to the top x,y coords of another specified element 1.833 + */ 1.834 +MozMillController.prototype.mouseMove = function (doc, start, dest) { 1.835 + // if one of these elements couldn't be looked up 1.836 + if (typeof start != 'object'){ 1.837 + throw new Error("received bad coordinates"); 1.838 + } 1.839 + 1.840 + if (typeof dest != 'object'){ 1.841 + throw new Error("received bad coordinates"); 1.842 + } 1.843 + 1.844 + var triggerMouseEvent = function (element, clientX, clientY) { 1.845 + clientX = clientX ? clientX: 0; 1.846 + clientY = clientY ? clientY: 0; 1.847 + 1.848 + // make the mouse understand where it is on the screen 1.849 + var screenX = element.boxObject.screenX ? element.boxObject.screenX : 0; 1.850 + var screenY = element.boxObject.screenY ? element.boxObject.screenY : 0; 1.851 + 1.852 + var evt = element.ownerDocument.createEvent('MouseEvents'); 1.853 + if (evt.initMouseEvent) { 1.854 + evt.initMouseEvent('mousemove', true, true, element.ownerDocument.defaultView, 1.855 + 1, screenX, screenY, clientX, clientY); 1.856 + } else { 1.857 + evt.initEvent('mousemove', true, true); 1.858 + } 1.859 + 1.860 + element.dispatchEvent(evt); 1.861 + }; 1.862 + 1.863 + // Do the initial move to the drag element position 1.864 + triggerMouseEvent(doc.body, start[0], start[1]); 1.865 + triggerMouseEvent(doc.body, dest[0], dest[1]); 1.866 + 1.867 + broker.pass({'function':'Controller.mouseMove()'}); 1.868 + return true; 1.869 +} 1.870 + 1.871 +/** 1.872 + * Drag an element to the specified offset on another element, firing mouse and 1.873 + * drag events. Adapted from ChromeUtils.js synthesizeDrop() 1.874 + * 1.875 + * @deprecated Use the MozMillElement object 1.876 + * 1.877 + * @param {MozElement} aSrc 1.878 + * Source element to be dragged 1.879 + * @param {MozElement} aDest 1.880 + * Destination element over which the drop occurs 1.881 + * @param {Number} [aOffsetX=element.width/2] 1.882 + * Relative x offset for dropping on the aDest element 1.883 + * @param {Number} [aOffsetY=element.height/2] 1.884 + * Relative y offset for dropping on the aDest element 1.885 + * @param {DOMWindow} [aSourceWindow=this.element.ownerDocument.defaultView] 1.886 + * Custom source Window to be used. 1.887 + * @param {String} [aDropEffect="move"] 1.888 + * Effect used for the drop event 1.889 + * @param {Object[]} [aDragData] 1.890 + * An array holding custom drag data to be used during the drag event 1.891 + * Format: [{ type: "text/plain", "Text to drag"}, ...] 1.892 + * 1.893 + * @returns {String} the captured dropEffect 1.894 + */ 1.895 +MozMillController.prototype.dragToElement = function (aSrc, aDest, aOffsetX, 1.896 + aOffsetY, aSourceWindow, 1.897 + aDropEffect, aDragData) { 1.898 + logDeprecated("controller.dragToElement", "Use the MozMillElement object."); 1.899 + return aSrc.dragToElement(aDest, aOffsetX, aOffsetY, aSourceWindow, null, 1.900 + aDropEffect, aDragData); 1.901 +}; 1.902 + 1.903 +function Tabs(controller) { 1.904 + this.controller = controller; 1.905 +} 1.906 + 1.907 +Tabs.prototype.getTab = function (index) { 1.908 + return this.controller.browserObject.browsers[index].contentDocument; 1.909 +} 1.910 + 1.911 +Tabs.prototype.__defineGetter__("activeTab", function () { 1.912 + return this.controller.browserObject.selectedBrowser.contentDocument; 1.913 +}); 1.914 + 1.915 +Tabs.prototype.selectTab = function (index) { 1.916 + // GO in to tab manager and grab the tab by index and call focus. 1.917 +} 1.918 + 1.919 +Tabs.prototype.findWindow = function (doc) { 1.920 + for (var i = 0; i <= (this.controller.window.frames.length - 1); i++) { 1.921 + if (this.controller.window.frames[i].document == doc) { 1.922 + return this.controller.window.frames[i]; 1.923 + } 1.924 + } 1.925 + 1.926 + throw new Error("Cannot find window for document. Doc title == " + doc.title); 1.927 +} 1.928 + 1.929 +Tabs.prototype.getTabWindow = function (index) { 1.930 + return this.findWindow(this.getTab(index)); 1.931 +} 1.932 + 1.933 +Tabs.prototype.__defineGetter__("activeTabWindow", function () { 1.934 + return this.findWindow(this.activeTab); 1.935 +}); 1.936 + 1.937 +Tabs.prototype.__defineGetter__("length", function () { 1.938 + return this.controller.browserObject.browsers.length; 1.939 +}); 1.940 + 1.941 +Tabs.prototype.__defineGetter__("activeTabIndex", function () { 1.942 + var browser = this.controller.browserObject; 1.943 + 1.944 + switch(this.controller.mozmillModule.Application) { 1.945 + case "MetroFirefox": 1.946 + return browser.tabs.indexOf(browser.selectedTab); 1.947 + case "Firefox": 1.948 + default: 1.949 + return browser.tabContainer.selectedIndex; 1.950 + } 1.951 +}); 1.952 + 1.953 +Tabs.prototype.selectTabIndex = function (aIndex) { 1.954 + var browser = this.controller.browserObject; 1.955 + 1.956 + switch(this.controller.mozmillModule.Application) { 1.957 + case "MetroFirefox": 1.958 + browser.selectedTab = browser.tabs[aIndex]; 1.959 + break; 1.960 + case "Firefox": 1.961 + default: 1.962 + browser.selectTabAtIndex(aIndex); 1.963 + } 1.964 +} 1.965 + 1.966 +function browserAdditions (controller) { 1.967 + controller.tabs = new Tabs(controller); 1.968 + 1.969 + controller.waitForPageLoad = function (aDocument, aTimeout, aInterval) { 1.970 + var timeout = aTimeout || 30000; 1.971 + var win = null; 1.972 + var timed_out = false; 1.973 + 1.974 + // If a user tries to do waitForPageLoad(2000), this will assign the 1.975 + // interval the first arg which is most likely what they were expecting 1.976 + if (typeof(aDocument) == "number"){ 1.977 + timeout = aDocument; 1.978 + } 1.979 + 1.980 + // If we have a real document use its default view 1.981 + if (aDocument && (typeof(aDocument) === "object") && 1.982 + "defaultView" in aDocument) 1.983 + win = aDocument.defaultView; 1.984 + 1.985 + // If no document has been specified, fallback to the default view of the 1.986 + // currently selected tab browser 1.987 + win = win || this.browserObject.selectedBrowser.contentWindow; 1.988 + 1.989 + // Wait until the content in the tab has been loaded 1.990 + try { 1.991 + this.waitFor(function () { 1.992 + return windows.map.hasPageLoaded(utils.getWindowId(win)); 1.993 + }, "Timeout", timeout, aInterval); 1.994 + } 1.995 + catch (ex if ex instanceof errors.TimeoutError) { 1.996 + timed_out = true; 1.997 + } 1.998 + finally { 1.999 + state = 'URI=' + win.document.location.href + 1.1000 + ', readyState=' + win.document.readyState; 1.1001 + message = "controller.waitForPageLoad(" + state + ")"; 1.1002 + 1.1003 + if (timed_out) { 1.1004 + throw new errors.AssertionError(message); 1.1005 + } 1.1006 + 1.1007 + broker.pass({'function': message}); 1.1008 + } 1.1009 + } 1.1010 +} 1.1011 + 1.1012 +var controllerAdditions = { 1.1013 + 'navigator:browser' :browserAdditions 1.1014 +}; 1.1015 + 1.1016 +/** 1.1017 + * DEPRECATION WARNING 1.1018 + * 1.1019 + * The following methods have all been DEPRECATED as of Mozmill 2.0 1.1020 + */ 1.1021 +MozMillController.prototype.assertProperty = function (el, attrib, val) { 1.1022 + logDeprecatedAssert("assertProperty"); 1.1023 + 1.1024 + return this.assertJSProperty(el, attrib, val); 1.1025 +}; 1.1026 + 1.1027 +MozMillController.prototype.assertPropertyNotExist = function (el, attrib) { 1.1028 + logDeprecatedAssert("assertPropertyNotExist"); 1.1029 + return this.assertNotJSProperty(el, attrib); 1.1030 +}; 1.1031 + 1.1032 +/** 1.1033 + * DEPRECATION WARNING 1.1034 + * 1.1035 + * The following methods have all been DEPRECATED as of Mozmill 2.0 1.1036 + * Use the MozMillElement object instead (https://developer.mozilla.org/en/Mozmill/Mozmill_Element_Object) 1.1037 + */ 1.1038 +MozMillController.prototype.select = function (aElement, index, option, value) { 1.1039 + logDeprecated("controller.select", "Use the MozMillElement object."); 1.1040 + 1.1041 + return aElement.select(index, option, value); 1.1042 +}; 1.1043 + 1.1044 +MozMillController.prototype.keypress = function (aElement, aKey, aModifiers, aExpectedEvent) { 1.1045 + logDeprecated("controller.keypress", "Use the MozMillElement object."); 1.1046 + 1.1047 + if (!aElement) { 1.1048 + aElement = new mozelement.MozMillElement("Elem", this.window); 1.1049 + } 1.1050 + 1.1051 + return aElement.keypress(aKey, aModifiers, aExpectedEvent); 1.1052 +} 1.1053 + 1.1054 +MozMillController.prototype.type = function (aElement, aText, aExpectedEvent) { 1.1055 + logDeprecated("controller.type", "Use the MozMillElement object."); 1.1056 + 1.1057 + if (!aElement) { 1.1058 + aElement = new mozelement.MozMillElement("Elem", this.window); 1.1059 + } 1.1060 + 1.1061 + var that = this; 1.1062 + var retval = true; 1.1063 + Array.forEach(aText, function (letter) { 1.1064 + if (!that.keypress(aElement, letter, {}, aExpectedEvent)) { 1.1065 + retval = false; } 1.1066 + }); 1.1067 + 1.1068 + return retval; 1.1069 +} 1.1070 + 1.1071 +MozMillController.prototype.mouseEvent = function (aElement, aOffsetX, aOffsetY, aEvent, aExpectedEvent) { 1.1072 + logDeprecated("controller.mouseEvent", "Use the MozMillElement object."); 1.1073 + 1.1074 + return aElement.mouseEvent(aOffsetX, aOffsetY, aEvent, aExpectedEvent); 1.1075 +} 1.1076 + 1.1077 +MozMillController.prototype.click = function (aElement, left, top, expectedEvent) { 1.1078 + logDeprecated("controller.click", "Use the MozMillElement object."); 1.1079 + 1.1080 + return aElement.click(left, top, expectedEvent); 1.1081 +} 1.1082 + 1.1083 +MozMillController.prototype.doubleClick = function (aElement, left, top, expectedEvent) { 1.1084 + logDeprecated("controller.doubleClick", "Use the MozMillElement object."); 1.1085 + 1.1086 + return aElement.doubleClick(left, top, expectedEvent); 1.1087 +} 1.1088 + 1.1089 +MozMillController.prototype.mouseDown = function (aElement, button, left, top, expectedEvent) { 1.1090 + logDeprecated("controller.mouseDown", "Use the MozMillElement object."); 1.1091 + 1.1092 + return aElement.mouseDown(button, left, top, expectedEvent); 1.1093 +}; 1.1094 + 1.1095 +MozMillController.prototype.mouseOut = function (aElement, button, left, top, expectedEvent) { 1.1096 + logDeprecated("controller.mouseOut", "Use the MozMillElement object."); 1.1097 + 1.1098 + return aElement.mouseOut(button, left, top, expectedEvent); 1.1099 +}; 1.1100 + 1.1101 +MozMillController.prototype.mouseOver = function (aElement, button, left, top, expectedEvent) { 1.1102 + logDeprecated("controller.mouseOver", "Use the MozMillElement object."); 1.1103 + 1.1104 + return aElement.mouseOver(button, left, top, expectedEvent); 1.1105 +}; 1.1106 + 1.1107 +MozMillController.prototype.mouseUp = function (aElement, button, left, top, expectedEvent) { 1.1108 + logDeprecated("controller.mouseUp", "Use the MozMillElement object."); 1.1109 + 1.1110 + return aElement.mouseUp(button, left, top, expectedEvent); 1.1111 +}; 1.1112 + 1.1113 +MozMillController.prototype.middleClick = function (aElement, left, top, expectedEvent) { 1.1114 + logDeprecated("controller.middleClick", "Use the MozMillElement object."); 1.1115 + 1.1116 + return aElement.middleClick(aElement, left, top, expectedEvent); 1.1117 +} 1.1118 + 1.1119 +MozMillController.prototype.rightClick = function (aElement, left, top, expectedEvent) { 1.1120 + logDeprecated("controller.rightClick", "Use the MozMillElement object."); 1.1121 + 1.1122 + return aElement.rightClick(left, top, expectedEvent); 1.1123 +} 1.1124 + 1.1125 +MozMillController.prototype.check = function (aElement, state) { 1.1126 + logDeprecated("controller.check", "Use the MozMillElement object."); 1.1127 + 1.1128 + return aElement.check(state); 1.1129 +} 1.1130 + 1.1131 +MozMillController.prototype.radio = function (aElement) { 1.1132 + logDeprecated("controller.radio", "Use the MozMillElement object."); 1.1133 + 1.1134 + return aElement.select(); 1.1135 +} 1.1136 + 1.1137 +MozMillController.prototype.waitThenClick = function (aElement, timeout, interval) { 1.1138 + logDeprecated("controller.waitThenClick", "Use the MozMillElement object."); 1.1139 + 1.1140 + return aElement.waitThenClick(timeout, interval); 1.1141 +} 1.1142 + 1.1143 +MozMillController.prototype.waitForElement = function (aElement, timeout, interval) { 1.1144 + logDeprecated("controller.waitForElement", "Use the MozMillElement object."); 1.1145 + 1.1146 + return aElement.waitForElement(timeout, interval); 1.1147 +} 1.1148 + 1.1149 +MozMillController.prototype.waitForElementNotPresent = function (aElement, timeout, interval) { 1.1150 + logDeprecated("controller.waitForElementNotPresent", "Use the MozMillElement object."); 1.1151 + 1.1152 + return aElement.waitForElementNotPresent(timeout, interval); 1.1153 +}