Front End Playwright Framework

One way to do Front End automation with Playwright Framework

A bit of background

From my previous experiences with automation, I'm usually the only automation tester on a project, so I'd write test specs with function names that were vague and not meaningful, but I knew what they did. This left the spec files only readable for myself or people who made the valiant effort to go through the code and understand the function.

This isn't an issue when I'm the primary user of the framework, but as the team grows, it becomes more difficult for new QAs to understand. Being on a project that has more than myself as the Automation QA, I understood the pain it took people to understand how my test suites worked before being able to add their own. After some discussion and feedback from my QA colleague, we came to an agreement which I have grown quite fond of, it not only simplifies reading the spec and understanding what it does but also has a clear understanding of why we wrote those test cases.

Starting out

This is how my automation test cases looked when I first started (albeit slightly better) and not much changed until recently.

typescript
import {fixtures} from "../fixtures";
import {SwagLabsPage} from "../pages/SwagLabsPage";
import {LoginPage} from "../pages/LoginPage";
const { it, describe, beforeEach } = fixtures;
let swagLabsPage;
let loginPage;
describe("SauceLabs UI", () => {
beforeEach(async ({ page }) => {
swagLabsPage = new SwagLabsPage(page);
loginPage = new LoginPage(page);
await page.goto("/");
});
it("Login", async () => {
//Logged in
await loginPage.isElementVisible(loginPage.loginButton)
await loginPage.setText(loginPage.usernameInput, "standard_user");
await loginPage.setText(loginPage.passwordInput, "secret_sauce");
await loginPage.page.click(loginPage.loginButton);
//Validate user logged in successfully
await swagLabsPage.isElementVisible(swagLabsPage.title)
await swagLabsPage.isElementVisible(swagLabsPage.burgerMenu)
await swagLabsPage.isElementVisible(swagLabsPage.inventory)
});
});

Are you able to understand what the test does without the grey comment lines? This was not something easily understandable, even from a developer's perspective.

The new and improved way:

The agreed way my colleague and I came to after some discussion on how the test cases can be improved. (Thanks Sandeep!)

typescript
import {fixtures} from "../fixtures";
import {SwagLabsPage} from "../pages/SwagLabsPage";
import {LoginPage} from "../pages/LoginPage";
const { it, describe, beforeEach } = fixtures;
let swagLabsPage;
let loginPage;
describe("SauceLabs UI", () => {
beforeEach(async ({ page }) => {
swagLabsPage = new SwagLabsPage(page);
loginPage = new LoginPage(page);
await page.goto("/");
});
it("Verify user able to login and view inventory page @smoke", async () => {
/**
* QA-1
* Scenario : As a consumer,
* I want to be able to log into the website,
* So that I can browse the products available
*/
await loginPage.login();
await swagLabsPage.assertUserLandsOnInventoryPage();
});
});

I feel it's much nicer with meaningful test names and a description with the correlating ticket number and the user story. In my perspective, this adds much more value, more easy to understand what is being tested, and where the requirement comes from. Going forward, I will be creating test cases like this from the beginning.

Underneath the hood:

This is easily achievable by moving all the actions and expectations into functions with meaningful names.

typescript
import { Page } from "playwright";
import {BasePage} from "./BasePage";
export class LoginPage extends BasePage {
usernameInput = `input[data-test="username"]`
passwordInput = `input[data-test="password"]`
loginButton = `input[data-test="login-button"]`
constructor(page: Page) {
super(page);
this.page = page;
}
async login(){
this.isElementVisible(this.loginButton)
this.setText(this.usernameInput, "standard_user");
this.setText(this.passwordInput, "secret_sauce");
this.page.click(this.loginButton);
}
}
typescript
import { Page } from "playwright";
import { BasePage } from "./BasePage";
export class SwagLabsPage extends BasePage {
title = `span[class="title"]`
burgerMenu = `button[id="react-burger-menu-btn"]`
inventory = `div[id="inventory_container"]`
constructor(page: Page) {
super(page);
this.page = page;
}
async assertUserLandsOnInventoryPage(){
await this.isElementVisible(this.title)
await this.isElementVisible(this.burgerMenu)
await this.isElementVisible(this.inventory)
}
}

Not a lot of change or rework is required to achieve this cleaner state.

Share your thoughts

What are your thoughts on the two versions, and which would you prefer? Do you see any improvements that you would make, or anything that isn't useful that should be removed?