Home > Blog > Playwright: Organizing Test Dependencies with Fixtures
Author: Tomotaka ASAGI Published: Feb 07, 2026
Know the features, make better decisions.
Introduction
Test data is tricky, isn't it?
Login credentials, URLs, input values, expected display values, verification points… The same value appears in multiple places, and as tests grow, it gets harder to see which data pairs with which. You start losing track of the relationships between them.
When data is written directly in test cases, every spec change brings the same questions: "Where do I need to update?" "Was this value used somewhere else too?" Missed updates become a regular occurrence.
Separating test cases from the data they need makes maintenance easier. For small-scale tests, hard-coding data is perfectly fine. But as the scale grows, you need a deliberate approach to organizing cases and their data clearly.
Test data design is just as critical as test design itself when it comes to test automation.
In this article, I'll introduce Fixtures as one approach to this challenge.
What Are Fixtures?
Fixtures are a mechanism for preparing and providing what each test needs.
Official documentation: Playwright Test Fixtures
In test automation, you need "setup" work before tests run — preparing data, opening pages, and so on. Most test frameworks provide mechanisms like beforeEach and beforeAll to write common setup logic that runs before each test.
Fixtures are a different approach to this "setup" work. The key difference is that the test declares what it needs.
// beforeEach: runs for every test
let homePage: HomePage;
test.beforeEach(async ({ page }) => {
homePage = new HomePage(page); // Created even for tests that don't use it
});
// Fixtures: runs only when needed
test('verify home page', async ({ homePage }) => {
// homePage is created only for tests that declare it
});Where beforeEach says "prepare the same things for all tests," Fixtures say "each test receives only what it needs."
The official documentation highlights these advantages of Fixtures:
- Encapsulation: Setup and teardown in the same place
- Reusability: Define once, use in all test files
- On-demand: Only the fixtures needed by your test are set up
- Composable: Fixtures can depend on each other to provide complex behaviors
If you're familiar with software design patterns, you might recognize this as Dependency Injection (DI). That understanding is correct. But you don't need to know DI to use Fixtures effectively.
Fixtures in Practice: A Sample Repository
Let's look at how Fixtures work with real code.
The sample repository (toasagi/playwrite-tips) contains BDD-style tests using Playwright-bdd. It demonstrates two different approaches to test data management so you can compare them side by side.
First, let's look at the fixture definitions. In fixtures.ts, Page Objects and a test data container are defined:
Source code: steps/fixtures.ts
By defining Page Objects as fixtures, each test receives only what it needs as arguments. Profile tests use profilePage, cart tests use cartSection — anything not requested is never created.
Pay attention to testDataHolder at the end. This plays a key role in test data management.
Managing Test Data: Two Patterns
The "test data headache" I described in the introduction is addressed in this sample repository with two different patterns.
Pattern 1: Hard-coding data in Examples tables
The profile test in the sample repository writes data directly in Scenario Outline Examples:
Source code: features/user-profile.feature
With 3 fields (name, phone, payment), this table is still manageable.
But what happens when it grows to 10 or 20 fields? The table stretches horizontally, diffs become unreadable, and every row needs updating when a field is added.
Pattern 2: Loading data from external files with Fixtures
The cart test takes a different approach:
Scenario Outline: User adds products and verifies cart total
Given I am logged in as "<user>" with cart data
When I add the specified products to the cart
Then the cart total should be correct
Examples:
| user |
| yamada |
| sato |
| tanaka |Source code: features/shopping-cart.feature
Examples contains only the username. The detailed test data lives in JSON files:
{
"login": {
"username": "demo",
"password": "Demo@2025!"
},
"cart": {
"products": ["Smartphone", "T-shirt", "Programming Basics"],
"expectedTotal": "¥95,980"
}
}Source code: test-data/yamada.json
In the step definitions, the testDataHolder fixture loads JSON data and shares it across steps:
Source code: steps/shopping-cart.steps.ts
The benefits of this approach are clear:
- Test data is structured in JSON, making it highly readable
- Adding a field means updating only the JSON files
- Git diffs are easy to review
- Test data and test scenarios are separated, so each can be maintained independently
Combining with Authentication State
Notice the loadStorageState part in the step definition above.
This uses the storageState concept from Part 4, restoring pre-saved authentication state (cookies, localStorage). With Fixtures, "restoring authentication" and "loading test data" are combined into a single step, providing the complete test environment as a package.
Combined with Soft Assertions from Part 5, you can verify all results at once even on screens with many verification points.
storageState (Part 4) → Login once upfront
Soft Assertions (Part 5) → Verify multiple items at once
Fixtures (Part 6) → Package and deliver these to each testConclusion
Fixtures aren't for doing things that beforeEach "can't" do. They're for doing what beforeEach can do, but with better design.
- Test dependencies are clear from the declaration
- Only what's needed gets set up (no waste)
- Setup and cleanup live in the same place (easy to follow)
- Test data can be managed in external files and delivered through Fixtures
For more on Fixtures best practices, refer to the official documentation:
- Playwright Test Fixtures
- Playwright Best Practices
The sample code is available in the following repository:
- toasagi/playwrite-tips
This article is Part 6 of the Playwright series.
- Part 1: From Selenium to Playwright
- Part 2: Java vs TypeScript - Feature Differences by Language
- Part 3: Cucumber.js vs Playwright-bdd
- Part 4: Streamlining Authentication with storageState
- Part 5: Efficient Verification with Soft Assertions
- Part 6: Organizing Test Dependencies with Fixtures (this article)
Originally published at Arrangility.com