Bluetooth Module
The Bluetooth module provides control over the Web Bluetooth API for testing Bluetooth-enabled web applications.
Overview
The Bluetooth module allows you to:
- Simulate the Bluetooth adapter state (absent, powered off, powered on)
- Simulate Bluetooth peripherals and advertisements
- Simulate GATT services, characteristics, and descriptors
- Simulate GATT connection and disconnection responses
- Handle device selection prompts from
navigator.bluetooth.requestDevice - React to characteristic and descriptor events
Accessing the Module
BluetoothModule bluetooth = driver.Bluetooth;
Timeout and Cancellation
All commands in this module accept optional timeoutOverride and CancellationToken parameters. Use timeoutOverride to set a per-command timeout (defaults to BiDiDriver.DefaultCommandTimeout when omitted). Use CancellationToken for cooperative cancellation. See the API Design Guide for details and examples.
Simulating the Bluetooth Adapter
Simulate Adapter State
Use SimulateAdapterAsync to simulate the presence or absence of the Bluetooth adapter, along with its power state. Use AdapterState.PoweredOn, AdapterState.PoweredOff, or AdapterState.Absent:
// Simulate adapter powered on
SimulateAdapterCommandParameters parameters =
new SimulateAdapterCommandParameters(contextId, AdapterState.PoweredOn);
await driver.Bluetooth.SimulateAdapterAsync(parameters);
// Or simulate adapter absent or powered off
await driver.Bluetooth.SimulateAdapterAsync(
new SimulateAdapterCommandParameters(contextId, AdapterState.Absent));
Disable Simulation
Use DisableSimulationAsync to turn off Bluetooth simulation for a browsing context:
DisableSimulationCommandParameters parameters =
new DisableSimulationCommandParameters(contextId);
await driver.Bluetooth.DisableSimulationAsync(parameters);
Simulating Bluetooth Devices
Basic Device Simulation
Use SimulatePreconnectedPeripheralAsync to simulate a Bluetooth peripheral already connected to the page. The device appears as if it were pre-paired:
SimulatePreconnectedPeripheralCommandParameters parameters =
new SimulatePreconnectedPeripheralCommandParameters(
contextId,
"00:00:00:00:00:00",
"Test Device");
parameters.ManufacturerData.Add(new BluetoothManufacturerData(
0x004C,
Convert.ToBase64String(new byte[] { 0x01, 0x02, 0x03 })));
await driver.Bluetooth.SimulatePreconnectedPeripheralAsync(parameters);
Console.WriteLine("Bluetooth device simulated");
Device with Services
Add known service UUIDs so the device can be discovered by navigator.bluetooth.requestDevice with service filters:
SimulatePreconnectedPeripheralCommandParameters @params =
new SimulatePreconnectedPeripheralCommandParameters(
contextId,
"AA:BB:CC:DD:EE:FF",
"Heart Rate Monitor");
@params.KnownServiceUUIDs.Add("heart_rate");
await driver.Bluetooth.SimulatePreconnectedPeripheralAsync(@params);
Console.WriteLine("Bluetooth heart rate monitor simulated");
Simulating Advertisements
Basic Advertisement
Use SimulateAdvertisementAsync to simulate Bluetooth Low Energy advertisements. Create a ScanRecord and SimulateAdvertisementScanEntry with device address, RSSI (signal strength), and scan data:
ScanRecord scanRecord = new ScanRecord();
SimulateAdvertisementScanEntry scanEntry = new SimulateAdvertisementScanEntry(
"00:00:00:00:00:00",
-50,
scanRecord);
SimulateAdvertisementCommandParameters @params =
new SimulateAdvertisementCommandParameters(contextId, scanEntry);
await driver.Bluetooth.SimulateAdvertisementAsync(@params);
Console.WriteLine("Bluetooth advertisement simulated");
Advertisement with Signal Strength
RSSI values typically range from -30 (strong) to -90 (weak). Use different values to test signal-strength-dependent behavior:
// Simulate weak signal
ScanRecord scanRecord = new ScanRecord();
SimulateAdvertisementScanEntry weakSignal = new SimulateAdvertisementScanEntry(
"00:00:00:00:00:00",
-80,
scanRecord);
await driver.Bluetooth.SimulateAdvertisementAsync(
new SimulateAdvertisementCommandParameters(contextId, weakSignal));
// Simulate strong signal
SimulateAdvertisementScanEntry strongSignal = new SimulateAdvertisementScanEntry(
"00:00:00:00:00:00",
-30,
scanRecord);
await driver.Bluetooth.SimulateAdvertisementAsync(
new SimulateAdvertisementCommandParameters(contextId, strongSignal));
Simulating GATT Services
Use SimulateServiceAsync to add or remove GATT services on a simulated device. Use SimulateServiceType.Add or SimulateServiceType.Remove:
SimulateServiceCommandParameters addParams =
new SimulateServiceCommandParameters(
contextId,
"AA:BB:CC:DD:EE:FF",
"heart_rate",
SimulateServiceType.Add);
await driver.Bluetooth.SimulateServiceAsync(addParams);
// Remove service
SimulateServiceCommandParameters removeParams =
new SimulateServiceCommandParameters(
contextId,
"AA:BB:CC:DD:EE:FF",
"heart_rate",
SimulateServiceType.Remove);
await driver.Bluetooth.SimulateServiceAsync(removeParams);
Simulating GATT Characteristics
Add or Remove Characteristics
Use SimulateCharacteristicAsync to add or remove characteristics. CharacteristicProperties (read, write, notify, indicate, etc.) is required when Type is SimulateCharacteristicType.Add, and must be left null when it is SimulateCharacteristicType.Remove; a conforming remote end answers with invalid argument otherwise:
CharacteristicProperties properties = new CharacteristicProperties
{
IsRead = true,
IsNotify = true,
};
SimulateCharacteristicCommandParameters parameters =
new SimulateCharacteristicCommandParameters(
contextId,
"AA:BB:CC:DD:EE:FF",
"heart_rate",
"heart_rate_measurement",
SimulateCharacteristicType.Add)
{
CharacteristicProperties = properties,
};
await driver.Bluetooth.SimulateCharacteristicAsync(parameters);
Simulate Characteristic Responses
Use SimulateCharacteristicResponseAsync to simulate responses for read, write, subscribe, or unsubscribe operations. Use SimulateCharacteristicResponseType.Read, Write, SubscribeToNotifications, or UnsubscribeFromNotifications. Code 0 indicates success; other values indicate GATT error codes:
// Simulate successful read response (code 0 = success)
SimulateCharacteristicResponseCommandParameters readParams =
new SimulateCharacteristicResponseCommandParameters(
contextId,
"AA:BB:CC:DD:EE:FF",
"heart_rate",
"heart_rate_measurement",
SimulateCharacteristicResponseType.Read,
0)
{
Data = { 0x64 }, // Heart rate value 100
};
await driver.Bluetooth.SimulateCharacteristicResponseAsync(readParams);
Simulating GATT Descriptors
Add or Remove Descriptors
Use SimulateDescriptorAsync to add or remove descriptors on a characteristic:
SimulateDescriptorCommandParameters parameters =
new SimulateDescriptorCommandParameters(
contextId,
"AA:BB:CC:DD:EE:FF",
"heart_rate",
"heart_rate_measurement",
"gatt.characteristic_user_description",
SimulateDescriptorType.Add);
await driver.Bluetooth.SimulateDescriptorAsync(parameters);
Simulate Descriptor Responses
Use SimulateDescriptorResponseAsync to simulate read or write descriptor responses. Use SimulateDescriptorResponseType.Read or Write:
SimulateDescriptorResponseCommandParameters parameters =
new SimulateDescriptorResponseCommandParameters(
contextId,
"AA:BB:CC:DD:EE:FF",
"heart_rate",
"heart_rate_measurement",
"gatt.characteristic_user_description",
SimulateDescriptorResponseType.Read,
0)
{
Data = { 0x48, 0x65, 0x61, 0x72, 0x74, 0x20, 0x52, 0x61, 0x74, 0x65 },
};
await driver.Bluetooth.SimulateDescriptorResponseAsync(parameters);
Simulating GATT Connection and Disconnection
GATT Connection Response
When the page calls device.gatt.connect(), the OnGattConnectionAttempted event fires. Use SimulateGattConnectionResponseAsync to respond. Code 0 indicates success:
// Code 0 = success
SimulateGattConnectionResponseCommandParameters successParams =
new SimulateGattConnectionResponseCommandParameters(
contextId,
"AA:BB:CC:DD:EE:FF",
0);
await driver.Bluetooth.SimulateGattConnectionResponseAsync(successParams);
GATT Disconnection
Use SimulateGattDisconnectionAsync to simulate a device disconnecting:
SimulateGattDisconnectionCommandParameters parameters =
new SimulateGattDisconnectionCommandParameters(contextId, "AA:BB:CC:DD:EE:FF");
await driver.Bluetooth.SimulateGattDisconnectionAsync(parameters);
Handling Device Selection Prompts
When a page calls navigator.bluetooth.requestDevice(), a prompt appears. Subscribe to OnRequestDevicePromptUpdated to receive prompt events, then use HandleRequestDevicePromptAsync to accept (selecting a device) or cancel:
// Accept the prompt and select a device
HandleRequestDevicePromptAcceptCommandParameters acceptParams =
new HandleRequestDevicePromptAcceptCommandParameters(
"contextId",
"promptId",
"deviceId");
await driver.Bluetooth.HandleRequestDevicePromptAsync(acceptParams);
// Or cancel the prompt
HandleRequestDevicePromptCancelCommandParameters cancelParams =
new HandleRequestDevicePromptCancelCommandParameters("contextId", "promptId");
await driver.Bluetooth.HandleRequestDevicePromptAsync(cancelParams);
Use HandleRequestDevicePromptAcceptCommandParameters with a device ID to accept, or HandleRequestDevicePromptCancelCommandParameters to cancel.
Events
Subscribe to Bluetooth events via Session.SubscribeAsync after adding observers. Remember the two-step subscription pattern: add observer, then subscribe.
RequestDevicePromptUpdated
Fired when a Bluetooth device selection prompt is shown or updated. Use the event to get the prompt ID and available devices, then call HandleRequestDevicePromptAsync:
driver.Bluetooth.OnRequestDevicePromptUpdated.AddObserver((RequestDevicePromptUpdatedEventArgs e) =>
{
Console.WriteLine($"Prompt {e.PromptId} in context {e.BrowsingContextId}");
foreach (RequestDeviceInfo device in e.Devices)
{
Console.WriteLine($" Device: {device.DeviceName} ({device.DeviceId})");
}
});
SubscribeCommandParameters subscribe =
new SubscribeCommandParameters(driver.Bluetooth.OnRequestDevicePromptUpdated.EventName);
await driver.Session.SubscribeAsync(subscribe);
GattConnectionAttempted
Fired when the page attempts a GATT connection to a device. Use this to respond with SimulateGattConnectionResponseAsync:
driver.Bluetooth.OnGattConnectionAttempted.AddObserver((GattConnectionAttemptedEventArgs e) =>
{
Console.WriteLine($"GATT connection attempted: {e.Address} in context {e.BrowsingContextId}");
});
SubscribeCommandParameters subscribe =
new SubscribeCommandParameters(driver.Bluetooth.OnGattConnectionAttempted.EventName);
await driver.Session.SubscribeAsync(subscribe);
CharacteristicEventGenerated
Fired when the page reads, writes, or subscribes to a characteristic. Use this to respond with SimulateCharacteristicResponseAsync:
driver.Bluetooth.OnCharacteristicEventGenerated.AddObserver((CharacteristicEventGeneratedEventArgs e) =>
{
Console.WriteLine($"Characteristic {e.CharacteristicUuid} event: {e.Type}");
});
SubscribeCommandParameters subscribe =
new SubscribeCommandParameters(driver.Bluetooth.OnCharacteristicEventGenerated.EventName);
await driver.Session.SubscribeAsync(subscribe);
DescriptorEventGenerated
Fired when the page reads or writes a descriptor. Use this to respond with SimulateDescriptorResponseAsync:
driver.Bluetooth.OnDescriptorEventGenerated.AddObserver((DescriptorEventGeneratedEventArgs e) =>
{
Console.WriteLine($"Descriptor {e.DescriptorUuid} event: {e.Type}");
});
SubscribeCommandParameters subscribe =
new SubscribeCommandParameters(driver.Bluetooth.OnDescriptorEventGenerated.EventName);
await driver.Session.SubscribeAsync(subscribe);
Common Patterns
Testing Bluetooth Scanner
// Simulate a Bluetooth device
SimulatePreconnectedPeripheralCommandParameters deviceParams =
new SimulatePreconnectedPeripheralCommandParameters(
contextId,
"AA:BB:CC:DD:EE:FF",
"Heart Rate Monitor");
deviceParams.KnownServiceUUIDs.Add("heart_rate");
await driver.Bluetooth.SimulatePreconnectedPeripheralAsync(deviceParams);
// Navigate to page with Bluetooth functionality
await driver.BrowsingContext.NavigateAsync(
new NavigateCommandParameters(contextId, "https://example.com")
{ Wait = ReadinessState.Complete });
// Trigger Bluetooth scan in page
EvaluateResult result = await driver.Script.EvaluateAsync(
new EvaluateCommandParameters(
@"navigator.bluetooth.requestDevice({
filters: [{ services: ['heart_rate'] }]
}).then(device => device.name)",
new ContextTarget(contextId),
true));
if (result is EvaluateResultSuccess success &&
success.Result is StringRemoteValue deviceNameValue)
{
string deviceName = deviceNameValue.Value;
Console.WriteLine($"Found device: {deviceName}");
}
Testing Multiple Devices
List<string> deviceAddresses = new List<string>
{
"AA:BB:CC:DD:EE:01",
"AA:BB:CC:DD:EE:02",
"AA:BB:CC:DD:EE:03"
};
foreach (string address in deviceAddresses)
{
SimulatePreconnectedPeripheralCommandParameters @params =
new SimulatePreconnectedPeripheralCommandParameters(
contextId,
address,
$"Device {address[^2..]}");
@params.KnownServiceUUIDs.Add("battery_service");
await driver.Bluetooth.SimulatePreconnectedPeripheralAsync(@params);
}
// Test device discovery
EvaluateResult result = await driver.Script.EvaluateAsync(
new EvaluateCommandParameters(
@"navigator.bluetooth.requestDevice({
filters: [{ services: ['battery_service'] }],
acceptAllDevices: false
}).then(device => device.id)",
new ContextTarget(contextId),
true));
Testing Device Connection
// Simulate device
SimulatePreconnectedPeripheralCommandParameters deviceParams =
new SimulatePreconnectedPeripheralCommandParameters(
contextId,
"11:22:33:44:55:66",
"Temperature Sensor");
deviceParams.KnownServiceUUIDs.Add("environmental_sensing");
await driver.Bluetooth.SimulatePreconnectedPeripheralAsync(deviceParams);
// Advertise device
ScanRecord scanRecord = new ScanRecord { UUIDs = { "environmental_sensing" } };
SimulateAdvertisementScanEntry adScanEntry = new SimulateAdvertisementScanEntry(
"11:22:33:44:55:66",
-45,
scanRecord);
await driver.Bluetooth.SimulateAdvertisementAsync(
new SimulateAdvertisementCommandParameters(contextId, adScanEntry));
// Test connection
EvaluateResult result = await driver.Script.EvaluateAsync(
new EvaluateCommandParameters(
@"navigator.bluetooth.requestDevice({
filters: [{ services: ['environmental_sensing'] }]
})
.then(device => device.gatt.connect())
.then(server => 'connected')
.catch(err => `error: ${err.message}`)",
new ContextTarget(contextId),
true));
if (result is EvaluateResultSuccess success &&
success.Result is StringRemoteValue connectionValue)
{
string connectionStatus = connectionValue.Value;
Console.WriteLine($"Connection status: {connectionStatus}");
}
Browser Support
| Browser | Support Level |
|---|---|
| Chrome/Edge | ⚠️ Experimental (requires flags) |
| Firefox | ❌ Not supported |
| Safari | ❌ Not supported |
Note: Bluetooth module support is experimental and requires specific browser flags to be enabled.
Enabling Bluetooth Support
Chrome/Edge
The Bluetooth module requires Chromium's experimental web platform features, which are enabled by a browser command-line switch supplied when the browser process is started:
--enable-experimental-web-platform-features
This library does not launch browsers (see Browser Setup), so supply the switch through whatever starts the browser: the driver executable's capabilities, your own launcher, or the browser command line directly.
Best Practices
- Check browser support: Verify Bluetooth module is available before using
- Use realistic device addresses: Follow MAC address format (XX:XX:XX:XX:XX:XX)
- Set appropriate RSSI values: Use realistic signal strength values (-30 to -90)
- Test connection failures: Simulate both successful and failed connections
- Clean up devices: Call
DisableSimulationAsyncor remove simulated devices between tests - Subscribe before actions: Add observers and call
Session.SubscribeAsyncbefore triggering Bluetooth operations in the page
Common Issues
Bluetooth Not Supported
Problem: Module throws "not supported" errors.
Solution:
- Check browser version (Chrome 90+ recommended)
- Enable experimental web platform features
- Use Chrome or Edge (Firefox/Safari not supported)
- Launch browser with required flags
Device Not Discovered
Problem: Simulated device is not found by Web Bluetooth API.
Solution:
- Ensure device services match the filter criteria
- Verify device address format is correct
- Call
SimulateAdvertisementAsyncafterSimulatePreconnectedPeripheralAsync - Check that Web Bluetooth API is enabled in the browser
Manufacturer Data Format
Problem: Manufacturer data not recognized.
Solution:
- Use correct company identifier codes
- Pass the data as a base64 string, which is what
BluetoothManufacturerData.Dataholds - Reference Bluetooth Company Identifiers
Next Steps
- Permissions Module: Managing browser permissions
- WebExtension Module: Browser extension management
- Script Module: Executing JavaScript in pages
- API Reference: Complete API documentation