Introduction: Why Fixtures Are Important in Playwright Automation
As Playwright automation projects grow, one common challenge is avoiding duplicate setup code.
Imagine writing the same steps in every test:
- Launch browser
- Open application
- Login
- Create test data
- Navigate to dashboard
Repeating this code across hundreds of test files makes the framework difficult to maintain.
This is where Playwright Test Fixtures become one of the most powerful features of the Playwright Test Runner.
Fixtures help you create reusable setup and cleanup logic, making your tests cleaner, more maintainable, and easier to scale.
Whether you are:
- QA Automation Engineer
- SDET
- Selenium Engineer transitioning to Playwright
- Software Testing Student
- Developer
- Interview Candidate
Understanding Playwright Test Fixtures is essential for building enterprise-grade automation frameworks.
In this guide, you’ll learn:
- What Playwright Test Fixtures are
- How Playwright fixtures work
- Built-in fixtures
- Custom fixtures
- Test scope vs worker scope
- TypeScript examples
- Best practices
- Interview questions
- FAQs
Let’s begin.
What Are Playwright Test Fixtures?
A fixture is reusable setup and cleanup code that Playwright automatically provides to your tests.
Instead of manually creating objects inside every test, Playwright injects them when needed.
Simple Definition
Playwright Test Fixtures are reusable resources that prepare the environment before a test runs and clean it up after the test completes.
Fixtures reduce duplication and improve readability.
Why Fixtures Are Important
Without fixtures, every test contains repeated setup code.
Example:
test(‘Login Test’, async ({ page }) => {
await page.goto(‘https://example.com’);
// login
});
Another test:
test(‘Profile Test’, async ({ page }) => {
await page.goto(‘https://example.com’);
// login again
});
The same logic appears repeatedly.
Fixtures allow you to write the setup once and reuse it everywhere.
Benefits include:
- Better code reuse
- Easier maintenance
- Test isolation
- Cleaner test files
- Enterprise-ready frameworks
How Playwright Fixtures Work
Every Playwright test automatically receives fixtures.
Example:
test(‘Example’, async ({ page }) => {
});
The page object is already created by Playwright.
Execution flow:
Playwright Test Runner
↓
Create Fixtures
↓
Run Test
↓
Dispose Fixtures
↓
Next Test
The Test Runner manages the entire lifecycle automatically.
Built-in Fixtures Explained
Playwright provides several built-in fixtures.
1. page
The most commonly used fixture.
test(‘Home Page’, async ({ page }) => {
await page.goto(‘https://example.com’);
});
Used for browser interactions.
2. browser
Represents the browser instance.
test(‘Browser Example’, async ({ browser }) => {
const context = await browser.newContext();
});
Useful for creating additional Browser Contexts.
3. context
Represents the Browser Context.
test(‘Context Example’, async ({ context }) => {
const page = await context.newPage();
});
Each context provides isolated cookies, storage, and permissions.
4. request
Used for API testing.
test(‘API Example’, async ({ request }) => {
const response = await request.get(‘/users’);
});
This fixture is commonly used for backend validation.
5. browserName
Returns the current browser.
test(‘Browser Name’, async ({ browserName }) => {
console.log(browserName);
});
Useful for cross-browser testing.
Built-in Fixture Summary
| Fixture | Purpose | Typical Use |
| page | Browser page | UI testing |
| browser | Browser instance | Create contexts |
| context | Browser Context | Session isolation |
| request | API client | API testing |
| browserName | Browser identifier | Cross-browser logic |
How Playwright Fixtures Work Internally
When a test starts:
Playwright Test
↓
Fixture Created
↓
Injected into Test
↓
Test Executes
↓
Fixture Cleanup
You don’t need to manually create or destroy these resources.
Test Scope vs Worker Scope Fixtures
Understanding fixture scope is important for framework design.
Test-Scoped Fixture
Created for every test.
Test 1
↓
New Fixture
↓
Disposed
↓
Test 2
↓
New Fixture
Advantages:
- Complete isolation
- Parallel execution
- No shared state
Worker-Scoped Fixture
Created once per worker process.
Worker Starts
↓
Fixture Created
↓
Test 1
↓
Test 2
↓
Test 3
↓
Fixture Destroyed
Advantages:
- Faster execution
- Shared expensive resources
- Reduced setup time
Worker scope is useful for database connections or shared services.
Creating Custom Fixtures
One of the biggest advantages of Playwright is test.extend().
Example:
import { test as base } from ‘@playwright/test’;
export const test = base.extend({
loggedInPage: async ({ page }, use) => {
await page.goto(‘https://example.com/login’);
await page.fill(‘#username’, ‘admin’);
await page.fill(‘#password’, ‘admin123’);
await page.click(‘button’);
await use(page);
}
});
This custom fixture performs login before every test.
Using Custom Fixtures
import { test } from ‘./fixtures’;
test(‘Dashboard Test’, async ({ loggedInPage }) => {
await loggedInPage.goto(‘/dashboard’);
});
Notice how the login code disappears from the test.
This makes tests:
- Cleaner
- Smaller
- Easier to maintain
Real-World Playwright Fixture Example (TypeScript)
import { test as base, expect } from ‘@playwright/test’;
type MyFixtures = {
dashboardPage: import(‘@playwright/test’).Page;
};
export const test = base.extend<MyFixtures>({
dashboardPage: async ({ page }, use) => {
await page.goto(‘https://example.com/login’);
await page.getByLabel(‘Username’).fill(‘admin’);
await page.getByLabel(‘Password’).fill(‘admin123’);
await page.getByRole(‘button’, { name: ‘Login’ }).click();
await expect(page).toHaveURL(/dashboard/);
await use(page);
}
});
test(‘Verify Dashboard’, async ({ dashboardPage }) => {
await expect(
dashboardPage.getByRole(‘heading’, { name: ‘Dashboard’ })
).toBeVisible();
});
What Happens?
- Fixture launches login page.
- User logs in automatically.
- Dashboard opens.
- Test begins.
- Fixture cleans up after execution.
Fixture Lifecycle Diagram
Playwright Test Runner
│
▼
Create Fixture
│
▼
Fixture Setup
│
▼
Execute Test
│
▼
Fixture Cleanup
│
▼
Next Test
This automatic lifecycle helps keep tests isolated and maintainable.
Enterprise Authentication Fixture Example
Large automation projects rarely perform login inside every test. Instead, they create reusable authentication fixtures.
Without fixtures:
Test 1
↓
Open Browser
↓
Login
↓
Execute Test
↓
Logout
——————-
Test 2
↓
Open Browser
↓
Login Again
↓
Execute Test
The same login steps are repeated in every test.
With Playwright fixtures:
Authentication Fixture
↓
Login Once
↓
Provide Logged-in Page
↓
Run Test
↓
Automatic Cleanup
This reduces duplicate code and keeps tests focused on business logic.
Enterprise Framework Folder Structure
A well-organized Playwright framework separates fixtures from page objects, utilities, and test data.
playwright-framework/
│
├── tests/
│ ├── login.spec.ts
│ ├── dashboard.spec.ts
│ └── orders.spec.ts
│
├── pages/
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│ └── OrdersPage.ts
│
├── fixtures/
│ ├── auth.fixture.ts
│ ├── api.fixture.ts
│ └── database.fixture.ts
│
├── utils/
│ ├── config.ts
│ ├── helpers.ts
│ └── logger.ts
│
├── test-data/
│ ├── users.json
│ └── products.json
│
├── playwright.config.ts
│
└── package.json
This structure makes enterprise automation frameworks easier to maintain and scale.
Enterprise Login Fixture Example
Instead of writing login steps inside every test, create one reusable fixture.
auth.fixture.ts
import { test as base, expect } from ‘@playwright/test’;
type AuthFixtures = {
authenticatedPage: import(‘@playwright/test’).Page;
};
export const test = base.extend<AuthFixtures>({
authenticatedPage: async ({ page }, use) => {
await page.goto(‘https://example.com/login’);
await page.getByLabel(‘Username’).fill(‘admin’);
await page.getByLabel(‘Password’).fill(‘admin123’);
await page.getByRole(‘button’, {
name: ‘Login’
}).click();
await expect(page).toHaveURL(/dashboard/);
await use(page);
}
});
dashboard.spec.ts
import { test, expect } from ‘../fixtures/auth.fixture’;
test(‘Dashboard Test’, async ({ authenticatedPage }) => {
await expect(
authenticatedPage.getByText(‘Dashboard’)
).toBeVisible();
});
Notice how the test only contains business validation. Login is completely reusable.
Why Enterprises Use Fixtures
Playwright Fixtures help enterprise teams by:
- Eliminating repeated setup code
- Reducing maintenance effort
- Supporting parallel execution
- Improving test isolation
- Standardizing framework design
- Simplifying onboarding for new automation engineers
Best Practices for Using Fixtures
1. Keep Fixtures Small
A fixture should perform one responsibility only.
Good examples:
- Login
- API client setup
- Database connection
- Test data preparation
Avoid combining unrelated logic into a single fixture.
2. Prefer Test-Scoped Fixtures
Test-scoped fixtures provide better isolation and reduce the risk of tests affecting each other.
3. Use Worker Scope Carefully
Worker-scoped fixtures are ideal for expensive resources such as:
- Database connections
- Shared API clients
- Large datasets
Do not store test-specific state in worker fixtures.
4. Combine Fixtures with Page Object Model
Fixtures should create reusable objects, while Page Objects should contain page interactions.
Example:
authenticatedPage
↓
DashboardPage
↓
Dashboard Tests
This separation keeps the framework modular.
5. Use Meaningful Fixture Names
Prefer descriptive names such as:
- authenticatedPage
- adminUser
- apiClient
- databaseConnection
Instead of generic names like:
- page1
- fixture
6. Clean Up Resources
Always release resources after the test finishes.
Playwright automatically disposes built-in fixtures, but custom fixtures should also clean up any external resources if needed.
Common Mistakes to Avoid
Mistake 1: Writing Large Fixtures
Avoid creating fixtures that perform multiple unrelated tasks.
Mistake 2: Repeating Login Logic
Login should exist in one reusable fixture rather than in every test file.
Mistake 3: Ignoring Fixture Scope
Using worker-scoped fixtures for test-specific data can lead to shared state and unpredictable failures.
Mistake 4: Mixing Business Logic with Fixtures
Fixtures should prepare the environment, not verify application behavior.
Assertions belong in test cases.
Mistake 5: Not Reusing Fixtures
If the same setup appears in multiple tests, consider moving it into a fixture.
Troubleshooting Tips
Fixture Not Running
Check that the test imports the extended test object instead of the default Playwright test.
Login Happens Multiple Times
Review the fixture scope. A test-scoped fixture runs for every test, while a worker-scoped fixture runs once per worker.
Fixture Timeout
Ensure that setup actions, such as login or API initialization, complete within the configured timeout.
Shared State Between Tests
Avoid storing mutable data in worker-scoped fixtures unless it is intentionally shared.
Playwright Fixtures Interview Questions
1. What are Playwright Test Fixtures?
Answer:
Fixtures are reusable setup and cleanup resources that Playwright automatically injects into tests.
2. Why are fixtures useful?
Answer:
They improve code reuse, reduce duplication, simplify maintenance, and support test isolation.
3. Name some built-in Playwright fixtures.
Answer:
- page
- browser
- context
- request
- browserName
4. What is a custom fixture?
Answer:
A reusable fixture created using test.extend() to provide project-specific setup logic.
5. How do you create a custom fixture?
Answer:
Use:
const test = base.extend({
});
6. What is the difference between test-scoped and worker-scoped fixtures?
Answer:
- Test scope creates a new fixture for every test.
- Worker scope creates one fixture shared across tests running in the same worker.
7. Why should login be implemented as a fixture?
Answer:
It removes duplicate code and ensures consistent authentication across tests.
8. Can fixtures improve parallel execution?
Answer:
Yes. Proper fixture design supports isolated execution and efficient resource management.
9. How do fixtures improve maintainability?
Answer:
Changes to setup logic are made in one place rather than across multiple test files.
10. What is test.extend()?
Answer:
It is the Playwright API used to define custom fixtures.
Frequently Asked Questions (FAQs)
What are Playwright Test Fixtures?
Playwright Test Fixtures are reusable setup and cleanup resources automatically provided to test cases.
Are Playwright fixtures suitable for beginners?
Yes. Beginners can start with built-in fixtures like page and gradually create custom fixtures using test.extend().
What is the difference between built-in and custom fixtures?
Built-in fixtures are provided by Playwright, while custom fixtures are created by developers for reusable project-specific functionality.
When should I use worker-scoped fixtures?
Use them for expensive shared resources such as database connections or API clients that do not need to be recreated for every test.
Do fixtures improve test isolation?
Yes. Test-scoped fixtures create fresh resources for each test, preventing interference between tests.
Can fixtures work with the Page Object Model?
Yes. Fixtures and Page Objects complement each other, making automation frameworks cleaner and easier to maintain.
