Bug 1447415 [wpt PR 10113] - Automated Keyboard Input, a=testonly
authorJonathon Kereliuk <kereliuk@google.com>
Mon, 09 Apr 2018 22:00:32 +0000
changeset 467261 bf7432ecbe73a71b94693a43f3f59cb182fae9d6
parent 467260 633315e643cb927070109e6521522a742767b7b4
child 467262 5267a58d7b2533d3b516ebb31202aada7edd222d
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1447415, 10113
milestone61.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 1447415 [wpt PR 10113] - Automated Keyboard Input, a=testonly Automatic update from web-platform-testsAutomated Keyboard Input (#10113) wpt-commits: 8f93a78175a1c98e6d36bd8293ff5525edd0da91 wpt-pr: 10113 wpt-commits: 8f93a78175a1c98e6d36bd8293ff5525edd0da91 wpt-pr: 10113
testing/web-platform/meta/MANIFEST.json
testing/web-platform/tests/docs/_writing-tests/testdriver.md
testing/web-platform/tests/html/editing/focus/focus-01-manual.html
testing/web-platform/tests/html/editing/focus/focus-01.html
testing/web-platform/tests/infrastructure/testdriver/send_keys.html
testing/web-platform/tests/resources/testdriver.js
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/base.py
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorselenium.py
testing/web-platform/tests/tools/wptrunner/wptrunner/executors/protocol.py
testing/web-platform/tests/tools/wptrunner/wptrunner/testdriver-extra.js
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -14192,22 +14192,16 @@
     ]
    ],
    "html/editing/dnd/the-dropzone-attribute/dropzone_attribute_value_unordered_unique_space_separated-manual.html": [
     [
      "/html/editing/dnd/the-dropzone-attribute/dropzone_attribute_value_unordered_unique_space_separated-manual.html",
      {}
     ]
    ],
-   "html/editing/focus/focus-01-manual.html": [
-    [
-     "/html/editing/focus/focus-01-manual.html",
-     {}
-    ]
-   ],
    "html/editing/focus/focus-02-manual.html": [
     [
      "/html/editing/focus/focus-02-manual.html",
      {}
     ]
    ],
    "html/editing/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative-manual.html": [
     [
@@ -330385,16 +330379,24 @@
     ]
    ],
    "html/editing/focus/document-level-focus-apis/document-level-apis.html": [
     [
      "/html/editing/focus/document-level-focus-apis/document-level-apis.html",
      {}
     ]
    ],
+   "html/editing/focus/focus-01.html": [
+    [
+     "/html/editing/focus/focus-01.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "html/editing/focus/focus-management/focus-event-targets-simple.html": [
     [
      "/html/editing/focus/focus-management/focus-event-targets-simple.html",
      {}
     ]
    ],
    "html/editing/focus/focus-management/focus-events.html": [
     [
@@ -339639,16 +339641,24 @@
    "infrastructure/testdriver/click.html": [
     [
      "/infrastructure/testdriver/click.html",
      {
       "testdriver": true
      }
     ]
    ],
+   "infrastructure/testdriver/send_keys.html": [
+    [
+     "/infrastructure/testdriver/send_keys.html",
+     {
+      "testdriver": true
+     }
+    ]
+   ],
    "innerText/getter.html": [
     [
      "/innerText/getter.html",
      {}
     ]
    ],
    "innerText/multiple-text-nodes.window.js": [
     [
@@ -547297,17 +547307,17 @@
    "c9ed5970c091c7fe7aa16847b8c68783b133a25a",
    "support"
   ],
   "docs/_writing-tests/submission-process.md": [
    "62747b6d0328445778050f3e4d6ec46dbdc3a18c",
    "support"
   ],
   "docs/_writing-tests/testdriver.md": [
-   "72383e28f07286308a7c6233535536a9a4edeb5e",
+   "d7713d27281ec3a11641751ced787771bebb581f",
    "support"
   ],
   "docs/_writing-tests/testharness-api.md": [
    "5fa854f33ee9141f2226fa1e1b4e5aa44c3b75c7",
    "support"
   ],
   "docs/_writing-tests/testharness.md": [
    "569eef734f09d83c9a30eac5529087dd43e802ba",
@@ -563056,19 +563066,19 @@
   "html/editing/focus/document-level-focus-apis/test.html": [
    "a40de555605563e0cfc7492970d8555bfcd8c78d",
    "support"
   ],
   "html/editing/focus/element-level-focus-apis/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
   ],
-  "html/editing/focus/focus-01-manual.html": [
-   "de1a12925eabf37f14016bf48dd16c55732f87dc",
-   "manual"
+  "html/editing/focus/focus-01.html": [
+   "e41cfdeaf3e9ddeed0450298212443bfcad8298d",
+   "testharness"
   ],
   "html/editing/focus/focus-02-manual.html": [
    "adbca2e8e3cdaa58c33d452cf153fd70013c9f4b",
    "manual"
   ],
   "html/editing/focus/focus-management/.gitkeep": [
    "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "support"
@@ -574052,16 +574062,20 @@
   "infrastructure/reftest-wait.html": [
    "1a291b68cdf6edcfc28a2ff22e294e8e8ebc0c42",
    "reftest"
   ],
   "infrastructure/testdriver/click.html": [
    "afb1a08faa639bdd1ee4387069d76803c5e38d54",
    "testharness"
   ],
+  "infrastructure/testdriver/send_keys.html": [
+   "f2ede647416d150d7cd3720719f386b9f4d5fb6c",
+   "testharness"
+  ],
   "innerText/getter-tests.js": [
    "42f3e48763297d3360c1074a7c2b706a6f539d37",
    "support"
   ],
   "innerText/getter.html": [
    "bfcd91955e1645278f7825d472bcce03dbc8f773",
    "testharness"
   ],
--- a/testing/web-platform/tests/docs/_writing-tests/testdriver.md
+++ b/testing/web-platform/tests/docs/_writing-tests/testdriver.md
@@ -14,21 +14,36 @@ tests.
 
 testdriver.js exposes its API through the `test_driver` variable in
 the global scope.
 
 NB: presently, testdriver.js only works in the top-level test browsing
 context (and not therefore in any frame or window opened from it).
 
 ### `test_driver.click(element)`
+#### `element: a DOM Element object`
 
 This function causes a click to occur on the target element (an
 `Element` object), potentially scrolling the document to make it
 possible to click it. It returns a `Promise` that resolves after the
 click has occured or rejects if the element cannot be clicked (for
 example, it is obscured by an element on top of it).
 
 Note that if the element to be clicked does not have a unique ID, the
 document must not have any DOM mutations made between the function
 being called and the promise settling.
 
+### `test_driver.send_keys(element, keys)`
+#### `element: a DOM Element object`
+#### `keys: string to send to the element`
+
+This function causes the string `keys` to be send to the target
+element (an `Element` object), potentially scrolling the document to
+make it possible to send keys. It returns a `Promise` that resolves
+after the keys have been send or rejects if the keys cannot be sent
+to the element.
+
+Note that if the element that's keys need to be send to does not have
+a unique ID, the document must not have any DOM mutations made
+between the function being called and the promise settling.
+
 
 [testharness]: {{ site.baseurl }}{% link _writing-tests/testharness.md %}
deleted file mode 100644
--- a/testing/web-platform/tests/html/editing/focus/focus-01-manual.html
+++ /dev/null
@@ -1,43 +0,0 @@
-<!DOCTYPE html>
-<meta charset="utf-8">
-<title>HTML Test: focus - key events</title>
-<link rel="author" title="Intel" href="http://www.intel.com/">
-<link rel="help" href="https://html.spec.whatwg.org/multipage/#focus">
-<meta assert="flag" content="interact">
-<meta assert="assert" content="Check if the key events received by document are targeted at the element when it is focused">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<h2>Steps:</h2>
-<ol>
-  <li>Input any character into the textbox by keyboard in 10 seconds.</li>
-</ol>
-<h2>Expect results:</h2>
-<p>PASS</p>
-<div id="log"></div>
-<input id="test">
-<script>
-
-//These tests can be automated once we have an uniform way to use webdriver.
-var t1 = async_test("The keydown event must be targeted at the input element"),
-    t2 = async_test("The keypress event must be targeted at the input element"),
-    t3 = async_test("The keyup event must be targeted at the input element"),
-    testEle;
-
-setup(function () {
-  testEle = document.getElementById("test");
-  testEle.focus();
-}, {timeout: 10000});
-
-document.onkeydown = t1.step_func_done(function(evt){
-  assert_equals(evt.target, testEle, "The keydown events must be targeted at the input element.");
-});
-
-document.onkeypress = t2.step_func_done(function(evt){
-  assert_equals(evt.target, testEle, "The keypress events must be targeted at the input element.");
-});
-
-document.onkeyup = t3.step_func_done(function(evt){
-  assert_equals(evt.target, testEle, "The keyup events must be targeted at the input element.");
-});
-
-</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/editing/focus/focus-01.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>HTML Test: focus - key events</title>
+<link rel="author" title="Intel" href="http://www.intel.com/">
+<link rel="help" href="https://html.spec.whatwg.org/multipage/#focus">
+<meta assert="flag" content="interact">
+<meta assert="assert" content="Check if the key events received by document are targeted at the element when it is focused">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<div id="log"></div>
+<input id="test">
+<script>
+
+//These tests can be automated once we have an uniform way to use webdriver.
+var t1 = async_test("The keydown event must be targeted at the input element"),
+    t2 = async_test("The keypress event must be targeted at the input element"),
+    t3 = async_test("The keyup event must be targeted at the input element"),
+    testEle;
+
+setup(function () {
+  testEle = document.getElementById("test");
+  testEle.focus();
+}, {timeout: 10000});
+
+document.onkeydown = t1.step_func_done(function(evt){
+  assert_equals(evt.target, testEle, "The keydown events must be targeted at the input element.");
+});
+
+document.onkeypress = t2.step_func_done(function(evt){
+  assert_equals(evt.target, testEle, "The keypress events must be targeted at the input element.");
+});
+
+document.onkeyup = t3.step_func_done(function(evt){
+  assert_equals(evt.target, testEle, "The keyup events must be targeted at the input element.");
+});
+
+var input_element = document.getElementById("test");
+
+t1.step(function() {
+  test_driver.send_keys(input_element, "a");
+});
+
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/infrastructure/testdriver/send_keys.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>TestDriver send keys method</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<input type="text" id="text">Text Input</button>
+
+<script>
+async_test(t => {
+  let input_text = "Hello, wpt!";
+  let text_box = document.getElementById("text");
+  test_driver
+    .send_keys(text_box, input_text)
+    .then(() => {
+      assert_true(text_box.value == input_text);
+      t.done();
+    })
+    .catch(t.unreached_func("send keys failed"));
+});
+</script>
--- a/testing/web-platform/tests/resources/testdriver.js
+++ b/testing/web-platform/tests/resources/testdriver.js
@@ -78,24 +78,71 @@
                 return Promise.reject(new Error("element click intercepted error"));
             }
 
             var rect = element.getClientRects()[0];
             var centerPoint = getInViewCenterPoint(rect);
             return window.test_driver_internal.click(element,
                                                      {x: centerPoint[0],
                                                       y: centerPoint[1]});
+        },
+
+        /**
+         * Send keys to an element
+         *
+         * This matches the behaviour of the {@link
+         * https://w3c.github.io/webdriver/webdriver-spec.html#element-send-keys|WebDriver
+         * Send Keys command}.
+         *
+         * @param {Element} element - element to send keys to
+         * @param {String} keys - keys to send to the element
+         * @returns {Promise} fulfilled after keys are sent, or rejected in
+         *                    the cases the WebDriver command errors
+         */
+        send_keys: function(element, keys) {
+            if (window.top !== window) {
+                return Promise.reject(new Error("can only send keys in top-level window"));
+            }
+
+            if (!window.document.contains(element)) {
+                return Promise.reject(new Error("element in different document or shadow tree"));
+            }
+
+            if (!inView(element)) {
+                element.scrollIntoView({behavior: "instant",
+                                        block: "end",
+                                        inline: "nearest"});
+            }
+
+            var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
+            if (pointerInteractablePaintTree.length === 0 ||
+                !element.contains(pointerInteractablePaintTree[0])) {
+                return Promise.reject(new Error("element send_keys intercepted error"));
+            }
+
+            return window.test_driver_internal.send_keys(element, keys);
         }
     };
 
     window.test_driver_internal = {
         /**
          * Triggers a user-initated click
          *
          * @param {Element} element - element to be clicked
          * @param {{x: number, y: number} coords - viewport coordinates to click at
          * @returns {Promise} fulfilled after click occurs or rejected if click fails
          */
         click: function(element, coords) {
             return Promise.reject(new Error("unimplemented"));
+        },
+
+        /**
+         * Triggers a user-initated click
+         *
+         * @param {Element} element - element to be clicked
+         * @param {String} keys - keys to send to the element
+         * @returns {Promise} fulfilled after keys are sent or rejected if click fails
+         */
+        send_keys: function(element, keys) {
+            return Promise.reject(new Error("unimplemented"));
         }
     };
 })();
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/base.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/base.py
@@ -496,17 +496,18 @@ class CallbackHandler(object):
         self.test_window = test_window
         self.logger = logger
         self.callbacks = {
             "action": self.process_action,
             "complete": self.process_complete
         }
 
         self.actions = {
-            "click": ClickAction(self.logger, self.protocol)
+            "click": ClickAction(self.logger, self.protocol),
+            "send_keys": SendKeysAction(self.logger, self.protocol)
         }
 
     def __call__(self, result):
         url, command, payload = result
         self.logger.debug("Got async callback: %s" % result[1])
         try:
             callback = self.callbacks[command]
         except KeyError:
@@ -539,23 +540,38 @@ class CallbackHandler(object):
         finally:
             self.protocol.base.set_window(parent)
 
         return False, None
 
     def _send_message(self, message_type, status, message=None):
         self.protocol.testdriver.send_message(message_type, status, message=message)
 
-
 class ClickAction(object):
     def __init__(self, logger, protocol):
         self.logger = logger
         self.protocol = protocol
 
     def __call__(self, payload):
         selector = payload["selector"]
         elements = self.protocol.select.elements_by_selector(selector)
         if len(elements) == 0:
             raise ValueError("Selector matches no elements")
         elif len(elements) > 1:
             raise ValueError("Selector matches multiple elements")
         self.logger.debug("Clicking element: %s" % selector)
         self.protocol.click.element(elements[0])
+
+class SendKeysAction(object):
+    def __init__(self, logger, protocol):
+        self.logger = logger
+        self.protocol = protocol
+
+    def __call__(self, payload):
+        selector = payload["selector"]
+        keys = payload["keys"]
+        elements = self.protocol.select.elements_by_selector(selector)
+        if len(elements) == 0:
+            raise ValueError("Selector matches no elements")
+        elif len(elements) > 1:
+            raise ValueError("Selector matches multiple elements")
+        self.logger.debug("Sending keys to element: %s" % selector)
+        self.protocol.send_keys.send_keys(elements[0], keys)
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executormarionette.py
@@ -27,16 +27,17 @@ from .base import (CallbackHandler,
                    strip_server)
 from .protocol import (BaseProtocolPart,
                        TestharnessProtocolPart,
                        PrefsProtocolPart,
                        Protocol,
                        StorageProtocolPart,
                        SelectorProtocolPart,
                        ClickProtocolPart,
+                       SendKeysProtocolPart,
                        TestDriverProtocolPart)
 from ..testrunner import Stop
 from ..webdriver_server import GeckoDriverServer
 
 
 def do_delayed_imports():
     global errors, marionette
 
@@ -302,16 +303,22 @@ class MarionetteSelectorProtocolPart(Sel
 
 class MarionetteClickProtocolPart(ClickProtocolPart):
     def setup(self):
         self.marionette = self.parent.marionette
 
     def element(self, element):
         return element.click()
 
+class MarionetteSendKeysProtocolPart(SendKeysProtocolPart):
+    def setup(self):
+        self.marionette = self.parent.marionette
+
+    def send_keys(self, element, keys):
+        return element.send_keys(keys)
 
 class MarionetteTestDriverProtocolPart(TestDriverProtocolPart):
     def setup(self):
         self.marionette = self.parent.marionette
 
     def send_message(self, message_type, status, message=None):
         obj = {
             "type": "testdriver-%s" % str(message_type),
@@ -324,16 +331,17 @@ class MarionetteTestDriverProtocolPart(T
 
 class MarionetteProtocol(Protocol):
     implements = [MarionetteBaseProtocolPart,
                   MarionetteTestharnessProtocolPart,
                   MarionettePrefsProtocolPart,
                   MarionetteStorageProtocolPart,
                   MarionetteSelectorProtocolPart,
                   MarionetteClickProtocolPart,
+                  MarionetteSendKeysProtocolPart,
                   MarionetteTestDriverProtocolPart]
 
     def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1):
         do_delayed_imports()
 
         super(MarionetteProtocol, self).__init__(executor, browser)
         self.marionette = None
         self.marionette_port = browser.marionette_port
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorselenium.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/executorselenium.py
@@ -14,16 +14,17 @@ from .base import (CallbackHandler,
                    TestharnessExecutor,
                    extra_timeout,
                    strip_server)
 from .protocol import (BaseProtocolPart,
                        TestharnessProtocolPart,
                        Protocol,
                        SelectorProtocolPart,
                        ClickProtocolPart,
+                       SendKeysProtocolPart,
                        TestDriverProtocolPart)
 from ..testrunner import Stop
 
 here = os.path.join(os.path.split(__file__)[0])
 
 webdriver = None
 exceptions = None
 RemoteConnection = None
@@ -129,16 +130,23 @@ class SeleniumSelectorProtocolPart(Selec
 
 class SeleniumClickProtocolPart(ClickProtocolPart):
     def setup(self):
         self.webdriver = self.parent.webdriver
 
     def element(self, element):
         return element.click()
 
+class SeleniumSendKeysProtocolPart(SendKeysProtocolPart):
+    def setup(self):
+        self.webdriver = self.parent.webdriver
+
+    def send_keys(self, element, keys):
+        return element.send_keys(keys)
+
 
 class SeleniumTestDriverProtocolPart(TestDriverProtocolPart):
     def setup(self):
         self.webdriver = self.parent.webdriver
 
     def send_message(self, message_type, status, message=None):
         obj = {
             "type": "testdriver-%s" % str(message_type),
@@ -149,16 +157,17 @@ class SeleniumTestDriverProtocolPart(Tes
         self.webdriver.execute_script("window.postMessage(%s, '*')" % json.dumps(obj))
 
 
 class SeleniumProtocol(Protocol):
     implements = [SeleniumBaseProtocolPart,
                   SeleniumTestharnessProtocolPart,
                   SeleniumSelectorProtocolPart,
                   SeleniumClickProtocolPart,
+                  SeleniumSendKeysProtocolPart,
                   SeleniumTestDriverProtocolPart]
 
     def __init__(self, executor, browser, capabilities, **kwargs):
         do_delayed_imports()
 
         super(SeleniumProtocol, self).__init__(executor, browser)
         self.capabilities = capabilities
         self.url = browser.webdriver_url
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/protocol.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/executors/protocol.py
@@ -254,16 +254,30 @@ class ClickProtocolPart(ProtocolPart):
 
     @abstractmethod
     def element(self, element):
         """Perform a trusted click somewhere on a specific element.
 
         :param element: A protocol-specific handle to an element."""
         pass
 
+class SendKeysProtocolPart(ProtocolPart):
+    """Protocol part for performing trusted clicks"""
+    __metaclass__ = ABCMeta
+
+    name = "send_keys"
+
+    @abstractmethod
+    def send_keys(self, element, keys):
+        """Send keys to a specific element.
+
+        :param element: A protocol-specific handle to an element.
+        :param keys: A protocol-specific handle to a string of input keys."""
+        pass
+
 
 class TestDriverProtocolPart(ProtocolPart):
     """Protocol part that implements the basic functionality required for
     all testdriver-based tests."""
     __metaclass__ = ABCMeta
 
     name = "testdriver"
 
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/testdriver-extra.js
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/testdriver-extra.js
@@ -55,9 +55,19 @@
         const selector = get_selector(element);
         const pending_promise = new Promise(function(resolve, reject) {
             pending_resolve = resolve;
             pending_reject = reject;
         });
         window.opener.postMessage({"type": "action", "action": "click", "selector": selector}, "*");
         return pending_promise;
     };
+
+    window.test_driver_internal.send_keys = function(element, keys) {
+        const selector = get_selector(element);
+        const pending_promise = new Promise(function(resolve, reject) {
+            pending_resolve = resolve;
+            pending_reject = reject;
+        });
+        window.opener.postMessage({"type": "action", "action": "send_keys", "selector": selector, "keys": keys}, "*");
+        return pending_promise;
+    };
 })();