Last Good Version
1.59.1
First Bad Version
1.60.0 (still reproduces on 1.62.0, the latest)
Steps to reproduce
Minimal reproduction (one worker fixture, one trivial test, no browser needed): https://github.com/NoamGaash/playwright-teardown-forcekill-repro
git clone https://github.com/NoamGaash/playwright-teardown-forcekill-repro && cd playwright-teardown-forcekill-repro
npm install
PWTEST_CHILD_PROCESS_TIMEOUT=2000 npx playwright test (force-kill deadline 2 s < the fixture's 3 s teardown)
The entire repro is a worker-scoped fixture whose teardown legitimately takes 3 s, declared { timeout: 0 }, plus one test that passes instantly:
// teardown.spec.ts
import { test as base } from '@playwright/test'
const test = base.extend<{}, { slowTeardown: void }>({
slowTeardown: [
async ({}, use) => {
await use()
await new Promise(r => setTimeout(r, 3000)) // a legitimate 3s teardown
},
{ scope: 'worker', timeout: 0 }, // "teardown may take as long as it needs"
],
})
test('passes instantly', async ({ slowTeardown }) => {})
The 2 s deadline is only to keep the repro fast. The real-world case is a teardown that legitimately exceeds the 5-min default — e.g. a worker fixture flushing results to a backend or awaiting async cloud jobs at end-of-run. With PWTEST_CHILD_PROCESS_TIMEOUT unset (or ≥ 3000) the exact same run exits 0.
Expected behavior
The fixture sets timeout: 0 to disable the fixture timeout — worker-scoped fixtures have their own timeout, and the docs say you can change it. Since the teardown is making progress, it should be allowed to finish and the run should exit 0 — as it did on 1.59.1 and earlier. At minimum there should be a public / config-level way to control the process-exit grace period, and the fixture's own timeout should inform it.
Actual behavior
The parent runner force-kills the worker while its teardown is still running:
Running 1 test using 1 worker
✓ 1 teardown.spec.ts:16:1 › passes instantly, yet the worker is force-killed during its 3s teardown (4ms)
Error: worker-0 process did not exit within 2000ms after stop, force-killed it
Error: worker-0 process did not exit within 2000ms after stop, force-killed it
1 passed (2.7s)
2 errors were not a part of any test, see above for details
Exit code 1, even though the only test passed.
Additional context
This is the effect of #40637 ("fix(runner): force-kill worker that hangs on stop", first released in 1.60.0), which was the fix for #39753 — a genuinely-hung chrome-headless-shell that never exited. Killing a hung worker is reasonable; the issue is that the force-kill can't distinguish a hung worker from one whose timeout: 0 teardown is legitimately still running, and the only override is the undocumented PWTEST_CHILD_PROCESS_TIMEOUT env var, which a fixture or playwright.config cannot set for itself (it's read by the parent runner before playwright test starts).
Before 1.60.0 the runner awaited worker exit indefinitely, so a slow-but-healthy teardown completed. Force-kill logic: packages/playwright/src/runner/processHost.ts.
Suggested resolutions (any one would help):
- Let a worker fixture's
timeout (especially timeout: 0) extend/inform the process-exit grace period, so a fixture that opted into a long teardown isn't force-killed while still progressing.
- Expose the force-kill timeout through the public config (e.g. a
TestConfig field) rather than only the undocumented PWTEST_CHILD_PROCESS_TIMEOUT.
- At minimum: document
PWTEST_CHILD_PROCESS_TIMEOUT and make the error actionable — e.g. "…force-killed it; increase PWTEST_CHILD_PROCESS_TIMEOUT to allow a longer teardown."
Environment
System:
OS: Windows 11 10.0.26200
CPU: (32) x64 13th Gen Intel(R) Core(TM) i9-13900HX
Binaries:
Node: 20.16.0
npm: 10.8.2
npmPackages:
@playwright/test: 1.62.0 => 1.62.0
(Also reproduces on Linux / Node 20 in CI — see the repo's GitHub Actions run.)
Last Good Version
1.59.1
First Bad Version
1.60.0 (still reproduces on 1.62.0, the latest)
Steps to reproduce
Minimal reproduction (one worker fixture, one trivial test, no browser needed): https://github.com/NoamGaash/playwright-teardown-forcekill-repro
git clone https://github.com/NoamGaash/playwright-teardown-forcekill-repro && cd playwright-teardown-forcekill-repronpm installPWTEST_CHILD_PROCESS_TIMEOUT=2000 npx playwright test(force-kill deadline 2 s < the fixture's 3 s teardown)The entire repro is a worker-scoped fixture whose teardown legitimately takes 3 s, declared
{ timeout: 0 }, plus one test that passes instantly:Expected behavior
The fixture sets
timeout: 0to disable the fixture timeout — worker-scoped fixtures have their own timeout, and the docs say you can change it. Since the teardown is making progress, it should be allowed to finish and the run should exit0— as it did on 1.59.1 and earlier. At minimum there should be a public / config-level way to control the process-exit grace period, and the fixture's own timeout should inform it.Actual behavior
The parent runner force-kills the worker while its teardown is still running:
Exit code 1, even though the only test passed.
Additional context
This is the effect of #40637 ("fix(runner): force-kill worker that hangs on stop", first released in 1.60.0), which was the fix for #39753 — a genuinely-hung
chrome-headless-shellthat never exited. Killing a hung worker is reasonable; the issue is that the force-kill can't distinguish a hung worker from one whosetimeout: 0teardown is legitimately still running, and the only override is the undocumentedPWTEST_CHILD_PROCESS_TIMEOUTenv var, which a fixture orplaywright.configcannot set for itself (it's read by the parent runner beforeplaywright teststarts).Before 1.60.0 the runner awaited worker exit indefinitely, so a slow-but-healthy teardown completed. Force-kill logic:
packages/playwright/src/runner/processHost.ts.Suggested resolutions (any one would help):
timeout(especiallytimeout: 0) extend/inform the process-exit grace period, so a fixture that opted into a long teardown isn't force-killed while still progressing.TestConfigfield) rather than only the undocumentedPWTEST_CHILD_PROCESS_TIMEOUT.PWTEST_CHILD_PROCESS_TIMEOUTand make the error actionable — e.g. "…force-killed it; increasePWTEST_CHILD_PROCESS_TIMEOUTto allow a longer teardown."Environment