Bug 1204504 - Error on invalid selector; r=automatedtester
authorAndreas Tolfsen <ato@mozilla.com>
Thu, 17 Sep 2015 12:31:39 +0100
changeset 280160 d7025fe68e90e96ab2da2dbcd7688c1aec45ee2f
parent 280159 3d8d87127bc2383d2c9380f6af19b53752b9acf5
child 280161 73b822abb3e83943b4df17542c19c330cca66ad8
push id70343
push useratolfsen@mozilla.com
push dateFri, 15 Jan 2016 16:52:38 +0000
treeherdermozilla-inbound@d7025fe68e90 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersautomatedtester
bugs1204504
milestone46.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1204504 - Error on invalid selector; r=automatedtester Thanks to Alexei Barantsev for reporting.
testing/marionette/client/marionette/tests/unit/test_findelement.py
testing/marionette/driver.js
testing/marionette/elements.js
--- a/testing/marionette/client/marionette/tests/unit/test_findelement.py
+++ b/testing/marionette/client/marionette/tests/unit/test_findelement.py
@@ -65,27 +65,31 @@ class TestElements(MarionetteTestCase):
         el = self.marionette.execute_script("return window.document.getElementsByName('myInput')[0];")
         found_el = self.marionette.find_element(By.NAME, "myInput")
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
         found_el = self.marionette.find_elements(By.NAME, "myInput")[0]
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
 
-    def test_selector(self):
+    def test_css_selector(self):
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         el = self.marionette.execute_script("return window.document.getElementById('testh1');")
         found_el = self.marionette.find_element(By.CSS_SELECTOR, "h1")
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
         found_el = self.marionette.find_elements(By.CSS_SELECTOR, "h1")[0]
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
 
+    def test_invalid_css_selector_should_throw(self):
+        with self.assertRaises(InvalidSelectorException):
+            self.marionette.find_element(By.CSS_SELECTOR, "#")
+
     def test_link_text(self):
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         el = self.marionette.execute_script("return window.document.getElementById('mozLink');")
         found_el = self.marionette.find_element(By.LINK_TEXT, "Click me!")
         self.assertEqual(HTMLElement, type(found_el))
         self.assertEqual(el, found_el)
         found_el = self.marionette.find_elements(By.LINK_TEXT, "Click me!")[0]
@@ -150,30 +154,30 @@ class TestElements(MarionetteTestCase):
 
     def test_finding_active_element_returns_element(self):
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         fbody = self.marionette.find_element(By.TAG_NAME, 'body')
         abody = self.marionette.get_active_element()
         self.assertEqual(fbody, abody)
 
-    def test_throws_error_when_trying_to_use_invalid_selector_type(self):
-        test_html = self.marionette.absolute_url("test.html")
-        self.marionette.navigate(test_html)
-        self.assertRaises(InvalidSelectorException, self.marionette.find_element, "Brie Search Type", "doesn't matter")
+    def test_unknown_selector(self):
+        with self.assertRaises(InvalidSelectorException):
+            self.marionette.find_element("foo", "bar")
 
     def test_element_id_is_valid_uuid(self):
         import re
         test_html = self.marionette.absolute_url("test.html")
         self.marionette.navigate(test_html)
         el = self.marionette.find_element(By.TAG_NAME, "body")
         uuid_regex = re.compile('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$')
         self.assertIsNotNone(re.search(uuid_regex, el.id),
                              'UUID for the WebElement is not valid. ID is {}'\
                              .format(el.id))
+
     def test_should_find_elements_by_link_text(self):
         test_html = self.marionette.absolute_url("nestedElements.html")
         self.marionette.navigate(test_html)
         element = self.marionette.find_element(By.NAME, "div1")
         children = element.find_elements(By.LINK_TEXT, "hello world")
         self.assertEqual(len(children), 2)
         self.assertEqual("link1", children[0].get_attribute("name"))
         self.assertEqual("link2", children[1].get_attribute("name"))
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -1908,17 +1908,17 @@ GeckoDriver.prototype.multiAction = func
  *     Value the client is looking for.
  */
 GeckoDriver.prototype.findElement = function(cmd, resp) {
   switch (this.context) {
     case Context.CHROME:
       resp.body.value = yield new Promise((resolve, reject) => {
         let win = this.getCurrentWindow();
         this.curBrowser.elementManager.find(
-            { frame: win },
+            {frame: win},
             cmd.parameters,
             this.searchTimeout,
             false /* all */,
             resolve,
             reject);
       }).then(null, e => { throw e; });
       break;
 
@@ -1941,17 +1941,17 @@ GeckoDriver.prototype.findElement = func
  *     Value the client is looking for.
  */
 GeckoDriver.prototype.findElements = function(cmd, resp) {
   switch (this.context) {
     case Context.CHROME:
       resp.body = yield new Promise((resolve, reject) => {
         let win = this.getCurrentWindow();
         this.curBrowser.elementManager.find(
-            { frame: win },
+            {frame: win},
             cmd.parameters,
             this.searchTimeout,
             true /* all */,
             resolve,
             reject);
       }).then(null, e => { throw new NoSuchElementError(e.message); });
       break;
 
--- a/testing/marionette/elements.js
+++ b/testing/marionette/elements.js
@@ -1,28 +1,37 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var {utils: Cu} = Components;
+let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("chrome://marionette/content/error.js");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, 'setInterval',
   'resource://gre/modules/Timer.jsm');
 XPCOMUtils.defineLazyModuleGetter(this, 'clearInterval',
   'resource://gre/modules/Timer.jsm');
 
 /**
- * The ElementManager manages DOM references and interactions with elements.
- * According to the WebDriver spec (http://code.google.com/p/selenium/wiki/JsonWireProtocol), the
- * server sends the client an element reference, and maintains the map of reference to element.
- * The client uses this reference when querying/interacting with the element, and the
- * server uses maps this reference to the actual element when it executes the command.
+ * The ElementManager manages DOM element references and
+ * interactions with elements.
+ *
+ * A web element is an abstraction used to identify an element when it
+ * is transported across the protocol, between remote- and local ends.
+ *
+ * Each element has an associated web element reference (a UUID) that
+ * uniquely identifies the the element across all browsing contexts. The
+ * web element reference for every element representing the same element
+ * is the same.
+ *
+ * The element manager provides a mapping between web element references
+ * and DOM elements for each browsing context.  It also provides
+ * functionality for looking up and retrieving elements.
  */
 
 this.EXPORTED_SYMBOLS = [
   "Accessibility",
   "elements",
   "ElementManager",
   "CLASS_NAME",
   "SELECTOR",
@@ -235,17 +244,17 @@ Accessibility.prototype = {
       throw new ElementNotAccessibleError(message);
     }
     dump(Date.now() + " Marionette: " + message);
   }
 };
 
 this.ElementManager = function ElementManager(notSupported) {
   this.seenItems = {};
-  this.timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
+  this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   this.elementKey = 'ELEMENT';
   this.w3cElementKey = 'element-6066-11e4-a52e-4f735466cecf';
   this.elementStrategies = [CLASS_NAME, SELECTOR, ID, NAME, LINK_TEXT, PARTIAL_LINK_TEXT, TAG, XPATH, ANON, ANON_ATTRIBUTE];
   for (let i = 0; i < notSupported.length; i++) {
     this.elementStrategies.splice(this.elementStrategies.indexOf(notSupported[i]), 1);
   }
 }
 
@@ -613,82 +622,99 @@ ElementManager.prototype = {
     while (element) {
       elements.push(element);
       element = values.iterateNext();
     }
     return elements;
   },
 
   /**
-   * Helper method to find. Finds one element using find's criteria
+   * Finds a single element.
    *
-   * @param string using
-   *        String identifying which search method to use
-   * @param string value
-   *        Value to look for
-   * @param nsIDOMElement rootNode
-   *        Document root
-   * @param nsIDOMElement startNode
-   *        Node from which we start searching
+   * @param {String} using
+   *     Which selector search method to use.
+   * @param {String} value
+   *     Selector query.
+   * @param {nsIDOMElement} rootNode
+   *     Document root.
+   * @param {nsIDOMElement=} startNode
+   *     Optional node from which we start searching.
    *
-   * @return nsIDOMElement
-   *        Returns found element or throws Exception if not found
+   * @return {nsIDOMElement}
+   *     Returns found element.
+   * @throws {InvalidSelectorError}
+   *     If the selector query string (value) is invalid, or the selector
+   *     search method (using) is unknown.
    */
   findElement: function EM_findElement(using, value, rootNode, startNode) {
     let element;
+
     switch (using) {
       case ID:
         element = startNode.getElementById ?
                   startNode.getElementById(value) :
                   this.findByXPath(rootNode, './/*[@id="' + value + '"]', startNode);
         break;
+
       case NAME:
         element = startNode.getElementsByName ?
                   startNode.getElementsByName(value)[0] :
                   this.findByXPath(rootNode, './/*[@name="' + value + '"]', startNode);
         break;
+
       case CLASS_NAME:
         element = startNode.getElementsByClassName(value)[0]; //works for >=FF3
         break;
+
       case TAG:
         element = startNode.getElementsByTagName(value)[0]; //works for all elements
         break;
+
       case XPATH:
         element = this.findByXPath(rootNode, value, startNode);
         break;
+
       case LINK_TEXT:
       case PARTIAL_LINK_TEXT:
         let allLinks = startNode.getElementsByTagName('A');
         for (let i = 0; i < allLinks.length && !element; i++) {
           let text = allLinks[i].text;
           if (PARTIAL_LINK_TEXT == using) {
             if (text.indexOf(value) != -1) {
               element = allLinks[i];
             }
           } else if (text == value) {
             element = allLinks[i];
           }
         }
         break;
       case SELECTOR:
-        element = startNode.querySelector(value);
+        try {
+          element = startNode.querySelector(value);
+        } catch (e) {
+          throw new InvalidSelectorError(`${e.message}: "${value}"`);
+        }
         break;
+
       case ANON:
         element = rootNode.getAnonymousNodes(startNode);
         if (element != null) {
           element = element[0];
         }
         break;
+
       case ANON_ATTRIBUTE:
         let attr = Object.keys(value)[0];
         element = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
         break;
+
       default:
         throw new InvalidSelectorError("No such strategy: " + using);
     }
+
     return element;
   },
 
   /**
    * Helper method to find. Finds all element using find's criteria
    *
    * @param string using
    *        String identifying which search method to use