Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<!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();
await test_driver.bidi.speculation.prefetch_status_updated.subscribe();
});

promise_test(async t => {

const receivedEvents = [];
const expectedEvents = ['pending', 'ready'];

// 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) => {
receivedEvents.push(event);

// Status should be one of the expected values
const validStatuses = ['pending', 'running', 'ready', 'success', 'failure'];
assert_true(validStatuses.includes(event.status),
`Event status should be one of ${validStatuses.join(', ')}, got: ${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 targetUrl = window.location.origin + "/infrastructure/testdriver/bidi/speculation/resources/target.html";

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_true(receivedEvents.length >= 2,
`Should have received at least 2 events, but only received ${receivedEvents.length}. Events: ${receivedEvents.map(e => e.status).join(', ')}`);
assert_array_equals(
receivedEvents.map(e => e.status),
expectedEvents,
'Should have received pending and ready events in order'
);

}, "prefetch_status_updated event subscription and structure validation");

promise_test(async t => {

const receivedEvents = [];
const expectedEvents = ['pending', 'ready', 'success'];
let newWindow = null;

// 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) => {
receivedEvents.push(event);

// Status should be one of the expected values
const validStatuses = ['pending', 'running', 'ready', 'success', 'failure'];
assert_true(validStatuses.includes(event.status),
`Event status should be one of ${validStatuses.join(', ')}, got: ${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();
}
});
});

// 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";

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_true(receivedEvents.length >= 3,
`Should have received at least 3 events, but only received ${receivedEvents.length}. Events: ${receivedEvents.map(e => e.status).join(', ')}`);
assert_array_equals(
receivedEvents.map(e => e.status),
expectedEvents,
'Should have received pending, ready, and success events in order'
);

t.add_cleanup(() => {
if (newWindow && !newWindow.closed) {
newWindow.close();
}
});

}, "prefetch_status_updated event with navigation to success");

promise_test(async t => {

const receivedEvents = [];
const expectedEvents = ['pending', 'failure'];

// 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) => {
receivedEvents.push(event);

// Status should be one of the expected values
const validStatuses = ['pending', 'running', 'ready', 'success', 'failure'];
assert_true(validStatuses.includes(event.status),
`Event status should be one of ${validStatuses.join(', ')}, got: ${event.status}`);

// When we receive the failure event, we're done
if (event.status === 'failure') {
removeHandler();
resolve();
}
});
});

// Create prefetch rules for a non-existent page that should fail with 404
const errorUrl = window.location.origin + "/infrastructure/testdriver/bidi/speculation/resources/nonexistent-404-page.html";

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_true(receivedEvents.length >= 2,
`Should have received at least 2 events, but only received ${receivedEvents.length}. Events: ${receivedEvents.map(e => e.status).join(', ')}`);
assert_array_equals(
receivedEvents.map(e => e.status),
expectedEvents,
'Should have received pending and failure events in order'
);

}, "prefetch_status_updated event with prefetch failure");

</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'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 };
}
10 changes: 10 additions & 0 deletions infrastructure/testdriver/bidi/speculation/resources/target.html
Original file line number Diff line number Diff line change
@@ -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>
39 changes: 39 additions & 0 deletions resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,33 @@
},
}
},
speculation: {
prefetch_status_updated: {
subscribe: async function(params = {}) {
assertBidiIsEnabled();
return window.test_driver_internal.bidi.speculation
.prefetch_status_updated.subscribe(params);
},

on: function(callback) {
assertBidiIsEnabled();
return window.test_driver_internal.bidi.speculation
.prefetch_status_updated.on(callback);
},

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.
*/
Expand Down Expand Up @@ -2297,6 +2324,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');
}
},
}
},

Expand Down
34 changes: 34 additions & 0 deletions tools/wptrunner/wptrunner/testdriver-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,21 @@
}
};

const subscribe_global = async function (params) {
const action_result = await create_action("bidi.session.subscribe", {
// Subscribe to all contexts.
...params
});
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 =
Expand Down Expand Up @@ -665,4 +680,23 @@
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_global(
{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);
};
})();
Empty file.
45 changes: 45 additions & 0 deletions webdriver/tests/bidi/external/speculation/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import pytest
from typing import Any, Mapping

from webdriver.bidi.modules.script import ContextTarget


@pytest.fixture
def speculation_rules_helper(bidi_session):
"""Helper for adding speculation rules to a page."""

async def add_speculation_rules(context: Mapping[str, Any], rules: str):
"""Add speculation rules script to the page."""
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
)

return add_speculation_rules


@pytest.fixture
def add_prefetch_link(bidi_session):
"""Helper for adding links to the page that can be targeted by speculation rules."""

async def add_link(context: Mapping[str, Any], href: str, text: str = "Test Link", link_id: str = "prefetch-page"):
"""Add a link to the page for prefetch targeting."""
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_link
Loading
Loading