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 0dd2d80
Show file tree
Hide file tree
Showing 10 changed files with 427 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
Original file line number Diff line number Diff line change
@@ -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
91 changes: 91 additions & 0 deletions infrastructure/testdriver/virtual_sensors.https.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>TestDriver virtual sensors methods</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script>
"use strict";

promise_test(async t => {
const sensorType = 'gyroscope';
await test_driver.set_permission({name: sensorType}, 'granted');
await test_driver.create_virtual_sensor(sensorType);

const sensor = new Gyroscope();
t.add_cleanup(async () => {
sensor.stop();
await test_driver.remove_virtual_sensor(sensorType);
});
const watcher = new EventWatcher(t, sensor, ['activate']);

sensor.start();
await watcher.wait_for('activate');
}, "Test that virtual sensors can be created and removed.");

promise_test(async t => {
const sensorType = 'gyroscope';
await test_driver.set_permission({name: sensorType}, 'granted');
await test_driver.create_virtual_sensor(sensorType, {connected: false});

const sensor = new Gyroscope();
t.add_cleanup(async () => {
sensor.stop();
await test_driver.remove_virtual_sensor(sensorType);
});

const watcher = new EventWatcher(t, sensor, ['error']);
sensor.start();
const error = await watcher.wait_for('error');
assert_equals(error.error.name, 'NotReadableError');
}, "Test that unavailable virtual sensors can be created.");

promise_test(async t => {
const sensorType = 'gyroscope';
const minSamplingFrequency = 6.0;

await test_driver.set_permission({name: sensorType}, 'granted');
await test_driver.create_virtual_sensor(sensorType, {minSamplingFrequency});

const sensor = new Gyroscope({frequency: 5.0});
t.add_cleanup(async () => {
sensor.stop();
await test_driver.remove_virtual_sensor(sensorType);
});

const watcher = new EventWatcher(t, sensor, ['activate', 'reading', 'error']);
sensor.start();
await watcher.wait_for('activate');

const info = await test_driver.get_virtual_sensor_information(sensorType);

assert_equals(info['isReadingData'], true);
assert_equals(info['requestedSamplingFrequency'], minSamplingFrequency);
}, "Test that minimum frequency setting works and virtual sensor information can be fetched.");

promise_test(async t => {
const sensorType = 'accelerometer';
await test_driver.set_permission({name: sensorType}, 'granted');
await test_driver.create_virtual_sensor(sensorType);

const sensor = new Accelerometer();
t.add_cleanup(async () => {
sensor.stop();
await test_driver.remove_virtual_sensor(sensorType);
});

const watcher = new EventWatcher(t, sensor, ['activate', 'reading', 'error']);
sensor.start();
await watcher.wait_for('activate');

const reading = {'x': 1.0, 'y': 2.0, 'z': 3.0};
test_driver.update_virtual_sensor(sensorType, reading);

await watcher.wait_for('reading');
assert_equals(sensor.x, reading.x);
assert_equals(sensor.y, reading.y);
assert_equals(sensor.z, reading.z);
}, "Test that virtual sensors can be updated.");

</script>
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]
Loading

0 comments on commit 0dd2d80

Please sign in to comment.