0

bluetooth: Migrate some WPT tests to use bidi commands

This CL migrates some Web Bluetooth WPT tests to use WebDriver Bidi
bluetooth.simulatePreconnectedPeripheral command.

It also creates a new file web-bluetooth-bidi-test.js, which is a
counterpart of web-bluetooth-test.js. The following CLs will
incrementally implement other necessary functionalities in the file for
fully migrating Web Bluetooth WPT tests to run in headless shell.

Bug: 41484719

Change-Id: Ia19e8f4b6f637a913f48ae8d3445299ac336d919
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6203507
Commit-Queue: Jack Hsieh <chengweih@chromium.org>
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1423424}
This commit is contained in:
Chengwei Hsieh
2025-02-21 14:39:25 -08:00
committed by Chromium LUCI CQ
parent db39c7f37d
commit c4eb35d458
11 changed files with 149 additions and 26 deletions

@ -80,3 +80,5 @@ virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-i
virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-onerror.https.html
virtual/speech-with-unified-autoplay/external/wpt/speech-api/SpeechRecognition-onstart-onend.https.html
virtual/view-transition-mpa-serialization/external/wpt/css/css-view-transitions/window-resize-aborts-transition-before-ready.html
# TODO(crbug.com/393152490): Pending Web Bluetooth Permission API integration for running in headless shell.
virtual/web-bluetooth-new-permissions-backend/external/wpt/bluetooth/bidi/*

@ -7,7 +7,7 @@ const test_desc = 'getAvailability() resolves with false if the system does ' +
'not have an adapter.';
bluetooth_bidi_test(async () => {
await test_driver.bidi.bluetooth.simulate_adapter({state: "absent"});
await navigator.bluetooth.test.simulateCentral({state: 'absent'});
let availability = await navigator.bluetooth.getAvailability();
assert_false(
availability,

@ -7,7 +7,7 @@ const test_desc = 'getAvailability() resolves with true if the Bluetooth ' +
'radio is powered off, but the platform that supports Bluetooth LE.';
bluetooth_bidi_test(async () => {
await test_driver.bidi.bluetooth.simulate_adapter({state: "powered-off"});
await navigator.bluetooth.test.simulateCentral({state: 'powered-off'});
let availability = await navigator.bluetooth.getAvailability();
assert_true(
availability,

@ -7,7 +7,7 @@ const test_desc = 'getAvailability() resolves with true if the Bluetooth ' +
'radio is powered on and the platform supports Bluetooth LE.';
bluetooth_bidi_test(async () => {
await test_driver.bidi.bluetooth.simulate_adapter({state: "powered-on"});
await navigator.bluetooth.test.simulateCentral({state: 'powered-on'});
let availability = await navigator.bluetooth.getAvailability();
assert_true(
availability,

@ -10,7 +10,7 @@ const cross_origin_src = 'https://{{domains[www]}}:{{ports[https][0]}}' +
let iframe = document.createElement('iframe');
bluetooth_bidi_test(async () => {
await test_driver.bidi.bluetooth.simulate_adapter({state: "powered-on"});
await navigator.bluetooth.test.simulateCentral({state: 'powered-on'});
await new Promise(resolve => {
iframe.src = cross_origin_src;
document.body.appendChild(iframe);

@ -1,4 +1,4 @@
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver.js?feature=bidi
// META: script=/resources/testdriver-vendor.js
// META: script=/bluetooth/resources/bluetooth-test.js
// META: script=/bluetooth/resources/bluetooth-fake-devices.js
@ -6,14 +6,10 @@
const test_desc = 'Device with empty name and no UUIDs nearby. Should be ' +
'found if acceptAllDevices is true.';
bluetooth_test(async () => {
let { device } = await setUpPreconnectedFakeDevice({
fakeDeviceOptions: {
name: ''
},
requestDeviceOptions: {
acceptAllDevices: true
}
bluetooth_bidi_test(async () => {
let {device} = await setUpPreconnectedFakeDevice({
fakeDeviceOptions: {name: ''},
requestDeviceOptions: {acceptAllDevices: true}
});
assert_equals(device.name, '');
}, test_desc);

@ -1,4 +1,4 @@
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver.js?feature=bidi
// META: script=/resources/testdriver-vendor.js
// META: script=/bluetooth/resources/bluetooth-test.js
// META: script=/bluetooth/resources/bluetooth-fake-devices.js
@ -7,14 +7,10 @@ const test_desc =
'acceptAllDevices is true.';
const name = 'LE Device';
bluetooth_test(async () => {
let { device } = await setUpPreconnectedFakeDevice({
fakeDeviceOptions: {
name: name
},
requestDeviceOptions: {
acceptAllDevices: true
}
bluetooth_bidi_test(async () => {
let {device} = await setUpPreconnectedFakeDevice({
fakeDeviceOptions: {name: name},
requestDeviceOptions: {acceptAllDevices: true}
});
assert_equals(device.name, name);
}, test_desc);

@ -1,4 +1,4 @@
// META: script=/resources/testdriver.js
// META: script=/resources/testdriver.js?feature=bidi
// META: script=/resources/testdriver-vendor.js
// META: script=/bluetooth/resources/bluetooth-test.js
// META: script=/bluetooth/resources/bluetooth-fake-devices.js
@ -7,7 +7,7 @@ const test_desc = 'Reject with NotFoundError if there is no BT radio present.';
const expected =
new DOMException('Bluetooth adapter not available.', 'NotFoundError');
bluetooth_test(
bluetooth_bidi_test(
() => navigator.bluetooth.test.simulateCentral({state: 'absent'})
.then(
() => assert_promise_rejects_with_message(

@ -398,8 +398,11 @@ async function setUpPreconnectedFakeDevice(setupOptionsOverride) {
// Request the device if the request option isn't empty.
if (Object.keys(setupOptions.requestDeviceOptions).length !== 0) {
preconnectedDevice.device =
await requestDeviceWithTrustedClick(setupOptions.requestDeviceOptions);
const prompt_promise = selectFirstDeviceOnDevicePromptUpdated();
[preconnectedDevice.device] = await Promise.all([
requestDeviceWithTrustedClick(setupOptions.requestDeviceOptions),
prompt_promise
]);
}
// Set up services discovered state.

@ -1,5 +1,9 @@
'use strict';
// A flag indicating whether to use Web Bluetooth BiDi commands for Bluetooth
// emulation.
let useBidi = false;
/**
* Test Setup Helpers
*/
@ -105,6 +109,15 @@ function bluetooth_bidi_test(
test_function, name, properties, validate_response_consumed = true) {
return promise_test(async (t) => {
assert_implements(navigator.bluetooth, 'missing navigator.bluetooth');
// Necessary setup for Bluetooth emulation using WebDriver Bidi commands.
useBidi = true;
await loadScript('/resources/web-bluetooth-bidi-test.js');
await initializeBluetoothBidiResources();
assert_implements(
navigator.bluetooth.test, 'missing navigator.bluetooth.test');
await test_driver.bidi.bluetooth.request_device_prompt_updated.subscribe();
await test_function(t);
}, name, properties);
}
@ -153,6 +166,28 @@ async function callWithTrustedClick(callback) {
});
}
/**
* Registers a one-time handler that selects the first device in the device
* prompt upon a device prompt updated event.
* @returns {Promise<void>} Fulfilled after the Bluetooth device prompt
* is handled, or rejected if the operation fails.
*/
function selectFirstDeviceOnDevicePromptUpdated() {
if (!useBidi) {
// Return a resolved promise when there is no bidi support.
return Promise.resolve();
}
test_driver.bidi.bluetooth.request_device_prompt_updated.once().then(
(promptEvent) => {
assert_greater_than_equal(promptEvent.devices.length, 0);
return test_driver.bidi.bluetooth.handle_request_device_prompt({
prompt: promptEvent.prompt,
accept: true,
device: promptEvent.devices[0].id
});
});
}
/**
* Calls requestDevice() in a context that's 'allowed to show a popup'.
* @returns {Promise<BluetoothDevice>} Resolves with a Bluetooth device if

@ -0,0 +1,91 @@
'use strict'
// Convert `manufacturerData` to an array of bluetooth.BluetoothManufacturerData
// defined in
// https://webbluetoothcg.github.io/web-bluetooth/#bluetooth-bidi-definitions.
function convertToBidiManufacturerData(manufacturerData) {
const bidiManufacturerData = [];
for (const key in manufacturerData) {
bidiManufacturerData.push(
{key: parseInt(key), data: btoa(manufacturerData[key].buffer)})
}
return bidiManufacturerData;
}
class FakeBluetooth {
constructor() {
this.fake_central_ = null;
}
// Returns a promise that resolves with a FakeCentral that clients can use
// to simulate events that a device in the Central/Observer role would
// receive as well as monitor the operations performed by the device in the
// Central/Observer role.
//
// A "Central" object would allow its clients to receive advertising events
// and initiate connections to peripherals i.e. operations of two roles
// defined by the Bluetooth Spec: Observer and Central.
// See Bluetooth 4.2 Vol 3 Part C 2.2.2 "Roles when Operating over an
// LE Physical Transport".
async simulateCentral({state}) {
if (this.fake_central_) {
throw 'simulateCentral() should only be called once';
}
await test_driver.bidi.bluetooth.simulate_adapter({state: state});
this.fake_central_ = new FakeCentral();
return this.fake_central_;
}
}
// FakeCentral allows clients to simulate events that a device in the
// Central/Observer role would receive as well as monitor the operations
// performed by the device in the Central/Observer role.
class FakeCentral {
constructor() {
this.peripherals_ = new Map();
}
// Simulates a peripheral with |address|, |name|, |manufacturerData| and
// |known_service_uuids| that has already been connected to the system. If the
// peripheral existed already it updates its name, manufacturer data, and
// known UUIDs. |known_service_uuids| should be an array of
// BluetoothServiceUUIDs
// https://webbluetoothcg.github.io/web-bluetooth/#typedefdef-bluetoothserviceuuid
//
// Platforms offer methods to retrieve devices that have already been
// connected to the system or weren't connected through the UA e.g. a user
// connected a peripheral through the system's settings. This method is
// intended to simulate peripherals that those methods would return.
async simulatePreconnectedPeripheral(
{address, name, manufacturerData = {}, knownServiceUUIDs = []}) {
await test_driver.bidi.bluetooth.simulate_preconnected_peripheral({
address: address,
name: name,
manufacturerData: convertToBidiManufacturerData(manufacturerData),
knownServiceUuids: knownServiceUUIDs
});
return this.fetchOrCreatePeripheral_(address);
}
// Create a fake_peripheral object from the given address.
fetchOrCreatePeripheral_(address) {
let peripheral = this.peripherals_.get(address);
if (peripheral === undefined) {
peripheral = new FakePeripheral(address);
this.peripherals_.set(address, peripheral);
}
return peripheral;
}
}
class FakePeripheral {
constructor(address) {
this.address = address;
}
}
function initializeBluetoothBidiResources() {
navigator.bluetooth.test = new FakeBluetooth();
}