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.