Selenium Page Object Model (POM) with TestNG & Java: A Guide

In modern test automation, maintainability and scalability matter as much as execution speed. As applications grow, poorly structured Selenium tests quickly turn into fragile scripts. The combination of Selenium WebDriver, Page Object Model (POM), TestNG, and Java provides a clean and scalable automation architecture.

In this guide, you will learn how to build a complete Selenium Page Object Model framework using TestNG and Java with a real, working example.

Understanding the Core Components

What is Page Object Model (POM)?

Page Object Model is a design pattern where each web page is represented as a Java class. All page elements and actions are encapsulated inside that class. If UI changes, only the page class needs updating, not every test.

Why Selenium WebDriver?

Selenium WebDriver allows direct browser automation using real user interactions. It supports all major browsers and integrates well with Java-based test frameworks.

Why TestNG?

  • Annotation-based lifecycle management
  • Parallel test execution
  • Data-driven testing using DataProviders
  • Rich HTML reports

Project Structure


src/main/java
└── com/example/automation/pages
    ├── BasePage.java
    ├── LoginPage.java
    └── HomePage.java

src/test/java
└── com/example/automation/tests
    └── LoginTest.java

pom.xml
testng.xml

Creating the Base Page Class

The BasePage class handles WebDriver setup, teardown, and common functionality.


// BasePage.java
package com.example.automation.pages;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.PageFactory;
import io.github.bonigarcia.wdm.WebDriverManager;

import java.time.Duration;

public class BasePage {
    protected WebDriver driver;

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

    public void setUp() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
    }

    public void tearDown() {
        if (driver != null) {
            driver.quit();
        }
    }

    public void navigateTo(String url) {
        driver.get(url);
    }
}

Designing Page Object Classes

LoginPage.java


// LoginPage.java
package com.example.automation.pages;

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

public class LoginPage extends BasePage {

    @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;

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

    public HomePage login(String username, String password) {
        usernameInput.sendKeys(username);
        passwordInput.sendKeys(password);
        loginButton.click();
        return new HomePage(driver);
    }

    public LoginPage loginWithInvalidCredentials(String username, String password) {
        usernameInput.sendKeys(username);
        passwordInput.sendKeys(password);
        loginButton.click();
        return this;
    }

    public String getFlashMessageText() {
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        wait.until(ExpectedConditions.visibilityOf(flashMessage));
        return flashMessage.getText();
    }

    public boolean isLoginButtonDisplayed() {
        return loginButton.isDisplayed();
    }
}

HomePage.java


// HomePage.java
package com.example.automation.pages;

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

public class HomePage extends BasePage {

    @FindBy(className = "icon-signout")
    private WebElement logoutButton;

    @FindBy(xpath = "//h2[contains(text(),'Secure Area')]")
    private WebElement secureAreaHeader;

    public HomePage(WebDriver driver) {
        super(driver);
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
        wait.until(ExpectedConditions.visibilityOf(secureAreaHeader));
    }

    public boolean isLogoutButtonDisplayed() {
        return logoutButton.isDisplayed();
    }

    public String getSecureAreaHeaderText() {
        return secureAreaHeader.getText();
    }

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

Creating TestNG Test Class

LoginTest.java


// LoginTest.java
package com.example.automation.tests;

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

public class LoginTest {

    private WebDriver driver;
    private LoginPage loginPage;
    private String baseUrl = "http://the-internet.herokuapp.com/login";

    @BeforeMethod
    public void setup() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
        driver.get(baseUrl);
        loginPage = new LoginPage(driver);
    }

    @Test
    public void testSuccessfulLogin() {
        HomePage homePage = loginPage.login("tomsmith", "SuperSecretPassword!");
        Assert.assertTrue(homePage.isLogoutButtonDisplayed());
        Assert.assertEquals(homePage.getSecureAreaHeaderText(), "Secure Area");
        homePage.logout();
    }

    @Test
    public void testInvalidLogin() {
        loginPage.loginWithInvalidCredentials("wrong", "wrong");
        Assert.assertTrue(loginPage.getFlashMessageText().contains("invalid"));
    }

    @AfterMethod
    public void tearDown() {
        driver.quit();
    }
}

Running Tests Using testng.xml

<suite name="LoginSuite">
  <test name="LoginTests">
    <classes>
      <class name="com.example.automation.tests.LoginTest">
    </class></classes>
  </test>
</suite>

Best Practices

  • Keep assertions only in test classes
  • Use explicit waits instead of Thread.sleep()
  • One page = one Page Object
  • Reuse page methods across tests

Frequently Asked Questions (FAQs)

Q1: What is the main advantage of using the Page Object Model?
The main advantage of the Page Object Model is improved test maintainability and reusability. When UI elements or their locators change, only the corresponding page object class needs to be updated instead of modifying multiple test cases. This significantly reduces maintenance effort in large automation suites.

Q2: Can I use Page Object Model with frameworks other than TestNG?
Yes, the Page Object Model is a design pattern and is not limited to TestNG. It can be used with other testing frameworks such as JUnit, Cucumber, or even with different programming languages. TestNG is commonly preferred due to its powerful features like parallel execution and test grouping.

Q3: How can I manage multiple browser instances in a Selenium POM framework?
Multiple browser instances are usually managed using a thread-safe approach where each test thread has its own WebDriver instance. This is commonly achieved by using a ThreadLocal-based driver manager, which ensures isolated and conflict-free parallel test execution.

Q4: When should I avoid using the Page Object Model?
Page Object Model may be unnecessary for very small projects with only a few pages and minimal test cases. In such scenarios, the overhead of creating separate page classes might outweigh the benefits. However, for scalable or long-term projects, POM is strongly recommended.

Q5: What is the difference between Page Object Model and the Screenplay Pattern?
Page Object Model represents web pages as objects that contain UI elements and actions. The Screenplay Pattern follows an actor-based approach, where actors perform tasks and interact with UI targets. Screenplay promotes better separation of concerns and improves test readability for complex automation frameworks.

Conclusion

Using Selenium with Page Object Model, TestNG, and Java creates a clean, maintainable, and scalable automation framework. This structure is production-ready and widely used in enterprise automation projects.

Popular posts from this blog

Mastering Selenium Practice: Automating Web Tables with Demo Examples

10 Demo Websites for Selenium Automation Practice in 2026

14+ Best Selenium Practice Exercises to Master Automation Testing (with Code & Challenges)

Real-World AI Use Cases in End-to-End Test Automation (2026 Guide)

Top 10 Highly Paid Indian-Origin CEOs in the USA

Selenium Automation for E-commerce Websites

Top 7 Web Development Trends in the Market

12 AI Tools You Should Start Using Today to Save Time & Work Smarter (2026)

Behavior-Driven Development (BDD) with Python Behave: A Complete Tutorial

Selenium IDE Tutorial: A Beginner's Guide to No-Code Automation Testing