tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit ae3d51f0a49905e3de66af97a31d765f9a67eee9
parent 088a638a9ff8ade8461a91478b4b6fa190948b08
Author: Steven <stevenwei@microsoft.com>
Date:   Wed,  3 Dec 2025 14:42:20 +0000

Bug 1992907 [wpt PR 55274] - [testdriver] Add testdriver extension for prefetch event in speculation module, a=testonly

Automatic update from web-platform-tests
[testdriver] Add testdriver extension for prefetch event in speculation module (#55274)

--

wpt-commits: 14d7ab53ced2c3898901b1a3829fec562055ee87
wpt-pr: 55274

Diffstat:
Atesting/web-platform/tests/infrastructure/metadata/infrastructure/testdriver/bidi/speculation/prefetch_status_updated.https.html.ini | 3+++
Atesting/web-platform/tests/infrastructure/testdriver/bidi/speculation/prefetch_status_updated.https.html | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/infrastructure/testdriver/bidi/speculation/resources/bidi-speculation-helper.js | 40++++++++++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/infrastructure/testdriver/bidi/speculation/resources/target.html | 11+++++++++++
Mtesting/web-platform/tests/resources/testdriver.js | 84+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtesting/web-platform/tests/tools/wptrunner/wptrunner/testdriver-extra.js | 20++++++++++++++++++--
Atesting/web-platform/tests/webdriver/tests/bidi/external/speculation/__init__.py | 0
Atesting/web-platform/tests/webdriver/tests/bidi/external/speculation/conftest.py | 33+++++++++++++++++++++++++++++++++
Atesting/web-platform/tests/webdriver/tests/bidi/external/speculation/prefetch_status_updated/__init__.py | 0
Atesting/web-platform/tests/webdriver/tests/bidi/external/speculation/prefetch_status_updated/prefetch_status_updated.py | 251+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
10 files changed, 573 insertions(+), 2 deletions(-)

diff --git a/testing/web-platform/tests/infrastructure/metadata/infrastructure/testdriver/bidi/speculation/prefetch_status_updated.https.html.ini b/testing/web-platform/tests/infrastructure/metadata/infrastructure/testdriver/bidi/speculation/prefetch_status_updated.https.html.ini @@ -0,0 +1,3 @@ +[prefetch_status_updated.https.html] + expected: + if product != "chrome": ERROR diff --git a/testing/web-platform/tests/infrastructure/testdriver/bidi/speculation/prefetch_status_updated.https.html b/testing/web-platform/tests/infrastructure/testdriver/bidi/speculation/prefetch_status_updated.https.html @@ -0,0 +1,132 @@ +<!DOCTYPE html> +<meta charset="utf-8"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/common/dispatcher/dispatcher.js"></script> +<script src="/common/utils.js"></script> +<script src="/resources/testdriver.js?feature=bidi"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="resources/bidi-speculation-helper.js"></script> +<script> + promise_setup(async () => { + await waitForDocumentReady(); + }); + promise_test(async t => { + // Subscribe for this test only + const unsubscribe = await test_driver.bidi.speculation.prefetch_status_updated.subscribe(); + const receivedStatuses = []; + const expectedStatuses = ['pending', 'ready']; + const targetUrl = window.location.origin + "/infrastructure/testdriver/bidi/speculation/resources/target.html"; + // Create a promise that resolves when we receive the 'ready' event + const readyEventPromise = new Promise((resolve, reject) => { + const removeHandler = test_driver.bidi.speculation.prefetch_status_updated.on((event) => { + // Multiple events for the same status can be sent in cases of network retries, etc. + if (!receivedStatuses.includes(event.status)) { + receivedStatuses.push(event.status); + } + // When we receive the ready event, clean up and resolve + if (event.status === 'ready') { + removeHandler(); + resolve(); + } + }); + }); + // Create prefetch rules for our target page in resources + const speculationRules = { + prefetch: [{ + source: "list", + urls: [targetUrl] + }] + }; + // Use helper function to add both speculation rules and link + const { script, link } = addSpeculationRulesAndLink(speculationRules, targetUrl); + // Await the ready event + await readyEventPromise; + // Assert that we received the expected events + assert_array_equals(receivedStatuses, expectedStatuses, 'Should have received pending and ready events'); + t.add_cleanup(async () => { + await unsubscribe(); + }); + }, "prefetch_status_updated event subscription and structure validation"); + promise_test(async t => { + // Subscribe for this test only + const unsubscribe = await test_driver.bidi.speculation.prefetch_status_updated.subscribe(); + const receivedStatuses = []; + const expectedStatuses = ['pending', 'ready', 'success']; + let newWindow = null; + // Create prefetch rules for our target page in resources (different URL to avoid caching) + const targetUrl = window.location.origin + "/infrastructure/testdriver/bidi/speculation/resources/target.html?test=2"; + // Create a promise that resolves when we receive the 'success' event + const successEventPromise = new Promise((resolve, reject) => { + const removeHandler = test_driver.bidi.speculation.prefetch_status_updated.on((event) => { + // Multiple events for the same status can be sent for network retries, etc. + if (!receivedStatuses.includes(event.status)) { + receivedStatuses.push(event.status); + } + // When we receive the ready event, navigate to trigger success + if (event.status === 'ready') { + // Open the prefetched page in a new window to trigger success + newWindow = window.open(event.url, '_blank'); + } else if (event.status === 'success') { + removeHandler(); + resolve(); + } + }); + }); + const speculationRules = { + prefetch: [{ + source: "list", + urls: [targetUrl] + }] + }; + // Use helper function to add both speculation rules and link + const { script, link } = addSpeculationRulesAndLink(speculationRules, targetUrl); + // Await the success event + await successEventPromise; + // Assert that we received the expected events + assert_array_equals(receivedStatuses, expectedStatuses, 'Should have received pending, ready, and success events'); + t.add_cleanup(async () => { + await unsubscribe(); + if (newWindow && !newWindow.closed) { + newWindow.close(); + } + }); + }, "prefetch_status_updated event with navigation to success"); + promise_test(async t => { + // Subscribe for this test only + const unsubscribe = await test_driver.bidi.speculation.prefetch_status_updated.subscribe(); + const receivedStatuses = []; + const expectedStatuses = ['failure']; + // Set error url to fail at network layer and only expect failure event + const errorUrl = "http://0.0.0.0:1/test.html"; + // Create a promise that resolves when we receive the 'failure' event + const failureEventPromise = new Promise((resolve, reject) => { + const removeHandler = test_driver.bidi.speculation.prefetch_status_updated.on((event) => { + // Multiple events for the same status can be sent for network retries, etc. + if (!receivedStatuses.includes(event.status)) { + receivedStatuses.push(event.status); + } + // When we receive the failure event, we're done + if (event.status === 'failure') { + removeHandler(); + resolve(); + } + }); + }); + const speculationRules = { + prefetch: [{ + source: "list", + urls: [errorUrl] + }] + }; + // Use helper function to add both speculation rules and link + const { script, link } = addSpeculationRulesAndLink(speculationRules, errorUrl); + // Await the failure event + await failureEventPromise; + // Assert that we received the expected events + assert_array_equals(receivedStatuses, expectedStatuses, 'Should have received only failure event'); + t.add_cleanup(async () => { + await unsubscribe(); + }); + }, "prefetch_status_updated event with prefetch failure"); +</script> +\ No newline at end of file diff --git a/testing/web-platform/tests/infrastructure/testdriver/bidi/speculation/resources/bidi-speculation-helper.js b/testing/web-platform/tests/infrastructure/testdriver/bidi/speculation/resources/bidi-speculation-helper.js @@ -0,0 +1,39 @@ +'use strict'; +/** + * Helper functions for speculation rules BiDi testdriver tests + */ +/** + * Waits until the document has finished loading. + * @returns {Promise<void>} Resolves if the document is already completely + * loaded or when the 'onload' event is fired. + */ +function waitForDocumentReady() { + return new Promise(resolve => { + if (document.readyState === 'complete') { + resolve(); + } + window.addEventListener('load', () => { + resolve(); + }, {once: true}); + }); +} +/** + * Adds speculation rules and a corresponding link to the page. + * @param {Object} speculationRules - The speculation rules object to add + * @param {string} targetUrl - The URL to add as a link + * @param {string} linkText - The text content of the link (optional) + * @returns {Object} Object containing the created script and link elements + */ +function addSpeculationRulesAndLink(speculationRules, targetUrl, linkText = 'Test Link') { + // Add speculation rules script exactly like the working test + const script = document.createElement('script'); + script.type = 'speculationrules'; + script.textContent = JSON.stringify(speculationRules); + document.head.appendChild(script); + // Also add a link to the page (some implementations might need this) + const link = document.createElement('a'); + link.href = targetUrl; + link.textContent = linkText; + document.body.appendChild(link); + return { script, link }; +} +\ No newline at end of file diff --git a/testing/web-platform/tests/infrastructure/testdriver/bidi/speculation/resources/target.html b/testing/web-platform/tests/infrastructure/testdriver/bidi/speculation/resources/target.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <title>Prefetch Target Page</title> +</head> +<body> + <h1>This is a prefetch target page</h1> + <p>This page is used as a target for prefetch testing.</p> +</body> +</html> +\ No newline at end of file diff --git a/testing/web-platform/tests/resources/testdriver.js b/testing/web-platform/tests/resources/testdriver.js @@ -770,6 +770,78 @@ } }, /** + * `speculation <https://wicg.github.io/nav-speculation/prefetch.html>`_ module. + */ + speculation: { + /** + * `speculation.PrefetchStatusUpdated <https://wicg.github.io/nav-speculation/prefetch.html#speculation-prefetchstatusupdated-event>`_ + * event. + */ + prefetch_status_updated: { + /** + * @typedef {object} PrefetchStatusUpdated + * `speculation.PrefetchStatusUpdatedParameters <https://wicg.github.io/nav-speculation/prefetch.html#cddl-type-speculationprefetchstatusupdatedparameters>`_ + * event. + */ + + /** + * Subscribes to the event. Events will be emitted only if + * there is a subscription for the event. This method does + * not add actual listeners. To listen to the event, use the + * `on` or `once` methods. The buffered events will be + * emitted before the command promise is resolved. + * + * @param {object} [params] Parameters for the subscription. + * @param {null|Array.<(Context)>} [params.contexts] The + * optional contexts parameter specifies which browsing + * contexts to subscribe to the event on. It should be + * either an array of Context objects, or null. If null, the + * event will be subscribed to globally. If omitted, the + * event will be subscribed to on the current browsing + * context. + * @returns {Promise<(function(): Promise<void>)>} Callback + * for unsubscribing from the created subscription. + */ + subscribe: async function(params = {}) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.speculation + .prefetch_status_updated.subscribe(params); + }, + /** + * Adds an event listener for the event. + * + * @param {function(PrefetchStatusUpdated): void} callback The + * callback to be called when the event is emitted. The + * callback is called with the event object as a parameter. + * @returns {function(): void} A function that removes the + * added event listener when called. + */ + on: function(callback) { + assertBidiIsEnabled(); + return window.test_driver_internal.bidi.speculation + .prefetch_status_updated.on(callback); + }, + /** + * Adds an event listener for the event that is only called + * once and removed afterward. + * + * @return {Promise<PrefetchStatusUpdated>} The promise which + * is resolved with the event object when the event is emitted. + */ + once: function() { + assertBidiIsEnabled(); + return new Promise(resolve => { + const remove_handler = + window.test_driver_internal.bidi.speculation + .prefetch_status_updated.on(event => { + resolve(event); + remove_handler(); + }); + }); + } + } + }, + /** * `emulation <https://www.w3.org/TR/webdriver-bidi/#module-emulation>`_ module. */ emulation: { @@ -2337,6 +2409,18 @@ throw new Error( "bidi.permissions.set_permission() is not implemented by testdriver-vendor.js"); } + }, + speculation: { + prefetch_status_updated: { + async subscribe() { + throw new Error( + 'bidi.speculation.prefetch_status_updated.subscribe is not implemented by testdriver-vendor.js'); + }, + on() { + throw new Error( + 'bidi.speculation.prefetch_status_updated.on is not implemented by testdriver-vendor.js'); + } + }, } }, diff --git a/testing/web-platform/tests/tools/wptrunner/wptrunner/testdriver-extra.js b/testing/web-platform/tests/tools/wptrunner/wptrunner/testdriver-extra.js @@ -220,14 +220,12 @@ const subscription_id = action_result["subscription"]; return async ()=>{ - console.log("!!@@## unsubscribing") await create_action("bidi.session.unsubscribe", { // Default to subscribing to the window's events. subscriptions: [subscription_id] }); } }; - window.test_driver_internal.in_automation = true; window.test_driver_internal.bidi.bluetooth.handle_request_device_prompt = @@ -673,4 +671,22 @@ window.test_driver_internal.set_global_privacy_control = function(gpc, context=null) { return create_action("set_global_privacy_control", {gpc}); }; + + + window.test_driver_internal.bidi.speculation.prefetch_status_updated.subscribe = + function(params) { + return subscribe( + {...params, events: ['speculation.prefetchStatusUpdated']}) + }; + + window.test_driver_internal.bidi.speculation.prefetch_status_updated.on = + function(callback) { + const on_event = (event) => { + callback(event.payload); + }; + event_target.addEventListener( + 'speculation.prefetchStatusUpdated', on_event); + return () => event_target.removeEventListener( + 'speculation.prefetchStatusUpdated', on_event); + }; })(); diff --git a/testing/web-platform/tests/webdriver/tests/bidi/external/speculation/__init__.py b/testing/web-platform/tests/webdriver/tests/bidi/external/speculation/__init__.py diff --git a/testing/web-platform/tests/webdriver/tests/bidi/external/speculation/conftest.py b/testing/web-platform/tests/webdriver/tests/bidi/external/speculation/conftest.py @@ -0,0 +1,32 @@ +import pytest +from typing import Any, Mapping +from webdriver.bidi.modules.script import ContextTarget +@pytest.fixture +def add_speculation_rules_and_link(bidi_session): + """Helper for adding both speculation rules and a prefetch link to a page.""" + async def add_rules_and_link(context: Mapping[str, Any], rules: str, href: str, text: str = "Test Link", link_id: str = "prefetch-page"): + """Add speculation rules and a corresponding link to the page.""" + # Add speculation rules first + await bidi_session.script.evaluate( + expression=f""" + const script = document.createElement('script'); + script.type = 'speculationrules'; + script.textContent = `{rules}`; + document.head.appendChild(script); + """, + target=ContextTarget(context["context"]), + await_promise=False + ) + # Then add the link + await bidi_session.script.evaluate( + expression=f""" + const link = document.createElement('a'); + link.href = '{href}'; + link.textContent = '{text}'; + link.id = '{link_id}'; + document.body.appendChild(link); + """, + target={"context": context["context"]}, + await_promise=False + ) + return add_rules_and_link +\ No newline at end of file diff --git a/testing/web-platform/tests/webdriver/tests/bidi/external/speculation/prefetch_status_updated/__init__.py b/testing/web-platform/tests/webdriver/tests/bidi/external/speculation/prefetch_status_updated/__init__.py diff --git a/testing/web-platform/tests/webdriver/tests/bidi/external/speculation/prefetch_status_updated/prefetch_status_updated.py b/testing/web-platform/tests/webdriver/tests/bidi/external/speculation/prefetch_status_updated/prefetch_status_updated.py @@ -0,0 +1,250 @@ +import pytest +from webdriver.error import TimeoutException +pytestmark = pytest.mark.asyncio +@pytest.mark.asyncio +async def test_speculation_rules_generate_ready_events( + bidi_session, subscribe_events, new_tab, url, wait_for_events, add_speculation_rules_and_link +): + '''Test that speculation rules generate prefetch events with proper pending->ready sequence.''' + await subscribe_events(events=["speculation.prefetchStatusUpdated"]) + test_url = url("/common/blank.html", protocol="https") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="none", + ) + # Add speculation rules to trigger immediate prefetching + prefetch_target = url("/common/dummy.xml", protocol="https") + speculation_rules = f'''{{ + "prefetch": [{{ + "where": {{ "href_matches": "{prefetch_target}" }}, + "eagerness": "immediate" + }}] + }}''' + # Set up event waiter before triggering prefetch + with wait_for_events(["speculation.prefetchStatusUpdated"]) as waiter: + # Add speculation rules and link to trigger prefetching + await add_speculation_rules_and_link(new_tab, speculation_rules, prefetch_target) + # Wait for pending and ready events + events = await waiter.get_events(lambda events: len(events) == 2) + # Verify all events have correct structure and sequence + assert events == [ + ("speculation.prefetchStatusUpdated", { + "url": prefetch_target, + "status": "pending", + "context": new_tab["context"] + }), + ("speculation.prefetchStatusUpdated", { + "url": prefetch_target, + "status": "ready", + "context": new_tab["context"] + }) + ], f"Events don't match expected sequence: {events}" +@pytest.mark.asyncio +async def test_speculation_rules_generate_events_with_navigation( + bidi_session, subscribe_events, new_tab, url, wait_for_events, add_speculation_rules_and_link +): + '''Test that speculation rules generate prefetch events with navigation and success event after using prefetched page.''' + await subscribe_events(events=["speculation.prefetchStatusUpdated"]) + test_url = url("/common/blank.html", protocol="https") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="none", + ) + # Add speculation rules to trigger immediate prefetching + prefetch_target = url("/common/dummy.xml", protocol="https") + speculation_rules = f'''{{ + "prefetch": [{{ + "where": {{ "href_matches": "{prefetch_target}" }}, + "eagerness": "immediate" + }}] + }}''' + # Set up event waiter before triggering prefetch - capture all events through navigation + with wait_for_events(["speculation.prefetchStatusUpdated"]) as waiter: + # Add speculation rules and link to trigger prefetching + await add_speculation_rules_and_link(new_tab, speculation_rules, prefetch_target) + # Wait for pending and ready events first + events = await waiter.get_events(lambda events: len(events) >= 2) + # Verify we got pending and ready events + assert events == [ + ("speculation.prefetchStatusUpdated", { + "url": prefetch_target, + "status": "pending", + "context": new_tab["context"] + }), + ("speculation.prefetchStatusUpdated", { + "url": prefetch_target, + "status": "ready", + "context": new_tab["context"] + }) + ], f"Events don't match expected sequence: {events}" + with wait_for_events(["speculation.prefetchStatusUpdated"]) as waiter: + # Now navigate to the prefetched page to potentially trigger success event + # Navigate by clicking the link (user-initiated navigation to trigger success event) + await bidi_session.script.evaluate( + expression=''' + const prefetchLink = document.getElementById('prefetch-page'); + if (prefetchLink) { + prefetchLink.click(); + } + ''', + target={"context": new_tab["context"]}, + await_promise=False + ) + # Wait for success event after navigation to the prefetched page + success_event = await waiter.get_events(lambda events: len(events) >= 1) + # Verify success event has correct structure and sequence + assert success_event == [ + ("speculation.prefetchStatusUpdated", { + "url": prefetch_target, + "status": "success", + "context": new_tab["context"] + }) + ], f"Success event doesn't match expected sequence: {success_event}" +@pytest.mark.asyncio +async def test_speculation_rules_generate_failure_events( + bidi_session, subscribe_events, new_tab, url, wait_for_events, add_speculation_rules_and_link +): + '''Test that speculation rules generate pending and failure events for failed prefetch.''' + await subscribe_events(events=["speculation.prefetchStatusUpdated"]) + test_url = url("/common/blank.html", protocol="https") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="none", + ) + # Create a target that will return 404 - use a non-existent path + failed_target = url("/nonexistent/path/that/will/404.xml", protocol="https") + # Add speculation rules to trigger immediate prefetching of 404 page + speculation_rules = f'''{{ + "prefetch": [{{ + "where": {{ "href_matches": "{failed_target}" }}, + "eagerness": "immediate" + }}] + }}''' + # Set up event waiter before triggering prefetch + with wait_for_events(["speculation.prefetchStatusUpdated"]) as waiter: + # Add speculation rules and link to trigger prefetching + await add_speculation_rules_and_link(new_tab, speculation_rules, failed_target) + # Wait for events (pending and failure) + events = await waiter.get_events(lambda events: len(events) >= 2) + # Verify all events have correct structure and sequence + assert events == [ + ("speculation.prefetchStatusUpdated", { + "url": failed_target, + "status": "pending", + "context": new_tab["context"] + }), + ("speculation.prefetchStatusUpdated", { + "url": failed_target, + "status": "failure", + "context": new_tab["context"] + }) + ], f"Events don't match expected sequence: {events}" +@pytest.mark.asyncio +async def test_subscribe_unsubscribe_event_emission( + bidi_session, subscribe_events, new_tab, url, wait_for_events, add_speculation_rules_and_link +): + '''Test that events are emitted when subscribed and not emitted when unsubscribed.''' + test_url = url("/common/blank.html", protocol="https") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="complete", + ) + prefetch_target = url("/common/dummy.xml", protocol="https") + speculation_rules = f'''{{ + "prefetch": [{{ + "where": {{ "href_matches": "{prefetch_target}" }}, + "eagerness": "immediate" + }}] + }}''' + # Phase 1: Subscribe to specific event and assert events are emitted + subscription_result = await subscribe_events(events=["speculation.prefetchStatusUpdated"]) + subscription_id = subscription_result["subscription"] + # Trigger prefetch and collect events + with wait_for_events(["speculation.prefetchStatusUpdated"]) as waiter: + await add_speculation_rules_and_link(new_tab, speculation_rules, prefetch_target) + # Wait for events to be emitted + events = await waiter.get_events(lambda events: len(events) >= 2) + # Phase 2: Unsubscribe and assert events are NOT emitted + await bidi_session.session.unsubscribe(subscriptions=[subscription_id]) + # Reload the page to get a clean state + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="complete", + ) + # Trigger another prefetch after unsubscribing + prefetch_target_2 = url("/common/square.png", protocol="https") + speculation_rules_2 = f'''{{ + "prefetch": [{{ + "where": {{ "href_matches": "{prefetch_target_2}" }}, + "eagerness": "immediate" + }}] + }}''' + # Set up waiter but don't expect any events this time + with wait_for_events(["speculation.prefetchStatusUpdated"]) as waiter: + await add_speculation_rules_and_link(new_tab, speculation_rules_2, prefetch_target_2) + with pytest.raises(TimeoutException): + await waiter.get_events(lambda events: len(events) >= 1, timeout=0.5) +@pytest.mark.asyncio +async def test_subscribe_unsubscribe_module_subscription( + bidi_session, subscribe_events, new_tab, url, wait_for_events, add_speculation_rules_and_link +): + '''Test that module subscription ('speculation') works for subscribe/unsubscribe.''' + test_url = url("/common/blank.html", protocol="https") + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="complete", + ) + prefetch_target = url("/common/dummy.xml", protocol="https") + speculation_rules = f'''{{ + "prefetch": [{{ + "where": {{ "href_matches": "{prefetch_target}" }}, + "eagerness": "immediate" + }}] + }}''' + # Phase 1: Subscribe to module events and assert events are emitted + subscription_result = await subscribe_events(events=["speculation"]) # Module subscription + subscription_id = subscription_result["subscription"] + # Trigger prefetch and collect events + with wait_for_events(["speculation.prefetchStatusUpdated"]) as waiter: + await add_speculation_rules_and_link(new_tab, speculation_rules, prefetch_target) + # Wait for events to be emitted + events = await waiter.get_events(lambda events: len(events) >= 2) + # Phase 2: Unsubscribe from module and assert events are NOT emitted + await bidi_session.session.unsubscribe(subscriptions=[subscription_id]) + # Reload the page to get a clean state + await bidi_session.browsing_context.navigate( + context=new_tab["context"], + url=test_url, + wait="complete", + ) + # Trigger another prefetch after unsubscribing from module + prefetch_target_2 = url("/common/square.png", protocol="https") + speculation_rules_2 = f'''{{ + "prefetch": [{{ + "where": {{ "href_matches": "{prefetch_target_2}" }}, + "eagerness": "immediate" + }}] + }}''' + # Set up waiter but don't expect any events this time + with wait_for_events(["speculation.prefetchStatusUpdated"]) as waiter: + await add_speculation_rules_and_link(new_tab, speculation_rules_2, prefetch_target_2) + with pytest.raises(TimeoutException): + await waiter.get_events(lambda events: len(events) >= 1, timeout=0.5) +@pytest.mark.asyncio +async def test_unsubscribe_from_prefetch_status_updated( + bidi_session +): + '''Test unsubscribing from prefetch status updated events.''' + # Subscribe to prefetch status updated events + subscription_result = await bidi_session.session.subscribe( + events=["speculation.prefetchStatusUpdated"] + ) + subscription_id = subscription_result["subscription"] + # Unsubscribe immediately + await bidi_session.session.unsubscribe(subscriptions=[subscription_id]) +\ No newline at end of file