Skip to main content

๐Ÿ—๏ธ Module 6 โ€“ Page Object Model (POM)

Page Object Model is the most widely used design pattern in test automation.


โœ… What is POM?โ€‹

Without POM:

// Test file doing everything
test('Login test', async ({ page }) => {
await page.goto('https://practicetestautomation.com/practice-test-login/');
await page.locator('#username').fill('student');
await page.locator('#password').fill('Password123');
await page.locator('#submit').click();
await expect(page.locator('.post-title')).toHaveText('Logged In Successfully');
});

With POM:

// Test file is clean and readable
test('Login test', async ({ page }) => {
const homePage = new HomePage(page);
const loginResultsPage = new LoginResultsPage(page);

await homePage.goto();
await homePage.login('student', 'Password123');
await loginResultsPage.verifyLoginSuccess();
});

โœ… Why Use POM?โ€‹

Without POMWith POM
Locators duplicated everywhereLocators defined once
Hard to maintainEasy to maintain
Tests hard to readTests read like plain English
One change = fix everywhereOne change = fix in one place

โœ… Project Structureโ€‹

playwright-tests/
โ”‚
โ”œโ”€โ”€ pages/
โ”‚ โ”œโ”€โ”€ BasePage.js
โ”‚ โ”œโ”€โ”€ HomePage.js
โ”‚ โ””โ”€โ”€ LoginResultsPage.js
โ”‚
โ”œโ”€โ”€ tests/
โ”‚ โ””โ”€โ”€ google-search-pom.spec.js
โ”‚
โ””โ”€โ”€ playwright.config.js

โœ… BasePageโ€‹

BasePage contains common methods shared across all pages.

class BasePage {
constructor(page) {
this.page = page;
}

async navigate(url) {
await this.page.goto(url);
}

async getTitle() {
return await this.page.title();
}

async waitForPageLoad() {
await this.page.waitForLoadState('domcontentloaded');
}
}

module.exports = { BasePage };

Key Concept:โ€‹

  • Every page object extends BasePage
  • Common methods written once
  • All pages inherit them automatically

โœ… HomePageโ€‹

Each page object represents one page of the application.

const { BasePage } = require('./BasePage');

class HomePage extends BasePage {
constructor(page) {
super(page);

// Define locators here
this.usernameInput = page.locator('#username');
this.passwordInput = page.locator('#password');
this.loginButton = page.locator('#submit');
}

async goto() {
await this.navigate('https://practicetestautomation.com/practice-test-login/');
}

async login(username, password) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}

module.exports = { HomePage };

Key Concepts:โ€‹

  • constructor โ†’ defines locators
  • super(page) โ†’ passes page to BasePage
  • Methods represent user actions

โœ… LoginResultsPageโ€‹

const { BasePage } = require('./BasePage');
const { expect } = require('@playwright/test');

class LoginResultsPage extends BasePage {
constructor(page) {
super(page);

this.successMessage = page.locator('.post-title');
this.errorMessage = page.locator('#error');
this.logoutButton = page.getByRole('link', { name: 'Log out' });
}

async verifyLoginSuccess() {
await expect(this.successMessage).toHaveText('Logged In Successfully');
}

async verifyLogoutButtonVisible() {
await expect(this.logoutButton).toBeVisible();
}

async verifyLoginFailed() {
await expect(this.errorMessage).toBeVisible();
}
}

module.exports = { LoginResultsPage };

โœ… Test File Using POMโ€‹

const { test } = require('@playwright/test');
const { HomePage } = require('../pages/HomePage');
const { LoginResultsPage } = require('../pages/LoginResultsPage');

test.describe('Login Page - POM', () => {

test('Successful login with valid credentials', async ({ page }) => {
const homePage = new HomePage(page);
const loginResultsPage = new LoginResultsPage(page);

await homePage.goto();
await homePage.login('student', 'Password123');

await loginResultsPage.verifyLoginSuccess();
await loginResultsPage.verifyLogoutButtonVisible();
});

test('Failed login with invalid credentials', async ({ page }) => {
const homePage = new HomePage(page);
const loginResultsPage = new LoginResultsPage(page);

await homePage.goto();
await homePage.login('wronguser', 'wrongpassword');

await loginResultsPage.verifyLoginFailed();
});

});

โœ… POM Best Practicesโ€‹

  • โœ… One file per page
  • โœ… Define all locators in constructor
  • โœ… Methods should represent user actions
  • โœ… Assertions can go in page objects or test files
  • โœ… Always extend BasePage
  • โœ… Keep test files clean and readable
  • โœ… Never hardcode URLs in test files

โœ… POM Flow Diagramโ€‹

Test File
โ”‚
โ”œโ”€โ”€ HomePage
โ”‚ โ”œโ”€โ”€ goto()
โ”‚ โ””โ”€โ”€ login()
โ”‚
โ””โ”€โ”€ LoginResultsPage
โ”œโ”€โ”€ verifyLoginSuccess()
โ”œโ”€โ”€ verifyLogoutButtonVisible()
โ””โ”€โ”€ verifyLoginFailed()

๐ŸŽฏ Summaryโ€‹

  • โœ… POM separates test logic from page logic
  • โœ… BasePage holds shared methods
  • โœ… Each page has its own class
  • โœ… Tests become clean and readable
  • โœ… Easy to maintain when UI changes

๐Ÿš€ Keep Goingโ€‹

Apply what you learned in this module: