diff --git a/tests/utils.test.ts b/tests/utils.test.ts new file mode 100644 index 000000000..d7931a603 --- /dev/null +++ b/tests/utils.test.ts @@ -0,0 +1,34 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import assert from 'node:assert'; +import {describe, it} from 'node:test'; + +import type {Browser} from 'puppeteer'; + +import {withBrowser} from './utils.js'; + +describe('withBrowser', () => { + it('relaunches a cached browser that has disconnected', async () => { + let firstBrowser: Browser | undefined; + await withBrowser(async browser => { + firstBrowser = browser; + }); + assert.ok(firstBrowser); + + // Simulate a browser that died mid-run – the cache still holds the dead handle. + await firstBrowser.close(); + assert.ok(!firstBrowser.connected); + + // The next call with the same options must not reuse the dead browser. + let secondBrowser: Browser | undefined; + await withBrowser(async browser => { + secondBrowser = browser; + assert.ok(browser.connected); + }); + assert.notStrictEqual(secondBrowser, firstBrowser); + }); +}); diff --git a/tests/utils.ts b/tests/utils.ts index f2f12eee8..79dd01232 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -94,6 +94,13 @@ export async function withBrowser( const key = JSON.stringify(launchOptions); let browser = browsers.get(key); + // A cached browser can die mid-run (crash, pipe drop, teardown race). Reusing the + // dead handle makes every later same-key test fail with `Target closed`, so drop a + // disconnected one and let the miss path relaunch it. + if (browser && !browser.connected) { + browsers.delete(key); + browser = undefined; + } if (!browser) { browser = await puppeteer.launch(launchOptions); browsers.set(key, browser);