Flexible and Dynamic Test Fixtures in Playwright

Playwright may have not been around for as long as some other E2E testing frameworks — if we consider Puppeteer to be a separate one — but it is rapidly making itself known for the flexibility that if offers to developers and how many features it comes pre-packaged with.
Why fixtures?
Let’s take an example of a simple Sign up module from a Web application. In order to create tests for such module, you will need to some input data such as User names, email addresses, passwords, etc that you test it with and that can be categorised as Test Fixtures.
Generally, test fixtures are used to set up the specific environment for your test, that it requires.
In principle, in alignment with general good practices in Software development, fixtures should have the following traits:
- Encapsulation — should not pollute test and page objects and provide simplicity in setting up and to maintain
- Reusability — should not be tightly coupled to a single (or a group of) tests
- On-demand — your tests should have the flexibility of not using fixtures, if they not required and vice versa
- Flexibility — should support grouping/ combination and composition of fixtures as needed
Fixtures in Playwright
Playwright comes with built-in support for some basic fixtures like Browser context, environment configurations, etc.
The cooler part though, is the support for custom fixtures like external JSON files and complex objects.
Let’s have a look at examples for each type that follow the traits we described above.
Generally, the implementation is performed in 4 steps:
- Define fixture file (for e.g. JSON/ TS class)
- Create a Type for the fixture
- Extend
test
object to include the fixture via the type created in Step 2 - Use the fixture as argument for your
test()
JSON Fixtures
JSON is a popular choice to structured static data like localisation, UI content for validation, configs or input data. To use a JSON fixture in a test, let’s create a new file, say, fixtures.ts
. We implement our fixture steps as below:
Import test
and JSON data file:
import {test as baseTest} from '@playwright/test'
import pageContent from './content/pageContent.json'
Create a Type for the fixture:
type pageData = {
pageContentData: typeof pageContent;
}
As JSON is natively supported in TS, we do not have to worry about parsing it explicitly now or later.
To use the fixture in our tests, we extend the test
object to include our fixture, and naturally, export it:
const fixture = baseTest.extend<pageData>({
pageContentData: pageContent
})export const test = fixture;
In the test file, without needing to import anything other than test
from our fixtures.ts
file, we can simply use the fixture like so:
Notice how you can clearly use only the fixture(s) that you need instead of a complete file — i.e. it is “On-demand”. This is a great advantage that comes with Playwright.
Object Fixtures
JSON is great when the data is static and does not need to be unique. UI tests almost always require input data that is unique for each test run, this calls for the flexibility said data to be open for manipulation or even to be generated uniquely for each test. This is easily done by creating the data as arbitrary Objects — thereby enabling Flexibility and Encapsulation.
Let’s imagine that our test needs input for a User which contains information like Name, email, password, etc.
We can define the relevant data as an interface:
Then we can implement it like below:
We have hardcoded password as it is not the focus of our test and for invalid emails, we are using a list of invalid formed email addresses.
To generate unique data each time, for example, for Name and Email in our case, I am using google’s Intermock.
We use the function as a provider to the constructor for our Data object UserDataImpl
within our fixture userData
and finally call user(userData)
so that it becomes available for tests.
Our fixture is now ready! We can provide it to the test similar to JSON fixtures before:
Regardless of the type of fixture used, all fixtures in playwright are Reusable across tests seamlessly. When creating them, it is important to consider flexibility i.e. how they can be consumed by more than one test.
Naturally, more complex data can be similarly modelled following the same method, allowing your tests and page objects to be clutter-free with a lot more control on input data for you.