Playwright Fixtures & Parameterization
This chapter introduces Playwright's Fixture mechanism, how to create custom Fixtures, and techniques for parameterized testing.
What is a Fixture
A Fixture is a dependency injection mechanism for Playwright test function parameters.
Each test obtains the required Fixtures by destructuring parameters (e.g.,{ page }), and Playwright handles creation and cleanup.
Example
test('Using built-in Fixtures', async ({ page, context }) => {
// Playwright automatically creates page and context
// Automatically cleaned up after the test ends
await page.goto('https://www.example.com/');
});
Built-in Fixture Quick Reference
| Fixture | Type | Description |
|---|---|---|
page | Page | Independent browser tab |
context | BrowserContext | Browser context |
browser | Browser | Browser instance |
browserName | string | Current browser name |
request | APIRequestContext | HTTP request client |
Custom Fixtures
Viatest.extend()Create custom Fixtures to inject additional dependencies or override built-in Fixtures.
Example
import { test as base, expect } from '@playwright/test';
// Define the type of the custom Fixture
type MyFixtures = {
todoPage: { goto: () => Promise<void>; addTodo: (text: string) => Promise<void> };
authenticatedPage: any;
};
export const test = base.extend<MyFixtures>({
// 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
import { test, expect } from './fixtures';
test('Add Todo', async ({ todoPage }) => {
await todoPage.goto();
await todoPage.addTodo('Learn Playwright');
await todoPage.addTodo('Master Fixtures');
// Verify...
});
Fixture Scopes
Fixtures have three scopes, controlled by thescopeoption.
| Scope | Value | Lifecycle |
|---|---|---|
| Test-level (default) | scope: 'test' | Created once per test |
| Worker-level | scope: 'worker' | Created once per Worker process, shared by all tests in the same Worker |
Example
const test = base.extend({
sharedResource: [async ({}, use) => {
const resource = await createExpensiveResource();
await use(resource);
await resource.dispose();
}, { scope: 'worker' }], // Note the array syntax
});
Worker-level Fixtures are shared across multiple tests within the same Worker process, suitable for resources with high creation overhead such as database connections.
Overriding Built-in Fixtures
You can override built-in Fixtures to change default behavior.
Example
// Override the built-in page Fixture to change the default viewport
page: async ({ baseURL, page }, use) => {
await page.setViewportSize({ width: 1920, height: 1080 });
await use(page);
},
// Override storageState
storageState: 'playwright/.auth/admin.json',
});
Automatic Fixture Cleanup
The Fixture's cleanup logic is executed after theuse()call, similar to the effect ofafterEach.
Example
tempUser: async ({ request }, use) => {
// Create a 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);
// Automatic cleanup: Delete the temporary user
await request.delete(`/api/users/${user.id}`);
},
});
Parameterized Tests
Playwright does not have a built-in parameterization mechanism, but tests can be dynamically generated using loops.
Example
const testCases = [
{ username: 'admin', password: 'admin123', expected: 'Dashboard' },
{ username: 'user', password: 'user123', expected: 'Personal Center' },
{ username: 'guest', password: 'guest123', expected: 'Visitor Page' },
];
for (const { username, password, expected } of testCases) {
test(`User ${username}sees $ after logging in{expected}`, 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 are used to add metadata to tests, which can be viewed and filtered in the reporter.
Example
// Add annotation (inside the test function body)
test.info().annotations.push({
type: 'issue',
description: 'https://github.com/example/bug/123',
});
test.info().annotations.push({
type: 'description',
description: 'This test covers all branches of the login flow',
});
// Test logic...
});
// Using Tag (in the test name)
test('Quick smoke test @smoke @critical', async ({ page }) => {
// You can run only this test with npx playwright test --grep "@smoke"
});