Skip to main content
The Page Object Model (POM) is a design pattern that maps each page — or significant section of a page — to a dedicated Java class. Locators and interactions live inside that class; test logic lives in step definitions. This separation means that when the UI changes, you update one class, not every test that touches that page.

Why POM matters

  • Single source of truth — a locator is defined once and reused everywhere.
  • Readable testsloginModal.typeName("Fernando") is clearer than driver.findElement(By.id("name_input_input")).sendKeys("Fernando").
  • Easier maintenance — a changed selector requires editing one field, not hunting through dozens of step definitions.
  • Safe abstractions — page methods can bundle multiple low-level calls into a single intent-revealing operation.

The BasePage class

BasePage is the foundation every page object inherits from. It wraps raw Selenium calls with a 10-second WebDriverWait, so individual page objects never need to worry about timing.
public class BasePage {

    protected WebDriver driver;
    protected WebDriverWait wait;

    public BasePage(WebDriver driver) {
        this.driver = driver;
        this.wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    }

Core methods

Waits up to 10 seconds for the element to be visible in the DOM before returning it. Used internally by other BasePage methods.
protected WebElement find(By locator) {
    return wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
}
All boolean methods (isDisplayed, isClickable, isChecked) swallow exceptions intentionally. They are designed for assertions, not for control flow that must react to errors.

Concrete page objects

RiuHome

Represents the RIU Hotels & Resorts home page. Locators are private final fields; public methods expose named actions.
public class RiuHome extends BasePage {

    private final By menuButton    = By.xpath("//div/button[@aria-label='Menú']");
    private final By accessButton  = By.xpath("//div/button[@aria-label='RIU pro']");
    private final By acceptCookiesBtn = By.id("onetrust-accept-btn-handler");

    public RiuHome(WebDriver driver) {
        super(driver);
    }

    public void navigateToRiu()               { navigateTo("https://www.riu.com/es"); }
    public void clickAcceptCookies()           { click(this.acceptCookiesBtn); }
    public void clickRegister()                { click(this.accessButton); }
    public boolean isAcceptCookiesButtonVisible()  { return isDisplayed(this.acceptCookiesBtn); }
    public boolean isAcceptCookiesButtonClickable() { return isClickable(this.acceptCookiesBtn); }
    public boolean isAccessButtonVisible()     { return isDisplayed(this.accessButton); }
    public boolean isAccessButtonClickable()   { return isClickable(this.accessButton); }
}

LoginModal

Represents the registration/login modal dialog. It maps the full registration form — name, surname, email, country, date-of-birth calendar, gender, marketing consent, and terms checkbox.
public class LoginModal extends BasePage {

    private final By loginModal     = By.id("dialog");
    private final By registerTab    = By.id("riuClassRegister");
    private final By registerTitle  = By.xpath("//h2/b");
    private final By nameTextBox    = By.id("name_input_input");
    private final By surnameTextBox = By.id("surname_input_input");
    private final By emailTextBox   = By.id("email_input_input");
    private final By countrySelect  = By.id("country_select_menuButton");
    private final By Argentina      = By.id("AR");
    private final By genreSelect    = By.id("genderSelect_menuButton");
    private final By male           = By.id("H");
    private final By salesInfoRadioBtn = By.id("controlInformationYes");
    private final By termsCheckBox  = By.id("acceptConditionsCheck");
    private final By registerBtn    = By.xpath("//div[@id='dialog']//button[@type='submit']");
    private final By reqField       = By.xpath(
        "//riu-ui-calendar[@formcontrolname='birthDate']//div[text()=' Este campo es obligatorio. ']"
    );
    private final By calendar       = By.xpath(" //riu-ui-icon[@class='u-pointer']/i");
    private final By noDateBtn      = By.xpath(
        "//riu-ui-button//button[@data-qa='calendar-apply-button']"
    );

    public LoginModal(WebDriver driver) { super(driver); }

    // Navigation
    public void clickRegisterTab()             { click(this.registerTab); }
    public void clickRegisterBtn()             { click(this.registerBtn); }

    // Form filling
    public void typeName(String name)          { type(this.nameTextBox, name); }
    public void typeLastName(String lastname)  { type(this.surnameTextBox, lastname); }
    public void typeEmail(String email)        { type(this.emailTextBox, email); }

    // Dropdowns
    public void clickCountrySelect()           { click(this.countrySelect); }
    public void selectArgentina()              { click(this.Argentina); }
    public void clickGenreSelect()             { click(this.genreSelect); }
    public void selectMale()                   { click(this.male); }

    // Calendar
    public void openCalendar()                 { click(this.calendar); }
    public void continueWithoutDate()          { click(this.noDateBtn); }

    // Consent
    public void clickSalesRadioBtn()           { click(this.salesInfoRadioBtn); }
    public void clickTermsCheck()              { click(this.termsCheckBox); }
    public boolean isTermsUnchecked()          { return !isChecked(this.termsCheckBox); }

    // Visibility / state checks
    public boolean isLoginModalVisible()       { return isDisplayed(this.loginModal); }
    public boolean isRegisterTabVisible()      { return isDisplayed(this.registerTab); }
    public boolean isRegisterTabClickable()    { return isClickable(this.registerTab); }
    public boolean isRegisterTitleVisible()    { return isDisplayed(this.registerTitle); }
    public boolean isNameTextBoxVisible()      { return isDisplayed(this.nameTextBox); }
    public boolean isNameTextBoxClickable()    { return isClickable(this.nameTextBox); }
    public boolean isLastNameTextBoxVisible()  { return isDisplayed(this.surnameTextBox); }
    public boolean isLastNameTextBoxClickable(){ return isClickable(this.surnameTextBox); }
    public boolean isEmailTextBoxVisible()     { return isDisplayed(this.emailTextBox); }
    public boolean isEmailTextBoxClickable()   { return isClickable(this.emailTextBox); }
    public boolean isCountrySelectVisible()    { return isDisplayed(this.countrySelect); }
    public boolean isCountrySelectClickable()  { return isClickable(this.countrySelect); }
    public boolean isArgOptVisible()           { return isDisplayed(this.Argentina); }
    public boolean isNoDateBtnVisible()        { return isDisplayed(this.noDateBtn); }
    public boolean isGenreSelectVisible()      { return isDisplayed(this.genreSelect); }
    public boolean isGenreSelectClickable()    { return isClickable(this.genreSelect); }
    public boolean isMaleOptVisible()          { return isDisplayed(this.male); }
    public boolean isSalesRadioBtnVisible()    { return isDisplayed(this.salesInfoRadioBtn); }
    public boolean isSalesRadioBtnClickable()  { return isClickable(this.salesInfoRadioBtn); }
    public boolean isTermsCheckVisible()       { return isDisplayed(this.termsCheckBox); }
    public boolean isTermsCheckClickable()     { return isClickable(this.termsCheckBox); }
    public boolean isReqFielVisible()          { return isDisplayed(this.reqField); }

    // Data extraction
    public String  extractTitle()              { return locatorText(this.registerTitle); }
}

Inheritance chain

BasePage
├── RiuHome
└── LoginModal
All page objects inherit driver, wait, and every helper method from BasePage. They never interact with WebDriver directly — only through the inherited methods.

Extending BasePage for a new page

1

Create the class

Create a new file under src/test/java/pages/. Name it after the page it represents.
package pages;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import utilities.BasePage;

public class SearchResults extends BasePage {

    public SearchResults(WebDriver driver) {
        super(driver);
    }
}
2

Add locators as private fields

Declare each locator as a private final By field at the top of the class. Keep them close together so it is easy to see what the page maps.
private final By resultCards   = By.cssSelector(".hotel-card");
private final By filtersPanel  = By.id("filters-sidebar");
private final By sortDropdown  = By.id("sort-select");
3

Write intent-revealing public methods

Wrap locator interactions in descriptive methods. Avoid leaking By references into step definitions.
public boolean areResultsVisible() {
    return isDisplayed(this.resultCards);
}

public void selectSortOption(String optionId) {
    click(this.sortDropdown);
    click(By.id(optionId));
}
4

Instantiate it in a step definition

Create the page object inside the relevant step method, passing context.driver.
@When("the user searches for a hotel")
public void searchForHotel() {
    searchResults = new SearchResults(context.driver);
    Assert.assertTrue(searchResults.areResultsVisible());
}
Never store WebDriver as a static field. Static driver references cause test isolation failures when scenarios run in parallel. Always receive the driver through the constructor via TestContext.

Build docs developers (and LLMs) love