This page explains how @playwright/test's fixture system works: how fixtures are defined, scoped, and injected into tests, how the FixtureRunner manages dependency resolution and teardown guarantees, and how the built-in Playwright fixtures (browser, context, page, request) compose together to provide a ready-to-use browser environment for each test.
For test configuration (projects, use options, timeout values), see Test Configuration and Projects. For the higher-level test execution pipeline (Runner, Dispatcher, WorkerHost, WorkerMain), see Test Execution Architecture.
A fixture is a named, lazily-instantiated value (or resource) provided to tests and hooks via dependency injection. The fixture function separates setup from teardown using the use callback:
Dependencies are declared by name in the first destructuring argument. The runner resolves them automatically by inspecting parameter names via fixtureParameterNames() (imported from packages/playwright/src/common/fixtures.ts).
The public API for defining fixtures is test.extend<TestFixtures, WorkerFixtures>(fixtureMap), which produces a new TestType with the fixtures registered.
Every fixture belongs to one of two scopes, set via the scope property:
| Scope | Lifetime | Shared Across |
|---|---|---|
'test' (default) | Created per-test, torn down after each test | Not shared |
'worker' | Created once per worker process | All tests in the same worker |
A 'test'-scoped fixture cannot depend on a 'worker'-scoped fixture that itself has 'test'-scoped dependencies — scopes must be consistent along the entire dependency chain.
The second element of the fixture tuple is an options object:
| Option | Type | Meaning |
|---|---|---|
scope | 'test' | 'worker' | Fixture lifetime |
auto | boolean | 'all-hooks-included' | Set up automatically, without being explicitly requested |
option | boolean | Override via test.use() |
box | boolean | Hides internal stack frames; error points to test call-site instead |
timeout | number | Independent timeout in ms; 0 disables timeout |
title | string | Display name in traces and reports |
auto: 'all-hooks-included' extends automatic setup to beforeAll/afterAll hooks as well as tests. The internal _setupContextOptions and _setupArtifacts fixtures use this mode.
All built-in fixtures are defined in packages/playwright/src/index.ts inside the playwrightFixtures constant and registered via _baseTest.extend(playwrightFixtures) to produce the exported test object.
Diagram: worker-scope fixture dependencies
Sources: packages/playwright/src/index.ts63-125
Diagram: test-scope fixture dependencies
Sources: packages/playwright/src/index.ts127-514
| Fixture | Scope | Type | Purpose |
|---|---|---|---|
playwright | worker | PlaywrightImpl | The playwright-core API root object |
browserName | worker, option | string | Which browser: 'chromium', 'firefox', or 'webkit' |
launchOptions | worker, option | LaunchOptions | Merged into playwright[browserName].launch() args |
connectOptions | worker, option | ConnectOptions | Connect to a remote browser instead of launching |
browser | worker | Browser | Launched or connected browser instance |
contextOptions | test, option | BrowserContextOptions | Base context options; individual option fixtures read from here |
context | test | BrowserContext | Fresh context per test via browser.newContext() |
page | test | Page | Fresh page per test via context.newPage() |
request | test | APIRequestContext | HTTP client via playwright.request.newContext() |
trace | worker, option | TraceMode | Trace recording: 'off', 'on', 'retain-on-failure', etc. |
screenshot | worker, option | ScreenshotMode | Auto-screenshot mode on test end |
video | worker, option | VideoMode | Video recording mode |
baseURL | test, option | string | Injected into BrowserContextOptions.baseURL |
actionTimeout | test, option | number | Default per-action timeout |
navigationTimeout | test, option | number | Default navigation timeout |
Sources: packages/playwright/src/index.ts71-514
The test execution sequence, as driven by WorkerMain._runTest(), is:
Diagram: test execution and fixture lifecycle sequence
Sources: packages/playwright/src/worker/workerMain.ts294-450 packages/playwright/src/worker/fixtureRunner.ts1-270
Fixtures are set up lazily in dependency order. When a test or hook requests a fixture, FixtureRunner.setupFixtureForRegistration() first recursively sets up all declared dependencies, then runs the fixture function up to its use() call, storing the yielded value.
_setupContextOptions and _setupArtifacts are auto: 'all-hooks-included', meaning they are set up for every test and every hook invocation.
FixtureRunner.teardownScope() tears down all active fixtures for the given scope in reverse dependency order. If fixture B depends on A, B is torn down before A.
Teardown is guaranteed to run even when:
gracefullyClose()_setupArtifacts uses timeout: 0 to ensure trace/screenshot collection always completes, even after a test has timed out.
Sources: packages/playwright/src/worker/fixtureRunner.ts120-260 packages/playwright/src/worker/workerMain.ts114-141 packages/playwright/src/index.ts248-344
Each fixture setup and teardown is recorded as a TestStepInternal with category: 'fixture' (see TestInfoImpl._runAsStep()). These appear in traces under the "Before Hooks" and "After Hooks" top-level steps:
Before Hooks
Fixture "request"
Create request context
Fixture "browser"
Launch browser
Fixture "context"
Create context
Fixture "page"
Create page
After Hooks
Fixture "page"
Fixture "context"
Close context
Fixture "request"
Worker Cleanup
Fixture "browser"
Sources: packages/playwright/src/worker/testInfo.ts418-427 tests/playwright-test/playwright.trace.spec.ts90-107
Diagram: FixtureRunner class relationships
Sources: packages/playwright/src/worker/fixtureRunner.ts28-270 packages/playwright/src/worker/workerMain.ts40-78
WorkerMain calls FixtureRunner.setPool(test._pool) before each test group.setupFixtureForRegistration() is called._deps first.await use(value).value is stored on the Fixture instance._useFuncFinished ManualPromise is kept pending until teardown.teardownScope('test', testInfo, runnable) is called at the end of each test.Fixture, teardown() resolves _useFuncFinished, allowing the async generator to resume after use().TimeoutManager slot scoped to the runnable description.WorkerMain.gracefullyClose() using a synthetic TestInfoImpl with no test case.Sources: packages/playwright/src/worker/fixtureRunner.ts100-260 packages/playwright/src/worker/workerMain.ts114-141
Auto fixtures are set up without being named in test parameters. They are always active for their scope.
| Internal Fixture | Scope | Auto Mode | Function |
|---|---|---|---|
_browserOptions | worker | true | Merges headless/channel/launchOptions; injects into playwright._defaultLaunchOptions |
_setupContextOptions | test | 'all-hooks-included' | Sets playwright._defaultContextTimeout, configures testIdAttribute, enables debug mode |
_setupArtifacts | test | 'all-hooks-included', timeout: 0 | Starts/stops tracing per context; captures screenshots; installs ClientInstrumentationListener for pw:api steps |
_setupArtifacts installs a ClientInstrumentationListener on playwright._instrumentation that intercepts every Playwright API call to create TestStepInternal entries with category: 'pw:api'. This is what produces the detailed action tree seen in traces.
Sources: packages/playwright/src/index.ts87-101 packages/playwright/src/index.ts248-344
Option fixtures (option: true) act as configuration knobs overridable via test.use(). The override applies to all tests within the enclosing describe block, file, or project.
Context option fixtures (acceptDownloads, viewport, locale, isMobile, etc.) each default to reading from contextOptions. Direct fixture overrides take priority over contextOptions. All context options are merged into _combinedContextOptions, which is passed to browser.newContext().
When option values change, the worker's hash changes, causing the dispatcher to start a new worker rather than reuse an existing one.
Sources: packages/playwright/src/index.ts127-154 packages/playwright/src/index.ts157-232
A boxed fixture (box: true) rewrites the error stack trace: instead of showing the internals of the fixture body, the error location points to the user's test code where the fixture was requested. All built-in Playwright fixtures are boxed.
Internally, when a box: true fixture calls _addStep(), TestInfoImpl captures the external call-site stack in boxedStack and uses it for any errors that propagate out of that fixture.
Sources: packages/playwright/src/worker/testInfo.ts304-312 packages/playwright/src/index.ts72-85
The type parameters for extend match the Fixtures interface exported from packages/playwright/src/index.ts.
Pass the same name in extend() to override a built-in fixture. The override receives all the same dependencies as the original:
Sources: packages/playwright/src/index.ts836 tests/playwright-test/fixtures.spec.ts
The TimeoutManager class in packages/playwright/src/worker/timeoutManager.ts assigns a timeout "slot" to each active runnable: the test function, fixture setup, fixture teardown, and each hook. The current slot determines what error message is shown on timeout.
FixtureRunner.workerFixtureTimeout, set to project.timeout by WorkerMain._loadIfNeeded().timeout: N gets its own independent slot.timeout: 0 disables timeout for that fixture entirely (used by _setupArtifacts).When a test times out:
TestInfoImpl._interrupt() is called, resolving _interruptedPromise and calling TimeoutManager.interrupt().TimeoutManagerError._setupArtifacts (timeout: 0) always completes, saving traces and screenshots.Tearing down "myFixture" exceeded the test timeout of 500ms. is reported.Sources: packages/playwright/src/worker/timeoutManager.ts packages/playwright/src/worker/workerMain.ts358-450 packages/playwright/src/worker/testInfo.ts429-458 tests/playwright-test/fixture-errors.spec.ts19-42 tests/playwright-test/timeout.spec.ts19-40
beforeAll and afterAll hooks can request fixtures. Each hook invocation receives a fresh set of test-scoped fixtures for its duration.
However, the context and page fixtures explicitly check TestInfoImpl._currentHookType() via _contextFactory and reject invocations inside beforeAll/afterAll:
"context" and "page" fixtures are not supported in "beforeAll" since they are created on a per-test basis.
Worker-scoped fixtures like browser work normally inside beforeAll hooks because their lifetime spans the entire worker.
Sources: packages/playwright/src/index.ts354-361 packages/playwright/src/worker/testInfo.ts464-467 tests/playwright-test/hooks.spec.ts19-95
_contextFactory and Video RecordingThe _contextFactory test fixture provides a factory function that creates BrowserContext instances with optional video recording. It is called internally by the context fixture and can be used directly in tests that need multiple contexts.
When called, _contextFactory:
shouldCaptureVideo(videoMode, testInfo) — returns true for modes 'on', 'retain-on-failure', and 'on-first-retry' (on the correct retry).recordVideo: { dir: artifactsDir } to context options if capturing.{ context, close } where close() saves the video to testInfo.outputPath('video.webm') and attaches it to the test result.The browser fixture itself is torn down in the "Worker Cleanup" phase (worker scope), not per-test, so it appears at the bottom of the trace rather than in "After Hooks".
Sources: packages/playwright/src/index.ts346-414 tests/playwright-test/playwright.artifacts.spec.ts
When running under Playwright Inspector or UI Mode, context reuse mode is activated via the PW_TEST_REUSE_CONTEXT environment variable or _optionContextReuseMode: 'when-possible'.
In this mode:
_reuseContext (worker fixture) returns true if reuse is possible (video must be off).context calls browser._newContextForReuse() instead of browser.newContext().page reuses the existing page from the context rather than creating a new one.Refresh this wiki
This wiki was recently refreshed. Please wait 2 days to refresh again.