Bug 1359079 - Take <select multiple> into account for obscured click test; r?whimboo draft
authorAndreas Tolfsen <ato@mozilla.com>
Mon, 17 Apr 2017 17:24:19 +0100
changeset 569880 e89062bc2a3e98f55a285b7a90402a2e96b728ac
parent 569701 2cca333f546f38860f84940d4c72d7470a3410f4
child 626314 47bab7c2ce1bea6f90e67b905dabacbd0ed09781
push id56293
push userbmo:ato@mozilla.com
push dateThu, 27 Apr 2017 23:32:11 +0000
reviewerswhimboo
bugs1359079
milestone55.0a1
Bug 1359079 - Take <select multiple> into account for obscured click test; r?whimboo Because individual <option> elements are painted and represented in the DOM when they belong to a <select multiple> list, the center point of the list might be one of the options. To take this into account, we perform an inclusive descendant check (DOMElement.contains) to see if the <option> element is a descendant of the container <select> element. In the case the targetted element is the element itself, the test will still pass since it is an _inclusive_ descendant check. In other words, containerEl.contains(tree[0]), if tree[0] is equal to containerEl, will pass. The relevant specification changes were made in https://github.com/w3c/webdriver/pull/894/commits/40abcefd6acb86ac64befe2cad4b729b5e566932. MozReview-Commit-ID: ORX8zLxQJ
testing/marionette/element.js
testing/marionette/harness/marionette_harness/tests/unit/test_click.py
testing/marionette/interaction.js
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -944,25 +944,29 @@ element.isVisible = function (el, x = un
 };
 
 /**
  * A pointer-interactable element is defined to be the first
  * non-transparent element, defined by the paint order found at the centre
  * point of its rectangle that is inside the viewport, excluding the size
  * of any rendered scrollbars.
  *
+ * An element is obscured if the pointer-interactable paint tree at its
+ * centre point is empty, or the first element in this tree is not an
+ * inclusive descendant of itself.
+ *
  * @param {DOMElement} el
  *     Element determine if is pointer-interactable.
  *
  * @return {boolean}
- *     True if interactable, false otherwise.
+ *     True if element is obscured, false otherwise.
  */
-element.isPointerInteractable = function (el) {
+element.isObscured = function (el) {
   let tree = element.getPointerInteractablePaintTree(el);
-  return tree[0] === el;
+  return !el.contains(tree[0]);
 };
 
 /**
  * Calculate the in-view centre point of the area of the given DOM client
  * rectangle that is inside the viewport.
  *
  * @param {DOMRect} rect
  *     Element off a DOMRect sequence produced by calling |getClientRects|
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
@@ -264,16 +264,34 @@ class TestClick(TestLegacyClick):
         button = self.marionette.find_element(By.TAG_NAME, "button")
         self.assertEqual("none", button.value_of_css_property("pointer-events"))
 
         with self.assertRaisesRegexp(errors.ElementClickInterceptedException,
                                      "does not have pointer events enabled"):
             button.click()
         self.assertFalse(self.marionette.execute_script("return window.clicked", sandbox=None))
 
+    def test_inclusive_descendant(self):
+        self.marionette.navigate(inline("""
+            <select multiple>
+              <option>first
+              <option>second
+              <option>third
+             </select>"""))
+        select = self.marionette.find_element(By.TAG_NAME, "select")
+
+        # This tests that the pointer-interactability test does not
+        # cause an ElementClickInterceptedException.
+        #
+        # At a <select multiple>'s in-view centre point, you might
+        # find a fully rendered <option>.  Marionette should test that
+        # the paint tree at this point _contains_ <option>, not that the
+        # first element of the paint tree is _equal_ to <select>.
+        select.click()
+
 
 class TestClickNavigation(MarionetteTestCase):
 
     def setUp(self):
         super(TestClickNavigation, self).setUp()
 
         self.test_page = self.marionette.absolute_url("clicks.html")
         self.marionette.navigate(self.test_page)
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -152,17 +152,17 @@ function* webdriverClickElement (el, a11
     throw new ElementNotInteractableError(
         error.pprint`Element ${el} could not be scrolled into view`);
   }
 
   // step 7
   let rects = containerEl.getClientRects();
   let clickPoint = element.getInViewCentrePoint(rects[0], win);
 
-  if (!element.isPointerInteractable(containerEl)) {
+  if (element.isObscured(containerEl)) {
     throw new ElementClickInterceptedError(containerEl, clickPoint);
   }
 
   yield a11y.getAccessible(el, true).then(acc => {
     a11y.assertVisible(acc, el, true);
     a11y.assertEnabled(acc, el, true);
     a11y.assertActionable(acc, el);
   });