Table of contents
If you're using Playwright for end-to-end testing or synthetic monitoring with Checkly, you've likely considered reusing your test code across different test cases. A common approach for this is using Page Object Models (POMs). However, if you're like me, you might have mixed feelings about POMs—while they help organize your code, they can sometimes feel cumbersome to set up and maintain.
But recently, I found that pairing POMs with Playwright fixtures can significantly improve the developer experience. It simplifies test code and reduces the boilerplate needed to initialize and use POMs. If that sounds intriguing, let's dive in.
What is a Page Object Model?
If you're new to Page Object Models, here's a quick example. A POM is essentially a class that represents a specific area of your application under test. Let’s take a look at a DashboardPage
class for the Checkly web application:
class DashboardPage {
readonly url: string;
readonly page: Page;
readonly $homeDashboard: Locator;
constructor(page: Page) {
this.url = '<https://app.checklyhq.com/dashboard>';
this.page = page;
this.$homeDashboard = page.locator('#home-dashboard');
}
async navigate() {
await this.page.goto(this.url);
}
async isDashboardReady() {
await this.$homeDashboard.waitFor();
}
}
This DashboardPage
class encapsulates the locators and methods related to the dashboard area of the Checkly app. By using POMs, you can keep your test cases clean and maintainable.
Here’s an example of how you might use this in a test case:
test('log into Checkly', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login('user@example.com', 'password');
const dashboardPage = new DashboardPage(page);
await dashboardPage.isDashboardReady();
});
This approach is clear and readable, but it requires you to manually create instances of each POM and pass around the page
object. This is where Playwright fixtures can come in handy.
The Downsides of Page Object Models
Two things always bugged me when using POMs:
- Reaching outside of test cases: Playwright offers tools to keep functionality self-contained within your tests, but POMs often require you to reference external objects, which can feel messy.
- Object initialization: Manually initializing objects for every POM can be repetitive and tedious, especially in larger test suites.
Wouldn't it be nice if we could simply declare that we want to use a LoginPage
or DashboardPage
within our test cases and have them magically available without all the constructor calls? With Playwright fixtures, this is entirely possible.
Implementing Playwright Fixtures with Page Object Models
Let’s see how we can achieve this by extending Playwright’s fixtures.
First, create a base.ts
file where we'll extend Playwright’s test function:
import { test as base, expect } from '@playwright/test';
import { LoginPage } from './LoginPage';
import { DashboardPage } from './DashboardPage';
export const test = base.extend<{
loginPage: LoginPage;
dashboardPage: DashboardPage;
}>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
},
dashboardPage: async ({ page }, use) => {
await use(new DashboardPage(page));
},
});
export { expect };
In this file, we use Playwright’s extend
function to create custom fixtures. These fixtures automatically instantiate our LoginPage
and DashboardPage
classes, so they’re readily available in our test cases without additional setup.
Now, update your test cases to use the new test object from base.ts:
import { test } from './base';
test('log into Checkly', async ({ loginPage, dashboardPage }) => {
await loginPage.navigate();
await loginPage.login('user@example.com', 'password');
await dashboardPage.isDashboardReady();
});
Here’s what’s happening:
- We’ve replaced the standard
test
import with our extended test frombase.ts
. - In the test function,
loginPage
anddashboardPage
are now automatically available as fixtures.
This setup eliminates the need for repetitive constructor calls and keeps your test cases clean and concise.
Running the Tests
Let’s see this in action. Run your tests with:
npx playwright test --headed
You should see the test execute successfully, logging in and verifying the dashboard page, all with less boilerplate code.
See page object models with fixtures in action
Check out Stefan’s video on how to keep your code DRY with this method:
Conclusion
By combining Page Object Models with Playwright fixtures, you can streamline your test code and enhance the developer experience. This approach reduces repetition and makes your test cases more readable and maintainable. I’ve become a big fan of this method and will be incorporating more fixture-based POMs in my Playwright projects.
If you have any questions, feel free to ask in the comments or join our Checkly community Slack.