Playwright Fixtures & Test Organization
As an end-to-end suite grows from ten tests to a thousand, structure matters as much as individual tests. Playwright Test organizes suites around fixtures: reusable, composable pieces of setup and teardown (a logged-in page, a seeded database, an API client, a page object) that tests request by name. Fixtures replace sprawling beforeEach hooks with explicit, typed dependencies, and Playwright only sets up what each test actually uses.
Combined with projects (browsers, devices, environments), isolation (a fresh browser context per test), and parallelism, fixtures let large suites stay readable, fast, and reliable.
TL;DR
- Built-in fixtures:
page,context,browser,request(API client),browserName, andtestInfo. - Create custom fixtures with
test.extend<{…}>(): setup beforeuse(), teardown after. - Test-scoped fixtures run per test; worker-scoped fixtures run once per worker process (expensive shared resources).
- Page Object Models encapsulate page interactions, and are often provided as fixtures.
- Projects in
playwright.config.tsrun the suite across browsers, devices, and setups (including auth setup projects). - Each test gets a fresh browser context (isolated cookies and storage); tests run in parallel across workers.
Quick Example
Core Concepts
Built-In Fixtures
Custom Fixtures
test.extend defines fixtures as async functions receiving other fixtures and a use callback:
Key properties:
- On demand: fixtures are only set up if the test (or another fixture it uses) requests them.
- Composable: fixtures depend on other fixtures, and Playwright resolves the graph.
- Typed: TypeScript generics give tests autocomplete and type checking.
- Options:
{ option: true }fixtures are configurable per project inplaywright.config.ts(for examplelocaleor a user role). { auto: true }: runs for every test automatically (for example capturing console errors or checking for leaked requests).
Test vs Worker Scope
Playwright runs tests in parallel worker processes. Worker-scoped fixtures ({ scope: 'worker' }) are created once per worker and shared by its tests, which suits expensive, read-only or isolatable resources: a service token, a per-worker database schema, a started mock server. Test-scoped fixtures give each test fresh state.
Page Object Models
A page object wraps a page's locators and actions behind a readable API (cartPage.checkout()), so tests describe intent, and selector changes happen in one place. Keep page objects thin (locators plus actions), keep assertions mostly in tests, and provide them via fixtures so tests don't construct them manually. Component objects for reusable widgets (date pickers, tables) follow the same idea. See design patterns.
Configuration and Projects
Projects run the same tests with different settings: browsers, devices, locales, or logged-in roles. Project dependencies run setup projects first, which is the recommended way to handle authentication. See Playwright authentication. webServer starts the app automatically.
Isolation and Parallelism
Every test gets a new browser context (like a fresh incognito profile), so cookies, localStorage, and sessions don't leak between tests. Tests in different files run in parallel across workers, and fullyParallel: true also parallelizes tests within a file. Use test.describe.configure({ mode: 'serial' }) only when tests genuinely depend on each other. Usually it's better to make them independent.
Hooks, Tags, and Annotations
beforeEach, afterEach, beforeAll, and afterAll still exist, but fixtures are usually cleaner. Tags ({ tag: '@smoke' }) with --grep @smoke run subsets. Annotations (test.skip, test.fixme, test.slow, test.fail) document known conditions.
Best Practices
Seed Data Through APIs, Not the UI
Create test data with API calls or database helpers in fixtures. It's much faster and more reliable than clicking through setup screens. Reserve UI interactions for the behavior under test.
Make Every Test Independent
Each test should create what it needs and not depend on order or leftover state. Independence enables parallelism, retries, and running a single test in isolation.
Keep Fixtures Small and Focused
One fixture per concern (an authenticated user, a seeded order, a page object) composes better than a giant "world" fixture. Name them after what they provide.
Use Unique Data per Test
Include unique suffixes (timestamps, worker index, random IDs) in created entities, so parallel tests don't collide on unique constraints or shared records.
Common Mistakes
Sharing State Between Tests Through Globals
Module-level variables set in one test and read in another break under parallel execution and retries. Pass state through fixtures.
Heavy beforeAll Setup With Test-Level Dependencies
Creating data in beforeAll and mutating it in tests causes order-dependent failures. Use per-test fixtures, or worker-scoped read-only data.
Page Objects Full of Assertions and Logic
Page objects that assert, branch, and retry become a second, untested codebase. Keep them to locators and simple actions, with expectations in tests.
FAQ
What is a fixture in Playwright?
A reusable unit of setup and teardown that tests declare as a parameter. Playwright creates it before the test (only if requested), passes the value in, and runs teardown afterwards. Built-in fixtures include page and request, and custom ones are defined with test.extend.
When should I use worker-scoped fixtures?
For expensive setup that can be shared safely by all tests in a worker process: authentication tokens, database schemas isolated per worker, mock servers, or compiled resources. Avoid them for mutable state that tests could interfere with.
Should I use the Page Object Model with Playwright?
It helps in larger suites, by centralizing locators and actions so tests read like user flows and UI changes are fixed in one place. Keep page objects lean, and inject them via fixtures. For small suites, direct locators in tests may be simpler.
How do I run tests across multiple browsers?
Define projects in playwright.config.ts using devices presets (Desktop Chrome, Firefox, Safari, mobile devices). Every test runs once per project, and --project=chromium runs a single one.
Related Topics
- Playwright — The testing framework overview
- Playwright Locators — Locators used in page objects
- Playwright Authentication — Setup projects and storage state
- Playwright in CI — Sharding and reporting
- Integration Testing — Seeding data and real dependencies
- pytest — Fixture-based testing in Python, a similar model