Q29 of 42 · Playwright

How does Playwright run tests in parallel across workers, and what should be worker-scoped vs test-scoped?

PlaywrightSeniorplaywrightparallelfixturesworkerssenior

Short answer

Short answer: Each worker is a Node process with its own browser. By default tests in the same file run serially in a worker; tests in different files run in parallel across workers. Test-scope expensive setup belongs in test fixtures; worker-scope shared setup (DB seeds, slow auth) belongs in worker fixtures. Match scope to teardown cost and isolation needs.

Detail

The execution model:

  • Playwright spawns N worker processes (workers: N).
  • Each worker is a separate Node process with its own browser instance and context.
  • The runner distributes spec files (or test parallel groups within files) to idle workers.
  • Within a file, tests run serially by default. test.describe.parallel opts a group into intra-file parallelism.

Fixture scopes:

  • scope: 'test' (default): the fixture's setup/teardown runs once per test. page, context are test-scoped — fresh isolation per test.
  • scope: 'worker': setup/teardown runs once per worker, regardless of how many tests it processes. Used for expensive shared resources.

What belongs where:

Test-scoped (most fixtures):

  • Anything that needs isolation: a new browser context, a temp directory, a transactional DB session.
  • Fixtures that depend on test inputs.
  • Anything cheap to recreate.

Worker-scoped (sparingly):

  • Database seed: insert canonical fixture rows once per worker; tests read but don't modify.
  • Auth state: log in once per worker, share the storage state.
  • Containers / external services: spin up a Docker dependency once per worker.
  • Anything that takes >2 seconds to set up and is genuinely shareable.

Pitfalls:

  • Mutating worker-scoped state. If two tests in the same worker both modify the same shared resource, you have ordering coupling. Either keep worker resources read-only or use test-scoped state.
  • Cross-worker assumptions. Worker A's seed isn't visible to worker B's worker fixture; each runs setup independently. If you need true cross-worker shared state, use globalSetup (runs once before all workers).

globalSetup / globalTeardown: runs once before/after the entire run, across all workers. Right place for things like spinning up a Docker compose stack, running DB migrations, or one-time cleanup.

Typical scope hierarchy:

globalSetup       (1× per run)         — start docker, run migrations
worker fixture    (1× per worker)      — log in, seed read-only data
test fixture      (1× per test)        — fresh page, transactional DB session

The senior signal: knowing the scope ladder, naming globalSetup vs worker fixture, and the mutating-shared-state trap.

// WHAT INTERVIEWERS LOOK FOR

Naming the test/worker/global scope ladder, the mutating-shared-state trap, and matching scope to setup cost.

// COMMON PITFALL

Putting expensive setup in test fixtures (`scope: 'test'` defaults) and watching every test pay the cost.