Page Object Model with Playwright

Let’s play with Playwright and create a small automation test framework that implements Page Object Model.

A month ago Playwright had a big release and they introduces their own test runner. Four weeks later I finally found a bit of a time to play with it.

I’ve never touched this tool before and I spent just one hour to set up everything from scratch (the only thing I had installed was node.js) and create first working test and all pages implementation . That’s impressive! Especially if you compare this with pure Selenium, where you can spend hours just installing everything and writing wrappers for driver, page factories, etc.

So, let me show you everything. We start from the beginning.

Installation

First of all, you need to install `node.js`. If you don’t have it on your computer, please visit official website and follow installation steps.

Then create a folder for your project and run

$ npm init 

to initialise node project. It will guide you through a simple wizard to set up a project.

Once it’s done we can install Playwright with npm install -D playwright . This will install Playwright as dev dependency.

After that we can install Playwright test runner

$ npm install -D @playwright/test 

and

$ npx playwright install

to install all browsers (Playwright supports 3 browsers — Chromium, Firefox, WebKit) for test runner. Unlike Playwright itself, test runner doesn’t come with bundled browsers, so you have to install them separately.

And that’s pretty much it. Now you have everything installed. Playwright supports tests parallelisation out of the box, so you don’t need to spend your time developing complex testing framework, and can concentrate on tests.

Implementation

Playwright has three levels of abstraction when comes to managing browsers and parallelisation:

browser — represents browser and can be shared between tests to minimise resources;

context — represents isolated context for single test run. You can think of it as of a browser tab opened in incognito mode;

page — represents page and responsible for all actions performed on web pages.

Playwright test runner has implemented fixtures for each of this abstractions and it automatically manages their creation, so you don’t really need to think a lot about it and can just use page fixture in your tests.

And one of my favourite features — Playwright manages all waits, so you don’t need to care about it at all, just configure maximum timeout value and forget about pain.

With all that knowledge we can start. I decided to write tests for http://angular.realworld.io/ — I often use it for different tests projects.

We will test simple end to end scenario:

  1. Open home page
  2. Navigate to login page
  3. Login
  4. Check that user is logged in
  5. Logout
  6. Check that user is logged out

Let’s first implement HomePage class. I decided to use TypeScript in this example, because I feel myself more confident having types.

As I said earlier the only thing that we need to import from playwright is Page fixture.

For our scenario we need to implement these functions on home page:

  1. Open page
  2. Navigate to login page
  3. Check that user is logged in
pages/homepage.ts
import type { Page } from 'playwright';export class HomePage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async open() {
await this.page.goto('http://angular.realworld.io/');
}
async goToLoginPage() {
await this.page.click('a[routerlink="/login"]');
}
async userIsLoggedIn(): Promise<boolean> {
await page.waitForSelector(locator);
return await page.isVisible(locator);
}
}

Since Playwright is asynchronous we have to use async await syntax, which could be a little annoying. We will make assertion in the test, so our userIsLoggedIn() function should return boolean that we can then assert in test.

In our case login page has only one behaviour — perform login to user account.

pages/loginpage.ts
import type { Page } from 'playwright';export class LoginPage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
async login(email: string, password: string) {
await this.page.type('input[type="email"]', email);
await this.page.type('input[type="password"]', password);
await this.page.click('button[type="submit"]');
}
}

I think, you already get the idea. We need to implement 2 pages for our test, but I skip them, because they look exactly the same, you can see the whole working example in this repo.

Next thing is to implement test itself. Playwright test runner’s syntax is very similar to other JS test runners like Jest and Mocha. Playwright has library expect to perform assertions. It contains a bunch of different conditions odd the shelf and you can create your custom ones. Let’s see how it looks like:

import { test, expect } from '@playwright/test';import { user } from './testdata';
import { HomePage } from '../pages/home-page';
import { LoginPage } from '../pages/login-page';
import { SettingsPage } from '../pages/settings-page';
import { LogoutPage } from '../pages/logout-page';
test('User can login and logout', async ({ page }) => {
const homepage = new HomePage(page);

await homepage.open();
await homepage.goToLoginPage();
await new LoginPage(page).login(user.email, user.password)
const userIsLoggedIn = await homepage.userIsLoggedIn();
expect(userIsLoggedIn).toBeTruthy();
await homepage.goToSettings();
await new SettingsPage(page).logout();
const userIsLoggedOut =
await new LogoutPage(page).userIsLoggedOut();
expect(userIsLoggedOut).toBeTruthy();
});

We pass page fixture to our test function and then use it to create objects of page classes. If you worked with pytest fixtures it can look familiar.

That’s almost it. We already have working test and we can run it by simply typing to the console

$ npx playwright test

and it will run this test in chromium by default. We can also make some adjustments like timeout time, browser, window size, etc. in configuration file. I did like this. This configuration runs test in all 3 available browsers in parallel:

import { PlaywrightTestConfig } from '@playwright/test';const config: PlaywrightTestConfig = {
// Timeout
timeout: 10000,
use: {
// Browser options
headless: true,
// Context options
viewport: { width: 1280, height: 720 },
// Artifacts
screenshot: 'only-on-failure',
},
projects: [
{
name: 'Chrome',
use: { browserName: 'chromium' },
},
{
name: 'Firefox',
use: { browserName: 'firefox' },
},
{
name: 'WebKit',
use: { browserName: 'webkit' },
},
],
};
export default config;

Ok, this time that’s really it. We have working scenario and spend less than our for setting everything up from scratch.

Conclusion

I like Playwright. It’s easy to use, it’s fast, it’s evolving. Microsoft made a huge work with it and continue to improve, so maybe soon it can even compete with Selenium.

I just wanted to show how to create Page Object Pattern, so we have touched only small part of the Playwright here. It has a various different features that can be really helpful, but I need more time to try them.

I think, if you starting automation in your project or company and you have most of the business logic written on frontend and don’t have complex backend, you can really look on Playwright.

But for complex enterprise products Selenium is still the best tool to choose.

Thanks for reading this article!

You can find out more about Playwright on its official web page .

Completed project with this example is available on my github page.

If you have questions or just want to connect, you can find me on LinkedIn.

Test Automation Engineer