Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Implementation:SeleniumHQ Selenium Page Object Test Pattern

From Leeroopedia
Knowledge Sources
Domains Test_Design, Design_Patterns, Quality_Assurance
Last Updated 2026-02-11 00:00 GMT

Overview

Pattern documentation for structuring Selenium tests using Page Objects with PageFactory initialization and assertion separation.

Description

This is a Pattern Doc -- it documents a user-defined pattern, not a library API. The pattern involves: (1) creating Page Object classes with @FindBy annotations on WebElement or List<WebElement> fields, (2) initializing them with PageFactory.initElements() in the constructor, (3) writing domain-specific public methods that encapsulate element interactions, and (4) writing test methods that call Page Object methods and make assertions.

AbstractFindByBuilder validates annotations at initialization time. Its assertValidFindBy() method ensures at most one location strategy is specified per @FindBy (throwing IllegalArgumentException with a message like "You must specify at most one location strategy. Number found: N (...)"). Its assertValidFindBys() and assertValidFindAll() methods validate each @FindBy within @FindBys and @FindAll arrays respectively. If no locator attributes are set on a field, PageFactory defaults to looking up by the field name as an ID or name attribute.

Usage

Follow this pattern for all Page Object-based tests. The test class handles setup (WebDriver creation), teardown (driver.quit()), and assertions; the Page Object handles browser interaction. Use @CacheLookup for static elements to improve performance.

Code Reference

Source Location

  • Repository: Selenium
  • File: java/src/org/openqa/selenium/support/AbstractFindByBuilder.java (L25-121)
  • File: java/src/org/openqa/selenium/support/PageFactory.java (L34-133)
  • File: java/src/org/openqa/selenium/support/FindBy.java (L53-90)
  • File: java/src/org/openqa/selenium/support/CacheLookup.java (L29-31)

Interface Specification

// Pattern: Page Object class structure
public class ExamplePage {
    private WebDriver driver;

    // 1. Annotated WebElement fields with locator strategies
    @FindBy(id = "element-id")
    private WebElement element;

    @FindBy(css = ".static-element")
    @CacheLookup
    private WebElement cachedElement;

    // 2. Constructor with PageFactory initialization
    public ExamplePage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    // 3. Domain-specific interaction methods (return Page Objects for navigation)
    public ResultPage performAction(String input) {
        element.clear();
        element.sendKeys(input);
        element.submit();
        return new ResultPage(driver);
    }

    // 4. State query methods (for assertions in tests)
    public String getDisplayedText() {
        return cachedElement.getText();
    }

    public boolean isElementVisible() {
        return element.isDisplayed();
    }
}

// Pattern: Test class structure
public class ExampleTest {
    private WebDriver driver;

    @BeforeEach
    void setUp() {
        driver = new ChromeDriver();
    }

    @AfterEach
    void tearDown() {
        driver.quit();
    }

    @Test
    void testUserJourney() {
        driver.get("https://example.com");
        ExamplePage page = new ExamplePage(driver);
        ResultPage result = page.performAction("test input");
        assertEquals("Expected result", result.getDisplayedText());
    }
}

Import

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.CacheLookup;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.AfterEach;
import static org.junit.jupiter.api.Assertions.*;

I/O Contract

Inputs

Name Type Required Description
WebDriver WebDriver Yes Active browser session; passed to Page Object constructors and PageFactory.initElements()
Page Object classes Class Yes Classes with @FindBy-annotated fields and domain-specific methods
Test data Various Yes Input values for Page Object methods and expected values for assertions

Outputs

Name Type Description
Test results Pass/Fail JUnit/TestNG test outcomes based on assertions

Validation Behavior

Validation When Error
Multiple locator strategies in single @FindBy PageFactory.initElements() call IllegalArgumentException: "You must specify at most one location strategy"
how set without using PageFactory.initElements() call IllegalArgumentException: "If you set the 'how' property, you must also set 'using'"
Class cannot be instantiated PageFactory.initElements(context, Class) call RuntimeException wrapping ReflectiveOperationException

Usage Examples

Complete Test Example

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.CacheLookup;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.FindAll;
import org.openqa.selenium.support.PageFactory;
import org.junit.jupiter.api.*;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;

// Page Object: SearchPage
class SearchPage {
    private WebDriver driver;

    @FindBy(name = "q")
    private WebElement searchBox;

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

    public SearchPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public ResultsPage search(String query) {
        searchBox.clear();
        searchBox.sendKeys(query);
        searchButton.click();
        return new ResultsPage(driver);
    }
}

// Page Object: ResultsPage
class ResultsPage {
    private WebDriver driver;

    @FindAll({
        @FindBy(css = ".result-item"),
        @FindBy(css = ".search-result")
    })
    private List<WebElement> results;

    @FindBy(css = ".result-count")
    private WebElement resultCount;

    public ResultsPage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public int getResultCount() {
        return results.size();
    }

    public String getFirstResultText() {
        return results.get(0).getText();
    }

    public String getResultCountLabel() {
        return resultCount.getText();
    }
}

// Test Class
class SearchTest {
    private WebDriver driver;

    @BeforeEach
    void setUp() {
        driver = new ChromeDriver();
        driver.get("https://example.com/search");
    }

    @AfterEach
    void tearDown() {
        driver.quit();
    }

    @Test
    void shouldReturnResultsForValidQuery() {
        SearchPage searchPage = new SearchPage(driver);
        ResultsPage results = searchPage.search("selenium");
        assertTrue(results.getResultCount() > 0,
            "Expected at least one search result");
    }

    @Test
    void shouldDisplayResultCountLabel() {
        SearchPage searchPage = new SearchPage(driver);
        ResultsPage results = searchPage.search("webdriver");
        assertNotNull(results.getResultCountLabel(),
            "Expected result count label to be present");
    }
}

Using PageFactory Class-Based Initialization

// PageFactory creates the instance, trying WebDriver constructor first
// then falling back to no-arg constructor
@Test
void shouldLoginWithClassBasedInit() {
    LoginPage loginPage = PageFactory.initElements(driver, LoginPage.class);
    DashboardPage dashboard = loginPage.login("admin", "secret");
    assertEquals("Welcome, admin", dashboard.getGreeting());
}

Page Object Inheritance

// Base page with shared elements (PageFactory walks superclass hierarchy)
abstract class BasePage {
    protected WebDriver driver;

    @FindBy(id = "nav-menu")
    @CacheLookup
    protected WebElement navMenu;

    @FindBy(css = ".logout-btn")
    @CacheLookup
    protected WebElement logoutButton;

    public BasePage(WebDriver driver) {
        this.driver = driver;
        PageFactory.initElements(driver, this);
    }

    public LoginPage logout() {
        logoutButton.click();
        return new LoginPage(driver);
    }
}

class DashboardPage extends BasePage {
    @FindBy(css = ".greeting")
    private WebElement greeting;

    public DashboardPage(WebDriver driver) {
        super(driver);  // PageFactory initializes fields in both classes
    }

    public String getGreeting() {
        return greeting.getText();
    }
}

Related Pages

Implements Principle

Uses Heuristic

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment