Skip to content

[Regression]: worker fixture with timeout: 0 is force-killed mid-teardown by PWTEST_CHILD_PROCESS_TIMEOUT #42007

Description

@NoamGaash

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

  1. git clone https://github.com/NoamGaash/playwright-teardown-forcekill-repro && cd playwright-teardown-forcekill-repro
  2. npm install
  3. 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):

  1. 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.
  2. Expose the force-kill timeout through the public config (e.g. a TestConfig field) rather than only the undocumented PWTEST_CHILD_PROCESS_TIMEOUT.
  3. 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.)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions