Playwright Test Fixtures Explained – Complete Beginner Guide with TypeScript Examples & Custom Fixtures (2026 Guide)

Introduction: Why Fixtures Are Important in Playwright Automation

As Playwright automation projects grow, one common challenge is avoiding duplicate setup code.

Imagine writing the same steps in every test:

  • Launch browser
  • Open application
  • Login
  • Create test data
  • Navigate to dashboard

Repeating this code across hundreds of test files makes the framework difficult to maintain.

This is where Playwright Test Fixtures become one of the most powerful features of the Playwright Test Runner.

Fixtures help you create reusable setup and cleanup logic, making your tests cleaner, more maintainable, and easier to scale.

Whether you are:

  • QA Automation Engineer
  • SDET
  • Selenium Engineer transitioning to Playwright
  • Software Testing Student
  • Developer
  • Interview Candidate

Understanding Playwright Test Fixtures is essential for building enterprise-grade automation frameworks.

In this guide, you’ll learn:

  • What Playwright Test Fixtures are
  • How Playwright fixtures work
  • Built-in fixtures
  • Custom fixtures
  • Test scope vs worker scope
  • TypeScript examples
  • Best practices
  • Interview questions
  • FAQs

Let’s begin.


What Are Playwright Test Fixtures?

A fixture is reusable setup and cleanup code that Playwright automatically provides to your tests.

Instead of manually creating objects inside every test, Playwright injects them when needed.

Simple Definition

Playwright Test Fixtures are reusable resources that prepare the environment before a test runs and clean it up after the test completes.

Fixtures reduce duplication and improve readability.


Why Fixtures Are Important

Without fixtures, every test contains repeated setup code.

Example:

test(‘Login Test’, async ({ page }) => {

    await page.goto(‘https://example.com’);

    // login

});

Another test:

test(‘Profile Test’, async ({ page }) => {

    await page.goto(‘https://example.com’);

    // login again

});

The same logic appears repeatedly.

Fixtures allow you to write the setup once and reuse it everywhere.

Benefits include:

  • Better code reuse
  • Easier maintenance
  • Test isolation
  • Cleaner test files
  • Enterprise-ready frameworks

How Playwright Fixtures Work

Every Playwright test automatically receives fixtures.

Example:

test(‘Example’, async ({ page }) => {

});

The page object is already created by Playwright.

Execution flow:

Playwright Test Runner

Create Fixtures

Run Test

Dispose Fixtures

Next Test

The Test Runner manages the entire lifecycle automatically.


Built-in Fixtures Explained

Playwright provides several built-in fixtures.

1. page

The most commonly used fixture.

test(‘Home Page’, async ({ page }) => {

    await page.goto(‘https://example.com’);

});

Used for browser interactions.


2. browser

Represents the browser instance.

test(‘Browser Example’, async ({ browser }) => {

    const context = await browser.newContext();

});

Useful for creating additional Browser Contexts.


3. context

Represents the Browser Context.

test(‘Context Example’, async ({ context }) => {

    const page = await context.newPage();

});

Each context provides isolated cookies, storage, and permissions.


4. request

Used for API testing.

test(‘API Example’, async ({ request }) => {

    const response = await request.get(‘/users’);

});

This fixture is commonly used for backend validation.


5. browserName

Returns the current browser.

test(‘Browser Name’, async ({ browserName }) => {

    console.log(browserName);

});

Useful for cross-browser testing.


Built-in Fixture Summary

FixturePurposeTypical Use
pageBrowser pageUI testing
browserBrowser instanceCreate contexts
contextBrowser ContextSession isolation
requestAPI clientAPI testing
browserNameBrowser identifierCross-browser logic

How Playwright Fixtures Work Internally

When a test starts:

Playwright Test

Fixture Created

Injected into Test

Test Executes

Fixture Cleanup

You don’t need to manually create or destroy these resources.


Test Scope vs Worker Scope Fixtures

Understanding fixture scope is important for framework design.

Test-Scoped Fixture

Created for every test.

Test 1

New Fixture

Disposed

Test 2

New Fixture

Advantages:

  • Complete isolation
  • Parallel execution
  • No shared state

Worker-Scoped Fixture

Created once per worker process.

Worker Starts

Fixture Created

Test 1

Test 2

Test 3

Fixture Destroyed

Advantages:

  • Faster execution
  • Shared expensive resources
  • Reduced setup time

Worker scope is useful for database connections or shared services.


Creating Custom Fixtures

One of the biggest advantages of Playwright is test.extend().

Example:

import { test as base } from ‘@playwright/test’;

export const test = base.extend({

    loggedInPage: async ({ page }, use) => {

        await page.goto(‘https://example.com/login’);

        await page.fill(‘#username’, ‘admin’);

        await page.fill(‘#password’, ‘admin123’);

        await page.click(‘button’);

        await use(page);

    }

});

This custom fixture performs login before every test.


Using Custom Fixtures

import { test } from ‘./fixtures’;

test(‘Dashboard Test’, async ({ loggedInPage }) => {

    await loggedInPage.goto(‘/dashboard’);

});

Notice how the login code disappears from the test.

This makes tests:

  • Cleaner
  • Smaller
  • Easier to maintain

Real-World Playwright Fixture Example (TypeScript)

import { test as base, expect } from ‘@playwright/test’;

type MyFixtures = {

  dashboardPage: import(‘@playwright/test’).Page;

};

export const test = base.extend<MyFixtures>({

  dashboardPage: async ({ page }, use) => {

    await page.goto(‘https://example.com/login’);

    await page.getByLabel(‘Username’).fill(‘admin’);

    await page.getByLabel(‘Password’).fill(‘admin123’);

    await page.getByRole(‘button’, { name: ‘Login’ }).click();

    await expect(page).toHaveURL(/dashboard/);

    await use(page);

  }

});

test(‘Verify Dashboard’, async ({ dashboardPage }) => {

  await expect(

    dashboardPage.getByRole(‘heading’, { name: ‘Dashboard’ })

  ).toBeVisible();

});

What Happens?

  1. Fixture launches login page.
  2. User logs in automatically.
  3. Dashboard opens.
  4. Test begins.
  5. Fixture cleans up after execution.

Fixture Lifecycle Diagram

Playwright Test Runner

        │

        ▼

Create Fixture

        │

        ▼

Fixture Setup

        │

        ▼

Execute Test

        │

        ▼

Fixture Cleanup

        │

        ▼

Next Test

This automatic lifecycle helps keep tests isolated and maintainable.

Enterprise Authentication Fixture Example

Large automation projects rarely perform login inside every test. Instead, they create reusable authentication fixtures.

Without fixtures:

Test 1

Open Browser

Login

Execute Test

Logout

——————-

Test 2

Open Browser

Login Again

Execute Test

The same login steps are repeated in every test.

With Playwright fixtures:

Authentication Fixture

Login Once

Provide Logged-in Page

Run Test

Automatic Cleanup

This reduces duplicate code and keeps tests focused on business logic.


Enterprise Framework Folder Structure

A well-organized Playwright framework separates fixtures from page objects, utilities, and test data.

playwright-framework/

├── tests/

│     ├── login.spec.ts

│     ├── dashboard.spec.ts

│     └── orders.spec.ts

├── pages/

│     ├── LoginPage.ts

│     ├── DashboardPage.ts

│     └── OrdersPage.ts

├── fixtures/

│     ├── auth.fixture.ts

│     ├── api.fixture.ts

│     └── database.fixture.ts

├── utils/

│     ├── config.ts

│     ├── helpers.ts

│     └── logger.ts

├── test-data/

│     ├── users.json

│     └── products.json

├── playwright.config.ts

└── package.json

This structure makes enterprise automation frameworks easier to maintain and scale.


Enterprise Login Fixture Example

Instead of writing login steps inside every test, create one reusable fixture.

auth.fixture.ts

import { test as base, expect } from ‘@playwright/test’;

type AuthFixtures = {

    authenticatedPage: import(‘@playwright/test’).Page;

};

export const test = base.extend<AuthFixtures>({

    authenticatedPage: async ({ page }, use) => {

        await page.goto(‘https://example.com/login’);

        await page.getByLabel(‘Username’).fill(‘admin’);

        await page.getByLabel(‘Password’).fill(‘admin123’);

        await page.getByRole(‘button’, {

            name: ‘Login’

        }).click();

        await expect(page).toHaveURL(/dashboard/);

        await use(page);

    }

});


dashboard.spec.ts

import { test, expect } from ‘../fixtures/auth.fixture’;

test(‘Dashboard Test’, async ({ authenticatedPage }) => {

    await expect(

        authenticatedPage.getByText(‘Dashboard’)

    ).toBeVisible();

});

Notice how the test only contains business validation. Login is completely reusable.


Why Enterprises Use Fixtures

Playwright Fixtures help enterprise teams by:

  • Eliminating repeated setup code
  • Reducing maintenance effort
  • Supporting parallel execution
  • Improving test isolation
  • Standardizing framework design
  • Simplifying onboarding for new automation engineers

Best Practices for Using Fixtures

1. Keep Fixtures Small

A fixture should perform one responsibility only.

Good examples:

  • Login
  • API client setup
  • Database connection
  • Test data preparation

Avoid combining unrelated logic into a single fixture.


2. Prefer Test-Scoped Fixtures

Test-scoped fixtures provide better isolation and reduce the risk of tests affecting each other.


3. Use Worker Scope Carefully

Worker-scoped fixtures are ideal for expensive resources such as:

  • Database connections
  • Shared API clients
  • Large datasets

Do not store test-specific state in worker fixtures.


4. Combine Fixtures with Page Object Model

Fixtures should create reusable objects, while Page Objects should contain page interactions.

Example:

authenticatedPage

DashboardPage

Dashboard Tests

This separation keeps the framework modular.


5. Use Meaningful Fixture Names

Prefer descriptive names such as:

  • authenticatedPage
  • adminUser
  • apiClient
  • databaseConnection

Instead of generic names like:

  • page1
  • fixture

6. Clean Up Resources

Always release resources after the test finishes.

Playwright automatically disposes built-in fixtures, but custom fixtures should also clean up any external resources if needed.


Common Mistakes to Avoid

Mistake 1: Writing Large Fixtures

Avoid creating fixtures that perform multiple unrelated tasks.


Mistake 2: Repeating Login Logic

Login should exist in one reusable fixture rather than in every test file.


Mistake 3: Ignoring Fixture Scope

Using worker-scoped fixtures for test-specific data can lead to shared state and unpredictable failures.


Mistake 4: Mixing Business Logic with Fixtures

Fixtures should prepare the environment, not verify application behavior.

Assertions belong in test cases.


Mistake 5: Not Reusing Fixtures

If the same setup appears in multiple tests, consider moving it into a fixture.


Troubleshooting Tips

Fixture Not Running

Check that the test imports the extended test object instead of the default Playwright test.


Login Happens Multiple Times

Review the fixture scope. A test-scoped fixture runs for every test, while a worker-scoped fixture runs once per worker.


Fixture Timeout

Ensure that setup actions, such as login or API initialization, complete within the configured timeout.


Shared State Between Tests

Avoid storing mutable data in worker-scoped fixtures unless it is intentionally shared.


Playwright Fixtures Interview Questions

1. What are Playwright Test Fixtures?

Answer:
Fixtures are reusable setup and cleanup resources that Playwright automatically injects into tests.


2. Why are fixtures useful?

Answer:
They improve code reuse, reduce duplication, simplify maintenance, and support test isolation.


3. Name some built-in Playwright fixtures.

Answer:

  • page
  • browser
  • context
  • request
  • browserName

4. What is a custom fixture?

Answer:
A reusable fixture created using test.extend() to provide project-specific setup logic.


5. How do you create a custom fixture?

Answer:

Use:

const test = base.extend({

});


6. What is the difference between test-scoped and worker-scoped fixtures?

Answer:

  • Test scope creates a new fixture for every test.
  • Worker scope creates one fixture shared across tests running in the same worker.

7. Why should login be implemented as a fixture?

Answer:
It removes duplicate code and ensures consistent authentication across tests.


8. Can fixtures improve parallel execution?

Answer:
Yes. Proper fixture design supports isolated execution and efficient resource management.


9. How do fixtures improve maintainability?

Answer:
Changes to setup logic are made in one place rather than across multiple test files.


10. What is test.extend()?

Answer:
It is the Playwright API used to define custom fixtures.


Frequently Asked Questions (FAQs)

What are Playwright Test Fixtures?

Playwright Test Fixtures are reusable setup and cleanup resources automatically provided to test cases.


Are Playwright fixtures suitable for beginners?

Yes. Beginners can start with built-in fixtures like page and gradually create custom fixtures using test.extend().


What is the difference between built-in and custom fixtures?

Built-in fixtures are provided by Playwright, while custom fixtures are created by developers for reusable project-specific functionality.


When should I use worker-scoped fixtures?

Use them for expensive shared resources such as database connections or API clients that do not need to be recreated for every test.


Do fixtures improve test isolation?

Yes. Test-scoped fixtures create fresh resources for each test, preventing interference between tests.


Can fixtures work with the Page Object Model?

Yes. Fixtures and Page Objects complement each other, making automation frameworks cleaner and easier to maintain.

Leave a Comment

Your email address will not be published. Required fields are marked *