skip to content

A Playwright fixture that seeds admin-console staff accounts takes 45 seconds and every test times out. What do you change?

level: seniorimportance: should knowfreq 42%

answer

  1. Setup is charged to the test
  2. The message names the fixture
  3. Do not widen the whole suite
  4. A tuple carries fixture options
  5. Slow setup deserves its own clock

basics

~20 s

Fixture setup is charged to the test timeout by default, so a 45-second fixture blows a 30-second budget. Give that fixture its own timeout in its options tuple instead of raising the suite-wide timeout for every test.

solid answer

~40 s

The fixture's setup runs inside the test's budget, and the report says so: `Test timeout of 30000ms exceeded while setting up "seededStaff"`. That clause is the diagnosis — the setup is slow, not the test. Raising the top-level `timeout` to 120 seconds would go green and would be the wrong fix, because every genuine hang then takes two minutes to report. Instead give the fixture its own clock through its options tuple, `seededStaff: [async ({}, use) => { ... }, { timeout: 90_000 }]`, so the slow setup runs on 90 seconds while the test budget stays at 30. If the extra time genuinely belongs to the test, `test.info().setTimeout(test.info().timeout + 60_000)` inside the fixture widens every consumer instead. `beforeAll` carries its own budget, changed by calling `test.setTimeout()` inside the hook.

code

typescript · 9 lines
typescript
import { test as base } from '@playwright/test';
import { seedStaffAccounts } from './seed';

export const test = base.extend<{ seededStaff: string[] }>({
  seededStaff: [async ({}, use) => {
    const roles = await seedStaffAccounts(['auditor', 'editor', 'support']);
    await use(roles);
  }, { timeout: 90_000 }],
});

go deeper

for a junior

Take away one fact: a fixture's setup time counts against the test timeout, so slow setup makes tests fail on the clock even when the test body is trivial.

for a middle

Explain the mechanics and the repair: the tuple form of a fixture accepts a timeout option, and a fixture given one runs on that clock instead of sharing the test's budget.

for a senior

Show the diagnosis before the fix. Read the 'while setting up' clause, reject the global raise and its cost to hang detection, then decide whether the time belongs to the setup or to the tests.

for a principal

Own the pattern across suites: slow shared setup gets a named budget, per-test budgets stay tight enough to be meaningful, and repeated widening is treated as a signal to make the setup cheaper.

## Where the 45 seconds is being charged Playwright charges a test's fixture setup and teardown to the **test timeout**. The 30-second default covers the test body, its `beforeEach` and `afterEach` hooks, and every fixture the test requested — including a worker-scoped one, on the test that first triggers it. So a fixture that seeds staff accounts for 45 seconds does not merely make tests slow; it makes them impossible. The report states the cause directly: ``` Test timeout of 30000ms exceeded while setting up "seededStaff". ``` That "while setting up" clause is the diagnosis. Before changing any number, read it: a test timeout naming a fixture is a setup-cost problem, not a test problem. ## The fix that looks obvious and is wrong Raising the top-level `timeout` to 120 seconds turns the suite green and costs you the thing the budget was for: - every genuinely hung test now burns two minutes before it reports; - the 45-second setup stops being visible as a cost anyone owns; - the number no longer describes what a test is allowed to take, so nobody can tell later which tests needed the room. ## Give the fixture its own clock A fixture may be declared as a tuple whose second element carries options, and one of those options is `timeout`: ```typescript seededStaff: [async ({}, use) => { await use(await seedStaffAccounts(['auditor', 'editor', 'support'])); }, { timeout: 90_000 }], ``` A fixture with its own `timeout` runs on that clock instead of sharing the test's. That is exactly the separation you want: the slow shared setup gets 90 seconds, the test body keeps its tight 30, and the two kinds of failure stay distinguishable in the report. ## The alternative, and when it is right From inside a fixture you can widen the *test* instead: ```typescript test.info().setTimeout(test.info().timeout + 60_000); ``` This raises the budget of every test that requests the fixture, relative to whatever the config currently says. It is the right call when the extra time is genuinely part of what those tests do; it is the wrong call for shared setup, because the per-test budget then stops measuring the test. | Option | Effect on the test budget | Best for | |---|---|---| | Raise top-level `timeout` | inflated for every test | nothing in this scenario | | Fixture `{ timeout }` in its tuple | unchanged | slow shared setup | | `test.info().setTimeout()` in the fixture | inflated for consumers | work the tests own | | `test.setTimeout()` in `beforeAll` | hook budget only | slow one-off preparation | ## Hooks carry their own budget `beforeAll` and `afterAll` do not run inside any single test's clock. They have their own budget, defaulting to the test timeout, and `test.setTimeout()` called inside such a hook changes that hook's budget rather than the tests'. A per-file preparation step that honestly takes a minute is widened there, not in the config. ## The order to work through it 1. Read the failure text and confirm it names the fixture rather than the test body. 2. Ask whether the 45 seconds is necessary at all — seeding three roles through an API is faster than driving the console's forms, and the cheapest budget fix is not needing the time. 3. If it is necessary and shared, give the fixture its own `timeout` and leave the test budget alone. 4. If it is necessary and belongs to the tests, widen the test explicitly and say why at the place that does it. 5. Re-run and confirm the test budget still fails a hang quickly — that is the property you were protecting all along.

  • How do you tell a slow fixture from a slow test just by reading the report?
    Read the timeout message. Playwright appends 'while setting up "fixtureName"' when the clock ran out during fixture setup, so setup cost and test-body cost are already distinguished without you instrumenting anything.
  • When would you widen the test timeout rather than give the fixture its own?
    When the long work is part of what the test does — an import the test itself drives, for instance. Shared setup that every test merely pays for belongs on a fixture clock, so the per-test budget keeps measuring the test.

saying these in an interview costs you the question

  • Raises the global timeout to cover one slow fixture
  • Thinks fixture setup runs outside the test timeout
  • Believes globalTimeout covers slow fixture setup
  • Adds a retry so a second attempt gets a fresh budget
  • Assumes a worker-scoped fixture is exempt from the test clock