skip to content

In a growing Playwright suite, how do you decide which setup earns worker scope over test scope?

level: principalimportance: should knowfreq 34%

answer

  1. A dial, not a default to flip
  2. Mutability is the gate
  3. Measure the setup before promoting
  4. Blast radius widens to the whole worker
  5. Promote the immutable half only

basics

~20 s

Promote a Playwright fixture to worker scope only when its value is read-only once built and its setup cost is measured and material. Test scope stays the default, because per-test isolation is what keeps a suite diagnosable as it grows.

solid answer

~50 s

Treat scope as a dial with a number on one side and a risk on the other. Worker scope pays the setup once per worker instead of once per test, and since workers are far fewer than tests the saving is real; it costs a value shared by every test in the worker, a failure that takes out a batch rather than a case, and teardown deferred until the worker exits. The gate is mutability: only promote values that are effectively read-only once built, such as a booted stub process, a fetched configuration snapshot or an opened connection. Keep the mutable part in a test-scoped fixture that depends on the worker-scoped one, so the expensive boot is amortised while each test still gets clean state. Reserve `auto` for genuinely universal setup, and re-review promotions as the suite grows.

code

typescript · 13 lines
typescript
import { test as base } from '@playwright/test';

// Promoted: expensive and read-only once built.
// Not promoted: anything a test writes to.
export const test = base.extend<{ draft: Draft }, { catalog: Catalog }>({
  catalog: [async ({}, use) => {
    await use(Object.freeze(await fetchRoleCatalog()));
  }, { scope: 'worker' }],

  draft: async ({ catalog }, use) => {
    await use(createDraftFrom(catalog));
  },
});

go deeper

for a junior

Understand the tradeoff in outline: worker scope makes setup run less often, and test scope keeps every test independent, which is why test scope is the safe default.

for a middle

Be able to name the two costs of promoting a fixture, namely state shared with every test in that worker and teardown that waits until the worker shuts down.

for a senior

Bring a decision procedure: measure the setup, check whether the value is read-only, consider what a failing setup takes down, and split the fixture rather than promoting it whole.

for a principal

Own the suite-wide rule and the asymmetry behind it. The author sees the time saved, someone else pays for the order-dependent failure later, so keep the promoted set small and reviewed.

Fixture scope is the suite's reuse-against-isolation dial, and the only honest way to set it is per fixture, with a number on one side and a risk on the other. Test scope is the default because isolation is the property that keeps a suite diagnosable; worker scope is the exception you justify. ## What each side actually buys - **Test scope** buys independence: every test builds its own value, so a failure is about the test in front of you and the suite is safe to reorder, filter and rerun in any subset. - **Worker scope** buys amortisation: the setup is paid once per worker rather than once per test, and since workers are far fewer than tests, the saving scales with the suite. - **Worker scope costs** a shared surface, a wider failure blast radius, and cleanup that cannot happen until the worker exits. | | test scope | worker scope | worker scope with auto | |---|---|---|---| | Setup cost | once per test | once per worker | once per worker, always paid | | Isolation | complete | shared within the worker | shared, and invisible in test files | | Teardown timing | end of each test | worker shutdown | worker shutdown | | Failure blast radius | one test | that worker's tests | that worker's tests, including ones that never used it | ## The questions to ask before promoting a fixture 1. **Is the value read-only once built?** This is the gate, not a preference. A booted process, a fetched configuration snapshot, an opened connection with no per-test state -- these are safe to share. Anything a test writes to is not, and no amount of discipline in tests keeps it that way as the suite grows. 2. **Is the saving measured?** Multiply the setup time by the number of tests and compare it with the same setup times the worker count. If the setup is fifty milliseconds, the dial does not need turning. 3. **What happens when its setup fails?** At test scope, one test fails. At worker scope, the worker's whole batch is affected. Setup that depends on a flaky external system is a poor candidate however expensive it is. 4. **Does anything need releasing promptly?** Worker teardown is deferred to worker shutdown, so a licence seat, a lock or a lease held by the fixture is held for as long as that worker keeps taking tests. 5. **Does it need a per-worker resource?** If the fixture must bind a port or claim a slot in a fixed pool, derive it from `parallelIndex`, so the environment only ever has to provision as many as there are concurrent workers. ## A policy that survives a growing suite For a suite covering an internal admin console with several staff roles, the workable shape is: - **Default to test scope.** Promotion is a decision with evidence attached, not a habit. - **Promote the immutable half only.** Keep the expensive read-only part at worker scope and give tests a thin test-scoped fixture derived from it. This keeps the saving and returns the isolation. - **Treat worker-scoped fixtures as a reviewed surface.** They are few; the set should be small enough to list, and adding to it should be a conscious change rather than a convenience. - **Reserve `auto` for setup that is genuinely universal.** Every implicit fixture is a dependency a reader cannot see from the test signature. - **Re-examine promotions as the suite grows.** A fixture that was read-only when it was promoted acquires mutable state over time; the review is worth repeating. ## Why this is a lead's call rather than an author's The engineer adding a fixture sees the setup time in front of them; the cost of worker scope lands somewhere else -- on whoever debugs an order-dependent failure months later, in a suite that has since grown a hundred tests and started running in more workers. That asymmetry is the reason the rule belongs to the suite rather than the change. The defensible position is a small, deliberate set of worker-scoped fixtures whose values nobody writes to, with everything mutable derived per test on top of them.

  • How would you justify a promotion to worker scope with evidence rather than intuition?
    Time the setup, then compare the setup time multiplied by the test count against the same setup times the worker count. That difference is the whole benefit, and it is often smaller than expected. Set it beside the cost you are accepting: a shared value, a wider failure radius, and deferred cleanup. If the saving does not clear that bar, leave the fixture at test scope.
  • What rule keeps worker scope safe as a suite grows past a few hundred tests?
    That a worker-scoped value is read-only once built, with everything mutable derived per test on top of it. The rule survives growth because it does not rely on individual tests behaving well; the shared object simply has nothing worth writing to. Keeping the set of worker-scoped fixtures small enough to list in review is what makes the rule enforceable.
  • When is a fixture a bad candidate for worker scope even though it is expensive?
    When its setup depends on something flaky, because a failure now affects a worker's whole batch rather than one test, and when it holds a resource that should be released promptly, since worker teardown waits for the worker to shut down. Expense alone never settles it; the shape of the failure and the urgency of the cleanup both have a veto.

saying these in an interview costs you the question

  • Promotes fixtures to worker scope purely to make the suite faster
  • Ignores mutability and relies on tests behaving politely
  • Treats setup cost as the only input to the decision
  • Forgets that worker teardown is deferred to worker shutdown
  • Applies auto broadly so dependencies stop being visible
  • Never revisits a promotion once the fixture grows new state