Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bluetooth: Migrate some WPT tests to use bidi commands #50889

Merged
merged 1 commit into from
Feb 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
// 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
'use strict';
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);
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(
Expand Down
7 changes: 5 additions & 2 deletions bluetooth/resources/bluetooth-fake-devices.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
35 changes: 35 additions & 0 deletions bluetooth/resources/bluetooth-test.js
Original file line number Diff line number Diff line change
@@ -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
*/
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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
Expand Down
91 changes: 91 additions & 0 deletions resources/web-bluetooth-bidi-test.js
Original file line number Diff line number Diff line change
@@ -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();
}
Loading