Bug 1464538 [wpt PR 11173] - [testdriver] Enable manual interaction, a=testonly
authorjugglinmike <mike@mikepennisi.com>
Tue, 05 Mar 2019 12:16:27 +0000
changeset 522178 2e1f864ca5041d044027c9cbe52ab50c6630b1bd
parent 522177 ed2e428a7fb7ac020c2e30cc8b4f96240ff3e1b3
child 522179 66fb4cc726923c2d5e9ea2890cd71bb08b42ca16
push id10871
push usercbrindusan@mozilla.com
push dateMon, 18 Mar 2019 15:49:32 +0000
treeherdermozilla-beta@018abdd16060 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstestonly
bugs1464538, 11173
milestone67.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 1464538 [wpt PR 11173] - [testdriver] Enable manual interaction, a=testonly Automatic update from web-platform-tests [testdriver] Enable manual interaction (#11173) The testdriver.js utility allows tests which require user input to be fully automated. It requires that the browser be under the control of a WebDriver server in order to do this. In test maintenance contexts, this requirement can slow development because a new browsing session must be created for each trial, and debugging tools such as the developer's console cannot be used. This requirement also limits the tests' value to a larger audience when published online. Visitors to sites like w3c-test.org are interested in observing test results experimentally, but although they may be capable of providing the expected input, the hard requirement on WebDriver will cause tests to fail unconditionally. Extend the framework to accept traditional human input if the test is being run without the WebDriver technology. This allows test authors to debug tests and other interested parties to verify results in browsers of their choosing. It does not interfere with fully-automated test execution. -- wpt-commits: bfd9504d321d35ebfbbe56ab6c7f5c0642495719 wpt-pr: 11173
testing/web-platform/tests/docs/_writing-tests/testdriver.md
testing/web-platform/tests/resources/testdriver.js
testing/web-platform/tests/tools/wptrunner/wptrunner/testdriver-extra.js
--- a/testing/web-platform/tests/docs/_writing-tests/testdriver.md
+++ b/testing/web-platform/tests/docs/_writing-tests/testdriver.md
@@ -1,16 +1,18 @@
 ---
 layout: page
 title: testdriver.js Automation
 order: 8.5
 ---
 
 testdriver.js provides a means to automate tests that cannot be
-written purely using web platform APIs.
+written purely using web platform APIs. Outside of automation
+contexts, it allows human operators to provide expected input
+manually (for operations which may be described in simple terms).
 
 It is currently supported only for [testharness.js][testharness]
 tests.
 
 ## API
 
 testdriver.js exposes its API through the `test_driver` variable in
 the global scope.
--- a/testing/web-platform/tests/resources/testdriver.js
+++ b/testing/web-platform/tests/resources/testdriver.js
@@ -192,37 +192,80 @@
          * @returns {Promise} fufiled after the actions are performed, or rejected in
          *                    the cases the WebDriver command errors
          */
         action_sequence: function(actions) {
             return window.test_driver_internal.action_sequence(actions);
         }
     };
 
-    window.test_driver_internal = {
+    var manual = {
         /**
-         * Triggers a user-initiated click
+         * This flag should be set to `true` by any code which implements the
+         * internal methods defined below for automation purposes. Doing so
+         * allows the library to signal failure immediately when an automated
+         * implementation of one of the methods is not available.
+         */
+        in_automation: false,
+
+        /**
+         * Waits for a user-initiated 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
+         * @returns {Promise} fulfilled after click occurs
          */
         click: function(element, coords) {
-            return Promise.reject(new Error("unimplemented"));
+            if (this.in_automation) {
+                return Promise.reject(new Error('Not implemented'));
+            }
+
+            return new Promise(function(resolve, reject) {
+                element.addEventListener("click", resolve);
+            });
         },
 
         /**
-         * Triggers a user-initiated click
+         * Waits for an element to receive a series of key presses
          *
-         * @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
+         * @param {Element} element - element which should receve key presses
+         * @param {String} keys - keys to expect
+         * @returns {Promise} fulfilled after keys are received or rejected if
+         *                    an incorrect key sequence is received
          */
         send_keys: function(element, keys) {
-            return Promise.reject(new Error("unimplemented"));
+            if (this.in_automation) {
+                return Promise.reject(new Error('Not implemented'));
+            }
+
+            return new Promise(function(resolve, reject) {
+                var seen = "";
+
+                function remove() {
+                    element.removeEventListener("keydown", onKeyDown);
+                }
+
+                function onKeyDown(event) {
+                    if (event.key.length > 1) {
+                        return;
+                    }
+
+                    seen += event.key;
+
+                    if (keys.indexOf(seen) !== 0) {
+                        reject(new Error("Unexpected key sequence: " + seen));
+                        remove();
+                    } else if (seen === keys) {
+                        resolve();
+                        remove();
+                    }
+                }
+
+                element.addEventListener("keydown", onKeyDown);
+            });
         },
 
         /**
          * Freeze the current page
          *
          * @returns {Promise} fulfilled after freeze request is sent, otherwise
          * it gets rejected
          */
@@ -235,9 +278,12 @@
          *
          * @returns {Promise} fufilled after actions are sent, rejected if any actions
          *                    fail
          */
         action_sequence: function(actions) {
             return Promise.reject(new Error("unimplemented"));
         }
     };
+
+    window.test_driver_internal = Object.create(manual);
+
 })();
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/testdriver-extra.js
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/testdriver-extra.js
@@ -46,16 +46,18 @@
             segments.reverse();
 
             selector = segments.join(" > ");
         }
 
         return selector;
     };
 
+    window.test_driver_internal.in_automation = true;
+
     window.test_driver_internal.click = function(element) {
         const selector = get_selector(element);
         const pending_promise = new Promise(function(resolve, reject) {
             pending_resolve = resolve;
             pending_reject = reject;
         });
         window.__wptrunner_message_queue.push({"type": "action", "action": "click", "selector": selector});
         return pending_promise;