Fixtures in Playwright

Fixtures are one of the most powerful features in Playwright. They help create, manage, and share test resources across multiple test cases, making your test code cleaner, reusable, and easier to maintain.

Instead of repeatedly writing setup and teardown logic in every test, fixtures allow you to define it once and reuse it throughout your test suite.


What is a Fixture?

A fixture is a predefined resource or setup that is automatically provided to your test before execution and cleaned up afterward.

Examples of fixtures include:

  • Browser instances
  • Browser contexts
  • Pages
  • Authenticated users
  • Database connections
  • API clients
  • Test data

Why Use Fixtures?

  • Reduce code duplication
  • Improve test readability
  • Centralize setup logic
  • Simplify maintenance
  • Provide reusable resources
  • Enable test isolation

Built-in Fixtures in Playwright

Playwright provides several built-in fixtures that are automatically available in test files.

Fixture Description
browser Browser instance
context Browser context
page Browser page/tab
request API request context
browserName Current browser name

Using the Page Fixture

The page fixture is the most commonly used fixture.

import { test, expect } from '@playwright/test';

test('Homepage Test', async ({ page }) => {

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

  await expect(page)
    .toHaveTitle(/Example/);

});

Playwright automatically creates and closes the page for the test.


Using the Browser Fixture

import { test } from '@playwright/test';

test('Browser Fixture Example',
async ({ browser }) => {

  const page =
    await browser.newPage();

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

});

Using the Context Fixture

A browser context represents an isolated browser session.

test('Context Fixture Example',
async ({ context }) => {

  const page =
    await context.newPage();

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

});

Using the Request Fixture

The request fixture is used for API testing.

test('API Test',
async ({ request }) => {

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

  expect(response.status())
    .toBe(200);

});

Creating Custom Fixtures

Playwright allows you to create your own fixtures using test.extend().

import { test as base }
from '@playwright/test';

export const test =
base.extend({

  username: async ({}, use) => {

    const user = 'admin';

    await use(user);

  }

});

Using a Custom Fixture

import { test }
from './fixtures';

test('Custom Fixture Example',
async ({ username }) => {

  console.log(username);

});

Output:

admin

Fixture Lifecycle

Every fixture follows a lifecycle:

  1. Setup
  2. Provide resource to test
  3. Execute test
  4. Cleanup

Example:

const test = base.extend({

  dbConnection:
  async ({}, use) => {

    const connection =
      await connectDB();

    await use(connection);

    await connection.close();

  }

});

Worker Fixtures

Worker fixtures are created once per worker process and shared among tests.

Useful for expensive resources such as:

  • Database connections
  • Authentication tokens
  • External services
export const test =
base.extend({

  apiToken: [
    async ({}, use) => {

      const token =
        await generateToken();

      await use(token);

    },

    { scope: 'worker' }

  ]

});

Test Fixtures vs Worker Fixtures

Feature Test Fixture Worker Fixture
Created Per Test Per Worker
Isolation High Shared
Performance Slower Faster
Use Case Test Data Shared Resources

Authenticated User Fixture

One common use case is creating a reusable logged-in user fixture.

export const test =
base.extend({

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

    await page.goto('/login');

    await page.fill(
      '#username',
      'admin'
    );

    await page.fill(
      '#password',
      'password'
    );

    await page.click(
      '#loginBtn'
    );

    await use(page);

  }

});

Using Authenticated Fixture

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

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

});

Fixture Dependency

Fixtures can depend on other fixtures.

export const test =
base.extend({

  user:
  async ({}, use) => {

    const user = {
      name: 'John'
    };

    await use(user);

  },

  profile:
  async ({ user }, use) => {

    const profile = {
      username: user.name
    };

    await use(profile);

  }

});

Using Multiple Fixtures

test('Multiple Fixtures',
async ({
  page,
  username,
  apiToken
}) => {

  console.log(username);

  console.log(apiToken);

});

Fixture File Structure

project/

├── tests/
│   ├── login.spec.js
│   ├── dashboard.spec.js
│
├── fixtures/
│   └── customFixtures.js
│
├── playwright.config.js
│
└── package.json

Practical Example

Custom fixture for test data creation.

export const test =
base.extend({

  testUser:
  async ({}, use) => {

    const user = {
      username: 'john',
      password: 'password123'
    };

    await use(user);

  }

});

Usage:

test('Login Test',
async ({ testUser }) => {

  console.log(
    testUser.username
  );

});

Advantages of Fixtures

  • Reusable setup logic
  • Cleaner test code
  • Improved maintainability
  • Better test isolation
  • Centralized resource management
  • Reduced duplication

Common Use Cases

  • User authentication
  • Database setup
  • API clients
  • Test data generation
  • Environment configuration
  • Browser initialization

Best Practices

  1. Keep fixtures focused on a single responsibility.
  2. Use custom fixtures for reusable logic.
  3. Prefer worker fixtures for expensive resources.
  4. Clean up resources properly.
  5. Avoid unnecessary fixture dependencies.
  6. Keep fixture names meaningful and descriptive.
  7. Store fixtures in a dedicated fixtures folder.

Commonly Used Fixtures

Fixture Purpose
page Browser page operations
browser Browser instance access
context Browser session management
request API testing
loggedInPage Authenticated user session
apiToken Reusable API authentication

Conclusion

Fixtures are a core feature of Playwright that help organize setup and teardown operations efficiently. By using built-in and custom fixtures, teams can create cleaner, reusable, and scalable test automation frameworks. Proper fixture design improves maintainability, reduces code duplication, and enables robust end-to-end testing across large projects.

```