Series Navigation
→ Part 9: Handling Windows, Frames, Alerts and Pop-ups
The Problem Without POM
Consider 50 tests that all start with a login. Each has:
driver.findElement(By.id("username")).sendKeys("user@example.com");
driver.findElement(By.id("password")).sendKeys("password");
driver.findElement(By.id("loginBtn")).click();
The login button's ID changes from loginBtn to login-submit. You update 50 tests.
The Page Object Model fixes this: one change in one place.
What Is the Page Object Model?
Each page of your application gets a Java class. The class:
- Declares locators as fields
- Exposes methods representing user actions
- Never contains assertions (keep tests and pages separate)
Test Classes Page Object Classes
───────────── ───────────────────
LoginTest → LoginPage
(username, password, loginBtn locators)
(login(), clickForgotPassword() methods)
DashboardTest → DashboardPage
(welcomeMsg, logoutLink locators)
(getWelcomeMessage(), logout() methods)
Project Structure with POM
src/test/java/com/yourname/selenium/
├── base/
│ └── BaseTest.java ← browser setup/teardown
├── pages/
│ ├── BasePage.java ← shared page utilities
│ ├── LoginPage.java
│ ├── DashboardPage.java
│ ├── RegisterPage.java
│ └── components/
│ ├── Header.java ← reusable nav bar component
│ └── Footer.java
├── tests/
│ ├── LoginTest.java
│ ├── DashboardTest.java
│ └── RegisterTest.java
├── utils/
│ ├── WaitUtils.java
│ └── ScreenshotUtils.java
└── listeners/
└── TestListener.java
BasePage — Shared Utilities
// src/test/java/com/yourname/selenium/pages/BasePage.java
package com.yourname.selenium.pages;
import org.openqa.selenium.*;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
public abstract class BasePage {
protected WebDriver driver;
protected WebDriverWait wait;
public BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(driver, 15);
// Initialize @FindBy annotations
PageFactory.initElements(driver, this);
}
// Protected helpers available to all page objects
protected void click(WebElement element) {
wait.until(ExpectedConditions.elementToBeClickable(element));
element.click();
}
protected void type(WebElement element, String text) {
wait.until(ExpectedConditions.visibilityOf(element));
element.clear();
element.sendKeys(text);
}
protected String getText(WebElement element) {
wait.until(ExpectedConditions.visibilityOf(element));
return element.getText();
}
protected boolean isDisplayed(WebElement element) {
try {
return element.isDisplayed();
} catch (NoSuchElementException | StaleElementReferenceException e) {
return false;
}
}
protected void waitForUrl(String urlFragment) {
wait.until(ExpectedConditions.urlContains(urlFragment));
}
protected void scrollToElement(WebElement element) {
((JavascriptExecutor) driver)
.executeScript("arguments[0].scrollIntoView(true);", element);
}
public String getPageTitle() {
return driver.getTitle();
}
public String getCurrentUrl() {
return driver.getCurrentUrl();
}
}
LoginPage with PageFactory
// src/test/java/com/yourname/selenium/pages/LoginPage.java
package com.yourname.selenium.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
public class LoginPage extends BasePage {
// ── Locators declared as fields with @FindBy ───────────────────────────
@FindBy(id = "username")
private WebElement usernameInput;
@FindBy(id = "password")
private WebElement passwordInput;
@FindBy(css = "button[type='submit']")
private WebElement loginButton;
@FindBy(id = "flash")
private WebElement flashMessage;
@FindBy(css = "a[href='/forgot-password']")
private WebElement forgotPasswordLink;
@FindBy(css = ".error-container")
private WebElement errorContainer;
// ── Constructor ────────────────────────────────────────────────────────
public LoginPage(WebDriver driver) {
super(driver); // calls PageFactory.initElements
}
// ── Navigation ─────────────────────────────────────────────────────────
public LoginPage open() {
driver.get("https://the-internet.herokuapp.com/login");
return this;
}
// ── Actions ────────────────────────────────────────────────────────────
public LoginPage enterUsername(String username) {
type(usernameInput, username);
return this; // enables method chaining
}
public LoginPage enterPassword(String password) {
type(passwordInput, password);
return this;
}
public DashboardPage clickLogin() {
click(loginButton);
return new DashboardPage(driver); // return next page
}
public LoginPage clickLoginExpectingError() {
click(loginButton);
return this; // stay on login page on failure
}
// Convenience method for successful login
public DashboardPage loginAs(String username, String password) {
return enterUsername(username)
.enterPassword(password)
.clickLogin();
}
public LoginPage clickForgotPassword() {
click(forgotPasswordLink);
return this;
}
// ── Getters for assertions ─────────────────────────────────────────────
public String getFlashMessage() {
return getText(flashMessage);
}
public boolean isErrorDisplayed() {
return isDisplayed(errorContainer);
}
public String getErrorMessage() {
return getText(errorContainer);
}
public boolean isLoginPageDisplayed() {
return isDisplayed(usernameInput) && isDisplayed(passwordInput);
}
}
DashboardPage
// src/test/java/com/yourname/selenium/pages/DashboardPage.java
package com.yourname.selenium.pages;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
public class DashboardPage extends BasePage {
@FindBy(css = "h2.subheader")
private WebElement secureAreaHeading;
@FindBy(css = "a.button.secondary")
private WebElement logoutButton;
@FindBy(id = "flash")
private WebElement successMessage;
public DashboardPage(WebDriver driver) {
super(driver);
// Verify we're on the right page
waitForUrl("/secure");
}
public boolean isDashboardDisplayed() {
return isDisplayed(secureAreaHeading);
}
public String getHeadingText() {
return getText(secureAreaHeading);
}
public String getSuccessMessage() {
return getText(successMessage);
}
public LoginPage logout() {
click(logoutButton);
return new LoginPage(driver);
}
}
Clean Test Classes with POM
// src/test/java/com/yourname/selenium/tests/LoginTest.java
package com.yourname.selenium.tests;
import com.yourname.selenium.base.BaseTest;
import com.yourname.selenium.pages.DashboardPage;
import com.yourname.selenium.pages.LoginPage;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
public class LoginTest extends BaseTest {
@Test(description = "Valid login redirects to dashboard")
public void testSuccessfulLogin() {
DashboardPage dashboard = new LoginPage(getDriver())
.open()
.loginAs("tomsmith", "SuperSecretPassword!");
Assert.assertTrue(dashboard.isDashboardDisplayed(),
"Dashboard should be visible after login");
Assert.assertTrue(dashboard.getSuccessMessage().contains("logged into a secure area"),
"Success message should appear");
}
@Test(description = "Invalid password shows error")
public void testInvalidPassword() {
LoginPage loginPage = new LoginPage(getDriver())
.open()
.enterUsername("tomsmith")
.enterPassword("wrongpassword")
.clickLoginExpectingError();
Assert.assertTrue(loginPage.isLoginPageDisplayed(),
"Should stay on login page");
Assert.assertTrue(loginPage.getFlashMessage().contains("invalid"),
"Error message should appear");
}
@Test(description = "Logout returns to login page")
public void testLogout() {
LoginPage loginPage = new LoginPage(getDriver())
.open()
.loginAs("tomsmith", "SuperSecretPassword!")
.logout();
Assert.assertTrue(loginPage.isLoginPageDisplayed(),
"Should return to login page after logout");
Assert.assertTrue(loginPage.getFlashMessage().contains("logged out"),
"Logout confirmation message should appear");
}
@DataProvider(name = "invalidCredentials")
public Object[][] invalidCredentials() {
return new Object[][] {
{ "wronguser", "SuperSecretPassword!", "username is invalid" },
{ "tomsmith", "wrongpassword", "password is invalid" },
{ "", "", "username is invalid" },
};
}
@Test(dataProvider = "invalidCredentials")
public void testInvalidCredentials(String user, String pass, String expectedMsg) {
String actualMsg = new LoginPage(getDriver())
.open()
.enterUsername(user)
.enterPassword(pass)
.clickLoginExpectingError()
.getFlashMessage();
Assert.assertTrue(actualMsg.contains(expectedMsg),
"Expected: '" + expectedMsg + "' in '" + actualMsg + "'");
}
}
@FindBy Locator Strategies
// All supported @FindBy strategies
@FindBy(id = "username")
@FindBy(name = "email")
@FindBy(className = "btn-primary")
@FindBy(tagName = "h1")
@FindBy(linkText = "Sign in")
@FindBy(partialLinkText = "Sign")
@FindBy(xpath = "//input[@type='email']")
@FindBy(css = "input.form-control[type='email']")
// Multiple locators — tries each until one works
@FindBys({
@FindBy(id = "submit"),
@FindBy(css = "button[type='submit']")
})
// All elements matching ALL conditions (AND)
@FindAll({
@FindBy(css = ".btn"),
@FindBy(css = ".primary")
})
// List of elements
@FindBy(css = "table tbody tr")
private List<WebElement> tableRows;
What's Next
In Part 9 we handle browser complexity — switching between multiple windows and tabs, dealing with iframes that contain your content, and handling JavaScript alerts and confirmation dialogs.
Discussion
Loading...Leave a Comment
All comments are reviewed before appearing. No links please.