Intermediate Chapter 8 · 13 min read

Page Object Model (POM)

Structure tests using the Page Object design pattern — encapsulate page elements, actions, and verifications in reusable classes.

Page Object Model (POM)

The Page Object Model is the most important design pattern in Selenium testing. It separates test logic from page interaction code, making tests readable, maintainable, and resistant to UI changes.

Principles

  • Each page (or significant component) gets its own class
  • Locators and interactions are encapsulated in the page class
  • Test classes use page objects — they never touch raw WebDriver
  • When the UI changes, you update one page class, not dozens of tests

PageFactory (Java)

Java's PageFactory provides @FindBy annotations for declarative element location and lazy initialization. It's a cleaner syntax compared to manual driver.findElement() calls in page objects.

LoginPage.java
package com.selenium.course.pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class LoginPage {

    private WebDriver driver;

    // Locators via @FindBy annotations
    @FindBy(id = "username")
    private WebElement usernameInput;

    @FindBy(id = "password")
    private WebElement passwordInput;

    @FindBy(css = "button[type='submit']")
    private WebElement loginButton;

    @FindBy(css = ".error-message")
    private WebElement errorMessage;

    // Constructor — initializes PageFactory
    public LoginPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    // Actions
    public LoginPage enterUsername(String username) {
        usernameInput.clear();
        usernameInput.sendKeys(username);
        return this;  // Fluent API — enables chaining
    }

    public LoginPage enterPassword(String password) {
        passwordInput.clear();
        passwordInput.sendKeys(password);
        return this;
    }

    public HomePage clickLogin() {
        loginButton.click();
        return new HomePage(driver);
    }

    public LoginPage clickLoginExpectingError() {
        loginButton.click();
        return this;
    }

    // Verifications
    public String getErrorMessage() {
        return errorMessage.getText();
    }

    public boolean isLoginButtonDisplayed() {
        return loginButton.isDisplayed();
    }

    // Fluent login shortcut
    public HomePage loginAs(String username, String password) {
        return enterUsername(username)
            .enterPassword(password)
            .clickLogin();
    }
}
LoginTest.java
package com.selenium.course.tests;

import com.selenium.course.base.BaseTest;
import com.selenium.course.pages.LoginPage;
import com.selenium.course.pages.HomePage;
import org.testng.Assert;
import org.testng.annotations.Test;

public class LoginTest extends BaseTest {

    @Test
    public void testSuccessfulLogin() {
        LoginPage loginPage = new LoginPage(driver);

        // Fluent API chaining
        HomePage homePage = loginPage
            .enterUsername("admin")
            .enterPassword("admin123")
            .clickLogin();

        Assert.assertTrue(homePage.isWelcomeDisplayed());
    }

    @Test
    public void testInvalidLogin() {
        LoginPage loginPage = new LoginPage(driver);

        loginPage.enterUsername("wrong")
            .enterPassword("wrong")
            .clickLoginExpectingError();

        Assert.assertEquals(
            loginPage.getErrorMessage(),
            "Invalid credentials");
    }

    @Test
    public void testFluentLogin() {
        // One-liner using the convenience method
        LoginPage loginPage = new LoginPage(driver);
        HomePage home = loginPage.loginAs("admin", "admin123");
        Assert.assertTrue(home.isWelcomeDisplayed());
    }
}
pages/login_page.py
"""Login page object"""
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC


class LoginPage:

    # Locators
    USERNAME_INPUT = (By.ID, "username")
    PASSWORD_INPUT = (By.ID, "password")
    LOGIN_BUTTON = (By.CSS_SELECTOR, "button[type='submit']")
    ERROR_MESSAGE = (By.CSS_SELECTOR, ".error-message")

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    # Actions
    def enter_username(self, username):
        el = self.wait.until(EC.visibility_of_element_located(
            self.USERNAME_INPUT))
        el.clear()
        el.send_keys(username)
        return self  # Fluent API

    def enter_password(self, password):
        el = self.driver.find_element(*self.PASSWORD_INPUT)
        el.clear()
        el.send_keys(password)
        return self

    def click_login(self):
        self.driver.find_element(*self.LOGIN_BUTTON).click()
        from pages.home_page import HomePage
        return HomePage(self.driver)

    def click_login_expecting_error(self):
        self.driver.find_element(*self.LOGIN_BUTTON).click()
        return self

    # Verifications
    def get_error_message(self):
        return self.driver.find_element(*self.ERROR_MESSAGE).text

    def is_login_button_displayed(self):
        return self.driver.find_element(*self.LOGIN_BUTTON).is_displayed()

    # Convenience method
    def login_as(self, username, password):
        return (self.enter_username(username)
                    .enter_password(password)
                    .click_login())
tests/test_login.py
"""Login tests using Page Object Model"""
from pages.login_page import LoginPage
import pytest

def test_successful_login(driver):
    login_page = LoginPage(driver)

    home_page = (login_page
        .enter_username("admin")
        .enter_password("admin123")
        .click_login())

    assert home_page.is_welcome_displayed()

def test_invalid_login(driver):
    login_page = LoginPage(driver)

    (login_page
        .enter_username("wrong")
        .enter_password("wrong")
        .click_login_expecting_error())

    assert login_page.get_error_message() == "Invalid credentials"

def test_fluent_login(driver):
    login_page = LoginPage(driver)
    home = login_page.login_as("admin", "admin123")
    assert home.is_welcome_displayed()

Selenium Intermediate Page Object Model (POM)

Written by PV

© 2026 All Rights Reserved