[Bug]: Intercepted requests from dedicated workers are never fulfilled/continued/aborted
Minimal, reproducible example
import http from 'node:http';
import puppeteer from 'puppeteer';
// A worker has to be served from a real origin, so page.setContent() is not enough.
const WORKER_SRC = `
self.onmessage = async () => {
const results = await Promise.all(
Array.from({length: 8}, (_, i) =>
Promise.race([
fetch('https://stub.example/data/' + i).then(r => r.text()),
new Promise(r => setTimeout(() => r('TIMEOUT'), 5000)),
]),
),
);
self.postMessage(results);
};`;
const server = http.createServer((req, res) => {
if (req.url === '/worker.js') {
res.setHeader('content-type', 'text/javascript');
return res.end(WORKER_SRC);
}
res.setHeader('content-type', 'text/html');
res.end('<!doctype html><title>repro</title>');
});
await new Promise<void>(r => server.listen(0, r));
const {port} = server.address() as {port: number};
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', request => {
if (!request.url().startsWith('https://stub.example/')) {
void request.continue();
return;
}
void request.respond({
status: 200,
headers: {'access-control-allow-origin': '*'},
body: 'ok',
});
});
await page.goto(`http://127.0.0.1:${port}`);
const results = await page.evaluate(() => {
return new Promise(resolve => {
const worker = new Worker('/worker.js');
worker.onmessage = e => resolve(e.data);
worker.postMessage('go');
});
});
console.log(results); // expected: 8 × 'ok'
await browser.close();
server.close();Background
We intercept and stub network requests in pages under test. Pages that call fetch() from a dedicated worker hang. The worker's requests show up in page.on('request'), but answering them has no effect.
Expectation
Every worker fetch() resolves with the stubbed body, and the script logs 8 × 'ok'. Intercepted requests from dedicated workers should be fulfilled, continued or aborted the same way as page requests.
Reality
Most worker requests stay paused forever and log 'TIMEOUT'. request.respond() resolves without an error. continue() and abort() behave the same way.
- Chrome for Testing 154.0.8037.57 (the version pinned by 25.12.0): 26 of 80 fetches resolved over 10 runs. Only 1 run got 8/8.
- chrome-headless-shell 152.0.7977.75: 1 of 40 fetches resolved over 5 runs.
With DEBUG=puppeteer:*, the protocol log shows the cause. Fetch.requestPaused arrives on the page session before Network.requestWillBeSent arrives on the worker session. Puppeteer then sends Fetch.fulfillRequest on the worker session, which has no Fetch domain. Chrome rejects it with 'Fetch.fulfillRequest' wasn't found, and puppeteer logs and swallows that error.
NetworkEventManager.storeRequestPaused stores the paused event without the session it came on. The CdpHTTPRequest is then created with the requestWillBeSent session (the worker's), and it uses that session for Fetch.* commands. The few requests that resolve are those where requestWillBeSent happened to arrive first.
The same happens with CDP on main (6cfe4df6db1). A fix with tests is ready to open as a PR.
Puppeteer configuration file (if used)
Puppeteer version
25.12.0
Node version
22.16.0
Package manager
npm
Package manager version
10.9.2
Operating system
macOS
Source: puppeteer/puppeteer