assert_
glossary ↓ downloads
track: java · selenium
suite 04 · first selenium framework

The Page Object Model

First Selenium framework20 min
as a manual tester

Your test cases reference shared modules: 'see Login procedure' instead of copy-pasting steps into all 40 cases.

in automation

The Page Object Model is that, for code: one class per page owns its locators and actions. The OOP lesson's promise, delivered.

Your tests so far mix three concerns in one method: locators, actions, and assertions. Fine for two tests; a disaster for two hundred. When the login page changes, you'd edit every test that logs in. The industry's answer is the Page Object Model (POM):

pages/LoginPage.java
public class LoginPage {
    private WebDriver driver;

    // locators live here — and ONLY here
    private By usernameField = By.id("user-name");
    private By passwordField = By.id("password");
    private By loginButton   = By.id("login-button");
    private By errorMessage  = By.cssSelector("[data-test='error']");

    public LoginPage(WebDriver driver) { this.driver = driver; }

    public void open() { driver.get("https://www.saucedemo.com"); }

    public void loginAs(String user, String pass) {
        driver.findElement(usernameField).sendKeys(user);
        driver.findElement(passwordField).sendKeys(pass);
        driver.findElement(loginButton).click();
    }

    public String getErrorMessage() {          // questions return values...
        return driver.findElement(errorMessage).getText();
    }
}
tests/LoginTest.java — no locators in sight
@Test
void lockedOutUserSeesError() {
    LoginPage loginPage = new LoginPage(driver);
    loginPage.open();
    loginPage.loginAs("locked_out_user", "secret_sauce");

    System.out.println("Error shown: " + loginPage.getErrorMessage());
    assertTrue(loginPage.getErrorMessage().contains("locked out"));
}
predict: what does this print? think it through, then reveal
output
[INFO] Running LoginTest
Error shown: Epic sadface: Sorry, this user has been locked out.
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Read the test aloud — it's plain English. That's the POM payoff #1. Payoff #2: when the login page's ids change, you edit four lines in one file and every test heals.

the architecture — tests call pages, pages drive the browser
src/test · tests LoginTest CheckoutTest ✓ assertions live here src/main · pages LoginPage ProductsPage CartPage locators hidden inside calls methods WebDriver real Chrome, driven by code

The rules that keep POM clean

Naming you'll meet in the wild: a method that navigates can return the next page object (return new ProductsPage(driver);) enabling chained, fluent tests. Nice pattern — adopt it when the basics feel comfortable, not before.
⚠ when it breaks — the classics
NullPointerException the moment a page method runs
java.lang.NullPointerException: Cannot invoke "org.openqa.selenium.WebDriver.findElement(…)" because "this.driver" is null

Fix: The page object never received the driver — either the constructor doesn't assign it (this.driver = driver;) or you created the page before @BeforeEach ran. Create pages inside the test method.

The test can't see my page class
java: cannot find symbol  symbol: class LoginPage

Fix: Folder and import mismatch: pages live under src/main/java/pages, so the test needs import pages.LoginPage;. If IntelliJ underlines the folder, right-click src/main/java → Mark Directory as → Sources Root.

⚡ exercise · build ProductsPage and finish the flow

Using your Suite 03 locator inventory:

  1. Write ProductsPage with: getTitle(), addBackpackToCart(), getCartCount().
  2. Write a test: login as standard_user → assert title is "Products" → add backpack → assert cart count is "1".
  3. Feel the payoff: your test file should contain zero By. references.
show ProductsPage
public class ProductsPage {
    private WebDriver driver;
    private By title     = By.className("title");
    private By addBackpack = By.id("add-to-cart-sauce-labs-backpack");
    private By cartBadge  = By.className("shopping_cart_badge");

    public ProductsPage(WebDriver d) { driver = d; }
    public String getTitle()      { return driver.findElement(title).getText(); }
    public void   addBackpackToCart() { driver.findElement(addBackpack).click(); }
    public String getCartCount()  { return driver.findElement(cartBadge).getText(); }
}
key takeaways
  • One class per page; locators private inside it; tests hold zero locators.
  • Pages report state, tests assert on it — never mix.
  • A UI change becomes a one-file fix. This pattern is why suites survive.
← Smart waits — the flakiness cure Suite test →