Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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,175 @@
<!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 target url with unique id to ensure events are from this test
const uuid = self.crypto.randomUUID();
const targetUrl = window.location.origin + "/infrastructure/testdriver/bidi/speculation/resources/target.html?" + uuid;

// 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) => {
// Ignore events from other tests that could be running in parallel.
if (event.url !== targetUrl) {
return;
}
receivedEvents.push(event);

// 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(
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 target url with unique id to ensure events are from this test
const uuid = self.crypto.randomUUID();
const targetUrl = window.location.origin + "/infrastructure/testdriver/bidi/speculation/resources/target.html?" + uuid;

// 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) => {
// Ignore events from other tests that could be running in parallel.
if (event.url !== targetUrl) {
return;
}
receivedEvents.push(event);

// 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(
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 prefetch rules for a non-existent page that should fail with 404
const uuid = self.crypto.randomUUID();
const errorUrl = window.location.origin + "/infrastructure/testdriver/bidi/speculation/resources/nonexistent-404-page.html?" + uuid;

// 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) => {
// Ignore events from other tests that could be running in parallel.
if (event.url !== errorUrl) {
return;
}
receivedEvents.push(event);

// 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(
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>
84 changes: 84 additions & 0 deletions resources/testdriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -769,6 +769,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.
*/
Expand Down Expand Up @@ -2297,6 +2369,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
20 changes: 18 additions & 2 deletions tools/wptrunner/wptrunner/testdriver-extra.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -665,4 +663,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);
};
})();
Empty file.
Loading
Loading