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