Fixtures are one of the most powerful features in Playwright. They help create, manage, and share test resources across multiple test cases, making your test code cleaner, reusable, and easier to maintain.
Instead of repeatedly writing setup and teardown logic in every test, fixtures allow you to define it once and reuse it throughout your test suite.
What is a Fixture?
A fixture is a predefined resource or setup that is automatically provided to your test before execution and cleaned up afterward.
Examples of fixtures include:
- Browser instances
- Browser contexts
- Pages
- Authenticated users
- Database connections
- API clients
- Test data
Why Use Fixtures?
- Reduce code duplication
- Improve test readability
- Centralize setup logic
- Simplify maintenance
- Provide reusable resources
- Enable test isolation
Built-in Fixtures in Playwright
Playwright provides several built-in fixtures that are automatically available in test files.
| Fixture | Description |
|---|---|
| browser | Browser instance |
| context | Browser context |
| page | Browser page/tab |
| request | API request context |
| browserName | Current browser name |
Using the Page Fixture
The page fixture is the most commonly used fixture.
import { test, expect } from '@playwright/test';
test('Homepage Test', async ({ page }) => {
await page.goto('https://example.com');
await expect(page)
.toHaveTitle(/Example/);
});
Playwright automatically creates and closes the page for the test.
Using the Browser Fixture
import { test } from '@playwright/test';
test('Browser Fixture Example',
async ({ browser }) => {
const page =
await browser.newPage();
await page.goto(
'https://example.com'
);
});
Using the Context Fixture
A browser context represents an isolated browser session.
test('Context Fixture Example',
async ({ context }) => {
const page =
await context.newPage();
await page.goto(
'https://example.com'
);
});
Using the Request Fixture
The request fixture is used for API testing.
test('API Test',
async ({ request }) => {
const response =
await request.get('/users');
expect(response.status())
.toBe(200);
});
Creating Custom Fixtures
Playwright allows you to create your own fixtures using test.extend().
import { test as base }
from '@playwright/test';
export const test =
base.extend({
username: async ({}, use) => {
const user = 'admin';
await use(user);
}
});
Using a Custom Fixture
import { test }
from './fixtures';
test('Custom Fixture Example',
async ({ username }) => {
console.log(username);
});
Output:
admin
Fixture Lifecycle
Every fixture follows a lifecycle:
- Setup
- Provide resource to test
- Execute test
- Cleanup
Example:
const test = base.extend({
dbConnection:
async ({}, use) => {
const connection =
await connectDB();
await use(connection);
await connection.close();
}
});
Worker Fixtures
Worker fixtures are created once per worker process and shared among tests.
Useful for expensive resources such as:
- Database connections
- Authentication tokens
- External services
export const test =
base.extend({
apiToken: [
async ({}, use) => {
const token =
await generateToken();
await use(token);
},
{ scope: 'worker' }
]
});
Test Fixtures vs Worker Fixtures
| Feature | Test Fixture | Worker Fixture |
|---|---|---|
| Created | Per Test | Per Worker |
| Isolation | High | Shared |
| Performance | Slower | Faster |
| Use Case | Test Data | Shared Resources |
Authenticated User Fixture
One common use case is creating a reusable logged-in user fixture.
export const test =
base.extend({
loggedInPage:
async ({ page }, use) => {
await page.goto('/login');
await page.fill(
'#username',
'admin'
);
await page.fill(
'#password',
'password'
);
await page.click(
'#loginBtn'
);
await use(page);
}
});
Using Authenticated Fixture
test('Dashboard Test',
async ({ loggedInPage }) => {
await expect(
loggedInPage
).toHaveURL(
/dashboard/
);
});
Fixture Dependency
Fixtures can depend on other fixtures.
export const test =
base.extend({
user:
async ({}, use) => {
const user = {
name: 'John'
};
await use(user);
},
profile:
async ({ user }, use) => {
const profile = {
username: user.name
};
await use(profile);
}
});
Using Multiple Fixtures
test('Multiple Fixtures',
async ({
page,
username,
apiToken
}) => {
console.log(username);
console.log(apiToken);
});
Fixture File Structure
project/
├── tests/
│ ├── login.spec.js
│ ├── dashboard.spec.js
│
├── fixtures/
│ └── customFixtures.js
│
├── playwright.config.js
│
└── package.json
Practical Example
Custom fixture for test data creation.
export const test =
base.extend({
testUser:
async ({}, use) => {
const user = {
username: 'john',
password: 'password123'
};
await use(user);
}
});
Usage:
test('Login Test',
async ({ testUser }) => {
console.log(
testUser.username
);
});
Advantages of Fixtures
- Reusable setup logic
- Cleaner test code
- Improved maintainability
- Better test isolation
- Centralized resource management
- Reduced duplication
Common Use Cases
- User authentication
- Database setup
- API clients
- Test data generation
- Environment configuration
- Browser initialization
Best Practices
- Keep fixtures focused on a single responsibility.
- Use custom fixtures for reusable logic.
- Prefer worker fixtures for expensive resources.
- Clean up resources properly.
- Avoid unnecessary fixture dependencies.
- Keep fixture names meaningful and descriptive.
- Store fixtures in a dedicated fixtures folder.
Commonly Used Fixtures
| Fixture | Purpose |
|---|---|
| page | Browser page operations |
| browser | Browser instance access |
| context | Browser session management |
| request | API testing |
| loggedInPage | Authenticated user session |
| apiToken | Reusable API authentication |
Conclusion
Fixtures are a core feature of Playwright that help organize setup and teardown operations efficiently. By using built-in and custom fixtures, teams can create cleaner, reusable, and scalable test automation frameworks. Proper fixture design improves maintainability, reduces code duplication, and enables robust end-to-end testing across large projects.
```