diff --git a/tools/webdriver/webdriver/bidi/modules/script.py b/tools/webdriver/webdriver/bidi/modules/script.py
index f128b0d089c28b..737426a5d54641 100644
--- a/tools/webdriver/webdriver/bidi/modules/script.py
+++ b/tools/webdriver/webdriver/bidi/modules/script.py
@@ -96,6 +96,7 @@ def add_preload_script(
self,
function_declaration: str,
arguments: Optional[List[Mapping[str, Any]]] = None,
+ contexts: Optional[List[str]] = None,
sandbox: Optional[str] = None
) -> Mapping[str, Any]:
params: MutableMapping[str, Any] = {
@@ -104,6 +105,8 @@ def add_preload_script(
if arguments is not None:
params["arguments"] = arguments
+ if contexts is not None:
+ params["contexts"] = contexts
if sandbox is not None:
params["sandbox"] = sandbox
diff --git a/webdriver/tests/bidi/script/add_preload_script/contexts.py b/webdriver/tests/bidi/script/add_preload_script/contexts.py
new file mode 100644
index 00000000000000..97d00669d17136
--- /dev/null
+++ b/webdriver/tests/bidi/script/add_preload_script/contexts.py
@@ -0,0 +1,111 @@
+import pytest
+
+from webdriver.bidi.modules.script import ContextTarget
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("domain", ["", "alt"], ids=["same_origin", "cross_origin"])
+async def test_top_context_with_iframes(
+ bidi_session, add_preload_script, new_tab,
+ inline, iframe, domain):
+
+ iframe_content = f"
{domain}
"
+ url = inline(f"{iframe(iframe_content, domain=domain)}")
+
+ await add_preload_script(
+ function_declaration="() => { window.bar='foo'; }",
+ contexts=[new_tab["context"]])
+
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"],
+ url=url,
+ wait="complete",
+ )
+
+ # Check that preload script applied the changes to the context
+ result = await bidi_session.script.evaluate(
+ expression="window.bar",
+ target=ContextTarget(new_tab["context"]),
+ await_promise=True,
+ )
+ assert result == {"type": "string", "value": "foo"}
+
+ contexts = await bidi_session.browsing_context.get_tree(
+ root=new_tab["context"])
+
+ assert len(contexts[0]["children"]) == 1
+ frame_context = contexts[0]["children"][0]
+
+ # Check that preload script applied the changes to the iframe
+ result = await bidi_session.script.evaluate(
+ expression="window.bar",
+ target=ContextTarget(frame_context["context"]),
+ await_promise=True,
+ )
+ assert result == {"type": "string", "value": "foo"}
+
+
+@pytest.mark.asyncio
+@pytest.mark.parametrize("type_hint", ["tab", "window"])
+async def test_page_script_context_isolation(bidi_session, add_preload_script,
+ top_context, type_hint,
+ test_page):
+ await add_preload_script(function_declaration="() => { window.baz = 42; }",
+ contexts=[top_context['context']])
+
+ new_context = await bidi_session.browsing_context.create(
+ type_hint=type_hint)
+
+ # Navigate both contexts to ensure preload script is triggered
+ await bidi_session.browsing_context.navigate(
+ context=top_context['context'],
+ url=test_page,
+ wait="complete",
+ )
+ await bidi_session.browsing_context.navigate(
+ context=new_context["context"],
+ url=test_page,
+ wait="complete",
+ )
+
+ # Check that preload script applied the changes to the context
+ result = await bidi_session.script.evaluate(
+ expression="window.baz",
+ target=ContextTarget(top_context["context"]),
+ await_promise=True,
+ )
+ assert result == {"type": "number", "value": 42}
+
+ # Check that preload script did *not* apply the changes to the other context
+ result = await bidi_session.script.evaluate(
+ expression="window.baz",
+ target=ContextTarget(new_context["context"]),
+ await_promise=True,
+ )
+ assert result == {type: "undefined"}
+
+
+@pytest.mark.asyncio
+async def test_identical_contexts(
+ bidi_session, add_preload_script, new_tab,
+ inline):
+
+ url = inline(f"test
")
+
+ await add_preload_script(
+ function_declaration="() => { window.foo = window.foo ? window.foo + 1 : 1; }",
+ contexts=[new_tab["context"], new_tab["context"]])
+
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"],
+ url=url,
+ wait="complete",
+ )
+
+ # Check that preload script applied the changes to the context only once
+ result = await bidi_session.script.evaluate(
+ expression="window.foo",
+ target=ContextTarget(new_tab["context"]),
+ await_promise=True,
+ )
+ assert result == {"type": "number", "value": "1"}
diff --git a/webdriver/tests/bidi/script/add_preload_script/invalid.py b/webdriver/tests/bidi/script/add_preload_script/invalid.py
index 54440ff67804b6..58f1ba02e5fc10 100644
--- a/webdriver/tests/bidi/script/add_preload_script/invalid.py
+++ b/webdriver/tests/bidi/script/add_preload_script/invalid.py
@@ -71,7 +71,8 @@ async def test_params_arguments_channel_ownership_invalid_value(bidi_session):
with pytest.raises(error.InvalidArgumentException):
await bidi_session.script.add_preload_script(
function_declaration="() => {}",
- arguments=[{"type": "channel", "value": {"ownership": "_UNKNOWN_"}}],
+ arguments=[{"type": "channel", "value": {
+ "ownership": "_UNKNOWN_"}}],
)
@@ -186,6 +187,61 @@ async def test_params_arguments_channel_include_shadow_tree_invalid_value(bidi_s
)
+@pytest.mark.parametrize("contexts", [False, 42, '_UNKNOWN_', {}])
+async def test_params_contexts_invalid_type(bidi_session, contexts):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.script.add_preload_script(
+ function_declaration="() => {}",
+ contexts=contexts
+ ),
+
+
+async def test_params_contexts_empty_list(bidi_session):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.script.add_preload_script(
+ function_declaration="() => {}",
+ contexts=[]
+ ),
+
+
+@pytest.mark.parametrize("value", [None, False, 42, {}, []])
+async def test_params_contexts_context_invalid_type(bidi_session, value):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.script.add_preload_script(
+ function_declaration="() => {}",
+ contexts=[value]
+ ),
+
+
+@pytest.mark.parametrize("value", ["", "somestring"])
+async def test_params_contexts_context_invalid_value(bidi_session, value):
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.script.add_preload_script(
+ function_declaration="() => {}",
+ contexts=[value]
+ ),
+
+
+async def test_params_contexts_context_non_top_level(bidi_session, new_tab, test_page_same_origin_frame):
+ await bidi_session.browsing_context.navigate(
+ context=new_tab["context"],
+ url=test_page_same_origin_frame,
+ wait="complete",
+ )
+
+ contexts = await bidi_session.browsing_context.get_tree(root=new_tab["context"])
+
+ assert len(contexts) == 1
+ assert len(contexts[0]["children"]) == 1
+ child_info = contexts[0]["children"][0]
+
+ with pytest.raises(error.InvalidArgumentException):
+ await bidi_session.script.add_preload_script(
+ function_declaration="() => {}",
+ contexts=[child_info['context']]
+ ),
+
+
@pytest.mark.parametrize("sandbox", [False, 42, {}, []])
async def test_params_sandbox_invalid_type(bidi_session, sandbox):
with pytest.raises(error.InvalidArgumentException):
diff --git a/webdriver/tests/support/fixtures_bidi.py b/webdriver/tests/support/fixtures_bidi.py
index c073997215e7a6..f4c4c19c3bbe7f 100644
--- a/webdriver/tests/support/fixtures_bidi.py
+++ b/webdriver/tests/support/fixtures_bidi.py
@@ -20,10 +20,11 @@
async def add_preload_script(bidi_session):
preload_scripts_ids = []
- async def add_preload_script(function_declaration, arguments=None, sandbox=None):
+ async def add_preload_script(function_declaration, arguments=None, contexts=None, sandbox=None):
script = await bidi_session.script.add_preload_script(
function_declaration=function_declaration,
arguments=arguments,
+ contexts=contexts,
sandbox=sandbox,
)
preload_scripts_ids.append(script)