Skip to content

Commit

Permalink
sensors: Add virtual sensor-related commands to testdriver
Browse files Browse the repository at this point in the history
Spec PR: w3c/sensors#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.
  • Loading branch information
Raphael Kubo da Costa committed Oct 10, 2023
1 parent 28cb182 commit fd100e9
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 9 deletions.
8 changes: 8 additions & 0 deletions docs/writing-tests/testdriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,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)
Expand Down
159 changes: 159 additions & 0 deletions resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,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
* <https://w3c.github.io/sensors/#create-virtual-sensor-command>`_
* 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
* <https://w3c.github.io/sensors/#virtual-sensor-metadata-virtual-sensor-type>`_
* such as "accelerometer".
* @param {Object} [sensor_params={}] - Optional parameters described
* in `Create Virtual Sensor
* <https://w3c.github.io/sensors/#create-virtual-sensor-command>`_.
* @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
* <https://w3c.github.io/sensors/#update-virtual-sensor-reading-command>`_
* 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
* <https://w3c.github.io/sensors/#update-latest-reading>`_.
*
* @example
* // Avoid races between EventWatcher and update_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
* <https://w3c.github.io/sensors/#virtual-sensor-metadata-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
* <https://w3c.github.io/sensors/#delete-virtual-sensor-command>`_
* WebDriver command.
*
* @param {String} sensor_type - A `virtual sensor type
* <https://w3c.github.io/sensors/#virtual-sensor-metadata-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
* <https://w3c.github.io/sensors/#get-virtual-sensor-information-command>`_
* WebDriver command.
*
* @param {String} sensor_type - A `virtual sensor type
* <https://w3c.github.io/sensors/#virtual-sensor-metadata-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
* <https://w3c.github.io/sensors/#get-virtual-sensor-information-command>`_.
* 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);
}
};

Expand Down Expand Up @@ -980,6 +1123,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");
}
};
})();
3 changes: 3 additions & 0 deletions tools/wptrunner/wptrunner/browsers/chrome.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,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.
Expand Down
61 changes: 60 additions & 1 deletion tools/wptrunner/wptrunner/executors/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,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,
Expand Down Expand Up @@ -394,4 +449,8 @@ def __call__(self, payload):
GetFedCMDialogTitleAction,
GetFedCMDialogTypeAction,
SetFedCMDelayEnabledAction,
ResetFedCMCooldownAction]
ResetFedCMCooldownAction,
CreateVirtualSensorAction,
UpdateVirtualSensorAction,
RemoveVirtualSensorAction,
GetVirtualSensorInformationAction]
21 changes: 20 additions & 1 deletion tools/wptrunner/wptrunner/executors/executormarionette.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
SetPermissionProtocolPart,
PrintProtocolPart,
DebugProtocolPart,
VirtualSensorProtocolPart,
merge_dicts)


Expand Down Expand Up @@ -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,
Expand All @@ -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()
Expand Down
24 changes: 23 additions & 1 deletion tools/wptrunner/wptrunner/executors/executorwebdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
DebugProtocolPart,
SPCTransactionsProtocolPart,
FedCMProtocolPart,
VirtualSensorProtocolPart,
merge_dicts)

from webdriver.client import Session
Expand Down Expand Up @@ -396,6 +397,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,
Expand All @@ -412,7 +433,8 @@ class WebDriverProtocol(Protocol):
WebDriverVirtualAuthenticatorProtocolPart,
WebDriverSPCTransactionsProtocolPart,
WebDriverFedCMProtocolPart,
WebDriverDebugProtocolPart]
WebDriverDebugProtocolPart,
WebDriverVirtualSensorPart]

def __init__(self, executor, browser, capabilities, **kwargs):
super().__init__(executor, browser)
Expand Down
23 changes: 23 additions & 0 deletions tools/wptrunner/wptrunner/executors/protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -760,3 +760,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
Loading

0 comments on commit fd100e9

Please sign in to comment.