Lesson 7 of 14 ~35 min
Course progress
0%

Implementing Page Object Pattern

Learn to build maintainable test code with Page Object Model

Understanding Page Object Model (POM)

Page Object Model is a design pattern that creates an object repository for web UI elements, making test code more maintainable and reusable.

Why Page Object Model?

Without POM:

// ❌ Hard to maintain - locators scattered everywhere
@Test
public void testLogin() {
    driver.findElement(By.id("username")).sendKeys("user");
    driver.findElement(By.id("password")).sendKeys("pass");
    driver.findElement(By.id("loginBtn")).click();
}

@Test
public void testInvalidLogin() {
    driver.findElement(By.id("username")).sendKeys("wrong");
    driver.findElement(By.id("password")).sendKeys("wrong");
    driver.findElement(By.id("loginBtn")).click();
}

With POM:

// ✅ Clean, maintainable, reusable
@Test
public void testLogin() {
    loginPage.login("user", "pass");
}

@Test
public void testInvalidLogin() {
    loginPage.login("wrong", "wrong");
}

Core POM Principles

  1. Encapsulation - Keep locators and actions within page classes
  2. Abstraction - Hide implementation details from tests
  3. Reusability - Share page objects across multiple tests
  4. Maintainability - Change locators in one place

Basic Page Object Structure

package com.automation.pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;

public class LoginPage {
    private WebDriver driver;
    
    // Locators
    private By usernameField = By.id("username");
    private By passwordField = By.id("password");
    private By loginButton = By.id("loginBtn");
    private By errorMessage = By.className("error-message");
    
    // Constructor
    public LoginPage(WebDriver driver) {
        this.driver = driver;
    }
    
    // Actions
    public void enterUsername(String username) {
        driver.findElement(usernameField).sendKeys(username);
    }
    
    public void enterPassword(String password) {
        driver.findElement(passwordField).sendKeys(password);
    }
    
    public void clickLogin() {
        driver.findElement(loginButton).click();
    }
    
    // Combined action
    public void login(String username, String password) {
        enterUsername(username);
        enterPassword(password);
        clickLogin();
    }
    
    // Verification
    public String getErrorMessage() {
        return driver.findElement(errorMessage).getText();
    }
    
    public boolean isErrorDisplayed() {
        try {
            return driver.findElement(errorMessage).isDisplayed();
        } catch (Exception e) {
            return false;
        }
    }
}

Using PageFactory

Selenium provides PageFactory for cleaner initialization:

package com.automation.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;
    
    // Page elements using @FindBy
    @FindBy(id = "username")
    private WebElement usernameField;
    
    @FindBy(id = "password")
    private WebElement passwordField;
    
    @FindBy(id = "loginBtn")
    private WebElement loginButton;
    
    @FindBy(className = "error-message")
    private WebElement errorMessage;
    
    // Constructor with PageFactory initialization
    public LoginPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }
    
    // Actions - cleaner code
    public void enterUsername(String username) {
        usernameField.sendKeys(username);
    }
    
    public void enterPassword(String password) {
        passwordField.sendKeys(password);
    }
    
    public void clickLogin() {
        loginButton.click();
    }
    
    public HomePage login(String username, String password) {
        enterUsername(username);
        enterPassword(password);
        clickLogin();
        return new HomePage(driver);  // Page navigation
    }
    
    public String getErrorMessage() {
        return errorMessage.getText();
    }
}

Advanced FindBy Strategies

// By ID
@FindBy(id = "username")
private WebElement usernameField;

// By Name
@FindBy(name = "email")
private WebElement emailField;

// By ClassName
@FindBy(className = "btn-primary")
private WebElement submitButton;

// By CSS Selector
@FindBy(css = "input[type='email']")
private WebElement emailInput;

// By XPath
@FindBy(xpath = "//button[text()='Submit']")
private WebElement submitBtn;

// By LinkText
@FindBy(linkText = "Sign Up")
private WebElement signUpLink;

// By PartialLinkText
@FindBy(partialLinkText = "Sign")
private WebElement signLink;

// By TagName
@FindBy(tagName = "h1")
private WebElement pageHeading;

// Multiple locators (fallback)
@FindBy(how = How.ID, using = "username")
private WebElement username;

// List of elements
@FindBy(className = "product-item")
private List<WebElement> products;

Base Page Class

Create a base class for common functionality:

package com.automation.pages;

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;

public class BasePage {
    protected WebDriver driver;
    protected WebDriverWait wait;
    
    public BasePage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }
    
    // Wait methods
    protected void waitForElementToBeClickable(WebElement element) {
        wait.until(ExpectedConditions.elementToBeClickable(element));
    }
    
    protected void waitForElementToBeVisible(WebElement element) {
        wait.until(ExpectedConditions.visibilityOf(element));
    }
    
    // Click methods
    protected void click(WebElement element) {
        waitForElementToBeClickable(element);
        element.click();
    }
    
    protected void jsClick(WebElement element) {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript("arguments[0].click();", element);
    }
    
    // Input methods
    protected void type(WebElement element, String text) {
        waitForElementToBeVisible(element);
        element.clear();
        element.sendKeys(text);
    }
    
    // Get methods
    protected String getText(WebElement element) {
        waitForElementToBeVisible(element);
        return element.getText();
    }
    
    protected String getTitle() {
        return driver.getTitle();
    }
    
    // Scroll methods
    protected void scrollToElement(WebElement element) {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        js.executeScript("arguments[0].scrollIntoView(true);", element);
    }
    
    // Verification methods
    protected boolean isDisplayed(WebElement element) {
        try {
            return element.isDisplayed();
        } catch (Exception e) {
            return false;
        }
    }
}

Complete Page Object Example

package com.automation.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 ProductPage extends BasePage {
    
    @FindBy(id = "product-name")
    private WebElement productName;
    
    @FindBy(id = "product-price")
    private WebElement productPrice;
    
    @FindBy(id = "add-to-cart")
    private WebElement addToCartButton;
    
    @FindBy(className = "success-message")
    private WebElement successMessage;
    
    @FindBy(css = ".quantity-input")
    private WebElement quantityInput;
    
    public ProductPage(WebDriver driver) {
        super(driver);
        PageFactory.initElements(driver, this);
    }
    
    public String getProductName() {
        return getText(productName);
    }
    
    public String getProductPrice() {
        return getText(productPrice);
    }
    
    public void setQuantity(int quantity) {
        type(quantityInput, String.valueOf(quantity));
    }
    
    public void addToCart() {
        click(addToCartButton);
    }
    
    public boolean isSuccessMessageDisplayed() {
        return isDisplayed(successMessage);
    }
    
    public String getSuccessMessage() {
        return getText(successMessage);
    }
    
    // Fluent interface
    public ProductPage selectQuantity(int quantity) {
        setQuantity(quantity);
        return this;
    }
    
    public CartPage addToCartAndGoToCart() {
        addToCart();
        // Navigate to cart
        return new CartPage(driver);
    }
}

Test Class Using Page Objects

package com.automation.tests;

import com.automation.pages.LoginPage;
import com.automation.pages.HomePage;
import com.automation.pages.ProductPage;
import io.github.bonigarcia.wdm.WebDriverManager;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.testng.Assert;
import org.testng.annotations.*;

public class ProductTest {
    private WebDriver driver;
    private LoginPage loginPage;
    private HomePage homePage;
    private ProductPage productPage;
    
    @BeforeMethod
    public void setUp() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.get("https://example.com");
        
        // Initialize page objects
        loginPage = new LoginPage(driver);
        homePage = new HomePage(driver);
        productPage = new ProductPage(driver);
    }
    
    @Test
    public void testAddProductToCart() {
        // Login
        homePage = loginPage.login("testuser", "password123");
        
        // Navigate to product
        productPage = homePage.selectProduct("Laptop");
        
        // Verify product details
        Assert.assertEquals(productPage.getProductName(), "Laptop");
        
        // Add to cart
        productPage.selectQuantity(2).addToCart();
        
        // Verify success
        Assert.assertTrue(productPage.isSuccessMessageDisplayed());
        Assert.assertEquals(productPage.getSuccessMessage(), 
            "Product added to cart");
    }
    
    @AfterMethod
    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }
}

Page Object Best Practices

  1. One page class per web page or logical component
  2. Keep tests in separate packages from page objects
  3. Use meaningful method names that describe actions
  4. Return page objects for navigation (fluent interface)
  5. Avoid assertions in page objects - only in tests
  6. Use BasePage for common functionality
  7. Keep page objects simple - one method per action

Fluent Interface Pattern

// Chain methods for better readability
loginPage
    .enterUsername("testuser")
    .enterPassword("password")
    .clickLogin()
    .verifyLoginSuccess();

// With return types
HomePage homePage = loginPage
    .login("user", "pass")
    .verifyWelcomeMessage();

Handling Dynamic Elements

@FindBy(css = ".product-item")
private List<WebElement> productItems;

public ProductPage selectProductByName(String productName) {
    for (WebElement product : productItems) {
        if (product.getText().contains(productName)) {
            click(product);
            break;
        }
    }
    return new ProductPage(driver);
}

public int getProductCount() {
    return productItems.size();
}

Next Steps

In the next lesson, we’ll integrate TestNG framework for better test organization and execution.

Key Takeaways

✅ POM separates page structure from test logic
✅ PageFactory simplifies element initialization
✅ BasePage reduces code duplication
✅ Fluent interfaces improve test readability

What is the main benefit of Page Object Model?

Faster test execution
Better browser performance
Improved test maintainability
Automatic bug detection

Where should assertions be placed?

In page object classes
In test classes only
In both page objects and tests
In the BasePage class

What does PageFactory.initElements() do?

Initializes WebElements annotated with @FindBy
Creates new page instances
Starts the browser
Runs test methods