Skip to main content
test is the only registration surface. Every member registers synchronously while the test file is imported.
Calling any member outside collection, or after module evaluation finished, throws COLLECTION_ERROR.

Members

test.setup declares the sessions it saves, and must save each exactly once:
Session names must match /^[A-Za-z0-9_.-]{1,128}$/ and be unique. Not saving a declared session, or saving one twice, throws SESSION_CONTRACT at runtime.

Hooks

Hook order follows scope nesting, not position in the file: a file-level beforeEach declared below a test.describe still runs before the group’s own beforeEach, and a hook declared after a test still applies to it. A suite instance is one execution realm: the module is re-imported for every retry, for every serial group, and for every setup test, so beforeAll runs again in each. A scope’s afterAll runs as soon as its last runnable test in that realm is done, so one group’s teardown never runs after a sibling group’s tests. A realm that a failure discards runs afterAll for every scope whose beforeAll started. A failing afterAll ends the realm too: later tests in the file start in a fresh one, and inside a serial group the remaining members are skipped with cause hook-failed and the group fails without a retry. beforeEach and the body share the test timeout. Each afterEach hook then gets its own cleanupTimeout budget with working fixtures, so teardown can still drive the app after the body timed out. A hook that overruns that budget fails, its fixture operations are cancelled, and the next hook still runs. A throw inside beforeAll or afterAll is reported as HOOK_FAILED and skips the suite’s tests with cause hook-failed. Inside a serial group, a beforeAll failure skips every remaining member and fails the group without retrying it.

Options

Resolution order: test, nearest group, outer groups, config, built-in default. serial: true makes the group one ordered retry unit with shared app state. Secret redaction and pixel taint share that lifetime: provider-resolved values stay redacted in later members, and a secret fill withholds model pixels for the rest of the group’s attempt. A retry starts fresh isolation and fresh secret state. Inside it, retries, session, platforms, requires, skip, only, and a nested serial on a member are collection errors. Set them on the group.

Fixtures

Everything else is a contributed fixture the target’s engine declares, or one of your own. The zero-argument test.extend<{ ... }>() types a contributed fixture without defining anything: @e2edev/playwright contributes web and exports a test already typed with it; a device engine contributes device the same way. Reaching for a fixture neither the engine nor a test.extend() defines fails at first touch with UNSUPPORTED_CAPABILITY; declare requires: ['web'] to skip such tests at selection instead. Fixtures are lazy. Destructuring in the callback parameter list acquires them before the first statement of the body.

Your own fixtures

test.extend(fixtures) defines a fixture per test: a resource the body needs set up before it runs and cleaned up after, however the body ended. Each definition is one function in Playwright’s shape. Everything before await use(value) is the setup, value is what the test receives, and everything after is the teardown.
extend returns a new test; the one it was called on is unchanged. The new object carries every definition of the chain, the parent’s first, and every test, setup test, or hook registered through it records that chain. A definition is typed against the fixtures of the test it extends, not its siblings in the same call. A fixture that needs another fixture goes in a chained second extend: base.extend({ a }).extend({ b }) gives b a fixture object that already has a. Timing, per attempt:
  1. The engine’s fixtures exist. Each definition runs in declaration order, with the fixtures defined so far, until it calls use. Setup counts against the test timeout and a failure is reported in phase beforeEach, before any beforeEach hook has run.
  2. beforeEach hooks, then the body, then afterEach hooks, all with the same fixture object. The fixture set is decided by the test’s own chain: a hook registered through base that wraps a test registered through test sees workspace too, and a hook that reads workspace around a test registered through base fails with UNSUPPORTED_CAPABILITY.
  3. use resolves. The continuation of each definition runs in reverse order, after the last afterEach, whether or not the body passed. Each teardown has its own cleanupTimeout budget like an afterEach hook; an error there fails a passing attempt in phase afterEach and joins secondaryErrors after a body failure.
What fails, and where: A body or hook that hits the test timeout is abandoned, not cancelled: teardown starts while it may still be running, as with afterEach. A fixture whose setup was abandoned the same way has no teardown to run yet; if it reaches use later, use resolves at once (there is no attempt to hand the value to) and the code after it runs detached from the run, so what the setup allocated is still released.

Function types

Writing tests

expect

Signing in

Errors