Back to blog
    Playwright
    Fixtures
    Test Automation

    Playwright Fixtures: A Complete Guide with Working Examples

    Playwright fixtures explained — what they are, how they differ from before/after hooks, custom fixture patterns, the page object model, and how to inject test data cleanly.

    · 10 min read

    Fixtures are the most powerful concept in Playwright — and the most under-used. Understand them and your test suite becomes 50% smaller, dramatically more readable, and much easier to maintain. This guide covers what they are, how to write your own, and the patterns that scale.

    What is a fixture in Playwright?

    A fixture is a piece of test setup that Playwright provides to your test on demand. The built-in `page`, `browser`, `context`, and `request` parameters you destructure in every `test(...)` call are all fixtures.

    Playwright sets them up before your test runs, tears them down after, and shares the heavy ones (like `browser`) across tests for performance. You can write your own.

    Fixtures vs beforeEach / afterEach hooks

    Hooks are imperative and global. Fixtures are declarative and per-test. If a test does not destructure a fixture, the setup does not run for that test — automatic, no `if` statements.

    Hooks also share state through closure variables, which gets messy. Fixtures give each test its own value and clean up automatically.

    Writing a custom fixture

    Extend Playwright's base `test`: `export const test = base.extend<{ loggedInPage: Page }>({ loggedInPage: async ({ page }, use) => { await page.goto('/login'); await page.fill('#email', '...'); await page.fill('#password', '...'); await page.click('button[type=submit]'); await use(page); } });`

    Now any test that destructures `{ loggedInPage }` gets a logged-in browser page automatically. Tests that do not, do not pay the cost.

    How to use fixtures for test data

    The cleanest pattern: write a `testData` fixture that fetches the credentials, IDs, or payloads your test needs from a hosted catalog at runtime.

    With TestAssets: `testData: async ({}, use) => { const res = await fetch('https://api.testassets.io/assets?list=staging-logins', { headers: { apikey: process.env.TESTASSETS_KEY } }); await use(await res.json()); }`. Now no test in your suite has hardcoded credentials, and rotating staging passwords is a single change in one place.

    Page Object Model with fixtures

    POM in Playwright is just a fixture pattern. Define a `LoginPage` class with methods like `goto()` and `login(email, password)`. Expose it as a fixture: `loginPage: async ({ page }, use) => { await use(new LoginPage(page)); }`.

    Tests then read like English: `await loginPage.login(creds.email, creds.password);`. The page object encapsulates selectors; the fixture handles wiring; the test asserts behaviour.

    Worker-scoped fixtures

    By default fixtures are per-test. Mark a fixture as `{ scope: 'worker' }` and it is created once per parallel worker — perfect for expensive setup like spinning up a database, seeding a tenant, or fetching an OAuth token.

    Frequently asked questions

    We use essential cookies for core functionality and optional cookies to improve your experience. Learn more about our privacy policy