← Back to libraryQuestion 170 of 273
🎭PlaywrightIntermediate

Test Fixtures

📌 Definition:

Fixtures are Playwright's dependency-injection mechanism for setting up and tearing down the environment a test needs (page, context, request, or your own custom fixtures). A test declares the fixtures it wants as function arguments and Playwright builds exactly those, in the right order, per test.

📖 Detailed Explanation:

Built-in fixtures include page, context, browser, browserName and request. Playwright creates a fresh browser context and page for each test by default, which is why tests are isolated without manual cleanup. You can define custom fixtures to encapsulate setup — a logged-in page, seeded test data, a configured API client — so tests stay focused on behavior. Fixtures have scopes: 'test' (rebuilt per test) or 'worker' (shared across tests in a worker process, good for expensive one-time setup like a DB connection). Fixtures compose (one can depend on another) and run teardown after the yield/await, replacing scattered beforeEach/afterEach hooks with reusable, typed building blocks.

🔑 Key Points:
  • Declared as test arguments; Playwright injects only what a test asks for
  • Built-ins: page, context, browser, request, browserName — page/context are per-test by default
  • Custom fixtures encapsulate setup (authenticated page, seeded data, API client)
  • Scopes: 'test' (per test) vs 'worker' (shared across a worker for expensive setup)
  • Fixtures compose and include teardown, replacing repetitive beforeEach/afterEach
🌍 Real-World Example:

Twenty tests each need a logged-in admin. Instead of repeating login steps in every beforeEach, you define an adminPage fixture that logs in once (via storageState) and yields a ready page. Tests just declare async ({ adminPage }) and start asserting business logic — setup lives in one typed, reusable place.

📎 Code Example:
// fixtures.ts — a custom authenticated-page fixture
import { test as base } from '@playwright/test';

type Fixtures = { adminPage: import('@playwright/test').Page };

export const test = base.extend<Fixtures>({
  adminPage: async ({ browser }, use) => {
    const context = await browser.newContext({ storageState: 'auth/admin.json' });
    const page = await context.newPage();
    await use(page);            // hand the page to the test
    await context.close();      // teardown after the test
  },
});

// a test consuming it
test('admin can open settings', async ({ adminPage }) => {
  await adminPage.goto('/settings');
  await expect(adminPage.getByRole('heading', { name: 'Settings' })).toBeVisible();
});
🎯 Scenario-Based Interview Question:

Scenario: Your suite repeats the same 8-line login in beforeEach across five spec files. It's slow and when the login flow changes you edit it in five places. How do fixtures solve this, and how do you avoid re-logging-in for every single test?