Fixtures & Factories

Centralize setup so tests start from a known state without copy-paste.

  • unit
  • e2e
  • fixtures

Problem

The same setup — a logged-in user, a seeded cart, a configured client — is copy-pasted across files. It drifts over time and each test re-implements teardown.

Solution

Use the framework’s fixture mechanism to declare setup once and inject it where needed, with automatic teardown. A factory creates fresh, independent instances per test so there is no shared mutable state.

Compose fixtures: a "logged-in page" fixture builds on a "page" fixture, keeping each piece small and reusable.

Snippet

A Playwright fixture that provides a logged-in page
export const test = base.extend<{ authedPage: Page }>({
  authedPage: async ({ page }, use) => {
    await new LoginPage(page).login('user@example.com', 'secret');
    await use(page); // teardown (if any) runs after the test
  },
});

test('shows the dashboard', async ({ authedPage }) => {
  await expect(authedPage.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});

Pitfalls

  • A shared fixture instance mutated across tests — defeats isolation. Build fresh per test.
  • Heavy fixtures that do far more than a test needs, slowing every run.
  • Forgetting teardown, leaking resources (open handles, seeded rows) between tests.