Playwright Custom Fixtures with Examples
In Playwright, fixtures are reusable setup and teardown utilities that help you share test data, page objects, authentication, API clients, and configurations across tests.
Custom fixtures make your framework:
- Reusable
- Maintainable
- Scalable
- Cleaner and easier to read
1. What is a Fixture in Playwright?
A fixture is a setup environment provided to your test before execution.
Playwright already provides built-in fixtures such as:
pagebrowsercontextrequest
Example:
import { test, expect } from '@playwright/test';
test('example test', async ({ page }) => {
await page.goto('https://example.com');
});
Here, page is a built-in fixture.
2. Why Use Custom Fixtures?
Custom fixtures help when you want to:
- Reuse login steps
- Share page objects
- Create API clients
- Initialize test data
- Setup database connections
- Handle authentication
- Avoid duplicate code
3. Basic Structure of Custom Fixture
import { test as base } from '@playwright/test';
export const test = base.extend({
myFixture: async ({}, use) => {
// Setup
const data = "Hello Fixture";
// Provide fixture
await use(data);
// Teardown
console.log("Fixture cleanup");
}
});
4. Example 1 — Simple Custom Fixture
Folder Structure
project
├── tests
│ └── example.spec.ts
│
├── fixtures
│ └── testFixture.ts
Step 1: Create Fixture File
fixtures/testFixture.ts
import { test as base } from '@playwright/test';
type MyFixtures = {
userName: string;
};
export const test = base.extend<MyFixtures>({
userName: async ({}, use) => {
const name = 'Deepesh';
await use(name);
console.log('Test completed');
}
});
export { expect } from '@playwright/test';
Step 2: Use Fixture in Test
tests/example.spec.ts
import { test, expect } from '../fixtures/testFixture';
test('Custom Fixture Example', async ({ userName }) => {
console.log(userName);
expect(userName).toBe('Deepesh');
});
5. Example 2 — Page Object Fixture
This is the most common real-world usage.
Folder Structure
project
├── pages
│ └── LoginPage.ts
│
├── fixtures
│ └── pageFixture.ts
│
├── tests
│ └── login.spec.ts
Step 1: Create Page Object
pages/LoginPage.ts
import { Page } from '@playwright/test';
export class LoginPage {
constructor(private page: Page) {}
async navigate() {
await this.page.goto('https://example.com/login');
}
async login(username: string, password: string) {
await this.page.fill('#username', username);
await this.page.fill('#password', password);
await this.page.click('#loginBtn');
}
}
Step 2: Create Fixture
fixtures/pageFixture.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
type PageFixtures = {
loginPage: LoginPage;
};
export const test = base.extend<PageFixtures>({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
}
});
export { expect } from '@playwright/test';
Step 3: Use Fixture in Test
tests/login.spec.ts
import { test, expect } from '../fixtures/pageFixture';
test('Login Test', async ({ loginPage }) => {
await loginPage.navigate();
await loginPage.login(
'admin',
'password123'
);
});
6. Example 3 — Authentication Fixture
This is extremely useful in enterprise frameworks.
Authentication Fixture
import { test as base } from '@playwright/test';
type AuthFixtures = {
authenticatedPage: any;
};
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page }, use) => {
await page.goto('https://example.com/login');
await page.fill('#username', 'admin');
await page.fill('#password', 'admin123');
await page.click('#login');
// Reuse logged-in page
await use(page);
// Optional cleanup
await page.close();
}
});
export { expect } from '@playwright/test';
Test File
import { test, expect } from '../fixtures/authFixture';
test('Dashboard Test', async ({ authenticatedPage }) => {
await authenticatedPage.goto(
'https://example.com/dashboard'
);
await expect(
authenticatedPage.locator('h1')
).toContainText('Dashboard');
});
7. Example 4 — API Fixture
Useful for API automation frameworks.
fixtures/apiFixture.ts
import { test as base, request } from '@playwright/test';
type ApiFixture = {
apiContext: any;
};
export const test = base.extend<ApiFixture>({
apiContext: async ({}, use) => {
const apiContext = await request.newContext({
baseURL: 'https://reqres.in/api',
extraHTTPHeaders: {
'Accept': 'application/json'
}
});
await use(apiContext);
await apiContext.dispose();
}
});
export { expect } from '@playwright/test';
API Test
import { test, expect } from '../fixtures/apiFixture';
test('Get Users API', async ({ apiContext }) => {
const response = await apiContext.get('/users?page=2');
expect(response.status()).toBe(200);
const body = await response.json();
console.log(body);
});
8. Worker Fixture vs Test Fixture
Playwright supports two fixture scopes.
| Fixture Type | Scope | Runs |
|---|---|---|
| Test Fixture | Per Test | Every test |
| Worker Fixture | Per Worker | Once per worker |
Example of Worker Fixture
import { test as base } from '@playwright/test';
type WorkerFixture = {
token: string;
};
export const test = base.extend<{}, WorkerFixture>({
token: [async ({}, use) => {
console.log('Generate Token');
const token = 'ABC123TOKEN';
await use(token);
}, { scope: 'worker' }]
});
9. Auto Fixtures
Auto fixtures execute automatically before every test.
Example
import { test as base } from '@playwright/test';
export const test = base.extend({
autoLogger: [async ({}, use) => {
console.log('Before Test');
await use();
console.log('After Test');
}, { auto: true }]
});
10. Best Practices
Use Fixtures For
- Login handling
- API clients
- Database setup
- Page objects
- Environment setup
- Test data generation
Avoid
- Very large fixtures
- Too much business logic
- Circular dependencies
- Hardcoded credentials
11. Real Enterprise Framework Structure
project
├── fixtures
│ ├── authFixture.ts
│ ├── apiFixture.ts
│ ├── pageFixture.ts
│
├── pages
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│
├── tests
│ ├── ui
│ ├── api
│
├── utils
├── test-data
├── playwright.config.ts
12. Advantages of Custom Fixtures
| Benefit | Description |
|---|---|
| Reusability | Avoid duplicate code |
| Scalability | Easier framework growth |
| Maintainability | Centralized setup |
| Clean Tests | Better readability |
| Faster Development | Reuse common logic |
13. Interview Questions
Q1. What is a fixture in Playwright?
A reusable setup/teardown utility used in tests.
Q2. Difference between built-in and custom fixtures?
- Built-in fixtures are provided by Playwright.
- Custom fixtures are user-defined.
Q3. What is worker scope?
Fixture executes once per worker process.
Q4. Why use fixtures with Page Object Model?
To inject reusable page object instances into tests.
14. Recommended Real-World Usage
Most companies use fixtures for:
- Authentication
- Page Object initialization
- API clients
- Environment configuration
- Test reporting
- Database utilities
- Logging
15. Summary
Custom fixtures are one of the most important concepts in Playwright frameworks because they:
- Reduce code duplication
- Improve maintainability
- Support scalable automation frameworks
- Work perfectly with POM and API automation
Most enterprise Playwright frameworks heavily depend on custom fixtures.
6 thoughts on “Playwright Custom Fixtures with Examples”