Reuse code with custom test fixtures in Playwright
Playwright’s test fixtures are a powerful feature for sharing setup and teardown logic across tests, promoting code reuse and maintainability. This guide walks you through creating a custom test fixture, demonstrating its utility in a practical scenario.
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 setup and teardown.
Fixtures: Puppeteer vs Playwright
Both Playwright and Puppeteer are open source projects to help you simulate user behavior with a browser on your site. Their approach to fixtures—reusable pieces of code for setting up and tearing down tests—differs significantly. While fixtures are a basic component in Playwright, Puppeteer doesn’t offer native fixture support, relying on manually coded helper functions.
Puppeteer and Fixtures
Puppeteer, developed by the Chrome DevTools team, focuses on simplicity and direct control over the Chrome or Chromium browser. It doesn’t natively support fixtures in the same way Playwright does. Instead, setup and teardown routines must be manually coded within each test or abstracted into reusable functions by the developer. This approach offers flexibility but can lead to more boilerplate code and a higher chance of inconsistency across tests.
Custom Test Fixture Implementation
To use fixtures in Puppeteer, developers typically create helper functions or classes that encapsulate setup and teardown logic. This method requires more upfront work and a good understanding of asynchronous JavaScript patterns.
This pattern mimics fixture behavior but lacks the built-in, framework-level support found in Playwright, potentially leading to more complex and less maintainable test suites.
Playwright and Fixtures
Playwright and its test runner Playwright Test, on the other hand, 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.
Choosing between Puppeteer and Playwright
While both Puppeteer and Playwright offer powerful capabilities for browser automation, their approach to fixtures represents a significant difference in philosophy. Puppeteer offers flexibility and direct control, requiring developers to implement their own fixture-like functionality. Playwright, in contrast, integrates fixtures deeply into its testing framework, promoting code reuse, maintainability, and ease of use.
Choosing between Puppeteer and Playwright will depend on your project’s specific needs, the complexity of your tests, and your team’s familiarity with JavaScript testing patterns. If fixtures and test structure are a priority, Playwright Test might be the more suitable choice. However, for projects that require fine-grained browser control and are less concerned with test setup and teardown complexity, Puppeteer remains a strong option.
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.
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.