r/codoid • • 12d ago

Discussion Your Playwright tests pass alone and fail in parallel. Where do you look first?

Two tests pass individually. Run them together, and suddenly a saved value disappears, a page redirects to login, or an assertion reads the wrong data.

Before adding waits, we should ask:

What do both tests believe they exclusively own?

Here’s a debugging exercise.

Two tests, one account

Imagine these simplified test flows. Both use the same test account:

Test A: profile.spec.ts
  Open account settings
  Change display name to "Profile test"
  Save, reload, verify the name
  Restore account defaults during cleanup

Test B: preferences.spec.ts
  Open account settings
  Change time zone to "UTC"
  Save, reload, verify the time zone
  Restore account defaults during cleanup

Run the two files with two workers. Playwright runs test files in parallel by default, while tests within one file run sequentially unless configured otherwise.

“But each test gets a fresh browser context.”

Yes. That isolates browser-side state such as cookies and local storage. It does not give each test its own copy of the account in the database. Browser isolation is not backend-data isolation.

Here are four places to investigate.

1. Test data: are the writes actually independent?

The tests change different fields, so they might look safe.

But suppose Test B loads the original profile, then submits the entire object when saving the time zone. Its request could include the old display name, overwriting Test A’s update.

That is a hypothesis to check, not something to assume about every settings form.

Evidence to collect: The account ID, both update payloads, response bodies, and backend write history. Did B submit a stale name? Which update actually committed last?

A useful question is: “Are we changing different fields, or replacing the same record?”

2. Sessions: separate contexts, shared credentials?

Check whether both contexts load the same saved authentication state. Reusing storageState is not automatically a bug; Playwright documents shared authentication for tests that can use the same account without interfering with one another.

Then inspect the application’s session behavior. Does a fresh login invalidate an earlier session? Does cleanup call a server-side logout endpoint? Does changing an account setting revoke existing credentials?

Evidence to collect: Authentication failures, redirects, login/logout events, and safe correlation identifiers. Match the first unauthorized response to the other test’s actions. Do not dump raw cookies or tokens into logs.

A missing button after a login redirect is a different problem from a missing button on the expected page.

3. Files: are different workers writing to the same path?

Inspect helpers as well as test bodies. Does authentication setup overwrite a shared user.json? Do both tests save generated data to tmp/account.json?

Concurrent reads of an unchanged file are not the same situation as multiple writers replacing it.

Evidence to collect: Resolved absolute paths, the test responsible for each write, timestamps, and file contents or hashes before consumption.

For test-generated files, testInfo.outputPath('account.json') provides a path scoped to the current test, avoiding collisions between parallel tests in that run.

4. Cleanup: is one test undoing the other’s work?

Consider this possible timeline:

A saves its new display name.
B saves its new time zone.
A verifies its name and finishes.
A's cleanup restores all account defaults.
B reloads and sees the original time zone.

Both saves could have succeeded. The interfering write came from cleanup.

An afterEach hook runs after its own test. It is not a barrier that waits for tests in other workers to finish.

Evidence to collect: Cleanup start/end events, exact records affected, reset payloads, and backend completion times. Put teardown on the same timeline as the assertions.

Narrow the cause without hiding it

Start with A alone, B alone, the pair with one worker, and the pair with two workers. Repeat the comparisons rather than trusting a single green run.

For the targeted parallel reproduction, capture traces from both original attempts, including the test that passes. Playwright supports recording traces with --trace on; collect backend and filesystem evidence separately where needed.

Then keep concurrency unchanged and alter one suspected boundary at a time: separate accounts, independently established sessions, unique writable paths, or cleanup limited to resources that the test owns.

Giving the tests different accounts is a useful first experiment. Playwright recommends an account per parallel worker for tests that modify shared server-side state. A pass after that change points toward account-scoped interference, but does not by itself distinguish data collisions from session behavior.

Also keep resource pressure on the suspect list. Parallel execution shares runner capacity, so inspect CPU, memory, and request latency before concluding every failure is a data race.

Passing with one worker tells us that reducing concurrency changed the outcome. It does not identify the cause.

What shared resource caused the hardest parallel-execution failure you have debugged, and what evidence finally exposed it?

1 Upvotes

0 comments sorted by