diff --git a/infrastructure/metadata/infrastructure/webdriver/bidi/subscription.window.js.ini b/infrastructure/metadata/infrastructure/webdriver/bidi/subscription.window.js.ini
new file mode 100644
index 00000000000000..7c3127d167a740
--- /dev/null
+++ b/infrastructure/metadata/infrastructure/webdriver/bidi/subscription.window.js.ini
@@ -0,0 +1 @@
+disabled: https://github.com/web-platform-tests/wpt/issues/47544
diff --git a/infrastructure/webdriver/bidi/subscription.html b/infrastructure/webdriver/bidi/subscription.html
index 056c2e5f77fb95..134d4237cfcb9a 100644
--- a/infrastructure/webdriver/bidi/subscription.html
+++ b/infrastructure/webdriver/bidi/subscription.html
@@ -1,5 +1,6 @@
+
Test console log are present
diff --git a/infrastructure/webdriver/bidi/subscription.window.js b/infrastructure/webdriver/bidi/subscription.window.js
new file mode 100644
index 00000000000000..2fd3c383dd853c
--- /dev/null
+++ b/infrastructure/webdriver/bidi/subscription.window.js
@@ -0,0 +1,23 @@
+// META: title=Test console log are present
+// META: require_webdriver_bidi=true
+// META: script=/resources/testdriver.js
+
+'use strict';
+
+promise_test(async () => {
+ const some_message = "SOME MESSAGE";
+ // Subscribe to `log.entryAdded` BiDi events. This will not add a listener to the page.
+ await test_driver.bidi.log.entry_added.subscribe();
+ // Add a listener for the log.entryAdded event. This will not subscribe to the event, so the subscription is
+ // required before. The cleanup is done automatically after the test is finished.
+ const log_entry_promise = test_driver.bidi.log.entry_added.once();
+ // Emit a console.log message.
+ // Note: Lint rule is disabled in `lint.ignore` file.
+ console.log(some_message);
+ // Wait for the log.entryAdded event to be received.
+ const event = await log_entry_promise;
+ // Assert the log.entryAdded event has the expected message.
+ assert_equals(event.args.length, 1);
+ const event_message = event.args[0];
+ assert_equals(event_message.value, some_message);
+}, "Assert testdriver can subscribe and receive events");
diff --git a/lint.ignore b/lint.ignore
index ee19f061a74aa8..e59a64d2ed7197 100644
--- a/lint.ignore
+++ b/lint.ignore
@@ -122,6 +122,7 @@ CONSOLE: webaudio/resources/audit.js:41
# Intentional use of console.*
CONSOLE: infrastructure/webdriver/bidi/subscription.html
+CONSOLE: infrastructure/webdriver/bidi/subscription.window.js
# use of console in a public library - annotation-model ensures
# it is not actually used
diff --git a/tools/lint/lint.py b/tools/lint/lint.py
index 32bc7ece7b12f3..573fbffd58a8fe 100644
--- a/tools/lint/lint.py
+++ b/tools/lint/lint.py
@@ -442,6 +442,16 @@ def check_parsed(repo_root: Text, path: Text, f: IO[bytes]) -> List[rules.Error]
if timeout_value != "long":
errors.append(rules.InvalidTimeout.error(path, (timeout_value,)))
+ if len(source_file.require_webdriver_bidi_nodes) > 1:
+ errors.append(rules.MultipleRequireBidi.error(path))
+
+ for timeout_node in source_file.require_webdriver_bidi_nodes:
+ require_webdriver_bidi_value = timeout_node.attrib.get("content",
+ "").lower()
+ if require_webdriver_bidi_value != "true" and require_webdriver_bidi_value != "false":
+ errors.append(rules.InvalidRequireBidi.error(path, (
+ require_webdriver_bidi_value,)))
+
if source_file.content_is_ref_node or source_file.content_is_testharness:
for element in source_file.variant_nodes:
if "content" not in element.attrib:
diff --git a/tools/lint/rules.py b/tools/lint/rules.py
index 8dba7817a9e563..6325e48c558d6e 100644
--- a/tools/lint/rules.py
+++ b/tools/lint/rules.py
@@ -152,6 +152,24 @@ class InvalidTimeout(Rule):
to_fix = "replace the value of the `content` attribute with `long`"
+class MultipleRequireBidi(Rule):
+ name = "MULTIPLE-REQUIRE-WEBDRIVER-BIDI"
+ description = "More than one meta name='require_webdriver_bidi'"
+ to_fix = """
+ ensure each test file has only one instance of a `` element
+ """
+
+
+class InvalidRequireBidi(Rule):
+ name = "INVALID-REQUIRE-WEBDRIVER-BIDI"
+ description = collapse("""
+ Test file with `` element that has a `content`
+ attribute whose value is not a boolean: %s
+ """)
+ to_fix = "replace the value of the `content` attribute with `true` or `false`"
+
+
class MultipleTestharness(Rule):
name = "MULTIPLE-TESTHARNESS"
description = "More than one `