SoftwareTestPilot
Automation TestingPublished: 11 min read

Playwright Fixtures Deep Dive — Test Setup Done Right (2026)

Master Playwright fixtures: worker-scoped auth, per-test data seeding, custom pages, and the fixture composition patterns that eliminate beforeEach boilerplate.

Avinash K
Founder & QA Engineer at SoftwareTestPilot
Share:XLinkedInWhatsApp
Playwright fixtures deep dive — worker-scoped auth and data seeding diagram.
Playwright fixtures deep dive — worker-scoped auth and data seeding diagram.

Last updated 2026-07-20 · 11 min read · By Avinash K

beforeEach hooks are Playwright's biggest smell. Fixtures replace them with typed, composable, worker-scoped setup that runs faster and reads cleaner. Once your team internalizes fixtures, PR reviews get 30% shorter because setup is centralized.

Key takeaways

  • The 3 fixture scopes (test, worker, project) and when to use each.
  • Worker-scoped auth: sign in once per worker, save 90% of setup time.
  • Fixture composition — extend from base to feature suites.
  • Fixture teardown for cleanup that always runs.

1. Three fixture scopes

ScopeLifetimeUse for
testper testfresh data, page objects
workerper worker processauth state, DB pools, browser context
projectper test projectshared config, feature flags

2. A custom fixture from scratch

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

type MyFixtures = { loginPage: LoginPage; seededOrder: { id: string } };

export const test = base.extend<MyFixtures>({
  loginPage: async ({ page }, use) => {
    await use(new LoginPage(page));
  },
  seededOrder: async ({ request }, use) => {
    const order = await request.post('/api/orders', { data: { total: 100 } });
    await use(await order.json());
    await request.delete('/api/orders/' + (await order.json()).id);
  },
});
export { expect } from '@playwright/test';

Now every test that needs a seeded order gets one — and cleanup runs automatically after the test.

3. Worker-scoped auth — the 10x speedup

// auth.setup.ts — runs once, produces storageState.json
setup('authenticate', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.TEST_USER);
  await page.getByLabel('Password').fill(process.env.TEST_PASS);
  await page.getByRole('button', { name: 'Sign in' }).click();
  await page.context().storageState({ path: 'auth.json' });
});

// playwright.config.ts
projects: [
  { name: 'setup', testMatch: /auth\.setup\.ts/ },
  {
    name: 'chromium',
    use: { storageState: 'auth.json' },
    dependencies: ['setup'],
  },
];

A 500-test suite that used to log in 500 times now logs in once per worker. Real numbers from our stack: 42 min → 8 min. See the official auth guide.

4. Composing fixtures across suites

Build a base fixture with common setup, then extend per feature: const paymentTest = test.extend({ stripeMock: ... }). Compose don't duplicate. See our POM complete guide for how fixtures + POM together replace 90% of beforeEach.

5. Teardown that always runs

Everything after await use(value) is teardown. Even if the test fails, it runs. Use this for DB cleanup, feature-flag reset, and file deletes. Cross-reference the Playwright interview questions hub — fixture teardown is asked at 60% of senior SDET interviews.

Frequently asked questions

1.Should I use beforeEach or fixtures?
Fixtures — they are typed, composable, and scoped. beforeEach is fine for one-off setup within a single spec.
2.Can a worker-scoped fixture depend on a test-scoped one?
No — scopes only flow downward. Worker fixtures can only use worker or project fixtures.
3.How many fixtures is too many?
If a single test file imports more than 6 custom fixtures, split the file. Fixtures should be feature-cohesive.
4.Do fixtures work with parallel workers?
Yes — worker-scoped fixtures run once per worker. Use this for expensive setup like Docker containers.