Building Custom Fixtures for Your App

Custom fixtures let you encapsulate application-specific setup such as logging in, seeding data or configuring feature flags. Done well, they keep tests concise and expressive while centralising complex setup logic.

Creating Custom Fixtures for Your Application

You can extend the base test with new fixtures that build on existing ones. For example, an authPage fixture can log in once and provide an authenticated page object to tests.

// fixtures/auth.ts
import { test as base } from '@playwright/test';

export type AuthFixtures = {
  authPage: import('@playwright/test').Page;
};

export const test = base.extend({
  authPage: async ({ page }, use) => {
    await page.goto('https://demo.myshop.com/login');
    await page.getByRole('textbox', { name: 'Email' }).fill('user@example.com');
    await page.getByRole('textbox', { name: 'Password' }).fill('SuperSecret123');
    await page.getByRole('button', { name: 'Sign in' }).click();
    await use(page);
  },
});
// auth-tests.spec.ts
import { expect } from '@playwright/test';
import { test } from './fixtures/auth';

test('accesses a protected page using auth fixture', async ({ authPage }) => {
  await authPage.goto('https://demo.myshop.com/orders');
  await expect(authPage.getByRole('heading', { name: 'Your orders' })).toBeVisible();
});
Note: Custom fixtures can depend on other fixtures (such as page) and reuse them rather than re-creating browser contexts manually.
Tip: Keep fixture responsibilities narrow: one for authentication, another for seeded data, another for configuration flags, rather than a single “do everything” fixture.
Warning: Avoid hiding assertions inside fixtures; they should prepare state, not silently verify behaviour.

By centralising authentication flows and other repeated setup, you reduce duplication while keeping tests focused on their specific behaviours.

Common Mistakes

Mistake 1 β€” Embedding too much logic in a single mega-fixture

This becomes hard to reason about.

❌ Wrong: One fixture that logs in, seeds data, toggles flags and manipulates the UI in many ways.

βœ… Correct: Compose several smaller fixtures and use only what each test needs.

Mistake 2 β€” Making fixtures depend on global mutable state

This hurts isolation.

❌ Wrong: Using out-of-band globals to share data between fixtures.

βœ… Correct: Pass data through fixture parameters or configuration instead.

🧠 Test Yourself

What is a good use of custom Playwright fixtures?