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