AssertAssert
Guide

Playwright Fixtures: Reusable Setup Without the Boilerplate

Playwright fixtures are the framework's answer to shared test setup. Instead of copying the same beforeEach login block into every test file, you define a fixture once and Playwright handles the setup and teardown automatically. Used well, fixtures eliminate the most repetitive parts of test authoring without hiding what is happening.

What fixtures are and why they matter

A Playwright fixture is a named piece of test context — a logged-in page, a pre-seeded database record, an authenticated API client — that is automatically set up before your test and torn down afterwards. Fixtures are defined once in a test helper file and composed into any test that needs them.

The built-in fixtures you already use are page, browser, context, and request. Custom fixtures extend this system. A loggedInPage fixture that handles authentication can be used by any test that needs an authenticated session, with zero boilerplate in the test itself.

  • Fixtures scope to test, worker, or the entire test run
  • Setup and teardown are automatic — no manual cleanup required
  • Fixtures compose — a loggedInPage fixture can use the page fixture internally
  • Test code only sees what it asks for — clean, readable test bodies

Writing your first custom fixture

Custom fixtures are created by extending Playwright's test object with a new property. The fixture function receives other fixtures and a use callback — it does setup, calls use(), and then does teardown. Tests that include the fixture name in their argument list get the fixture automatically.

A common pattern is an authenticatedPage fixture that logs in via the API (bypassing the UI for speed), sets the session cookie, and returns a ready page object. Tests that need an authenticated session just list it as an argument — no beforeEach, no login steps in the test body.

When fixtures help and when they obscure

Fixtures are most valuable when setup is repeated frequently and is genuinely unrelated to what the test is testing. Authentication is the canonical example: a checkout test should not need to also test login. The fixture handles auth so the test can focus on checkout.

Fixtures can obscure intent when they set up state that is actually relevant to the test. A fixture that seeds a specific product into the database and also navigates to it hides two things the test might need to verify. Prefer fixtures that handle infrastructure concerns (auth, database seeding) and let the test control application navigation.

Custom authenticated page fixture
// fixtures.ts
import { test as base } from '@playwright/test';

export const test = base.extend({
  authenticatedPage: async ({ page, request }, use) => {
    // Authenticate via API — faster than UI login
    const response = await request.post('/api/auth/login', {
      data: { email: 'test@example.com', password: process.env.TEST_PASSWORD },
    });
    const { token } = await response.json();
    await page.context().addCookies([{
      name: 'session', value: token, domain: 'localhost', path: '/',
    }]);
    await use(page);
  },
});

// my-test.spec.ts
import { test } from './fixtures';

test('user can view their order history', async ({ authenticatedPage }) => {
  await authenticatedPage.goto('/account/orders');
  await expect(authenticatedPage.getByRole('heading', { name: 'Order History' })).toBeVisible();
});

FAQ

What is the difference between fixtures and beforeEach in Playwright?

beforeEach runs setup code before every test in a file. Fixtures are composable, reusable, and scoped — they can be shared across files, composed with other fixtures, and scoped to a test, worker, or entire run. For anything you use in more than one test file, a fixture is almost always the better choice.

Can Playwright fixtures be shared across multiple test files?

Yes. Define your custom fixtures in a separate file, export the extended test object, and import it in each test file that needs those fixtures. This is the standard pattern for sharing auth, database connections, or API clients across a large test suite.

What scope should I use for Playwright fixtures?

Use test scope (the default) for anything that needs to be fresh per test — page objects, application state. Use worker scope for expensive setup that can safely be shared across tests in the same worker, like database connections or API clients. Avoid mixing state between tests even in worker-scoped fixtures.

Put the workflow in your repo, not in a chat transcript

Assert is strongest when scenarios become durable project assets: readable Markdown in the repo, generated execution underneath, and result inspection in the dashboard.