skip to content

In Playwright, in what order do dependent custom fixtures set up and tear down around a test?

level: middleimportance: should knowfreq 46%

answer

  1. Fixtures ask the same way tests do
  2. Object key order is not the answer
  3. A graph is built from names
  4. Dependencies come up first
  5. Teardown unwinds in reverse

basics

~20 s

A fixture asks for another by naming it in its first argument, and Playwright builds a dependency graph from those names. Dependencies set up first and tear down last: teardown runs in the reverse order of setup.

solid answer

~40 s

One fixture consumes another exactly the way a test does — by destructuring the name: `adminShell: async ({ page, auditLog }, use) => {}`. Playwright reads those names to build a dependency graph, so **the order of keys in the `test.extend` object is irrelevant**; what matters is who names whom, and which fixtures the test itself names. Setup runs dependencies-first, teardown unwinds in reverse, so a fixture is still allowed to call its dependencies in the lines after `await use(...)` — they are guaranteed to be alive until everything that used them has finished. Two fixtures that name each other form a cycle, which Playwright reports as an error before any test runs; the fix is to pull the shared piece into a third fixture both can depend on.

code

typescript · 22 lines
typescript
import { test as base, expect } from '@playwright/test';

type Fixtures = { auditLog: string[]; adminShell: { role: string } };

export const test = base.extend<Fixtures>({
  auditLog: async ({}, use) => {
    const entries: string[] = [];
    entries.push('audit-log:open');
    await use(entries);
    entries.push('audit-log:close');
  },
  adminShell: async ({ page, auditLog }, use) => {
    auditLog.push('shell:open');
    await page.goto('/admin');
    await use({ role: 'supervisor' });
    auditLog.push('shell:close');
  },
});

test('the shell is set up after the log it depends on', async ({ adminShell }) => {
  expect(adminShell.role).toBe('supervisor');
});

go deeper

for a junior

Remember that a fixture asks for another fixture by naming it inside the braces of its first argument, exactly the way a test asks for page.

for a middle

Explain that Playwright derives a dependency graph from those names, which is why key order in the object literal is irrelevant, and that teardown unwinds in the reverse of setup.

for a senior

Use the ordering guarantee on purpose: a fixture can still call its dependencies during its own teardown, because a dependency stays alive until everything that named it has finished.

for a principal

Watch the depth of the graph. A chain where each fixture pulls in the one below means every test pays for the whole chain, so keep the graph wide and shallow and let tests name what they need.

## Dependencies are declared by name A custom fixture asks for another fixture with the same syntax a test uses — destructuring the first argument: ```typescript const test = base.extend<Fixtures>({ auditLog: async ({}, use) => { /* ... */ }, adminShell: async ({ page, auditLog }, use) => { /* ... */ }, }); ``` `adminShell` depends on the built-in `page` and on the custom `auditLog`. There is no registration list, no ordering annotation, and no priority number. The dependency graph is derived entirely from the destructured names, plus the names each test, hook and override mentions. A consequence people often get wrong in interviews: **the key order inside `test.extend` means nothing**. Defining `adminShell` above `auditLog` changes nothing about which runs first. Nor does alphabetical order, nor the order of the fixture names in the test's own signature. ## Setup order, then teardown order For the graph above, a test that names `adminShell` produces: 1. `page` is set up (it is a dependency of `adminShell`). 2. `auditLog` runs its setup and reaches `await use(entries)`. 3. `adminShell` runs its setup and reaches `await use(shell)`. 4. The test body runs. 5. `adminShell` resumes and runs the lines after its `use`. 6. `auditLog` resumes and runs the lines after its `use`. 7. `page` is torn down. | Phase | Direction | |---|---| | Setup | dependencies first, dependants after | | Teardown | dependants first, dependencies after | That reverse order is a real guarantee to design against, not a coincidence: **a fixture may still use its dependencies during its own teardown**. A fixture that wrote to `auditLog` during setup can write a closing entry to it after `use`, because `auditLog` cannot be torn down until every fixture that named it is finished. ## What this buys you - **Composition instead of one giant fixture.** A seat-creating fixture, a signed-in-shell fixture and a log fixture can each do one thing and name the others. - **No manual ordering.** Add a dependency name and the order fixes itself; remove one and nothing else has to change. - **Reuse of a single instance.** Two fixtures naming `auditLog` in the same test receive the *same* value; the fixture is set up once per test, not once per consumer. - **A readable graph.** The signature of each fixture is the whole story of what it needs. ## Cycles are an error If `a` names `b` and `b` names `a`, there is no valid order, and Playwright fails the run with a fixture-dependency error rather than guessing. The fix is structural: extract the part they both actually need into a third fixture, and let each depend on that. ## Failure in the middle of the chain If a dependency's setup throws, the fixtures above it in the graph never start — there is nothing of theirs to tear down. Fixtures that were already set up **do** run their teardown, in the usual reverse order. So a failure part-way through a chain unwinds cleanly for everything that had been constructed. ## Keep the graph wide, not deep Because every dependency of a named fixture is set up before the test, a long chain means each test pays for the whole chain even when it wanted only the last link. In a suite for an internal admin console it is usually better to have several small fixtures a test can name individually — a seat, a role, a log — than one `everything` fixture that transitively drags in the rest. Depth also makes failures harder to read: the further down a chain the throw happened, the more setup already ran and had to unwind. ## Quick checklist - Name what you need; do not reorder keys hoping to change execution order. - Expect teardown to unwind in reverse, and use that to talk to dependencies after `use`. - Break a cycle by extracting a third fixture, never by duplicating setup. - Prefer several small fixtures over one deep chain.

  • What happens if two fixtures depend on each other?
    There is no valid setup order, so Playwright reports a fixture dependency cycle and the run fails before any test body executes. Break it by extracting whatever both genuinely need into a third fixture and depending on that, rather than duplicating the setup into one of them.
  • Does the order fixtures are listed in test.extend affect setup order?
    No. Order comes from the dependency names each fixture destructures and from the names the test itself lists. Object key order, alphabetical order and the order of arguments in the test signature are all irrelevant to when a fixture runs.

saying these in an interview costs you the question

  • Thinks key order in test.extend controls setup order
  • Says a fixture cannot depend on another fixture
  • Believes teardown runs in the same order as setup
  • Expects two consumers to get two separate fixture instances
  • Resolves a dependency cycle by copying setup into one fixture