testing/marionette/marionette-elements.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 /**
michael@0 7 * The ElementManager manages DOM references and interactions with elements.
michael@0 8 * According to the WebDriver spec (http://code.google.com/p/selenium/wiki/JsonWireProtocol), the
michael@0 9 * server sends the client an element reference, and maintains the map of reference to element.
michael@0 10 * The client uses this reference when querying/interacting with the element, and the
michael@0 11 * server uses maps this reference to the actual element when it executes the command.
michael@0 12 */
michael@0 13
michael@0 14 this.EXPORTED_SYMBOLS = [
michael@0 15 "ElementManager",
michael@0 16 "CLASS_NAME",
michael@0 17 "SELECTOR",
michael@0 18 "ID",
michael@0 19 "NAME",
michael@0 20 "LINK_TEXT",
michael@0 21 "PARTIAL_LINK_TEXT",
michael@0 22 "TAG",
michael@0 23 "XPATH"
michael@0 24 ];
michael@0 25
michael@0 26 const DOCUMENT_POSITION_DISCONNECTED = 1;
michael@0 27
michael@0 28 let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
michael@0 29 .getService(Components.interfaces.nsIUUIDGenerator);
michael@0 30
michael@0 31 this.CLASS_NAME = "class name";
michael@0 32 this.SELECTOR = "css selector";
michael@0 33 this.ID = "id";
michael@0 34 this.NAME = "name";
michael@0 35 this.LINK_TEXT = "link text";
michael@0 36 this.PARTIAL_LINK_TEXT = "partial link text";
michael@0 37 this.TAG = "tag name";
michael@0 38 this.XPATH = "xpath";
michael@0 39
michael@0 40 function ElementException(msg, num, stack) {
michael@0 41 this.message = msg;
michael@0 42 this.code = num;
michael@0 43 this.stack = stack;
michael@0 44 }
michael@0 45
michael@0 46 this.ElementManager = function ElementManager(notSupported) {
michael@0 47 this.seenItems = {};
michael@0 48 this.timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
michael@0 49 this.elementStrategies = [CLASS_NAME, SELECTOR, ID, NAME, LINK_TEXT, PARTIAL_LINK_TEXT, TAG, XPATH];
michael@0 50 for (let i = 0; i < notSupported.length; i++) {
michael@0 51 this.elementStrategies.splice(this.elementStrategies.indexOf(notSupported[i]), 1);
michael@0 52 }
michael@0 53 }
michael@0 54
michael@0 55 ElementManager.prototype = {
michael@0 56 /**
michael@0 57 * Reset values
michael@0 58 */
michael@0 59 reset: function EM_clear() {
michael@0 60 this.seenItems = {};
michael@0 61 },
michael@0 62
michael@0 63 /**
michael@0 64 * Add element to list of seen elements
michael@0 65 *
michael@0 66 * @param nsIDOMElement element
michael@0 67 * The element to add
michael@0 68 *
michael@0 69 * @return string
michael@0 70 * Returns the server-assigned reference ID
michael@0 71 */
michael@0 72 addToKnownElements: function EM_addToKnownElements(element) {
michael@0 73 for (let i in this.seenItems) {
michael@0 74 let foundEl = null;
michael@0 75 try {
michael@0 76 foundEl = this.seenItems[i].get();
michael@0 77 }
michael@0 78 catch(e) {}
michael@0 79 if (foundEl) {
michael@0 80 if (XPCNativeWrapper(foundEl) == XPCNativeWrapper(element)) {
michael@0 81 return i;
michael@0 82 }
michael@0 83 }
michael@0 84 else {
michael@0 85 //cleanup reference to GC'd element
michael@0 86 delete this.seenItems[i];
michael@0 87 }
michael@0 88 }
michael@0 89 var id = uuidGen.generateUUID().toString();
michael@0 90 this.seenItems[id] = Components.utils.getWeakReference(element);
michael@0 91 return id;
michael@0 92 },
michael@0 93
michael@0 94 /**
michael@0 95 * Retrieve element from its unique ID
michael@0 96 *
michael@0 97 * @param String id
michael@0 98 * The DOM reference ID
michael@0 99 * @param nsIDOMWindow win
michael@0 100 * The window that contains the element
michael@0 101 *
michael@0 102 * @returns nsIDOMElement
michael@0 103 * Returns the element or throws Exception if not found
michael@0 104 */
michael@0 105 getKnownElement: function EM_getKnownElement(id, win) {
michael@0 106 let el = this.seenItems[id];
michael@0 107 if (!el) {
michael@0 108 throw new ElementException("Element has not been seen before. Id given was " + id, 17, null);
michael@0 109 }
michael@0 110 try {
michael@0 111 el = el.get();
michael@0 112 }
michael@0 113 catch(e) {
michael@0 114 el = null;
michael@0 115 delete this.seenItems[id];
michael@0 116 }
michael@0 117 // use XPCNativeWrapper to compare elements; see bug 834266
michael@0 118 let wrappedWin = XPCNativeWrapper(win);
michael@0 119 if (!el ||
michael@0 120 !(XPCNativeWrapper(el).ownerDocument == wrappedWin.document) ||
michael@0 121 (XPCNativeWrapper(el).compareDocumentPosition(wrappedWin.document.documentElement) &
michael@0 122 DOCUMENT_POSITION_DISCONNECTED)) {
michael@0 123 throw new ElementException("The element reference is stale. Either the element " +
michael@0 124 "is no longer attached to the DOM or the page has been refreshed.", 10, null);
michael@0 125 }
michael@0 126 return el;
michael@0 127 },
michael@0 128
michael@0 129 /**
michael@0 130 * Convert values to primitives that can be transported over the
michael@0 131 * Marionette protocol.
michael@0 132 *
michael@0 133 * This function implements the marshaling algorithm defined in the
michael@0 134 * WebDriver specification:
michael@0 135 *
michael@0 136 * https://dvcs.w3.org/hg/webdriver/raw-file/tip/webdriver-spec.html#synchronous-javascript-execution
michael@0 137 *
michael@0 138 * @param object val
michael@0 139 * object to be marshaled
michael@0 140 *
michael@0 141 * @return object
michael@0 142 * Returns a JSON primitive or Object
michael@0 143 */
michael@0 144 wrapValue: function EM_wrapValue(val) {
michael@0 145 let result = null;
michael@0 146
michael@0 147 switch (typeof(val)) {
michael@0 148 case "undefined":
michael@0 149 result = null;
michael@0 150 break;
michael@0 151
michael@0 152 case "string":
michael@0 153 case "number":
michael@0 154 case "boolean":
michael@0 155 result = val;
michael@0 156 break;
michael@0 157
michael@0 158 case "object":
michael@0 159 let type = Object.prototype.toString.call(val);
michael@0 160 if (type == "[object Array]" ||
michael@0 161 type == "[object NodeList]") {
michael@0 162 result = [];
michael@0 163 for (let i = 0; i < val.length; ++i) {
michael@0 164 result.push(this.wrapValue(val[i]));
michael@0 165 }
michael@0 166 }
michael@0 167 else if (val == null) {
michael@0 168 result = null;
michael@0 169 }
michael@0 170 else if (val.nodeType == 1) {
michael@0 171 result = {'ELEMENT': this.addToKnownElements(val)};
michael@0 172 }
michael@0 173 else {
michael@0 174 result = {};
michael@0 175 for (let prop in val) {
michael@0 176 result[prop] = this.wrapValue(val[prop]);
michael@0 177 }
michael@0 178 }
michael@0 179 break;
michael@0 180 }
michael@0 181
michael@0 182 return result;
michael@0 183 },
michael@0 184
michael@0 185 /**
michael@0 186 * Convert any ELEMENT references in 'args' to the actual elements
michael@0 187 *
michael@0 188 * @param object args
michael@0 189 * Arguments passed in by client
michael@0 190 * @param nsIDOMWindow win
michael@0 191 * The window that contains the elements
michael@0 192 *
michael@0 193 * @returns object
michael@0 194 * Returns the objects passed in by the client, with the
michael@0 195 * reference IDs replaced by the actual elements.
michael@0 196 */
michael@0 197 convertWrappedArguments: function EM_convertWrappedArguments(args, win) {
michael@0 198 let converted;
michael@0 199 switch (typeof(args)) {
michael@0 200 case 'number':
michael@0 201 case 'string':
michael@0 202 case 'boolean':
michael@0 203 converted = args;
michael@0 204 break;
michael@0 205 case 'object':
michael@0 206 if (args == null) {
michael@0 207 converted = null;
michael@0 208 }
michael@0 209 else if (Object.prototype.toString.call(args) == '[object Array]') {
michael@0 210 converted = [];
michael@0 211 for (let i in args) {
michael@0 212 converted.push(this.convertWrappedArguments(args[i], win));
michael@0 213 }
michael@0 214 }
michael@0 215 else if (typeof(args['ELEMENT'] === 'string') &&
michael@0 216 args.hasOwnProperty('ELEMENT')) {
michael@0 217 converted = this.getKnownElement(args['ELEMENT'], win);
michael@0 218 if (converted == null)
michael@0 219 throw new ElementException("Unknown element: " + args['ELEMENT'], 500, null);
michael@0 220 }
michael@0 221 else {
michael@0 222 converted = {};
michael@0 223 for (let prop in args) {
michael@0 224 converted[prop] = this.convertWrappedArguments(args[prop], win);
michael@0 225 }
michael@0 226 }
michael@0 227 break;
michael@0 228 }
michael@0 229 return converted;
michael@0 230 },
michael@0 231
michael@0 232 /*
michael@0 233 * Execute* helpers
michael@0 234 */
michael@0 235
michael@0 236 /**
michael@0 237 * Return an object with any namedArgs applied to it. Used
michael@0 238 * to let clients use given names when refering to arguments
michael@0 239 * in execute calls, instead of using the arguments list.
michael@0 240 *
michael@0 241 * @param object args
michael@0 242 * list of arguments being passed in
michael@0 243 *
michael@0 244 * @return object
michael@0 245 * If '__marionetteArgs' is in args, then
michael@0 246 * it will return an object with these arguments
michael@0 247 * as its members.
michael@0 248 */
michael@0 249 applyNamedArgs: function EM_applyNamedArgs(args) {
michael@0 250 namedArgs = {};
michael@0 251 args.forEach(function(arg) {
michael@0 252 if (typeof(arg['__marionetteArgs']) === 'object') {
michael@0 253 for (let prop in arg['__marionetteArgs']) {
michael@0 254 namedArgs[prop] = arg['__marionetteArgs'][prop];
michael@0 255 }
michael@0 256 }
michael@0 257 });
michael@0 258 return namedArgs;
michael@0 259 },
michael@0 260
michael@0 261 /**
michael@0 262 * Find an element or elements starting at the document root or
michael@0 263 * given node, using the given search strategy. Search
michael@0 264 * will continue until the search timelimit has been reached.
michael@0 265 *
michael@0 266 * @param nsIDOMWindow win
michael@0 267 * The window to search in
michael@0 268 * @param object values
michael@0 269 * The 'using' member of values will tell us which search
michael@0 270 * method to use. The 'value' member tells us the value we
michael@0 271 * are looking for.
michael@0 272 * If this object has an 'element' member, this will be used
michael@0 273 * as the start node instead of the document root
michael@0 274 * If this object has a 'time' member, this number will be
michael@0 275 * used to see if we have hit the search timelimit.
michael@0 276 * @param function on_success
michael@0 277 * The notification callback used when we are returning successfully.
michael@0 278 * @param function on_error
michael@0 279 The callback to invoke when an error occurs.
michael@0 280 * @param boolean all
michael@0 281 * If true, all found elements will be returned.
michael@0 282 * If false, only the first element will be returned.
michael@0 283 *
michael@0 284 * @return nsIDOMElement or list of nsIDOMElements
michael@0 285 * Returns the element(s) by calling the on_success function.
michael@0 286 */
michael@0 287 find: function EM_find(win, values, searchTimeout, on_success, on_error, all, command_id) {
michael@0 288 let startTime = values.time ? values.time : new Date().getTime();
michael@0 289 let startNode = (values.element != undefined) ?
michael@0 290 this.getKnownElement(values.element, win) : win.document;
michael@0 291 if (this.elementStrategies.indexOf(values.using) < 0) {
michael@0 292 throw new ElementException("No such strategy.", 17, null);
michael@0 293 }
michael@0 294 let found = all ? this.findElements(values.using, values.value, win.document, startNode) :
michael@0 295 this.findElement(values.using, values.value, win.document, startNode);
michael@0 296 if (found) {
michael@0 297 let type = Object.prototype.toString.call(found);
michael@0 298 if ((type == '[object Array]') || (type == '[object HTMLCollection]') || (type == '[object NodeList]')) {
michael@0 299 let ids = []
michael@0 300 for (let i = 0 ; i < found.length ; i++) {
michael@0 301 ids.push(this.addToKnownElements(found[i]));
michael@0 302 }
michael@0 303 on_success(ids, command_id);
michael@0 304 }
michael@0 305 else {
michael@0 306 let id = this.addToKnownElements(found);
michael@0 307 on_success({'ELEMENT':id}, command_id);
michael@0 308 }
michael@0 309 return;
michael@0 310 } else {
michael@0 311 if (!searchTimeout || new Date().getTime() - startTime > searchTimeout) {
michael@0 312 on_error("Unable to locate element: " + values.value, 7, null, command_id);
michael@0 313 } else {
michael@0 314 values.time = startTime;
michael@0 315 this.timer.initWithCallback(this.find.bind(this, win, values,
michael@0 316 searchTimeout,
michael@0 317 on_success, on_error, all,
michael@0 318 command_id),
michael@0 319 100,
michael@0 320 Components.interfaces.nsITimer.TYPE_ONE_SHOT);
michael@0 321 }
michael@0 322 }
michael@0 323 },
michael@0 324
michael@0 325 /**
michael@0 326 * Find a value by XPATH
michael@0 327 *
michael@0 328 * @param nsIDOMElement root
michael@0 329 * Document root
michael@0 330 * @param string value
michael@0 331 * XPATH search string
michael@0 332 * @param nsIDOMElement node
michael@0 333 * start node
michael@0 334 *
michael@0 335 * @return nsIDOMElement
michael@0 336 * returns the found element
michael@0 337 */
michael@0 338 findByXPath: function EM_findByXPath(root, value, node) {
michael@0 339 return root.evaluate(value, node, null,
michael@0 340 Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
michael@0 341 },
michael@0 342
michael@0 343 /**
michael@0 344 * Find values by XPATH
michael@0 345 *
michael@0 346 * @param nsIDOMElement root
michael@0 347 * Document root
michael@0 348 * @param string value
michael@0 349 * XPATH search string
michael@0 350 * @param nsIDOMElement node
michael@0 351 * start node
michael@0 352 *
michael@0 353 * @return object
michael@0 354 * returns a list of found nsIDOMElements
michael@0 355 */
michael@0 356 findByXPathAll: function EM_findByXPathAll(root, value, node) {
michael@0 357 let values = root.evaluate(value, node, null,
michael@0 358 Components.interfaces.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
michael@0 359 let elements = [];
michael@0 360 let element = values.iterateNext();
michael@0 361 while (element) {
michael@0 362 elements.push(element);
michael@0 363 element = values.iterateNext();
michael@0 364 }
michael@0 365 return elements;
michael@0 366 },
michael@0 367
michael@0 368 /**
michael@0 369 * Helper method to find. Finds one element using find's criteria
michael@0 370 *
michael@0 371 * @param string using
michael@0 372 * String identifying which search method to use
michael@0 373 * @param string value
michael@0 374 * Value to look for
michael@0 375 * @param nsIDOMElement rootNode
michael@0 376 * Document root
michael@0 377 * @param nsIDOMElement startNode
michael@0 378 * Node from which we start searching
michael@0 379 *
michael@0 380 * @return nsIDOMElement
michael@0 381 * Returns found element or throws Exception if not found
michael@0 382 */
michael@0 383 findElement: function EM_findElement(using, value, rootNode, startNode) {
michael@0 384 let element;
michael@0 385 switch (using) {
michael@0 386 case ID:
michael@0 387 element = startNode.getElementById ?
michael@0 388 startNode.getElementById(value) :
michael@0 389 this.findByXPath(rootNode, './/*[@id="' + value + '"]', startNode);
michael@0 390 break;
michael@0 391 case NAME:
michael@0 392 element = startNode.getElementsByName ?
michael@0 393 startNode.getElementsByName(value)[0] :
michael@0 394 this.findByXPath(rootNode, './/*[@name="' + value + '"]', startNode);
michael@0 395 break;
michael@0 396 case CLASS_NAME:
michael@0 397 element = startNode.getElementsByClassName(value)[0]; //works for >=FF3
michael@0 398 break;
michael@0 399 case TAG:
michael@0 400 element = startNode.getElementsByTagName(value)[0]; //works for all elements
michael@0 401 break;
michael@0 402 case XPATH:
michael@0 403 element = this.findByXPath(rootNode, value, startNode);
michael@0 404 break;
michael@0 405 case LINK_TEXT:
michael@0 406 case PARTIAL_LINK_TEXT:
michael@0 407 let allLinks = startNode.getElementsByTagName('A');
michael@0 408 for (let i = 0; i < allLinks.length && !element; i++) {
michael@0 409 let text = allLinks[i].text;
michael@0 410 if (PARTIAL_LINK_TEXT == using) {
michael@0 411 if (text.indexOf(value) != -1) {
michael@0 412 element = allLinks[i];
michael@0 413 }
michael@0 414 } else if (text == value) {
michael@0 415 element = allLinks[i];
michael@0 416 }
michael@0 417 }
michael@0 418 break;
michael@0 419 case SELECTOR:
michael@0 420 element = startNode.querySelector(value);
michael@0 421 break;
michael@0 422 default:
michael@0 423 throw new ElementException("No such strategy", 500, null);
michael@0 424 }
michael@0 425 return element;
michael@0 426 },
michael@0 427
michael@0 428 /**
michael@0 429 * Helper method to find. Finds all element using find's criteria
michael@0 430 *
michael@0 431 * @param string using
michael@0 432 * String identifying which search method to use
michael@0 433 * @param string value
michael@0 434 * Value to look for
michael@0 435 * @param nsIDOMElement rootNode
michael@0 436 * Document root
michael@0 437 * @param nsIDOMElement startNode
michael@0 438 * Node from which we start searching
michael@0 439 *
michael@0 440 * @return nsIDOMElement
michael@0 441 * Returns found elements or throws Exception if not found
michael@0 442 */
michael@0 443 findElements: function EM_findElements(using, value, rootNode, startNode) {
michael@0 444 let elements = [];
michael@0 445 switch (using) {
michael@0 446 case ID:
michael@0 447 value = './/*[@id="' + value + '"]';
michael@0 448 case XPATH:
michael@0 449 elements = this.findByXPathAll(rootNode, value, startNode);
michael@0 450 break;
michael@0 451 case NAME:
michael@0 452 elements = startNode.getElementsByName ?
michael@0 453 startNode.getElementsByName(value) :
michael@0 454 this.findByXPathAll(rootNode, './/*[@name="' + value + '"]', startNode);
michael@0 455 break;
michael@0 456 case CLASS_NAME:
michael@0 457 elements = startNode.getElementsByClassName(value);
michael@0 458 break;
michael@0 459 case TAG:
michael@0 460 elements = startNode.getElementsByTagName(value);
michael@0 461 break;
michael@0 462 case LINK_TEXT:
michael@0 463 case PARTIAL_LINK_TEXT:
michael@0 464 let allLinks = rootNode.getElementsByTagName('A');
michael@0 465 for (let i = 0; i < allLinks.length; i++) {
michael@0 466 let text = allLinks[i].text;
michael@0 467 if (PARTIAL_LINK_TEXT == using) {
michael@0 468 if (text.indexOf(value) != -1) {
michael@0 469 elements.push(allLinks[i]);
michael@0 470 }
michael@0 471 } else if (text == value) {
michael@0 472 elements.push(allLinks[i]);
michael@0 473 }
michael@0 474 }
michael@0 475 break;
michael@0 476 case SELECTOR:
michael@0 477 elements = Array.slice(startNode.querySelectorAll(value));
michael@0 478 break;
michael@0 479 default:
michael@0 480 throw new ElementException("No such strategy", 500, null);
michael@0 481 }
michael@0 482 return elements;
michael@0 483 },
michael@0 484 }

mercurial