Reuse code with custom test fixtures in Playwright
Playwright’s test fixtures are a powerful feature for sharing setup and logic across tests, promoting code reuse and maintainability. With custom fixtures, you can share code across multiple tests, resulting in a test base that is easier to expand and easier to maintain.
Understanding Playwright Test Fixtures
At their core, fixtures in Playwright are predefined components like page
, context
, browser
, and browserName
that you can use across different test cases. They encapsulate functionality into modular blocks, simplifying test code.
When writing multiple tests for a single site or service, you’re likely to end up performing the same tasks over and over, so we’d like to add fixtures to our Playwright tests that developers can re-use, rather than copying and pasting big blocks of code. This helps us follow the DRY prinicple: don’t repeat yourself when writing code!
Why we need custom fixtures
In our web shop, we’ve got a login process that we want to simulate and test before doing further account actions:
To open this dialog and log in, we might use some lines like the following:
This is a fine practice for a single test, but if we have dozens that all require login as a first step, we’ll find ourself copying and pasting this code several times. This means a lot of copied code, such that if we ever tweak the login process, we’ll have to update this copied code in dozens of places, not good!
Playwright and Fixtures
Playwright and its test runner Playwright Test, were designed with a broader testing scope and includes first-class support for fixtures. This design choice streamlines the setup and teardown process, making tests cleaner and reducing redundancy.
Built-in Support
Playwright’s test runner provides a rich set of predefined fixtures, such as browser
, context
, page
, and request
, and allows for easy customization and extension of these fixtures. This built-in support enables more structured and maintainable tests, with less manual setup required.
The ability to extend and customize fixtures in Playwright not only reduces boilerplate but also enhances test isolation and reusability across your test suite.
Create a Custom Test Fixture in Playwright Test
Imagine testing a web application where multiple tests require logging in. Repeating login logic in every test is inefficient and violates the DRY (Don’t Repeat Yourself) principle.
Let’s implement a fixture to reuse and share code across Playwright test cases.
Step 1: Extend the Test Object
First, create a new test file or use an existing one. Here, we’re testing a web application with Playwright, accessing environment variables, and performing UI interactions.
To create a custom fixture, extend the test
object provided by Playwright:
By using custom fixtures, test cases can reuse code defined in the test.extend
call. In this case, all tests using the extended test
method can rely on the webApp
fixture.
Step 2: Use the Custom Fixture
With the webApp
fixture defined, you can use it in your tests. The fixture code will only run when a test “requests” to use it. For example, the custom webApp
fixture will only be executed if a test case uses it.
Replace page
with webApp
and use the fixture in your test cases:
The webApp
fixture encapsulates the login logic, making it accessible to any test that requires it.
Step 3: Debugging and Validation
Debugging is crucial to ensure your fixture works as expected. Playwright’s inspector is invaluable here, allowing you to step through test execution and verify the setup and teardown processes.
Step 4: Sharing Across Tests
To share your custom fixture across different test files, encapsulate it in a separate module:
Then, in your test files, import this extended test
and use the custom fixtures:
This approach keeps your tests clean and focused, with shared logic abstracted into fixtures.
Replace beforeEach
/ afterEach
Hooks with Automatic Fixtures
As explained in the previous section, fixtures enable the sharing and distributing of code across Playwright tests. By default, fixture code is only executed when a test case accesses a fixture. But there are scenarios in which you always want to run your fixture code, like global beforeEach
and afterEach
hooks.
Let’s implement an automatic fixture to log the current time before and after every test case.
Define an Automatic Fixture
If you want to run code before and after each test case, you can reach for the commonly known beforeEach
and afterEach
hooks.
In the following example, we log the current time before and after every test to the console.
This approach works fine, but if you want to run the same code for every test, you must place these beforeEach
and afterEach
calls in every spec
file. Additionally, beforeEach
and afterEach
don’t share the same function scope. This behavior leads to unexpected errors in some scenarios.
A more convenient way to implement global test hooks is relying on automatic fixtures. Like the webApp
fixture defined in the previous section, you must extend the test
object to use automatic fixtures.
The main difference between default and automatic fixtures is that automatic fixtures run regardless. In our example, the timeLogger
fixture will be executed for every running test.
Note that you’ll want to update your other test spec files to import test
from our base.ts
, they probably have something like:
You’ll want to update them to read something like:
Conclusion
Custom test fixtures in Playwright streamline test setup and teardown, fostering code reuse and reducing boilerplate. Encapsulating common functionality, such as user logins or global setups, into fixtures ensures your tests are cleaner, more maintainable, and easier to read.
Playwright Fixtures Explained on YouTube
To discover more practical fixture examples, check out our YouTube playlist, which covers fixtures in even more depth.