diff --git a/docs/writing-tests/testdriver.md b/docs/writing-tests/testdriver.md
index d831e2b7c6af87..462c7b85503cb9 100644
--- a/docs/writing-tests/testdriver.md
+++ b/docs/writing-tests/testdriver.md
@@ -278,3 +278,11 @@ const event = await log_entry_promise;
.. js:autofunction:: test_driver.bidi.log.entry_added.on
.. js:autofunction:: test_driver.bidi.log.entry_added.once
```
+
+### Bluetooth ###
+
+The module provides access to [Web Bluetooth](https://webbluetoothcg.github.io/web-bluetooth).
+
+```eval_rst
+.. js:autofunction:: test_driver.bidi.bluetooth.simulate_adapter
+```
diff --git a/infrastructure/metadata/infrastructure/testdriver/bidi/bluetooth/simulate_adapter.https.html.ini b/infrastructure/metadata/infrastructure/testdriver/bidi/bluetooth/simulate_adapter.https.html.ini
new file mode 100644
index 00000000000000..7c3127d167a740
--- /dev/null
+++ b/infrastructure/metadata/infrastructure/testdriver/bidi/bluetooth/simulate_adapter.https.html.ini
@@ -0,0 +1 @@
+disabled: https://github.com/web-platform-tests/wpt/issues/47544
diff --git a/infrastructure/testdriver/bidi/bluetooth/simulate_adapter.https.html b/infrastructure/testdriver/bidi/bluetooth/simulate_adapter.https.html
new file mode 100644
index 00000000000000..3850fa26974a32
--- /dev/null
+++ b/infrastructure/testdriver/bidi/bluetooth/simulate_adapter.https.html
@@ -0,0 +1,36 @@
+
+
+
TestDriver bidi.bluetooth.simulate_adapter method
+
+
+
+
+
+
diff --git a/resources/testdriver.js b/resources/testdriver.js
index 2afd86d8761fe6..bdd18cc0e7d063 100644
--- a/resources/testdriver.js
+++ b/resources/testdriver.js
@@ -58,7 +58,35 @@
* be specified by its ID (a string) or using a `WindowProxy`
* object.
*/
-
+ /**
+ * `bluetooth `_ module.
+ */
+ bluetooth: {
+ /**
+ * Creates a simulated bluetooth adapter with the given params. Matches the
+ * `bluetooth.simulateAdapter `_
+ * WebDriver BiDi command.
+ *
+ * @example
+ * await test_driver.bidi.bluetooth.simulate_adapter({
+ * state: "powered-on"
+ * });
+ *
+ * @param {object} params - Parameters for the command.
+ * @param {string} params.state The state of the simulated bluetooth adapter.
+ * Matches the
+ * `bluetooth.SimulateAdapterParameters:state `_
+ * value.
+ * @param {Context} [params.context] The optional context parameter specifies in
+ * which browsing context the simulated bluetooth adapter should be set. If not
+ * provided, the current browsing context is used.
+ * @returns {Promise} fulfilled after the simulated bluetooth adapter is created
+ * and set, or rejected if the operation fails.
+ */
+ simulate_adapter: function (params) {
+ return window.test_driver_internal.bidi.bluetooth.simulate_adapter(params);
+ }
+ },
/**
* `log `_ module.
*/
@@ -1287,6 +1315,12 @@
in_automation: false,
bidi: {
+ bluetooth: {
+ simulate_adapter: function () {
+ throw new Error(
+ "bidi.bluetooth.simulate_adapter is not implemented by testdriver-vendor.js");
+ }
+ },
log: {
entry_added: {
async subscribe() {
diff --git a/tools/wptrunner/wptrunner/executors/asyncactions.py b/tools/wptrunner/wptrunner/executors/asyncactions.py
index c49c2a3ca61d82..ef5344e19b29a7 100644
--- a/tools/wptrunner/wptrunner/executors/asyncactions.py
+++ b/tools/wptrunner/wptrunner/executors/asyncactions.py
@@ -7,6 +7,32 @@ def do_delayed_imports():
global webdriver
import webdriver
+
+class BidiBluetoothSimulateAdapterAction:
+ name = "bidi.bluetooth.simulate_adapter"
+
+ def __init__(self, logger, protocol):
+ do_delayed_imports()
+ self.logger = logger
+ self.protocol = protocol
+
+ async def __call__(self, payload):
+ if payload["context"] is None:
+ raise ValueError("Missing required parameter: context")
+
+ context = payload["context"]
+ if isinstance(context, str):
+ pass
+ elif isinstance(context, webdriver.bidi.protocol.BidiWindow):
+ # Context can be a serialized WindowProxy.
+ context = context.browsing_context
+ else:
+ raise ValueError("Unexpected context type: %s" % context)
+
+ state = payload["state"]
+ return await self.protocol.bidi_bluetooth.simulate_adapter(context, state)
+
+
class BidiSessionSubscribeAction:
name = "bidi.session.subscribe"
@@ -49,4 +75,7 @@ async def __call__(self, payload):
origin)
-async_actions = [BidiPermissionsSetPermissionAction, BidiSessionSubscribeAction]
+async_actions = [
+ BidiBluetoothSimulateAdapterAction,
+ BidiPermissionsSetPermissionAction,
+ BidiSessionSubscribeAction]
diff --git a/tools/wptrunner/wptrunner/executors/executorwebdriver.py b/tools/wptrunner/wptrunner/executors/executorwebdriver.py
index 8ec2fbe27979fb..c2923d13a9ead5 100644
--- a/tools/wptrunner/wptrunner/executors/executorwebdriver.py
+++ b/tools/wptrunner/wptrunner/executors/executorwebdriver.py
@@ -37,6 +37,7 @@
RPHRegistrationsProtocolPart,
FedCMProtocolPart,
VirtualSensorProtocolPart,
+ BidiBluetoothProtocolPart,
BidiBrowsingContextProtocolPart,
BidiEventsProtocolPart,
BidiPermissionsProtocolPart,
@@ -115,6 +116,21 @@ def wait(self):
return False
+class WebDriverBidiBluetoothProtocolPart(BidiBluetoothProtocolPart):
+ def __init__(self, parent):
+ super().__init__(parent)
+ self.webdriver = None
+
+ def setup(self):
+ self.webdriver = self.parent.webdriver
+
+ async def simulate_adapter(self,
+ context: str,
+ state: str) -> None:
+ await self.webdriver.bidi_session.bluetooth.simulate_adapter(
+ context=context, state=state)
+
+
class WebDriverBidiBrowsingContextProtocolPart(BidiBrowsingContextProtocolPart):
def __init__(self, parent):
super().__init__(parent)
@@ -692,7 +708,8 @@ def after_connect(self):
class WebDriverBidiProtocol(WebDriverProtocol):
enable_bidi = True
- implements = [WebDriverBidiBrowsingContextProtocolPart,
+ implements = [WebDriverBidiBluetoothProtocolPart,
+ WebDriverBidiBrowsingContextProtocolPart,
WebDriverBidiEventsProtocolPart,
WebDriverBidiPermissionsProtocolPart,
WebDriverBidiScriptProtocolPart,
diff --git a/tools/wptrunner/wptrunner/executors/protocol.py b/tools/wptrunner/wptrunner/executors/protocol.py
index 33f4066893ed5f..19d5b49d2b7fb4 100644
--- a/tools/wptrunner/wptrunner/executors/protocol.py
+++ b/tools/wptrunner/wptrunner/executors/protocol.py
@@ -331,6 +331,23 @@ def get_computed_role(self, element):
pass
+class BidiBluetoothProtocolPart(ProtocolPart):
+ """Protocol part for managing BiDi events"""
+ __metaclass__ = ABCMeta
+ name = "bidi_bluetooth"
+
+ @abstractmethod
+ async def simulate_adapter(self,
+ context: str,
+ state: str) -> None:
+ """
+ Creates a simulated bluetooth adapter.
+ :param context: Browsing context to set the simulated adapter to.
+ :param state: The state of the simulated bluetooth adapter.
+ """
+ pass
+
+
class BidiBrowsingContextProtocolPart(ProtocolPart):
"""Protocol part for managing BiDi events"""
__metaclass__ = ABCMeta
diff --git a/tools/wptrunner/wptrunner/testdriver-extra.js b/tools/wptrunner/wptrunner/testdriver-extra.js
index 88eb95a09c5912..c4a02ef40a7eda 100644
--- a/tools/wptrunner/wptrunner/testdriver-extra.js
+++ b/tools/wptrunner/wptrunner/testdriver-extra.js
@@ -208,6 +208,14 @@
window.test_driver_internal.in_automation = true;
+ window.test_driver_internal.bidi.bluetooth.simulate_adapter = function (params) {
+ return create_action("bidi.bluetooth.simulate_adapter", {
+ // Default to the current window.
+ context: window,
+ ...params
+ });
+ }
+
window.test_driver_internal.bidi.log.entry_added.subscribe =
function (params) {
return subscribe({