#15500·puppeteer

[Bug]: WebWorker.evaluate() and close() never settle when the worker script fails to load

Author: alexivanovCreated Sep 26, 2026Updated Sep 26, 2026

Minimal, reproducible example

typescript
import http from 'node:http';
import puppeteer from 'puppeteer';

// A worker needs a real origin, so page.setContent() is not enough.
const server = http.createServer((req, res) => {
  if (req.url === '/missing-worker.js') {
    res.statusCode = 404;
    res.end('not found');
    return;
  }
  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.goto(`http://127.0.0.1:${port}`);

page.on('workerdestroyed', () => console.log('workerdestroyed fired'));
const workerCreated = new Promise<any>(r => page.once('workercreated', r));
await page.evaluate(() => {
  new Worker('/missing-worker.js');
});
const worker = await workerCreated;

const settle = (promise: Promise<unknown>) =>
  Promise.race([
    promise.then(
      value => `resolved: ${value}`,
      error => `rejected: ${error.message}`,
    ),
    new Promise(r => setTimeout(() => r('still pending after 10s'), 10_000)),
  ]);

console.log('evaluate():', await settle(worker.evaluate(() => 1 + 1)));
console.log('close():', await settle(worker.close()));

await browser.close();
server.close();

Background

We found this while working on #15497. There, a dedicated worker's script request stays paused by request interception, so the script never loads. Every worker.evaluate() on that worker then hangs with no error and no timeout. The same happens whenever a dedicated worker's script fails to load, and a 404 is the simplest way to show it.

Expectation

Once the worker is destroyed (workerdestroyed fires), worker.evaluate(), worker.evaluateHandle() and worker.close() reject, like they did in 25.1.0:

workerdestroyed fired
evaluate(): rejected: Protocol error (Runtime.callFunctionOn): Target closed
close(): rejected: Execution context is not available in detached frame or worker "http://127.0.0.1:62724/missing-worker.js" (are you trying to evaluate?)

Reality

workerdestroyed fires, but evaluate() and close() never settle:

workerdestroyed fired
evaluate(): still pending after 10s
close(): still pending after 10s

Where it started: this is a regression from #15099 ("fix: await for Worker script execution"), first released in 25.2.0. I got the output above on 25.2.0 and 25.12.0; 25.1.0 rejects as shown under Expectation. All three used Chrome for Testing 154.0.8037.57.

Puppeteer configuration file (if used)

typescript

Puppeteer version

25.12.0

Node version

22.16.0

Package manager

npm

Package manager version

10.9.2

Operating system

macOS