You have written 10 tests. They all go to the login page, fill in email, fill in password, click the login button. Now the designer changes the button text from "Login" to "Sign In." You update 10 tests. Next week, they add a captcha checkbox. You update 10 tests again. This is the life of a QA engineer without Page Object Model.
If you are coming from Selenium, you already know this pain. POM is not a new idea. But TypeScript makes it significantly cleaner than Java. No more PageFactory annotations, no @FindBy decorators, no initElements boilerplate. Just classes, properties, and methods. Clean. Typed. Simple.
import { test, expect } from '@playwright/test';
// Test 1
test('successful login', async ({ page }) => {
await page.goto('/banking/login');
await page.getByLabel('Email').fill('john@test.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByTestId('welcome-message')).toBeVisible();
});
// Test 2 -- same locators, copy-pasted
test('invalid credentials', async ({ page }) => {
await page.goto('/banking/login');
await page.getByLabel('Email').fill('wrong@test.com');
await page.getByLabel('Password').fill('wrongpass');
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByTestId('error-message')).toHaveText('Invalid email or password');
});
// Test 3 -- same locators again!
test('empty email shows validation', async ({ page }) => {
await page.goto('/banking/login');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Log in' }).click();
await expect(page.getByLabel('Email')).toHaveAttribute('aria-invalid', 'true');
});Three tests, and getByLabel('Email') appears 4 times. getByRole('button', { name: 'Log in' }) appears 3 times. When the button text changes, you fix 3 places. When you have 50 tests, you fix 50 places. This does not scale.
Page Object Model says: put all locators and actions for a page in a single class. Tests use the class methods. When the UI changes, you update one class. Zero tests change.
Q: Why do we need Page Object Model?
A: POM solves the duplicate locator problem. Without POM, every test has its own copy of locators like getByLabel('Email') and getByRole('button', { name: 'Login' }). When the UI changes -- a button is renamed, a field is moved -- you update every test file. With POM, locators live in a single page class. Tests call methods like loginPage.login(email, password). When the UI changes, you update one class. Zero tests change. It is the single most impactful pattern for maintaining large test suites.
Key Point: POM is not optional for serious projects. The moment you have 10+ tests touching the same page, you need a page object. TypeScript makes POM cleaner than Java or Python because of readonly properties, type safety, and zero boilerplate.
Key Point: POM centralizes locators in page classes. One UI change = one class update, zero test changes. TypeScript makes POM cleaner than any other language.