QA How-To
How to Fix Playwright "Worker process exited unexpectedly"
Fix Playwright Worker Process Exited Unexpectedly with checks for worker count, Docker memory, forced exits, CI timeouts, leaks, and browser failures.
19 min read | 3,288 words
TL;DR
Run the affected file with --workers=1 --retries=0, then inspect the exit signal and preceding logs. Fix the confirmed cause: worker overcommit, Docker memory or IPC pressure, a forced exit, a CI deadline, leaked resources, or a browser launch failure.
Key Takeaways
- Read the exit code, signal, and earlier stderr before changing configuration.
- Run the affected file with one worker and retries off to isolate concurrency.
- Check CI and container memory before concluding a worker was killed by OOM.
- Replace process.exit in test helpers with a reported test or hook failure.
- Use version-matched Playwright images and check Chromium shared memory in Docker.
- Verify the focused file repeatedly and then the full intended project.
Fix Playwright Worker Process Exited Unexpectedly when npx playwright test stops because a test worker dies before the runner receives a normal test result. It can appear during a local run, after several files in CI, or while Chromium runs in a container.
Error: worker process exited unexpectedly (code=7, signal=null)
TL;DR
Run the failing file with one worker and no retries, then compare it with the usual parallel run:
npx playwright test tests/problem.spec.ts --workers=1 --retries=0 --reporter=list
npx playwright test tests/problem.spec.ts --retries=0 --reporter=list
Replace tests/problem.spec.ts with the path named in your failure. If the single-worker run passes repeatedly but the parallel run dies, check memory pressure, shared resources, and worker count. If it dies even with one worker, inspect the first failure, fixture imports, process.exit calls, and container or CI termination logs. On a Linux Docker host, check for an OOM kill and whether Chromium has adequate shared memory. Playwright's parallelism guide explains worker isolation and its supported --workers=1 diagnostic.
A quick CI containment is workers: process.env.CI ? 1 : undefined in playwright.config.ts. It reduces simultaneous browsers but cannot repair a forced exit or platform timeout. Keep retries off during diagnosis.
What the Error Actually Means
That line is a reported example, not a universal exit code. Your run may show another code or signal. Preserve the exact line and the log entries immediately before it: they distinguish a process killed by the operating system from a test or fixture that terminated its own Node process.
Playwright Test runs test code in child worker processes. The runner schedules tests, while each worker loads the test file, fixtures, hooks, and its own browser. A normal assertion failure is returned as a test result. Playwright then discards that worker and starts another for isolation. That expected restart is different from a worker process disappearing before it can finish reporting, which yields this headline. See the official retry behavior and worker model.
The parenthetical code and signal are process exit information, not a Playwright locator error. signal=SIGKILL suggests an external kill, but does not identify whether it came from an OOM controller, a human, or a CI platform. A nonzero code with signal=null can arise from an explicit exit or another process-level failure. Read earlier stderr, system events, and CI annotations before assigning a cause. The exact numeric value alone is not a reliable diagnosis.
Do not confuse a browser child process with the Playwright Test worker. A browser may crash while the Node worker remains alive and reports browser has been closed or a browser launch error. The browser crash can still be related to memory pressure, but its evidence appears in browser stderr and DEBUG=pw:browser output. If the headline instead concerns a closed page or context, follow the target page or browser closed guide.
Record the command, file, project, worker count, exit details, memory limit, and first preceding error. A worker can die while importing a module, running a hook or test, or tearing down. The last visible test name may only identify the assigned test.
Root-Cause Decision Table
| Symptom | Likely root cause | First fix and proof |
|---|---|---|
| Parallel run dies, one worker passes | Too many simultaneous workers for available memory or CPU | Lower --workers, repeat the same file, then inspect peak memory |
| Docker Chromium run dies under load | Container memory or shared memory pressure | Use a suitable memory limit and Playwright's documented --ipc=host option; rerun in that container |
| Same file exits immediately with one worker | Test, fixture, or imported module calls process.exit() or triggers a native crash |
Search process-exit paths, replace forced exit with a thrown error, rerun file |
| CI job ends at a fixed duration | CI job timeout or external termination | Raise the actual job limit or shorten/shard the suite; compare timestamps |
| Failure appears after many tests, not at startup | Leaked pages, contexts, child processes, or large data retained by hooks | Close resources with finally; repeat the file and inspect memory trend |
| One browser project fails, others pass | Browser-specific launch or environment failure | Run that project with DEBUG=pw:browser and inspect browser stderr |
This table narrows the next experiment; none of its rows proves a cause by itself. For example, reducing workers can make a shared test-account race disappear as well as reducing memory use. A second check on the relevant resource is needed before a permanent fix.
1. Fix Playwright Worker Process Exited Unexpectedly by Sizing Workers to the Runner
Each Playwright worker is a Node process that starts its own browser. A laptop may have spare memory while a CI container has a small quota. A default based on visible CPU cores can overcommit a memory-limited runner, especially with video, tracing, multiple projects, or a heavy application under test. Playwright recommends one worker in CI as a stable starting point. Treat it as a baseline, then increase only after measuring.
Put the limit in the test configuration so local and CI behavior are deliberate. This is a complete minimal playwright.config.ts for a project with tests under tests/:
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
workers: process.env.CI ? 1 : undefined,
retries: 0,
reporter: 'list',
use: { browserName: 'chromium', trace: 'retain-on-failure' },
});
The CI variable changes worker count only when it is present. If your existing config defines projects, keep those projects and add only the workers setting at the top level. Do not create a second config file or silently remove browser coverage. retries: 0 is useful during diagnosis; restore your intentional retry policy after you have a stable explanation.
Verify both modes with the same test selection. On a Unix-like shell, the second command simulates CI without changing your workflow:
npx playwright test tests/problem.spec.ts --workers=1 --retries=0
CI=1 npx playwright test tests/problem.spec.ts --retries=0
Both should report one worker. If one worker still dies, worker count is not sufficient as a fix. If the test passes only at one worker, watch the runner's memory graph while raising to two workers, rather than jumping back to the previous value. Also check whether another job runs the application server in the same memory quota. A test runner limited to one worker can still be killed if the application consumes the remaining memory.
2. Fix Playwright Worker Process Exited Unexpectedly in Docker
Docker adds two separate resource questions: how much memory the container may use, and how much shared memory Chromium can access. Playwright's Docker guide recommends --ipc=host for Chromium, because insufficient shared memory can crash it, and --init to handle PID 1 process behavior. Inspect browser stderr before attributing a worker exit to Chromium shared memory.
From a Node project with a lockfile, use an official image whose tag matches the installed Playwright package. Substitute your actual installed version for the placeholder, without guessing a number:
npm ls @playwright/test
docker run --rm --init --ipc=host -v "$PWD:/work" -w /work mcr.microsoft.com/playwright:v<your-playwright-version>-noble /bin/sh -c 'npm ci && npx playwright test tests/problem.spec.ts --workers=1 --retries=0'
This command verifies the fix in the image that launches the browser. Do not use --ipc=host as a universal policy without reviewing your infrastructure isolation requirements. If that option is unavailable, configure sufficient shared memory by a mechanism supported by your container platform, then run the same test. Check the container exit status and platform events as well as the Playwright reporter.
The official image includes browsers and system packages; npm ci supplies your project dependencies. A mismatched image tag can cause launch failures. Match it to the version printed by npm ls @playwright/test. For a missing binary, use the Playwright executable guide.
3. Remove Forced Exits From Tests and Worker Fixtures
A direct process.exit() inside a test, fixture, imported helper, or beforeAll hook terminates the Node worker before Playwright can create a normal test result. This can be hidden in a custom login utility or a setup module that was written for a CLI script. It is especially easy to miss when the module runs at import time, before a test title appears. Search the code that the failing file imports:
rg -n 'process\.exit\s*\(|process\.kill\s*\(' --glob '!node_modules/**' --glob '!playwright-report/**' .
npx playwright test tests/problem.spec.ts --workers=1 --retries=0 --reporter=list
Adjust the search directories to your repository. The first command only finds explicit JavaScript exit calls; a native addon can terminate a process without one. Review each hit in context. A helper used by tests should throw an error or return a failure value, not decide when the whole worker ends.
For example, replace an exit-on-error health check with a failing Playwright assertion. This standalone test can be saved as tests/worker-health.spec.ts; it uses no application server and proves that a failed precondition is reported as a test failure while the worker remains under runner control:
import { test, expect } from '@playwright/test';
test('reports setup failure without exiting the worker', async ({ page }) => {
await page.setContent('<main data-ready="yes">Ready</main>');
await expect(page.locator('main')).toHaveAttribute('data-ready', 'yes');
});
npx playwright test tests/worker-health.spec.ts --workers=1 --retries=0
For a real prerequisite, replace the fixed page assertion with your actual condition and use throw new Error('...') when it fails. Playwright will associate the error with the test or hook. If your helper spawns a child process, use spawn or execFile from node:child_process, await its exit, and reject on a nonzero result. Do not call process.exit() in a library just because a child command failed. If the exit comes from a third-party native extension, isolate the import in the single failing file and report its stderr and package version.
4. Distinguish a CI Timeout From a Worker Crash
A CI system can stop a job when its time budget expires. The runner may then print a worker-exit line during shutdown, making the last line look like the root cause. Compare the timestamp of the first Playwright failure with the job's timeout annotation. If each failure occurs at nearly the same elapsed duration, inspect the workflow or platform limit before changing locators or browser options.
Use Playwright's own list reporter and one worker to see which test was active near termination. The command below limits diagnosis to the affected file. It does not override your CI provider's job timeout:
CI=1 npx playwright test tests/problem.spec.ts --workers=1 --retries=0 --reporter=list
If it consistently completes locally but the full CI job ends at its configured deadline, split work into separate jobs with Playwright sharding or adjust the authorized CI timeout. Sharding divides files across runners; it does not give an individual worker more memory. Follow the Playwright sharding across machines guide when a large suite, rather than a single hung test, consumes the budget.
A hanging test has its own Playwright timeout and usually produces a test-timeout report if the worker stays alive. A platform kill can interrupt even that report. Keep those boundaries separate: the test timeout belongs in playwright.config.ts, whereas the job timeout belongs in CI configuration. Do not set Playwright's test timeout to an enormous value merely to escape a platform limit. First find the slow file and why it waits, then choose a budget based on its expected runtime. The test timeout troubleshooting guide applies when the worker survives to report a timeout.
5. Close Resources That Accumulate Across Files
A worker can run several test files in sequence. A leak in a worker-scoped fixture, a manually created browser context, or a server process can gradually increase memory until the operating system kills the worker. This pattern differs from an immediate import-time exit: the same first few tests pass, then the process disappears later. Look for rising resident memory, a growing number of browser processes, or open files across repetitions.
Playwright manages its built-in page and context fixtures. Close resources that you create manually. Here is a complete test that opens a separate context and guarantees cleanup even if an assertion fails:
import { test, expect } from '@playwright/test';
test('separate context is closed after use', async ({ browser }) => {
const context = await browser.newContext();
try {
const page = await context.newPage();
await page.setContent('<h1>Isolated</h1>');
await expect(page.getByRole('heading', { name: 'Isolated' })).toBeVisible();
} finally {
await context.close();
}
});
Save it as tests/context-cleanup.spec.ts and verify it directly:
npx playwright test tests/context-cleanup.spec.ts --workers=1 --retries=0
Apply the same try/finally shape to temporary files, HTTP servers, database clients, and child processes your tests own. Avoid closing Playwright's injected browser fixture yourself; it is shared at the worker level. Repeat suspect files with the supported --repeat-each flag, then compare memory at the start and end of the job.
The worker fixtures guide explains why worker-scoped state may survive across files assigned to the same worker. If your fixture creates one account or service per worker, its teardown must be paired with setup, and the service must tolerate a worker disappearing without cleanup. A process killed by the OS cannot run a JavaScript finally block, so external test data may need scheduled cleanup as well.
6. Investigate Browser-Specific Launch Failures Before Changing Test Code
A worker message may appear beside a browser launch or disconnect error. Test Chromium, Firefox, and WebKit separately if your config runs several projects. The browser that fails only in CI points toward its binaries, native dependencies, sandbox environment, or resource profile. A generic reinstall of every package can hide the useful difference.
Use Playwright's documented browser debug namespace for the failing project. The following example assumes your project is named chromium; substitute the project name from npx playwright test --list if necessary:
npx playwright test --list
DEBUG=pw:browser npx playwright test tests/problem.spec.ts --project=chromium --workers=1 --retries=0
The debug stream shows launch commands and browser stderr. Executable doesn't exist points to browser installation, while Host system is missing dependencies to run browsers points to missing native libraries on the Linux host. Those are specific errors with specific fixes. Neither should be described as a proven Node worker OOM merely because the run eventually ends with an exit headline. Check the missing executable guide for the first case.
If only a headed run fails on a Linux CI machine, confirm it has a display server. Playwright's CI guide uses xvfb-run npx playwright test --headed when Xvfb is available. Headless and headed modes exercise different launch paths. Run the smallest failing project and mode, capture the first browser stderr line, and then retest with your normal worker count. This keeps a browser startup issue from being mistaken for a parallel-test race.
How to Verify the Fix
Verification needs a clean before-and-after comparison in the same environment. Record the original command and exit line, apply one targeted change, then rerun the affected file with retries disabled. Repeat the file to catch intermittent failures and run the full intended browser project after the focused test is stable. The supported --repeat-each option provides a short stress check:
npx playwright test tests/problem.spec.ts --workers=1 --retries=0 --repeat-each=5 --reporter=list
npx playwright test --project=chromium --retries=0 --reporter=list
Five repetitions are an illustrative diagnostic count, not a guarantee of reliability. If your original failure took hours, use enough repetitions or duration to cover the previous failure window. If the focused file passes but the full suite fails, inspect suite-level competition for memory, test accounts, ports, and database rows. A single-file pass is only proof for that smaller load.
For an OOM hypothesis, also check the host or container event record for an OOM kill and compare peak memory before and after lowering worker count. For a forced-exit hypothesis, verify that a deliberately failed precondition now appears as an ordinary failed test with a stack trace. For a CI-timeout hypothesis, verify that the job completes comfortably before the platform limit. For a Docker IPC hypothesis, verify in the same image and launch configuration used by CI, not only on a developer machine.
Keep the failing run's artifacts. trace: 'retain-on-failure' can preserve browser actions leading to a test failure, but a process killed abruptly may not flush a complete trace. Combine traces with runner logs and system events; do not assume a missing trace means no browser action happened. The trace on retry guide helps when the worker survives long enough for a normal failed attempt and a retry.
Prevent It From Coming Back
Pin Playwright through your lockfile and match the Docker image to that package version. Keep a documented worker budget for each CI runner class. If a runner gains CPU cores but not memory, revisit the worker limit rather than letting concurrency increase automatically. Place the limit in config or the workflow where reviewers can see it. The GitHub Actions for Playwright guide covers a stable job layout for install, run, and artifact upload.
Make fixture teardown explicit. Prefer built-in page and context fixtures for routine tests; wrap manually created contexts, streams, and child processes in try/finally. Give parallel workers distinct test data and files. Playwright's parallelism documentation notes that processes do not share JavaScript state, but they can still collide in a database, shared account, or output path. Stable isolation prevents failures that happen to disappear when you reduce workers.
Keep first-failure evidence available in CI: list reporter output, browser launch stderr when needed, platform termination reason, and a memory graph. Review any recurring worker process exited unexpectedly line even if the final job status is green.
Run a small, version-matched container or runner smoke test after image changes. Use one minimal browser launch, one affected test file, and the normal full project. Preserve the command that validates each image or runner change.
Interview Questions and Answers
Q: What is the first distinction you make when seeing this error?
I separate a normal failed test from an unexpectedly terminated worker. Playwright intentionally restarts workers after ordinary failures, so I look for the process exit line and its preceding stderr. I then run the same file with one worker and no retries to narrow concurrency and isolate the first failure.
Q: Does a SIGKILL value prove an OOM kill?
No. It proves the process received that signal. I inspect container events, kernel logs where available, and the CI platform's termination reason before attributing it to memory pressure. I compare memory usage and rerun under a lower worker count as supporting evidence.
Q: Why can lowering workers help?
Each worker is a separate Node process and launches its own browser. Fewer concurrent workers reduce simultaneous memory and CPU demand. The same change can also suppress a shared-resource race, so I verify the actual bottleneck rather than treating a green one-worker run as the whole diagnosis.
Q: What makes Docker different?
The container has its own memory and IPC constraints. For Chromium, Playwright documents --ipc=host as a way to avoid shared-memory crashes and recommends --init for process handling. I match the image tag to the installed Playwright version and repeat the failing command in the final container configuration.
Q: How can a test helper create this error?
If a helper calls process.exit() inside the worker, the runner loses the process before it receives a standard assertion result. I search imports and hooks, replace forced exit with a thrown error or assertion, and verify the failure now appears with a normal test stack trace.
Q: Why not turn on retries immediately?
Playwright restarts a worker for a retry, so a later pass can mask an intermittent kill. I disable retries while reproducing and preserve the first attempt's logs. Once the cause is fixed, I restore the project's deliberate retry policy.
Common Mistakes
- Treating the last line of output as the cause while ignoring an earlier browser or fixture error.
- Increasing
--timeoutfor a CI job killed by its platform deadline; these are different controls. - Calling every
SIGKILLan OOM event without checking host or container records. - Adding retries until the job passes, leaving a recurring worker death in the logs.
- Reducing workers permanently without checking for a shared database account or file collision.
- Using
--ipc=hostlocally, then testing a CI container that has different IPC settings. - Closing Playwright's injected worker-level
browserin an individual test. - Running an image tag that does not match the locked Playwright package.
- Assuming a missing trace proves the worker never reached the browser; abrupt termination may prevent artifact flushing.
Conclusion
To Fix Playwright Worker Process Exited Unexpectedly, reproduce the smallest failing run without retries, preserve the exit signal and earlier logs, then test the matching root cause. Worker count, Docker memory and IPC, forced exits, job deadlines, leaked resources, and browser launch failures each need a different repair and a matching verification command.
After the focused file is stable, run the complete intended project in the same CI or container environment that failed. For interview practice, explain the evidence that distinguishes a worker exit from a normal test failure in QAJobFit practice.
Interview Questions and Answers
How would you triage an unexpected Playwright worker exit?
I capture the exact exit code and signal and read the first preceding error. I rerun the affected file with one worker and no retries. Then I compare local and CI resource limits, fixture imports, and platform termination events before changing configuration.
How is a normal test failure different from a worker process death?
A normal assertion or hook error is reported to the runner, which then discards and replaces the worker for isolation. An unexpected death removes the process before it sends a complete result. The latter requires process and host evidence, not just a trace of browser actions.
What evidence confirms an OOM kill?
I look for an OOMKilled container status, kernel OOM event, or CI memory termination annotation, then compare timestamps with the Playwright exit. A SIGKILL alone is insufficient. A lower worker count improving the run supports the hypothesis but does not prove it.
Why does worker count matter to Playwright memory use?
Every worker is a separate Node process with its own browser. Increasing workers raises concurrent browser and test memory demand. I start constrained CI with one worker, measure peak usage, and raise concurrency only when the runner has capacity.
How would you debug a Docker-only Chromium crash?
I run the exact failing file in the final image with DEBUG=pw:browser and one worker. I check the image tag against the installed Playwright package, inspect memory and shared memory, and compare the container launch options with Playwright Docker recommendations. I verify in the same CI runtime after changing IPC or limits.
How do you repair a helper that exits the worker?
I find process.exit in test imports, hooks, or fixtures and replace it with a thrown error or failed assertion. The caller then reports a test failure rather than terminating Node. I run the file with retries off and confirm the expected stack trace appears.
What if the error occurs at a fixed CI duration?
I compare the failure timestamp with the platform job deadline. If the platform terminated the job, I shorten or shard the suite or adjust the job limit; changing Playwright test timeout alone cannot extend a CI deadline. I rerun and ensure the job finishes before the limit.
Frequently Asked Questions
What does Playwright "worker process exited unexpectedly" mean?
The Playwright Test runner lost a child worker before it received a normal test result. The line does not identify the cause. Read the code or signal, the preceding stderr, and host or CI termination events.
Will --workers=1 fix a worker process exit?
It can remove excess concurrency on a constrained runner and is a useful diagnostic. If the same file still exits with one worker, investigate fixture code, forced exits, native crashes, and external termination. A one-worker pass may also hide a shared-resource race.
Does signal=SIGKILL mean the worker ran out of memory?
It means the operating system or another controller killed the process with SIGKILL. An OOM event is one possibility, but you need container events, kernel records, or a CI termination reason to confirm it.
Why does this happen in Docker but not locally?
The container has separate memory and IPC limits and may run a different Playwright image. Inspect browser stderr, compare the image tag with the installed package, and check Chromium shared-memory configuration. Rerun the affected file inside the final CI image.
Can process.exit in a test cause this error?
Yes. Tests and fixtures run inside a Node worker, so process.exit terminates that worker before it can send a standard failure result. Replace it with a thrown error or Playwright assertion and confirm the runner reports the failure normally.
Should I enable retries to solve worker exits?
Retries can start a fresh worker and make an intermittent failure appear green, but they do not repair the underlying process exit. Disable them while reproducing, fix the cause, then restore your intended retry policy.
Can a browser crash cause the same worker message?
A browser crash normally produces browser or context-closed diagnostics while the Node worker may remain alive. Both can stem from resource pressure, but inspect browser stderr with DEBUG=pw:browser and the worker exit line separately.
Related Guides
- How to Fix Playwright "Element is not attached to the DOM"
- How to Fix Playwright locator resolved to hidden element
- How to Fix Playwright waiting for element to be visible enabled and stable
- How to Fix "Cannot find module '@playwright/test'" in Playwright
- How to Fix "Cypress failed to start" and cypress verify Errors
- How to Fix "Playwright Test did not expect test() to be called here"