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}); + }; })();