Bug 1204504 - Error on invalid selector; r=automatedtester
authorAndreas Tolfsen <ato@mozilla.com>
Thu, 17 Sep 2015 12:31:39 +0100
changeset 322107 d7025fe68e90e96ab2da2dbcd7688c1aec45ee2f
parent 322106 3d8d87127bc2383d2c9380f6af19b53752b9acf5
child 322108 73b822abb3e83943b4df17542c19c330cca66ad8
push id9530
push userdmitchell@mozilla.com
push dateFri, 15 Jan 2016 19:42:24 +0000
reviewersautomatedtester
bugs1204504
milestone46.0a1
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