From a09b779b80f6445ded0c6eaa102cb91437f903c9 Mon Sep 17 00:00:00 2001 From: Raphael Kubo da Costa Date: Mon, 11 Sep 2023 11:06:37 +0200 Subject: [PATCH] sensors: Add virtual sensor-related commands to testdriver Spec PR: https://github.com/w3c/sensors/pull/470 The Generic Sensor spec used to have an Automation section, but it leaked implementation-specific details and was not implemented anywhere. The changes above have been implemented in Chromium and there are pending patches there to convert the existing Generic Sensor web tests in WPT to the new virtual sensor model, which entirely removes the dependency on Mojo mocks and makes the tests more interoperable. This PR adds the required infrastructure to manipulate virtual sensors from testdriver. The 4 new commands correspond to the 4 WebDriver extension commands added by the spec PR above. This change was co-authored with @JuhaVainio. Related to #9686. --- docs/writing-tests/testdriver.md | 8 + .../testdriver/virtual_sensors.https.html.ini | 18 ++ .../testdriver/virtual_sensors.https.html | 91 ++++++++++ resources/testdriver.js | 159 ++++++++++++++++++ tools/wptrunner/wptrunner/browsers/chrome.py | 3 + .../wptrunner/wptrunner/executors/actions.py | 61 ++++++- .../wptrunner/executors/executormarionette.py | 21 ++- .../wptrunner/executors/executorwebdriver.py | 24 ++- .../wptrunner/wptrunner/executors/protocol.py | 23 +++ tools/wptrunner/wptrunner/testdriver-extra.js | 28 ++- 10 files changed, 427 insertions(+), 9 deletions(-) create mode 100644 infrastructure/metadata/infrastructure/testdriver/virtual_sensors.https.html.ini create mode 100644 infrastructure/testdriver/virtual_sensors.https.html diff --git a/docs/writing-tests/testdriver.md b/docs/writing-tests/testdriver.md index e482452aef39a56..17a328cd615b2bd 100644 --- a/docs/writing-tests/testdriver.md +++ b/docs/writing-tests/testdriver.md @@ -110,6 +110,14 @@ the global scope. .. js:autofunction:: test_driver.reset_fedcm_cooldown ``` +### Sensors ### +```eval_rst +.. js:autofunction:: test_driver.create_virtual_sensor +.. js:autofunction:: test_driver.update_virtual_sensor +.. js:autofunction:: test_driver.remove_virtual_sensor +.. js:autofunction:: test_driver.get_virtual_sensor_information +``` + ### Using test_driver in other browsing contexts ### Testdriver can be used in browsing contexts (i.e. windows or frames) diff --git a/infrastructure/metadata/infrastructure/testdriver/virtual_sensors.https.html.ini b/infrastructure/metadata/infrastructure/testdriver/virtual_sensors.https.html.ini new file mode 100644 index 000000000000000..0a1ae495048e9ce --- /dev/null +++ b/infrastructure/metadata/infrastructure/testdriver/virtual_sensors.https.html.ini @@ -0,0 +1,18 @@ +# The tests below require test_driver.set_permission() support in addition to +# support for the virtual sensor calls themselves. +[virtual_sensors.https.html] + [Test that virtual sensors can be created and removed.] + expected: + if product != "chrome": FAIL + + [Test that unavailable virtual sensors can be created.] + expected: + if product != "chrome": FAIL + + [Test that minimum frequency setting works and virtual sensor information can be fetched.] + expected: + if product != "chrome": FAIL + + [Test that virtual sensors can be updated.] + expected: + if product != "chrome": FAIL diff --git a/infrastructure/testdriver/virtual_sensors.https.html b/infrastructure/testdriver/virtual_sensors.https.html new file mode 100644 index 000000000000000..434e856558bf254 --- /dev/null +++ b/infrastructure/testdriver/virtual_sensors.https.html @@ -0,0 +1,91 @@ + + +TestDriver virtual sensors methods + + + + + diff --git a/resources/testdriver.js b/resources/testdriver.js index a23d6eaf4cf3831..6365f80f27d51e8 100644 --- a/resources/testdriver.js +++ b/resources/testdriver.js @@ -801,6 +801,149 @@ */ reset_fedcm_cooldown: function(context=null) { return window.test_driver_internal.reset_fedcm_cooldown(context); + }, + + /** + * Creates a virtual sensor for use with the Generic Sensors APIs. + * + * Matches the `Create Virtual Sensor + * `_ + * WebDriver command. + * + * Once created, a virtual sensor is available to all navigables under + * the same top-level traversable (i.e. all frames in the same page, + * regardless of origin). + * + * @param {String} sensor_type - A `virtual sensor type + * `_ + * such as "accelerometer". + * @param {Object} [sensor_params={}] - Optional parameters described + * in `Create Virtual Sensor + * `_. + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled when virtual sensor is created. + * Rejected in case the WebDriver command errors out + * (including if a virtual sensor of the same type + * already exists). + */ + create_virtual_sensor: function(sensor_type, sensor_params={}, context=null) { + return window.test_driver_internal.create_virtual_sensor(sensor_type, sensor_params, context); + }, + + /** + * Causes a virtual sensor to report a new reading to any connected + * platform sensor. + * + * Matches the `Update Virtual Sensor Reading + * `_ + * WebDriver command. + * + * Note: The ``Promise`` it returns may fulfill before or after a + * "reading" event is fired. When using + * :js:func:`EventWatcher.wait_for`, it is necessary to take this into + * account: + * + * Note: New values may also be discarded due to the checks in `update + * latest reading + * `_. + * + * @example + * // Avoid races between EventWatcher and udpate_virtual_sensor(). + * // This assumes you are sure this reading will be processed (see + * // the example below otherwise). + * const reading = { x: 1, y: 2, z: 3 }; + * await Promise.all([ + * test_driver.update_virtual_sensor('gyroscope', reading), + * watcher.wait_for('reading') + * ]); + * + * @example + * // Do not wait forever if you are not sure the reading will be + * // processed. + * const readingPromise = watcher.wait_for('reading'); + * const timeoutPromise = new Promise(resolve => { + * t.step_timeout(() => resolve('TIMEOUT', 3000)) + * }); + * + * const reading = { x: 1, y: 2, z: 3 }; + * await test_driver.update_virtual_sensor('gyroscope', 'reading'); + * + * const value = + * await Promise.race([timeoutPromise, readingPromise]); + * if (value !== 'TIMEOUT') { + * // Do something. The "reading" event was fired. + * } + * + * @param {String} sensor_type - A `virtual sensor type + * `_ + * such as "accelerometer". + * @param {Object} reading - An Object describing a reading in a format + * dependent on ``sensor_type`` (e.g. ``{x: + * 1, y: 2, z: 3}`` or ``{ illuminance: 42 + * }``). + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled after the reading update reaches the + * virtual sensor. Rejected in case the WebDriver + * command errors out (including if a virtual sensor + * of the given type does not exist). + */ + update_virtual_sensor: function(sensor_type, reading, context=null) { + return window.test_driver_internal.update_virtual_sensor(sensor_type, reading, context); + }, + + /** + * Triggers the removal of a virtual sensor if it exists. + * + * Matches the `Delete Virtual Sensor + * `_ + * WebDriver command. + * + * @param {String} sensor_type - A `virtual sensor type + * `_ + * such as "accelerometer". + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled after the virtual sensor has been + * removed or if a sensor of the given type does not + * exist. Rejected in case the WebDriver command + * errors out. + + */ + remove_virtual_sensor: function(sensor_type, context=null) { + return window.test_driver_internal.remove_virtual_sensor(sensor_type, context); + }, + + /** + * Returns information about a virtual sensor. + * + * Matches the `Get Virtual Sensor Information + * `_ + * WebDriver command. + * + * @param {String} sensor_type - A `virtual sensor type + * `_ + * such as "accelerometer". + * @param {WindowProxy} [context=null] - Browsing context in which to + * run the call, or null for the + * current browsing context. + * + * @returns {Promise} Fulfilled with an Object with the properties + * described in `Get Virtual Sensor Information + * `_. + * Rejected in case the WebDriver command errors out + * (including if a virtual sensor of the given type + * does not exist). + */ + get_virtual_sensor_information: function(sensor_type, context=null) { + return window.test_driver_internal.get_virtual_sensor_information(sensor_type, context); } }; @@ -953,6 +1096,22 @@ async reset_fedcm_cooldown(context=null) { throw new Error("reset_fedcm_cooldown() is not implemented by testdriver-vendor.js"); + }, + + async create_virtual_sensor(sensor_type, sensor_params, context=null) { + throw new Error("create_virtual_sensor() is not implemented by testdriver-vendor.js"); + }, + + async update_virtual_sensor(sensor_type, reading, context=null) { + throw new Error("update_virtual_sensor() is not implemented by testdriver-vendor.js"); + }, + + async remove_virtual_sensor(sensor_type, context=null) { + throw new Error("remove_virtual_sensor() is not implemented by testdriver-vendor.js"); + }, + + async get_virtual_sensor_information(sensor_type, context=null) { + throw new Error("get_virtual_sensor_information() is not implemented by testdriver-vendor.js"); } }; })(); diff --git a/tools/wptrunner/wptrunner/browsers/chrome.py b/tools/wptrunner/wptrunner/browsers/chrome.py index d0a788cd277f21a..3347d5aeeceaa3c 100644 --- a/tools/wptrunner/wptrunner/browsers/chrome.py +++ b/tools/wptrunner/wptrunner/browsers/chrome.py @@ -95,6 +95,9 @@ def executor_kwargs(logger, test_type, test_environment, run_info_data, chrome_options["args"].append("--enable-features=SecurePaymentConfirmationBrowser") # For WebTransport tests. chrome_options["args"].append("--webtransport-developer-mode") + # The GenericSensorExtraClasses flag enables the browser-side + # implementation of sensors such as Ambient Light Sensor. + chrome_options["args"].append("--enable-features=GenericSensorExtraClasses") # Classify `http-private`, `http-public` and https variants in the # appropriate IP address spaces. diff --git a/tools/wptrunner/wptrunner/executors/actions.py b/tools/wptrunner/wptrunner/executors/actions.py index f3fe970484e57d5..4073f8db4c6c4b4 100644 --- a/tools/wptrunner/wptrunner/executors/actions.py +++ b/tools/wptrunner/wptrunner/executors/actions.py @@ -356,6 +356,61 @@ def __call__(self, payload): self.logger.debug("Resetting FedCM cooldown") return self.protocol.fedcm.reset_fedcm_cooldown() + +class CreateVirtualSensorAction: + name = "create_virtual_sensor" + + def __init__(self, logger, protocol): + self.logger = logger + self.protocol = protocol + + def __call__(self, payload): + sensor_type = payload["sensor_type"] + sensor_params = payload["sensor_params"] + self.logger.debug("Creating %s sensor with %s values" % (sensor_type, sensor_params)) + return self.protocol.virtual_sensor.create_virtual_sensor(sensor_type, sensor_params) + + +class UpdateVirtualSensorAction: + name = "update_virtual_sensor" + + def __init__(self, logger, protocol): + self.logger = logger + self.protocol = protocol + + def __call__(self, payload): + sensor_type = payload["sensor_type"] + reading = payload["reading"] + self.logger.debug("Updating %s sensor with new readings: %s" % (sensor_type, reading)) + return self.protocol.virtual_sensor.update_virtual_sensor(sensor_type, reading) + + +class RemoveVirtualSensorAction: + name = "remove_virtual_sensor" + + def __init__(self, logger, protocol): + self.logger = logger + self.protocol = protocol + + def __call__(self, payload): + sensor_type = payload["sensor_type"] + self.logger.debug("Removing %s sensor" % sensor_type) + return self.protocol.virtual_sensor.remove_virtual_sensor(sensor_type) + + +class GetVirtualSensorInformationAction: + name = "get_virtual_sensor_information" + + def __init__(self, logger, protocol): + self.logger = logger + self.protocol = protocol + + def __call__(self, payload): + sensor_type = payload["sensor_type"] + self.logger.debug("Requesting information from %s sensor" % sensor_type) + return self.protocol.virtual_sensor.get_virtual_sensor_information(sensor_type) + + actions = [ClickAction, DeleteAllCookiesAction, GetAllCookiesAction, @@ -382,4 +437,8 @@ def __call__(self, payload): GetFedCMDialogTitleAction, GetFedCMDialogTypeAction, SetFedCMDelayEnabledAction, - ResetFedCMCooldownAction] + ResetFedCMCooldownAction, + CreateVirtualSensorAction, + UpdateVirtualSensorAction, + RemoveVirtualSensorAction, + GetVirtualSensorInformationAction] diff --git a/tools/wptrunner/wptrunner/executors/executormarionette.py b/tools/wptrunner/wptrunner/executors/executormarionette.py index a3b56fe08df11e4..dd0e3259cead1e8 100644 --- a/tools/wptrunner/wptrunner/executors/executormarionette.py +++ b/tools/wptrunner/wptrunner/executors/executormarionette.py @@ -44,6 +44,7 @@ SetPermissionProtocolPart, PrintProtocolPart, DebugProtocolPart, + VirtualSensorProtocolPart, merge_dicts) @@ -720,6 +721,23 @@ def get_computed_role(self, element): return element.computed_role +class MarionetteVirtualSensorProtocolPart(VirtualSensorProtocolPart): + def setup(self): + self.marionette = self.parent.marionette + + def create_virtual_sensor(self, sensor_type, sensor_params): + raise NotImplementedError("create_virtual_sensor not yet implemented") + + def update_virtual_sensor(self, sensor_type, reading): + raise NotImplementedError("update_virtual_sensor not yet implemented") + + def remove_virtual_sensor(self, remove_parameters): + raise NotImplementedError("remove_virtual_sensor not yet implemented") + + def get_virtual_sensor_information(self, information_parameters): + raise NotImplementedError("get_virtual_sensor_information not yet implemented") + + class MarionetteProtocol(Protocol): implements = [MarionetteBaseProtocolPart, MarionetteTestharnessProtocolPart, @@ -739,7 +757,8 @@ class MarionetteProtocol(Protocol): MarionetteSetPermissionProtocolPart, MarionettePrintProtocolPart, MarionetteDebugProtocolPart, - MarionetteAccessibilityProtocolPart] + MarionetteAccessibilityProtocolPart, + MarionetteVirtualSensorProtocolPart] def __init__(self, executor, browser, capabilities=None, timeout_multiplier=1, e10s=True, ccov=False): do_delayed_imports() diff --git a/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/tools/wptrunner/wptrunner/executors/executorwebdriver.py index b31c84d8e88f3e1..3fbc327af2ef951 100644 --- a/tools/wptrunner/wptrunner/executors/executorwebdriver.py +++ b/tools/wptrunner/wptrunner/executors/executorwebdriver.py @@ -33,6 +33,7 @@ DebugProtocolPart, SPCTransactionsProtocolPart, FedCMProtocolPart, + VirtualSensorProtocolPart, merge_dicts) from webdriver.client import Session @@ -394,6 +395,26 @@ def load_devtools(self): raise NotImplementedError() +class WebDriverVirtualSensorPart(VirtualSensorProtocolPart): + def setup(self): + self.webdriver = self.parent.webdriver + + def create_virtual_sensor(self, sensor_type, sensor_params): + body = {"type": sensor_type} + body.update(sensor_params) + return self.webdriver.send_session_command("POST", "sensor", body) + + def update_virtual_sensor(self, sensor_type, reading): + body = {"reading": reading} + return self.webdriver.send_session_command("POST", "sensor/%s" % sensor_type, body) + + def remove_virtual_sensor(self, sensor_type): + return self.webdriver.send_session_command("DELETE", "sensor/%s" % sensor_type) + + def get_virtual_sensor_information(self, sensor_type): + return self.webdriver.send_session_command("GET", "sensor/%s" % sensor_type) + + class WebDriverProtocol(Protocol): implements = [WebDriverBaseProtocolPart, WebDriverTestharnessProtocolPart, @@ -410,7 +431,8 @@ class WebDriverProtocol(Protocol): WebDriverVirtualAuthenticatorProtocolPart, WebDriverSPCTransactionsProtocolPart, WebDriverFedCMProtocolPart, - WebDriverDebugProtocolPart] + WebDriverDebugProtocolPart, + WebDriverVirtualSensorPart] def __init__(self, executor, browser, capabilities, **kwargs): super().__init__(executor, browser) diff --git a/tools/wptrunner/wptrunner/executors/protocol.py b/tools/wptrunner/wptrunner/executors/protocol.py index 170c27cd687d62c..90e5f1a5c13c624 100644 --- a/tools/wptrunner/wptrunner/executors/protocol.py +++ b/tools/wptrunner/wptrunner/executors/protocol.py @@ -754,3 +754,26 @@ def is_alive(self): conn.request("HEAD", "/invalid") res = conn.getresponse() return res.status == 404 + + +class VirtualSensorProtocolPart(ProtocolPart): + """Protocol part for Sensors""" + __metaclass__ = ABCMeta + + name = "virtual_sensor" + + @abstractmethod + def create_virtual_sensor(self, sensor_type, sensor_params): + pass + + @abstractmethod + def update_virtual_sensor(self, sensor_type, reading): + pass + + @abstractmethod + def remove_virtual_sensor(self, sensor_type): + pass + + @abstractmethod + def get_virtual_sensor_information(self, sensor_type): + pass diff --git a/tools/wptrunner/wptrunner/testdriver-extra.js b/tools/wptrunner/wptrunner/testdriver-extra.js index b5cc9cdbbbf176f..297f9d9dcd4f595 100644 --- a/tools/wptrunner/wptrunner/testdriver-extra.js +++ b/tools/wptrunner/wptrunner/testdriver-extra.js @@ -268,27 +268,27 @@ window.test_driver_internal.set_spc_transaction_mode = function(mode, context = null) { return create_action("set_spc_transaction_mode", {mode, context}); }; - + window.test_driver_internal.cancel_fedcm_dialog = function(context = null) { return create_action("cancel_fedcm_dialog", {context}); }; - + window.test_driver_internal.select_fedcm_account = function(account_index, context = null) { return create_action("select_fedcm_account", {account_index, context}); }; - + window.test_driver_internal.get_fedcm_account_list = function(context = null) { return create_action("get_fedcm_account_list", {context}); }; - + window.test_driver_internal.get_fedcm_dialog_title = function(context = null) { return create_action("get_fedcm_dialog_title", {context}); }; - + window.test_driver_internal.get_fedcm_dialog_type = function(context = null) { return create_action("get_fedcm_dialog_type", {context}); }; - + window.test_driver_internal.set_fedcm_delay_enabled = function(enabled, context = null) { return create_action("set_fedcm_delay_enabled", {enabled, context}); }; @@ -296,4 +296,20 @@ window.test_driver_internal.reset_fedcm_cooldown = function(context = null) { return create_action("reset_fedcm_cooldown", {context}); }; + + window.test_driver_internal.create_virtual_sensor = function(sensor_type, sensor_params={}, context=null) { + return create_action("create_virtual_sensor", {sensor_type, sensor_params, context}); + }; + + window.test_driver_internal.update_virtual_sensor = function(sensor_type, reading, context=null) { + return create_action("update_virtual_sensor", {sensor_type, reading, context}); + }; + + window.test_driver_internal.remove_virtual_sensor = function(sensor_type, context=null) { + return create_action("remove_virtual_sensor", {sensor_type, context}); + }; + + window.test_driver_internal.get_virtual_sensor_information = function(sensor_type, context=null) { + return create_action("get_virtual_sensor_information", {sensor_type, context}); + }; })();