THURSDAY, JULY 9, 2026VOL. I NO. 1

THE PLAYWRIGHTPAD JOURNAL

Intelligent Automation News

Playwright Fixtures: Custom Fixtures Guide

Step-by-step guide on creating custom Playwright fixtures. Expose page objects, mock data, and environments across your test files.

PE
PlaywrightPad Editorial
2026-07-0310 min read
Playwright Architecture Matrix

playwright-v1-49-matrix

Advertisement

Playwright Fixtures: Custom Fixtures Guide

Fixtures establish clean environments for test executions. They encapsulate page objects, login states, and mock databases. This guide explains how to define and use custom fixtures in your test suite.

Introduction

Traditional testing frameworks rely on beforeEach and afterAll hooks to manage test environments. This pattern creates execution dependency and duplication. Playwright resolves this by introducing component fixtures.

Fixtures list dependencies explicitly. The runner analyzes these declarations, builds the environment dynamically, and cleans up assets automatically on completion.

The Playwright Fixtures Model

Playwright fixtures are lazy-loaded. The runner only compiles and boots environments requested by the test arguments:

MERMAID
graph TD
    TestRun["Start Test: test('name', ({ todoPage }) => ...)"] --> CheckDeps["Check: Does todoPage exist in registry?"]
    CheckDeps --> SetupFixture["Run Setup: page.goto('/todo') and mock APIs"]
    SetupFixture --> InjectTest["Inject todoPage instance into Test Function"]
    InjectTest --> RunTestBody["Execute Test Body Assertions"]
    RunTestBody --> TeardownFixture["Run Teardown: Reset cache & cookies"]
    TeardownFixture --> TestComplete["Test Completes Cleanly"]

This model reduces setup execution overhead.

Fixture Initialization Sequence

A custom fixture execution matches the following runtime sequence:

MERMAID
sequenceDiagram
    participant Runner as Test Runner
    participant Fixture as Custom Fixture (todoPage)
    participant Page as Browser Page

    Runner->>Fixture: Request fixture instance
    Fixture->>Page: Allocate fresh page context
    Fixture->>Page: Navigate to url & perform actions
    Fixture-->>Runner: Yield page instance to test
    Runner->>Runner: Execute test assertions
    Runner->>Fixture: Terminate test (post-yield)
    Fixture->>Page: Close page context and clean cache
    Fixture-->>Runner: Return teardown complete

Implementation Steps

Follow these steps to construct a custom page object fixture.

1. Define the Page Object Class

Create the page helper class pages/TodoPage.ts to encapsulate page actions:

TYPESCRIPT
import { Page, Locator } from '@playwright/test';

export class TodoPage {
  readonly page: Page;
  readonly todoInput: Locator;
  readonly todoItems: Locator;

  constructor(page: Page) {
    this.page = page;
    this.todoInput = page.getByPlaceholder('What needs to be done?');
    this.todoItems = page.getByRole('listitem');
  }

  async goto() {
    await this.page.goto('/todo');
  }

  async addTodo(text: string) {
    await this.todoInput.fill(text);
    await this.todoInput.press('Enter');
  }
}

2. Extend expect and test Settings

Create the extended test runner file fixtures/todoFixture.ts to register the class:

TYPESCRIPT
import { test as base } from '@playwright/test';
import { TodoPage } from '../pages/TodoPage';

// Declare custom fixture interfaces
type MyFixtures = {
  todoPage: TodoPage;
};

// Extend base test configuration with custom page fixture
export const test = base.extend<MyFixtures>({
  todoPage: async ({ page }, use) => {
    // 1. Setup phase: initialize class and navigate
    const todoPage = new TodoPage(page);
    await todoPage.goto();
    await todoPage.addTodo('Standard Task');

    // 2. Yield fixture instance to test execution
    await use(todoPage);

    // 3. Teardown phase: execute cleaning scripts
    await page.evaluate(() => localStorage.clear());
  },
});

export { expect } from '@playwright/test';

3. Implement in Test Files

Import your custom extended test script and use the fixture:

TYPESCRIPT
import { test, expect } from '../fixtures/todoFixture';

test('should add custom task items', async ({ todoPage }) => {
  // Use pre-populated fixture instance
  await todoPage.addTodo('Buy milk');

  // Assert item rendering
  await expect(todoPage.todoItems).toContainText(['Standard Task', 'Buy milk']);
});

Fixture Scope Comparison

The table below contrasts the execution life cycle scopes available in Playwright.

Scope LevelInitialization RateTeardown TriggerRecommended Use Case
Test ScopeOnce per test file runAfter individual test file exitsDefault page object setups
Worker ScopeOnce per worker processOn worker terminationDatabase configurations, browser contexts
Global ScopeOnce per test execution runAfter all tests finishServer spin-ups, global state builds

Best Practices for Custom Environments

💡 TIP
Keep worker-scoped fixtures stateless. Modifying worker-scoped state inside tests leads to execution dependencies.

Here are a few key practices:

  • Deconstruct fixtures cleanly: Write teardown actions after the use() statement to guarantee resource releases.
  • Compose fixtures: Chain fixtures together (e.g. inject an API client fixture into a page object fixture).
  • Enforce worker boundaries: Keep worker fixtures isolated to avoid cache leakage between threads.
  • Common Mistakes to Avoid

    ⚠️ WARNING
    Do NOT perform setup actions in custom fixtures without calling the use() yield hook. This blocks test runners.
    Bad PatternRecommended Alternative
    Nested beforeEach setups across filesDefine a common extended test fixture
    Modifying shared worker variablesKeep test states encapsulated in test-scoped files
    Forgetting to call use()Always yield the initialized instance

    Frequently Asked Questions

    What happens if I forget to export custom fixtures?

    The runner fails to resolve references, throwing undefined property errors during test suite parsing.

    Can I override standard fixtures like page?

    Yes. Extend the base runner and define custom logic under the page property key to override default settings.

    What is the difference between fixtures and hooks?

    Hooks run statically on every test file. Fixtures load dynamically, executing setups only when requested by test arguments.

    Can I use multiple custom fixtures in a single test?

    Yes. Declare the arguments list (e.g. { todoPage, apiHelper }) to run setup routines for both modules.

    How do I configure worker-scoped fixtures?

    Specify the scope parameter in the declaration block (e.g. { scope: 'worker' }).

    Are custom fixtures reusable across browser projects?

    Yes. The extension configurations execute across all projects (Chromium, WebKit, Firefox) defined in the main configuration.

    How do I access browser credentials inside fixtures?

    Expose saved credentials files within the setup properties and import them inside context definitions.

    Can I use page object models inside fixtures?

    Yes. Page object initialization is the primary use case for custom test fixtures.

    How do I pass configurations to worker fixtures?

    Inject environment variables or read global configuration parameters during worker setup.

    Does teardown execute on test failures?

    Yes. Playwright guarantees that statements written after the use() line run even if tests crash.

    Summary

    Custom fixtures improve test speed and readability. Structuring environments in a modular format simplifies test suite maintenance.

    Related Articles

  • Playwright Installation Complete Tutorial Guide
  • Mastering Playwright Locators & Selectors
  • Playwright Assertions: Complete Guide
  • Playwright Authentication: Reusing Logged-In State in 2026
  • #playwright#fixtures#custom#testing
    Advertisement

    About The Author

    PlaywrightPad Editorial

    PlaywrightPad Editorial reports on Chromium engines, E2E test optimizations, and AI integration specifications.

    Newsletter

    Get weekly browser reports sent directly to your inbox.

    Advertisement