๐ญ Playwright Custom Fixtures: Your Personal Stage Crew
Imagine youโre putting on a play. Before the actors perform, the stage crew sets up everythingโlights, props, costumes. After the show, they clean it all up. Custom fixtures in Playwright work exactly like your personal stage crew!
๐ What Are Custom Fixtures?
Think of fixtures like helpful friends who prepare things before you need them and clean up after youโre done.
Real Life Example:
- Before a birthday party โ Your mom sets up decorations ๐
- During the party โ You have fun!
- After the party โ Mom cleans everything up
In Playwright:
- Before a test โ Fixture prepares what you need
- During test โ You use it!
- After test โ Fixture cleans up automatically
๐ง Creating Custom Fixtures
Creating a fixture is like teaching a helper what to prepare for you.
// fixtures.js
import { test as base } from '@playwright/test';
// Create a custom fixture
export const test = base.extend({
// 'todoPage' is our helper's name
todoPage: async ({ page }, use) => {
// BEFORE: Go to the todo app
await page.goto('https://demo.com/todos');
// DURING: Hand it to the test
await use(page);
// AFTER: Clean up (optional)
await page.close();
}
});
Whatโs happening?
async ({ page }, use)โ Takes what it needs (page)- Code before
useโ Sets things up await use(page)โ Gives it to your test- Code after
useโ Cleans up
๐ฆ The test.extend Method
test.extend is like a magic box that creates new helpers from existing ones.
graph TD A["Base Test"] --> B["test.extend"] B --> C["Your Custom Test"] C --> D["Has All Original Powers"] C --> E["Plus Your New Fixtures!"]
Simple Example:
import { test as base } from '@playwright/test';
export const test = base.extend({
// Add a logged-in user helper
loggedInPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('#email', 'test@test.com');
await page.fill('#password', 'secret');
await page.click('button[type=submit]');
await use(page);
}
});
Now every test can start already logged in! ๐
โฐ Fixture Scopes: How Long Do Helpers Stay?
Fixtures can stay for different amounts of time. Itโs like hiring helpers:
| Scope | Like Hiringโฆ | Example |
|---|---|---|
test |
A helper for ONE task | Fresh page per test |
worker |
A helper for the whole day | Database connection |
export const test = base.extend({
// Scope: 'test' (default)
// New page for EACH test
freshPage: async ({ page }, use) => {
await use(page);
},
// Scope: 'worker'
// Same database for ALL tests
database: [async ({}, use) => {
const db = await connectDB();
await use(db);
await db.close();
}, { scope: 'worker' }]
});
When to use which?
testscope โ When each test needs a clean startworkerscope โ For expensive things (databases, servers)
๐ค Auto-use Fixtures: Always-On Helpers
Some helpers should always be there, like a night light that turns on automatically when itโs dark.
export const test = base.extend({
// This runs for EVERY test automatically!
autoScreenshot: [async ({ page }, use) => {
await use();
// Take screenshot after every test
await page.screenshot({
path: `screenshots/${Date.now()}.png`
});
}, { auto: true }] // โ Magic switch!
});
Common auto-use examples:
- ๐ธ Take screenshots after tests
- ๐ Log test start/end times
- ๐ง Set up common settings
๐ Fixture Dependencies: Helpers That Need Other Helpers
Sometimes one helper needs another helper first. Itโs like a chain of helpers!
graph TD A["browser"] --> B["context"] B --> C["page"] C --> D["loggedInPage"] D --> E["dashboardPage"]
export const test = base.extend({
// Helper 1: Logged in user
userPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('#email', 'user@test.com');
await page.click('#login-btn');
await use(page);
},
// Helper 2: NEEDS userPage first!
dashboardPage: async ({ userPage }, use) => {
// userPage is already logged in
await userPage.goto('/dashboard');
await use(userPage);
}
});
The magic: Playwright figures out the order automatically! ๐ช
๐งน Fixture Teardown: The Cleanup Crew
Everything after await use() is teardown. Itโs like the cleanup after a party.
export const test = base.extend({
tempFile: async ({}, use) => {
// SETUP: Create a temporary file
const file = await createTempFile();
console.log('๐ Created temp file');
// HAND OVER to test
await use(file);
// TEARDOWN: Delete the file
await deleteTempFile(file);
console.log('๐๏ธ Cleaned up temp file');
}
});
Important rules:
- Teardown runs even if test FAILS โ
- Teardown runs in REVERSE order ๐
- Always clean up what you create! ๐งน
๐ฏ Complete Example: Putting It All Together
// fixtures.js
import { test as base } from '@playwright/test';
export const test = base.extend({
// Auto-use: Timer for every test
testTimer: [async ({}, use) => {
const start = Date.now();
await use();
const time = Date.now() - start;
console.log(`โฑ๏ธ Test took ${time}ms`);
}, { auto: true }],
// Worker scope: Expensive database
db: [async ({}, use) => {
const db = await connectDatabase();
await use(db);
await db.disconnect();
}, { scope: 'worker' }],
// Depends on page: Logged in user
authPage: async ({ page }, use) => {
await page.goto('/login');
await page.fill('#user', 'admin');
await page.fill('#pass', '12345');
await page.click('#submit');
await use(page);
},
// Depends on authPage: Admin dashboard
adminDashboard: async ({ authPage }, use) => {
await authPage.goto('/admin');
await use(authPage);
}
});
// In your test file:
test('admin can see users', async ({ adminDashboard, db }) => {
// adminDashboard is ready and logged in!
// db is connected and shared across tests!
await expect(adminDashboard.locator('h1'))
.toHaveText('Admin Panel');
});
๐ Quick Summary
| Concept | What It Does | Think Of It Asโฆ |
|---|---|---|
| Custom Fixture | Prepares stuff before tests | Stage crew |
| test.extend | Creates your custom test | Magic box |
| Scope: test | Fresh for each test | Daily helper |
| Scope: worker | Shared across tests | Full-time helper |
| Auto-use | Runs automatically | Night light |
| Dependencies | One fixture needs another | Chain of helpers |
| Teardown | Cleans up after | Cleanup crew |
๐ก Remember This!
Fixtures are like setting up a playdate:
- Mom prepares snacks (Setup)
- Kids play (Your test runs)
- Mom cleans up (Teardown)
You just focus on playing! The rest happens automatically. ๐ฎ
Youโve got this! Custom fixtures make your tests cleaner, faster, and easier to write. Now go build something amazing! ๐
