Skip to main content

What is Page Object Model?

The Page Object Model (POM) is a design pattern that creates an object-oriented representation of web pages in your test automation framework. Each page in your application is represented by a class that encapsulates the page’s elements and behaviors.
Key Benefits:
  • Separates test logic from page-specific code
  • Reduces code duplication
  • Makes tests easier to maintain when UI changes
  • Improves test readability

Structure of a Page Object

A typical page object class contains:
  1. Constructor - Initializes the page with a Playwright Page instance
  2. Locators - Element selectors stored as class attributes
  3. Methods - Actions that can be performed on the page

Real Examples from Our Test Suite

Let’s look at the LoginPage class from our test suite:
pages/login.py
from playwright.sync_api import Page
import pytest

class LoginPage:
    def __init__(self, page):
        self.page = page
        self.username_input = page.locator("[data-test=\"username\"]")
        self.password_input = page.locator("[data-test=\"password\"]")
        self.login_button = page.locator("[data-test=\"login-button\"]")

    def navigate(self):
        self.page.goto("https://www.saucedemo.com/")

    def login(self, username, password):
        self.username_input.fill(username)
        self.password_input.fill(password)
        self.login_button.click()
Key Components:
  • Locators: Defined in __init__ using data-test attributes
  • navigate(): Handles page navigation
  • login(): Encapsulates the login workflow

Using Page Objects in Tests

Here’s how these page objects are used in actual test cases:
from pages.login import LoginPage

def test_successful_login(page):
    login_page = LoginPage(page)
    login_page.navigate()
    login_page.login("standard_user", "secret_sauce")

    assert page.get_by_test_id("title").is_visible

Best Practices

Each method should do one thing well. For example, login() handles only the login action, not navigation and assertion.
# Good: Separate concerns
login_page.navigate()
login_page.login(username, password)

# Avoid: Doing too much in one method
login_page.navigate_and_login_and_verify(username, password)
Store locators in the constructor with meaningful names:
def __init__(self, page):
    self.page = page
    # Clear, descriptive names
    self.username_input = page.locator("[data-test='username']")
    self.password_input = page.locator("[data-test='password']")
    self.login_button = page.locator("[data-test='login-button']")
Page objects should return data for tests to assert on:
# Good: Return data
def get_cart_count(self) -> int:
    if self.cart_badge.count() == 0:
        return 0
    return int(self.cart_badge.text_content())

# Usage in test
assert cart_page.get_cart_count() == 2

# Avoid: Assertions in page objects
def verify_cart_count(self, expected):
    assert self.get_cart_count() == expected
Use parameters for dynamic elements:
def add_product(self, product_name: str):
    add_button = self.page.locator(f"#add-to-cart-{product_name}")
    add_button.click()

# Usage
cart_page.add_product("sauce-labs-backpack")
cart_page.add_product("sauce-labs-bike-light")

Comparison: With vs Without POM

Tests contain all page interaction details:
tests/test_login.py
def test_valid_login(page: Page):
    page.goto("https://www.saucedemo.com/")
    
    username_input = page.get_by_placeholder("Username")
    username_input.fill("standard_user")

    password_input = page.get_by_placeholder("Password")
    password_input.fill("secret_sauce")

    login_button = page.locator("input#login-button")
    login_button.click()
    
    assert page.get_by_test_id("title").is_visible
    assert page.url == "https://www.saucedemo.com/inventory.html"
Problems:
  • Locators repeated across tests
  • Hard to maintain when UI changes
  • Test logic mixed with implementation details
Common Mistake: Don’t include assertions in page object methods. Page objects should only interact with the page and return data. Let your tests handle assertions.

Next Steps

Fixtures

Learn how to set up test data and page objects using pytest fixtures

Test Data

Discover how to manage test data separately from test logic

Build docs developers (and LLMs) love