| name | playwright-fixtures-and-hooks |
| user-invocable | false |
| description | Use when managing test state and infrastructure with reusable Playwright fixtures and lifecycle hooks for efficient test setup and teardown. |
| allowed-tools | ["Bash","Read","Write","Edit"] |
Playwright Fixtures and Hooks
Master Playwright's fixture system and lifecycle hooks to create reusable
test infrastructure, manage test state, and build maintainable test suites.
This skill covers built-in fixtures, custom fixtures, and best practices
for test setup and teardown.
Built-in Fixtures
Core Fixtures
import { test, expect } from '@playwright/test';
test('using built-in fixtures', async ({
page,
context,
browser,
request,
}) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});
Page Fixture
test('page fixture examples', async ({ page }) => {
await page.goto('https://example.com');
await page.getByRole('button', { name: 'Click me' }).click();
await page.waitForLoadState('networkidle');
const title = await page.title();
expect(title).toBe('Example Domain');
});
Context Fixture
test('context fixture examples', async ({ context, page }) => {
await context.addCookies([
{
name: 'session',
value: 'abc123',
domain: 'example.com',
path: '/',
},
]);
await context.grantPermissions(['geolocation']);
const page2 = await context.newPage();
await page2.goto('https://example.com');
await page.goto('https://example.com');
});
Browser Fixture
test('browser fixture examples', async ({ browser }) => {
const context = await browser.newContext({
viewport: { width: 1920, height: 1080 },
locale: 'en-US',
timezoneId: 'America/New_York',
permissions: ['geolocation'],
});
const page = await context.newPage();
await page.goto('https://example.com');
await context.close();
});
Request Fixture
test('API testing with request fixture', async ({ request }) => {
const response = await request.get('https://api.example.com/users');
expect(response.ok()).toBeTruthy();
expect(response.status()).toBe(200);
const users = await response.json();
expect(users).toHaveLength(10);
const createResponse = await request.post('https://api.example.com/users', {
data: {
name: 'John Doe',
email: 'john@example.com',
},
});
expect(createResponse.ok()).toBeTruthy();
});
Custom Fixtures
Basic Custom Fixture
import { test as base } from '@playwright/test';
type MyFixtures = {
timestamp: string;
};
export const test = base.extend<MyFixtures>({
timestamp: async ({}, use) => {
const timestamp = new Date().toISOString();
await use(timestamp);
},
});
export { expect } from '@playwright/test';
import { test, expect } from '../fixtures/base-fixtures';
test('using custom timestamp fixture', async ({ timestamp, page }) => {
console.log(`Test started at: ${timestamp}`);
await page.goto('https://example.com');
});
Fixture with Setup and Teardown
import { test as base } from '@playwright/test';
type DatabaseFixtures = {
database: Database;
};
export const test = base.extend<DatabaseFixtures>({
database: async ({}, use) => {
const db = await createDatabaseConnection();
console.log('Database connected');
await use(db);
await db.close();
console.log('Database closed');
},
});
Fixture Scopes: Test vs Worker
import { test as base } from '@playwright/test';
type TestScopedFixtures = {
uniqueId: string;
};
type WorkerScopedFixtures = {
apiToken: string;
};
export const test = base.extend<TestScopedFixtures, WorkerScopedFixtures>({
uniqueId: async ({}, use) => {
const id = `test-${Date.now()}-${Math.random()}`;
await use(id);
},
apiToken: [
async ({}, use) => {
const token = await generateApiToken();
await use(token);
await revokeApiToken(token);
},
{ scope: 'worker' },
],
});
Authentication Fixtures
Authenticated User Fixture
import { test as base } from '@playwright/test';
type AuthFixtures = {
authenticatedPage: Page;
};
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'auth.json',
});
const page = await context.newPage();
await use(page);
await context.close();
},
});
export { expect } from '@playwright/test';
Multiple User Roles
import { test as base } from '@playwright/test';
type UserFixtures = {
adminPage: Page;
userPage: Page;
guestPage: Page;
};
export const test = base.extend<UserFixtures>({
adminPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'auth/admin.json',
});
const page = await context.newPage();
await use(page);
await context.close();
},
userPage: async ({ browser }, use) => {
const context = await browser.newContext({
storageState: 'auth/user.json',
});
const page = await context.newPage();
await use(page);
await context.close();
},
guestPage: async ({ browser }, use) => {
const context = await browser.();
page = context.();
(page);
context.();
},
});
Authentication Setup
import { test as setup } from '@playwright/test';
setup('authenticate as admin', async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByLabel('Email').fill('admin@example.com');
await page.getByLabel('Password').fill('admin123');
await page.getByRole('button', { name: 'Login' }).click();
await page.waitForURL('**/dashboard');
await page.context().storageState({ path: 'auth/admin.json' });
});
setup('authenticate as user', async ({ page }) => {
await page.goto('https://example.com/login');
await page.getByLabel('Email').fill('user@example.com');
await page.getByLabel('Password').fill();
page.(, { : }).();
page.();
page.().({ : });
});
Database Fixtures
Test Database Fixture
import { test as base } from '@playwright/test';
import { PrismaClient } from '@prisma/client';
type DatabaseFixtures = {
db: PrismaClient;
cleanDb: void;
};
export const test = base.extend<DatabaseFixtures>({
db: [
async ({}, use) => {
const db = new PrismaClient();
await use(db);
await db.$disconnect();
},
{ scope: 'worker' },
],
cleanDb: async ({ db }, use) => {
await db.user.deleteMany();
await db.product.deleteMany();
await db.order.deleteMany();
await use();
await db.user.deleteMany();
await db.product.deleteMany();
db..();
},
});
Seeded Data Fixture
import { test as base } from './database-fixtures';
type SeedFixtures = {
testUser: User;
testProducts: Product[];
};
export const test = base.extend<SeedFixtures>({
testUser: async ({ db, cleanDb }, use) => {
const user = await db.user.create({
data: {
email: 'test@example.com',
name: 'Test User',
password: 'hashedpassword',
},
});
await use(user);
},
testProducts: async ({ db, cleanDb }, use) => {
const products = await db.product.createMany({
data: [
{ name: 'Product 1', price: 10.99 },
{ name: 'Product 2', price: 20.99 },
{ name: 'Product 3', price: 30.99 },
],
});
const allProducts = db..();
(allProducts);
},
});
API Mocking Fixtures
Mock API Fixture
import { test as base } from '@playwright/test';
type MockApiFixtures = {
mockApi: void;
};
export const test = base.extend<MockApiFixtures>({
mockApi: async ({ page }, use) => {
await page.route('**/api/users', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ id: 1, name: 'User 1' },
{ id: 2, name: 'User 2' },
]),
});
});
await page.route('**/api/products', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([
{ : , : , : },
{ : , : , : },
]),
});
});
();
page.();
},
});
Conditional Mocking
import { test as base } from '@playwright/test';
type ConditionalMockFixtures = {
mockFailedApi: void;
mockSlowApi: void;
};
export const test = base.extend<ConditionalMockFixtures>({
mockFailedApi: async ({ page }, use) => {
await page.route('**/api/**', async (route) => {
await route.fulfill({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Internal Server Error' }),
});
});
await use();
await page.unrouteAll();
},
mockSlowApi: async ({ page }, use) => {
await page.route('**/api/**', async (route) => {
await new Promise((resolve) => setTimeout(resolve, ));
route.();
});
();
page.();
},
});
Lifecycle Hooks
Test Hooks
import { test, expect } from '@playwright/test';
test.describe('User Management', () => {
test.beforeAll(async () => {
console.log('Setting up test suite');
});
test.beforeEach(async ({ page }) => {
await page.goto('https://example.com');
console.log('Test starting');
});
test.afterEach(async ({ page }, testInfo) => {
console.log(`Test ${testInfo.status}: ${testInfo.title}`);
if (testInfo.status !== testInfo.expectedStatus) {
const screenshot = await page.screenshot();
await testInfo.attach('failure-screenshot', {
body: screenshot,
contentType: 'image/png',
});
}
});
test.afterAll(async () => {
.();
});
(, ({ page }) => {
});
(, ({ page }) => {
});
});
Nested Hooks
test.describe('Parent Suite', () => {
test.beforeEach(async ({ page }) => {
console.log('Parent beforeEach');
await page.goto('https://example.com');
});
test.describe('Child Suite 1', () => {
test.beforeEach(async ({ page }) => {
console.log('Child 1 beforeEach');
await page.getByRole('link', { name: 'Products' }).click();
});
test('test in child 1', async ({ page }) => {
});
});
test.describe('Child Suite 2', () => {
test.beforeEach(async ({ page }) => {
console.log('Child 2 beforeEach');
await page.getByRole('link', { name: 'About' }).click();
});
test('test in child 2', async ({ page }) => {
});
});
});
Conditional Hooks
test.describe('Feature Tests', () => {
test.beforeEach(async ({ page, browserName }) => {
if (browserName === 'firefox') {
test.skip();
}
await page.goto('https://example.com');
});
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status === 'failed') {
await page.screenshot({ path: `failure-${testInfo.title}.png` });
}
});
test('feature test', async ({ page }) => {
});
});
Fixture Dependencies
Dependent Fixtures
import { test as base } from '@playwright/test';
type DependentFixtures = {
config: Config;
apiClient: ApiClient;
authenticatedClient: ApiClient;
};
export const test = base.extend<DependentFixtures>({
config: async ({}, use) => {
const config = {
apiUrl: process.env.API_URL || 'http://localhost:3000',
timeout: 30000,
};
await use(config);
},
apiClient: async ({ config }, use) => {
const client = new ApiClient(config.apiUrl, config.timeout);
await use(client);
},
authenticatedClient: async ({ apiClient }, use) => {
const token = await apiClient.login('user@example.com', 'password');
apiClient.setAuthToken(token);
(apiClient);
},
});
Combining Multiple Fixtures
import { test as base } from '@playwright/test';
type CombinedFixtures = {
setupComplete: void;
};
export const test = base.extend<CombinedFixtures>({
setupComplete: async (
{ page, db, mockApi, testUser },
use
) => {
await page.goto('https://example.com');
await page.context().addCookies([
{
name: 'userId',
value: testUser.id.toString(),
domain: 'example.com',
path: '/',
},
]);
await use();
},
});
Advanced Fixture Patterns
Factory Fixtures
import { test as base } from '@playwright/test';
type FactoryFixtures = {
createUser: (data: Partial<User>) => Promise<User>;
createProduct: (data: Partial<Product>) => Promise<Product>;
};
export const test = base.extend<FactoryFixtures>({
createUser: async ({ db }, use) => {
const users: User[] = [];
const createUser = async (data: Partial<User>) => {
const user = await db.user.create({
data: {
email: data.email || `user-${Date.now()}@example.com`,
name: data.name || 'Test User',
password: data. || ,
...data,
},
});
users.(user);
user;
};
(createUser);
( user users) {
db..({ : { : user. } });
}
},
: ({ db }, use) => {
: [] = [];
= () => {
product = db..({
: {
: data. || ,
: data. || ,
: data. || ,
...data,
},
});
products.(product);
product;
};
(createProduct);
( product products) {
db..({ : { : product. } });
}
},
});
Option Fixtures
import { test as base } from '@playwright/test';
type OptionsFixtures = {
slowNetwork: boolean;
};
export const test = base.extend<OptionsFixtures>({
slowNetwork: [false, { option: true }],
page: async ({ page, slowNetwork }, use) => {
if (slowNetwork) {
await page.route('**/*', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 1000));
await route.continue();
});
}
await use(page);
},
});
import { test, expect } from '../fixtures/option-fixtures';
test('test with slow network', async ({ page }) => {
test.use({ slowNetwork: true });
await page.goto('https://example.com');
});
test('test with normal network', async ({ page }) => {
await page.goto('https://example.com');
});
Test Info and Attachments
Using Test Info
test('example with test info', async ({ page }, testInfo) => {
console.log(`Test title: ${testInfo.title}`);
console.log(`Project: ${testInfo.project.name}`);
console.log(`Retry: ${testInfo.retry}`);
await page.goto('https://example.com');
const screenshot = await page.screenshot();
await testInfo.attach('page-screenshot', {
body: screenshot,
contentType: 'image/png',
});
await testInfo.attach('test-data', {
body: JSON.stringify({ foo: 'bar' }),
contentType: 'application/json',
});
await testInfo.attach('notes', {
body: 'Test completed successfully',
contentType: 'text/plain',
});
});
Conditional Test Execution
test('browser-specific test', async ({ page, browserName }) => {
test.skip(browserName === 'webkit', 'Not supported in Safari');
await page.goto('https://example.com');
});
test('slow test', async ({ page }) => {
test.slow();
await page.goto('https://slow-site.example.com');
});
test('expected to fail', async ({ page }) => {
test.fail();
await page.goto('https://example.com');
await expect(page.getByText('Non-existent')).toBeVisible();
});
Fixture Best Practices
Organizing Fixtures
fixtures/
โโโ index.ts # Export all fixtures
โโโ auth-fixtures.ts # Authentication fixtures
โโโ database-fixtures.ts # Database fixtures
โโโ mock-api-fixtures.ts # API mocking fixtures
โโโ page-fixtures.ts # Page-related fixtures
import { test as authTest } from './auth-fixtures';
import { test as dbTest } from './database-fixtures';
import { test as mockTest } from './mock-api-fixtures';
export const test = authTest.extend(dbTest.fixtures).extend(mockTest.fixtures);
export { expect } from '@playwright/test';
Fixture Naming Conventions
export const test = base.extend({
authenticatedPage: async ({}, use) => { },
testUser: async ({}, use) => { },
mockApi: async ({}, use) => { },
});
export const test = base.extend({
page2: async ({}, use) => { },
data: async ({}, use) => { },
fixture1: async ({}, use) => { },
});
When to Use This Skill
- Setting up reusable test infrastructure
- Managing authentication state across tests
- Creating database seeding and cleanup logic
- Implementing API mocking for tests
- Building factory fixtures for test data generation
- Establishing test lifecycle patterns
- Creating worker-scoped fixtures for performance
- Organizing complex test setup and teardown
- Implementing conditional test behavior
- Building type-safe fixture systems
Resources