YouTip LogoYouTip

Playwright Fixtures

Playwright Fixtures and Parameterized Testing |

This chapter introduces Playwright’s Fixture mechanism, how to create custom Fixtures, and techniques for parameterized testing.


What is a Fixture?

A Fixture is Playwright’s dependency injection mechanism for test function parameters.

Each test retrieves the required Fixture(s) via destructuring (e.g., { page }), and Playwright handles creation and cleanup automatically.

Example

// `page` and `context` are built-in Fixtures

test('Using built-in Fixtures', async ({ page, context }) => {

  // Playwright automatically creates `page` and `context`

  // Automatically cleans up after the test completes

  await page.goto('');

});

Built-in Fixtures Reference

Fixture Type Description
page Page An independent browser tab
context BrowserContext Browser context
browser Browser Browser instance
browserName string Name of the current browser
request APIRequestContext HTTP request client

Custom Fixtures

Use test.extend() to create custom Fixtures, enabling injection of additional dependencies or overriding built-in Fixtures.

Example

// File path: tests/fixtures.ts

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

// Define types for custom Fixtures
type MyFixtures = {
  todoPage: { goto: () => Promise; addTodo: (text: string) => Promise };
  authenticatedPage: any;
};

export const test = base.extend({
  // Custom Fixture: Todo page object
  todoPage: async ({ page }, use) => {
    // Create
    const todoPage = {
      goto: async () => {
        await page.goto('https://demo.playwright.dev/todomvc');
      },
      addTodo: async (text: string) => {
        await page.getByPlaceholder('What needs to be done?').fill(text);
        await page.keyboard.press('Enter');
      },
    };

    // Inject into test
    await use(todoPage);

    // Cleanup logic (if needed)
  },

  // Custom Fixture: authenticated page
  authenticatedPage: async ({ browser }, use) => {
    const context = await browser.newContext({
      storageState: 'playwright/.auth/user.json',
    });
    const page = await context.newPage();
    await use(page);
    await context.close();
  },
});

export { expect };

Using Custom Fixtures

Example

// File path: tests/todo.spec.ts

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

test('Add Todo', async ({ todoPage }) => {
  await todoPage.goto();
  await todoPage.addTodo('Learn Playwright');
  await todoPage.addTodo('Master Fixtures');
  // Validation...
});

Fixture Scopes

Fixtures support three scopes, controlled via the scope option:

Scope Value Lifecycle
Test-level (default) scope: 'test' Created once per test
Worker-level scope: 'worker' Created once per Worker process; shared across all tests within the same Worker

Example

// Worker-level Fixture (shared across all tests within the same Worker)
const test = base.extend({
  sharedResource: [
    async ({}, use) => {
      const resource = await createExpensiveResource();
      await use(resource);
      await resource.dispose();
    },
    { scope: 'worker' } // Note: array syntax
  ],
});

Worker-level Fixtures are shared across multiple tests within the same Worker process, ideal for expensive resources like database connections.


Overriding Built-in Fixtures

You can override built-in Fixtures to change their default behavior.

Example

const test = base.extend({
  // Override built-in `page` Fixture to change default viewport
  page: async ({ baseURL, page }, use) => {
    await page.setViewportSize({ width: 1920, height: 1080 });
    await use(page);
  },

  // Override `storageState`
  storageState: 'playwright/.auth/admin.json',
});

Fixture Auto-Cleanup

Fixture cleanup logic runs after the use() call, similar to afterEach behavior.

Example

const test = base.extend({
  tempUser: async ({ request }, use) => {
    // Create temporary user
    const resp = await request.post('/api/users', {
      data: { name: 'temp_' + Date.now() },
    });
    const user = await resp.json();

    // Inject into test
    await use(user);

    // Auto cleanup: delete temporary user
    await request.delete(`/api/users/${user.id}`);
  },
});

Parameterized Testing

Playwright does not have a built-in parameterized testing mechanism, but tests can be dynamically generated using loops.

Example

// Data-driven testing
const testCases = [
  { username: 'admin', password: 'admin123', expected: 'Dashboard' },
  { username: 'user', password: 'user123', expected: 'Profile' },
  { username: 'guest', password: 'guest123', expected: 'Guest Page' },
];

for (const { username, password, expected } of testCases) {
  test(`User ${username} sees ${expected} after login`, async ({ page }) => {
    await page.goto('/login');
    await page.getByLabel('Username').fill(username);
    await page.getByLabel('Password').fill(password);
    await page.getByRole('button', { name: 'Login' }).click();
    await expect(page.getByText(expected)).toBeVisible();
  });
}

Test Annotations

Annotations add metadata to tests, which can be viewed and filtered in test reports.

Example

test('Test with annotations', async ({ page }) => {
  // Add annotations (inside test function body)
  test.info().annotations.push({
    type: 'issue',
    description: '
  });
  test.info().annotations.push({
    type: 'description',
    description: 'This test covers all branches of the login flow',
  });

  // Test logic...
});
// Using Tags (in test name)
test('Fast smoke test @smoke @critical', async ({ page }) => {
  // Can run only this test via: npx playwright test --grep "@smoke"
});
← Playwright Ci CdPlaywright Frame β†’