🎭 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! 🌟
