Bug 1204504 - Error on invalid selector; r=automatedtester
Thanks to Alexei Barantsev for reporting.
--- 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