Bug 1321516 - Test element in view with pointer events enabled draft
authorAndreas Tolfsen <ato@mozilla.com>
Mon, 17 Apr 2017 19:34:44 +0100
changeset 565896 218bf7c82a8b43f014413d2bf2b3563798763014
parent 565895 2af563e0ec35e72bf37abd7926be06d8add02f88
child 565897 8291f120dc37813b27767e552fe50a80cd7b7c95
push id55051
push userbmo:ato@mozilla.com
push dateThu, 20 Apr 2017 17:08:50 +0000
bugs1321516
milestone55.0a1
Bug 1321516 - Test element in view with pointer events enabled If an element has its style property pointer-events set to "none", the element in view test will fail due to document.elementsFromPoint excluding it due to non-interactability. This is only a problem when checking if the element is inside the viewport, and not when actually performing interaction with the element. Proposed specification change in https://github.com/w3c/webdriver/pull/896. MozReview-Commit-ID: JwZB6fm7P9A
testing/marionette/element.js
testing/marionette/error.js
testing/marionette/harness/marionette_harness/tests/unit/test_click.py
testing/marionette/test_error.js
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -878,25 +878,37 @@ element.getContainer = function (el) {
 /**
  * An element is in view if it is a member of its own pointer-interactable
  * paint tree.
  *
  * This means an element is considered to be in view, but not necessarily
  * pointer-interactable, if it is found somewhere in the
  * |elementsFromPoint| list at |el|'s in-view centre coordinates.
  *
+ * Before running the check, we change |el|'s pointerEvents style property
+ * to "auto", since elements without pointer events enabled do not turn
+ * up in the paint tree we get from document.elementsFromPoint.  This is
+ * a specialisation that is only relevant when checking if the element is
+ * in view.
+ *
  * @param {Element} el
  *     Element to check if is in view.
  *
  * @return {boolean}
  *     True if |el| is inside the viewport, or false otherwise.
  */
 element.isInView = function (el) {
-  let tree = element.getPointerInteractablePaintTree(el);
-  return tree.includes(el);
+  let originalPointerEvents = el.style.pointerEvents;
+  try {
+    el.style.pointerEvents = "auto";
+    const tree = element.getPointerInteractablePaintTree(el);
+    return tree.includes(el);
+  } finally {
+    el.style.pointerEvents = originalPointerEvents;
+  }
 };
 
 // TODO(ato): Only used by deprecated action API
 /**
  * This function throws the visibility of the element error if the element is
  * not displayed or the given coordinates are not within the viewport.
  *
  * @param {Element} el
--- a/testing/marionette/error.js
+++ b/testing/marionette/error.js
@@ -266,20 +266,33 @@ class ElementNotAccessibleError extends 
  *     will produce a nicer error message.
  */
 class ElementClickInterceptedError extends WebDriverError {
   constructor (obscuredEl = undefined, coords = undefined) {
     let msg = "";
     if (obscuredEl && coords) {
       const doc = obscuredEl.ownerDocument;
       const overlayingEl = doc.elementFromPoint(coords.x, coords.y);
-      msg = error.pprint`Element ${obscuredEl} is not clickable ` +
-          `at point (${coords.x},${coords.y}) ` +
-          error.pprint`because another element ${overlayingEl} ` +
-          `obscures it`;
+
+      switch (obscuredEl.style.pointerEvents) {
+        case "none":
+          msg = error.pprint`Element ${obscuredEl} is not clickable ` +
+              `at point (${coords.x},${coords.y}) ` +
+              `because it does not have pointer events enabled, ` +
+              error.pprint`and element ${overlayingEl} ` +
+              `would receive the click instead`;
+          break;
+
+        default:
+          msg = error.pprint`Element ${obscuredEl} is not clickable ` +
+              `at point (${coords.x},${coords.y}) ` +
+              error.pprint`because another element ${overlayingEl} ` +
+              `obscures it`;
+          break;
+      }
     }
 
     super(msg);
     this.status = "element click intercepted";
   }
 }
 
 class ElementNotInteractableError extends WebDriverError {
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_click.py
@@ -255,8 +255,24 @@ class TestClick(TestLegacyClick):
         self.marionette.navigate(obscured_overlay)
         overlay = self.marionette.find_element(By.ID, "overlay")
         obscured = self.marionette.find_element(By.ID, "obscured")
 
         overlay.click()
         with self.assertRaises(errors.ElementClickInterceptedException):
             obscured.click()
         self.assertFalse(self.marionette.execute_script("return window.clicked", sandbox=None))
+
+    def test_pointer_events_none(self):
+        self.marionette.navigate(inline("""
+            <button style="pointer-events: none">click me</button>
+            <script>
+              window.clicks = 0;
+              let button = document.querySelector("button");
+              button.addEventListener("click", () => window.clicks++);
+            </script>
+        """))
+        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()
--- a/testing/marionette/test_error.js
+++ b/testing/marionette/test_error.js
@@ -204,25 +204,35 @@ add_test(function test_ElementClickInter
     nodeType: 1,
     localName: "b",
     classList: [],
     ownerDocument: {
       elementFromPoint: function (x, y) {
         return otherEl;
       },
     },
+    style: {
+      pointerEvents: "auto",
+    }
   };
 
-  let err = new ElementClickInterceptedError(obscuredEl, {x: 1, y: 2});
-  equal("ElementClickInterceptedError", err.name);
+  let err1 = new ElementClickInterceptedError(obscuredEl, {x: 1, y: 2});
+  equal("ElementClickInterceptedError", err1.name);
   equal("Element <b> is not clickable at point (1,2) " +
       "because another element <a> obscures it",
-      err.message);
-  equal("element click intercepted", err.status);
-  ok(err instanceof WebDriverError);
+      err1.message);
+  equal("element click intercepted", err1.status);
+  ok(err1 instanceof WebDriverError);
+
+  obscuredEl.style.pointerEvents = "none";
+  let err2 = new ElementClickInterceptedError(obscuredEl, {x: 1, y: 2});
+  equal("Element <b> is not clickable at point (1,2) " +
+      "because it does not have pointer events enabled, " +
+      "and element <a> would receive the click instead",
+      err2.message);
 
   run_next_test();
 });
 
 add_test(function test_ElementNotAccessibleError() {
   let err = new ElementNotAccessibleError("foo");
   equal("ElementNotAccessibleError", err.name);
   equal("foo", err.message);