Skip to content

Built on demand, torn down in reverse

4.2 · Test runner · test.extend()

How Playwright decides when your fixtures start and stop.

setup or teardownin usetest bodyfailed test

A test asks for fixtures by name 1. Playwright builds those fixtures and the fixtures they depend on. It does not build a fixture that no test asks for, unless the fixture is auto.

In your test · fixtures
export const test = base.extend<
{ todoPage: TodoPage },
{ account: Account }
>({
account: [async ({}, use) => {
const acc = await createAccount();
await use(acc); // tests run here
await deleteAccount(acc);
}, { scope: 'worker' }],
todoPage: async ({ page }, use) => {
const todo = new TodoPage(page);
await todo.goto();
await use(todo); // the test runs
await todo.removeAll();
},
});

Each fixture runs up to await use(value) 2. That part is its setup. The test then gets the value. The code after use() is the teardown. It runs after the test.

Setup goes from the dependency to the fixture that needs it: browser, context, page, todoPage 3. Teardown goes the other way: todoPage, page, context. If one teardown fails, Playwright still runs the others.

Inside Playwright · fixtureRunner.ts
// Teardown fixtures in the reverse order.
const allFixtures = Array.from(this.instanceForId.values()).reverse();

A worker fixture lives as long as the worker process 4. Tests in one worker share the same account. When a test fails, Playwright stops the worker and tears down its worker fixtures too. The next test runs in a new worker, which builds them again.

A worker fixture cannot depend on a test fixture 5. Playwright reports this error, and the test does not run.

In your test · scope
// ✗ A worker fixture cannot use page
account: [async ({ page }, use) => {
…
}, { scope: 'worker' }],
// ✓ Use a worker-scoped dependency
account: [async ({ browser }, use) => {
…
}, { scope: 'worker' }],

What you see in the output

  • use() was not called in fixture "todoPage"The fixture returned without calling use(). Call it once.
  • Cannot provide fixture value for the second timeThe fixture called use() twice.
  • worker fixture "account" cannot depend on a test fixture "page" defined in …A worker fixture asks for a test fixture. Make it test-scoped, or remove the dependency.
  • Fixtures "a" -> "b" -> "a" form a dependency cycle: …Two fixtures depend on each other.
  • Fixture "todoPage" has unknown parameter "pgae".A dependency name has a typo, or the fixture is not registered.
  • Worker CleanupA step in the report after a failed test: the worker stops and its worker fixtures are torn down.

Docs vs. code

  • Adds detailTeardown errorsDocs: teardown runs after the test. Code: when one teardown fails, Playwright still runs the others, then reports the first error. docs · code
  • Adds detailCalling use()Docs: setup comes before await use(), teardown after it. Code: a fixture must call use() exactly once. Zero or two calls is an error. docs · code
  • Adds detailWhich failures restart the workerDocs: any failed test discards the worker. Code: only an unexpected result does. A skipped test, or a test.fail() test that fails as expected, keeps the worker. docs · code
Runs in:test runner (worker process)