Bug 1080764 - Add 'anon' and 'anon attribute' search strategies to marionette.find_element(), r=AutomatedTester
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Tue, 28 Oct 2014 17:37:24 -0400
changeset 212934 8bde606f4d52b70018791e502d76f70b5542ee49
parent 212933 75de7e0fe0867833d3d16c257e1ce74226ed82e5
child 212935 42da94fcdfd9ec198404948488257354d41dc94e
push id27736
push userryanvm@gmail.com
push dateWed, 29 Oct 2014 20:49:13 +0000
treeherdermozilla-central@80e18ff7c7b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersAutomatedTester
bugs1080764
milestone36.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 1080764 - Add 'anon' and 'anon attribute' search strategies to marionette.find_element(), r=AutomatedTester The 'anon' search strategy maps directly to nsIXULDocument.getAnonymousNodes() whereas the 'anon attribute' strategy maps to nsIXULDocument.getAnonymousElementByAttribute(). These strategies are needed for clients who wish to find and manipulate anonymous content, typically found in the Firefox chrome. For more details, see: https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/Anonymous_Content
testing/marionette/client/marionette/marionette.py
testing/marionette/client/marionette/tests/unit/test_anonymous_content.py
testing/marionette/client/marionette/tests/unit/test_switch_anonymous_content.py
testing/marionette/client/marionette/tests/unit/unit-tests.ini
testing/marionette/marionette-elements.js
--- a/testing/marionette/client/marionette/marionette.py
+++ b/testing/marionette/client/marionette/marionette.py
@@ -1276,24 +1276,25 @@ class Marionette(object):
         An HTMLElement instance may be used to call other methods on the
         element, such as click().  If no element is immediately found, the
         attempt to locate an element will be repeated for up to the amount of
         time set by set_search_timeout(). If multiple elements match the given
         criteria, only the first is returned. If no element matches, a
         NoSuchElementException will be raised.
 
         :param method: The method to use to locate the element; one of: "id",
-         "name", "class name", "tag name", "css selector", "link text",
-         "partial link text" and "xpath". Note that the methods supported in
-         the chrome dom are only "id", "class name", "tag name" and "xpath".
+                       "name", "class name", "tag name", "css selector", "link text",
+                       "partial link text", "xpath", "anon" and "anon attribute".
+                       Note that the "name", "css selector", "link text" and
+                       "partial link test" methods are not supported in the chrome dom.
         :param target: The target of the search.  For example, if method =
-         "tag", target might equal "div".  If method = "id", target would be
-         an element id.
+                       "tag", target might equal "div".  If method = "id", target would be
+                       an element id.
         :param id: If specified, search for elements only inside the element
-         with the specified id.
+                   with the specified id.
         '''
         kwargs = { 'value': target, 'using': method }
         if id:
             kwargs['element'] = id
         response = self._send_message('findElement', 'value', **kwargs)
         element = HTMLElement(self, response['ELEMENT'])
         return element
 
@@ -1302,24 +1303,25 @@ class Marionette(object):
         Returns a list of all HTMLElement instances that match the specified method and target in the current context.
 
         An HTMLElement instance may be used to call other methods on the
         element, such as click().  If no element is immediately found, the
         attempt to locate an element will be repeated for up to the amount of
         time set by set_search_timeout().
 
         :param method: The method to use to locate the elements; one of:
-         "id", "name", "class name", "tag name", "css selector", "link text",
-         "partial link text" and "xpath". Note that the methods supported in
-         the chrome dom are only "id", "class name", "tag name" and "xpath".
+                       "id", "name", "class name", "tag name", "css selector", "link text",
+                       "partial link text", "xpath", "anon" and "anon attribute".
+                       Note that the "name", "css selector", "link text" and
+                       "partial link test" methods are not supported in the chrome dom.
         :param target: The target of the search.  For example, if method =
-         "tag", target might equal "div".  If method = "id", target would be
-         an element id.
+                       "tag", target might equal "div".  If method = "id", target would be
+                       an element id.
         :param id: If specified, search for elements only inside the element
-         with the specified id.
+                   with the specified id.
         '''
         kwargs = { 'value': target, 'using': method }
         if id:
             kwargs['element'] = id
         response = self._send_message('findElements', 'value', **kwargs)
         assert(isinstance(response, list))
         elements = []
         for x in response:
rename from testing/marionette/client/marionette/tests/unit/test_switch_anonymous_content.py
rename to testing/marionette/client/marionette/tests/unit/test_anonymous_content.py
--- a/testing/marionette/client/marionette/tests/unit/test_switch_anonymous_content.py
+++ b/testing/marionette/client/marionette/tests/unit/test_anonymous_content.py
@@ -1,30 +1,52 @@
 # 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/.
 
+from marionette import HTMLElement
 from marionette_test import MarionetteTestCase
-from errors import JavascriptException, NoSuchElementException
+from errors import NoSuchElementException
+from expected import element_present
+from wait import Wait
 
-class TestSwitchFrameChrome(MarionetteTestCase):
+class TestAnonymousContent(MarionetteTestCase):
     def setUp(self):
         MarionetteTestCase.setUp(self)
         self.marionette.set_context("chrome")
         self.win = self.marionette.current_window_handle
         self.marionette.execute_script("window.open('chrome://marionette/content/test_anonymous_content.xul', 'foo', 'chrome,centerscreen');")
         self.marionette.switch_to_window('foo')
         self.assertNotEqual(self.win, self.marionette.current_window_handle)
 
     def tearDown(self):
         self.assertNotEqual(self.win, self.marionette.current_window_handle)
         self.marionette.execute_script("window.close();")
         self.marionette.switch_to_window(self.win)
         MarionetteTestCase.tearDown(self)
 
-    def test_switch(self):
+    def test_switch_to_anonymous_frame(self):
         self.marionette.find_element("id", "testAnonymousContentBox")
         anon_browser_el = self.marionette.find_element("id", "browser")
         self.assertTrue("test_anonymous_content.xul" in self.marionette.get_url())
         self.marionette.switch_to_frame(anon_browser_el)
         self.assertTrue("test.xul" in self.marionette.get_url())
         self.marionette.find_element("id", "testXulBox")
         self.assertRaises(NoSuchElementException, self.marionette.find_element, "id", "testAnonymousContentBox")
+
+    def test_find_anonymous_element_by_attribute(self):
+        el = Wait(self.marionette).until(element_present("id", "dia"))
+        self.assertEquals(HTMLElement, type(el.find_element("anon attribute", {"anonid": "buttons"})))
+        self.assertEquals(1, len(el.find_elements("anon attribute", {"anonid": "buttons"})))
+
+        with self.assertRaises(NoSuchElementException):
+            el.find_element("anon attribute", {"anonid": "nonexistent"})
+        self.assertEquals([], el.find_elements("anon attribute", {"anonid": "nonexistent"}))
+
+    def test_find_anonymous_children(self):
+        el = Wait(self.marionette).until(element_present("id", "dia"))
+        self.assertEquals(HTMLElement, type(el.find_element("anon", None)))
+        self.assertEquals(2, len(el.find_elements("anon", None)))
+
+        el = self.marionette.find_element("id", "framebox")
+        with self.assertRaises(NoSuchElementException):
+            el.find_element("anon", None)
+        self.assertEquals([], el.find_elements("anon", None))
--- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini
@@ -82,17 +82,17 @@ disabled = "Bug 1060060"
 b2g = true
 browser = false
 
 [test_simpletest_pass.js]
 [test_simpletest_sanity.py]
 [test_simpletest_chrome.js]
 [test_simpletest_timeout.js]
 [test_specialpowers.py]
-[test_switch_anonymous_content.py]
+[test_anonymous_content.py]
 [test_switch_frame.py]
 b2g = false
 skip-if = os == "win" # Bug 1078237
 [test_switch_frame_chrome.py]
 b2g = false
 [test_switch_remote_frame.py]
 b2g = false
 
--- a/testing/marionette/marionette-elements.js
+++ b/testing/marionette/marionette-elements.js
@@ -15,43 +15,47 @@ this.EXPORTED_SYMBOLS = [
   "ElementManager",
   "CLASS_NAME",
   "SELECTOR",
   "ID",
   "NAME",
   "LINK_TEXT",
   "PARTIAL_LINK_TEXT",
   "TAG",
-  "XPATH"
+  "XPATH",
+  "ANON",
+  "ANON_ATTRIBUTE"
 ];
 
 const DOCUMENT_POSITION_DISCONNECTED = 1;
 
 let uuidGen = Components.classes["@mozilla.org/uuid-generator;1"]
              .getService(Components.interfaces.nsIUUIDGenerator);
 
 this.CLASS_NAME = "class name";
 this.SELECTOR = "css selector";
 this.ID = "id";
 this.NAME = "name";
 this.LINK_TEXT = "link text";
 this.PARTIAL_LINK_TEXT = "partial link text";
 this.TAG = "tag name";
 this.XPATH = "xpath";
+this.ANON= "anon";
+this.ANON_ATTRIBUTE = "anon attribute";
 
 function ElementException(msg, num, stack) {
   this.message = msg;
   this.code = num;
   this.stack = stack;
 }
 
 this.ElementManager = function ElementManager(notSupported) {
   this.seenItems = {};
   this.timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
-  this.elementStrategies = [CLASS_NAME, SELECTOR, ID, NAME, LINK_TEXT, PARTIAL_LINK_TEXT, TAG, XPATH];
+  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);
   }
 }
 
 ElementManager.prototype = {
   /**
    * Reset values
@@ -304,17 +308,24 @@ ElementManager.prototype = {
       }
       else {
         let id = this.addToKnownElements(found);
         on_success({'ELEMENT':id}, command_id);
       }
       return;
     } else {
       if (!searchTimeout || new Date().getTime() - startTime > searchTimeout) {
-        on_error("Unable to locate element: " + values.value, 7, null, command_id);
+        // Format message depending on strategy if necessary
+        let message = "Unable to locate element: " + values.value;
+        if (values.using == ANON) {
+          message = "Unable to locate anonymous children";
+        } else if (values.using == ANON_ATTRIBUTE) {
+          message = "Unable to locate anonymous element: " + JSON.stringify(values.value);
+        }
+        on_error(message, 7, null, command_id);
       } else {
         values.time = startTime;
         this.timer.initWithCallback(this.find.bind(this, win, values,
                                                    searchTimeout,
                                                    on_success, on_error, all,
                                                    command_id),
                                     100,
                                     Components.interfaces.nsITimer.TYPE_ONE_SHOT);
@@ -414,16 +425,26 @@ ElementManager.prototype = {
           } else if (text == value) {
             element = allLinks[i];
           }
         }
         break;
       case SELECTOR:
         element = startNode.querySelector(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 ElementException("No such strategy", 500, null);
     }
     return element;
   },
 
   /**
    * Helper method to find. Finds all element using find's criteria
@@ -471,14 +492,24 @@ ElementManager.prototype = {
           } else if (text == value) {
             elements.push(allLinks[i]);
           }
         }
         break;
       case SELECTOR:
         elements = Array.slice(startNode.querySelectorAll(value));
         break;
+      case ANON:
+        elements = rootNode.getAnonymousNodes(startNode) || [];
+        break;
+      case ANON_ATTRIBUTE:
+        let attr = Object.keys(value)[0];
+        let el = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
+        if (el != null) {
+          elements = [el];
+        }
+        break;
       default:
         throw new ElementException("No such strategy", 500, null);
     }
     return elements;
   },
 }