Next-Gen App & Browser
Testing Cloud
Trusted by 2 Mn+ QAs & Devs to accelerate their release cycles
Understand the Page Object Model in detail, its purpose, structure, and how Page Factory helps build cleaner, maintainable, and scalable automated test suites.
Published on: October 12, 2025
In automation testing, keeping test scripts reliable and easy to maintain is often challenging. Test scripts can become brittle, redundant code can build up, and adapting to frequent UI changes can slow down development. This is where the Page Object Model (POM) solves these issues. It organizes web elements and actions into separate classes, creating a clear separation between test logic and UI structure. This approach reduces duplicate code, makes tests easier to maintain, and lets you update them quickly whenever the UI changes.
The Page Object Model is a test automation design pattern that organizes pages or components into classes, encapsulating locators and actions for readable, maintainable, resilient tests.
Why Use Page Object Model?
Adopting the Page Object Model provides several practical advantages for test automation, delivering stability, clarity, and maintainability through well-structured code. Some of the key benefits include:
How to Use Page Object Model?
Implementing the Page Object Model requires a structured, practical approach that keeps your automation code clean, maintainable, and scalable. Here are the steps to use POM:
Page Object Model in automation testing is a design pattern where each page is a class with locators and methods, separating UI from test logic to improve readability, reuse, and maintenance.
In POM:
You can also check out this video tutorial on Page Object Model by Koushik Chatterjee. He is a Founder of LetCode and a Senior Software Test Automation Engineer. Koushik brings close to 10 years of experience in test automation, specializing in building reliable frameworks and driving quality at scale.
POM centralizes page actions, hides UI details, and reduces duplicate code. It also improves readability, eases maintenance, and enables reusable, scalable, and well-structured test scripts.
Benefits:
Note: Run automated tests across over 3000 real environments. Try LambdaTest Now!
In the Page Object Model, an organized approach is followed, built around core components including page classes, locators, and methods. These core concepts are important for ensuring that the test automation scripts remain well-structured, maintainable, and easy to manage.
In POM, a dedicated class is created for every web page. This dedicated class contains all the page objects for the web elements on the page, including buttons, text boxes, checkboxes, links, etc. It also has the action methods, which are used for interacting with these web elements on the page.
By using page classes, POM helps keep test scripts clean, organized, and improves their maintainability. For example, if you are testing the Registration Page of a web application, the locators related to the respective page will be updated in the RegistrationPage class. Similarly, for the login page, a new class, LoginPage, will be created to store its web elements.
public class RegistrationPage {
WebDriver driver;
private By name = By.id("name"), email = By.id("email"), pass = By.id("password"),
terms = By.id("terms"), register = By.id("registerBtn");
public RegistrationPage(WebDriver driver) { this.driver = driver; }
public void registerUser(String n, String e, String p) {
driver.findElement(name).sendKeys(n);
driver.findElement(email).sendKeys(e);
driver.findElement(pass).sendKeys(p);
driver.findElement(terms).click();
driver.findElement(register).click();
}
}
// Test
RegistrationPage reg = new RegistrationPage(driver);
reg.registerUser("John Doe", "john@example.com", "Password123");
Locators specify the way web elements are identified on a page. For example, automation testing tools like Selenium offer different locator strategies to locate a WebElement. Locators in Selenium WebDriver include ID, Name, CSS Selector, XPath, etc. With POM’s centralized approach, it's easier to update locators whenever the UI changes.
private By emailField = By.id("email");
private By passwordField = By.id("password");
private By loginButton = By.id("loginBtn");
Locators can be stored as constant variables within the page classes, so if a locator changes, only the corresponding variable needs to be updated without altering the core test scripts. Alternatively, locators can be maintained in external files such as JSON, YAML, or Properties files.
Methods in POM are designed to interact with the web elements and perform the required actions. The automation tests can call these methods, enhancing the code readability and reusability.
For example, a performLogin() method can be added to the page object class to simulate a user logging in by entering the email and password and clicking the login button. This method can then be called directly in the test, keeping the test script concise and easy to read.
public class LoginPage {
WebDriver driver;
private By email = By.id("email");
private By password = By.id("password");
private By loginBtn = By.id("loginBtn");
public LoginPage(WebDriver driver) { this.driver = driver; }
// Method to perform login
public void performLogin(String user, String pass) {
driver.findElement(email).sendKeys(user);
driver.findElement(password).sendKeys(pass);
driver.findElement(loginBtn).click();
}
}
// Test script
LoginPage login = new LoginPage(driver);
login.performLogin("user@example.com", "password123");
You can implement Page Object Model by creating separate classes for each page, encapsulating locators and methods, and using these objects in test scripts to improve maintainability and readability.
Let’s implement the Page Object Model using testing tools like Selenium. If you are new to Selenium, refer to this step-by-step Selenium tutorial.
Create a new Maven Project and add the following dependencies for Selenium WebDriver and TestNG in the pom.xml file.
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>4.35.0</version>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>7.11.0</version>
<scope>test</scope>
</dependency>
We’ll run the tests on the latest version of the Chrome browser on the local machine. Create a BaseTest.java class to separate the browser configuration from the tests.
public class BaseTest {
protected WebDriver driver;
@BeforeClass
public void setup () {
driver = new ChromeDriver ();
this.driver.manage ()
.timeouts ()
.implicitlyWait (Duration.ofSeconds (10));
}
}
The setup() method will instantiate a new WebDriver session on the local machine. The Selenium Manager will handle driver and browser management by downloading the required ChromeDriver and browser at run time.
@AfterClass
public void tearDown () {
this.driver.quit ();
}
}
The tearDown() method closes the driver session cleanly once all the tests have finished running.
We will implement POM for both registration and login test scenarios in the LambdaTest eCommerce Playground. This clearly shows how shared pages and elements can be reused efficiently. The real value of POM appears when multiple workflows use the same parts of the web application.
Test Scenario 1:
Implementation:
Create a LambdaTestECommerceTests class to write the automation scripts for the test scenario. This class extends the BaseTest.java class to enable code reuse and promote modularity.
Add a testRegisterUser() method that carries out the test scenario for user registration.
public class LambdaTestECommerceTests extends BaseTest {
private static final String EMAIL = "johndoe77@email.com";
private static final String PASSWORD = "Password@321";
@Test
public void testRegisterUser () {
this.driver.navigate ()
.to ("https://ecommerce-playground.lambdatest.io/index.php?route=account/register");
final RegistrationPage registrationPage = new RegistrationPage (this.driver);
final RegistrationSuccessPage registrationSuccessPage = registrationPage.registerUser ("John", "Doe", EMAIL,
"00974633", PASSWORD);
assertEquals (registrationSuccessPage.getSuccessMessage (), "Your Account Has Been Created!");
}
The Page Object Model has been used to enhance code reusability, readability, and easy maintenance.
public class BasePage {
protected WebDriver driver;
public BasePage (final WebDriver driver) {
this.driver = driver;
}
}
The BasePage class is defined to store the common page objects throughout the website, allowing them to be reused. It is a parent class in the Page Object Model and stores the WebDriver instance.
The constructor of the BasePage class accepts a WebDriver object and assigns it to the class’s driver, so all the page classes that extend BasePage can use the same driver, avoiding duplicate code and easily sharing the browser instance across different page objects.
The RegistrationPage class is created as a page object class for the registration page, holding all the respective page objects.
public class RegistrationPage extends BasePage {
private final By firstNameField = By.id ("input-firstname");
private final By lastNameField = By.id ("input-lastname");
private final By emailField = By.id ("input-email");
private final By telephoneField = By.id ("input-telephone");
private final By passwordField = By.id ("input-password");
private final By confirmPassword = By.id ("input-confirm");
private final By agreePolicy = By.id ("input-agree");
private final By continueBtn = By.cssSelector ("input.btn-primary");
public RegistrationPage (final WebDriver driver) {
super (driver);
}
public RegistrationSuccessPage registerUser (final String firstName, final String lastName, final String emailId,
final String telephoneNumber, final String password) {
this.driver.findElement (this.firstNameField)
.sendKeys (firstName);
this.driver.findElement (this.lastNameField)
.sendKeys (lastName);
this.driver.findElement (this.emailField)
.sendKeys (emailId);
this.driver.findElement (this.telephoneField)
.sendKeys (telephoneNumber);
this.driver.findElement (this.passwordField)
.sendKeys (password);
this.driver.findElement (this.confirmPassword)
.sendKeys (password);
final WebElement agreePolicyCheckBox = this.driver.findElement (this.agreePolicy);
final Actions actions = new Actions (this.driver);
actions.moveToElement (agreePolicyCheckBox)
.click ()
.build ()
.perform ();
this.driver.findElement (this.continueBtn)
.click ();
return new RegistrationSuccessPage (this.driver);
}
}
The locators are defined as constants for the fields, allowing them to be reused across the codebase and preventing duplication.
The registerUser() method performs the interaction with the fields on the page and simulates the user registration action. Finally, it returns a new instance of the RegistrationSuccessPage class, which displays the registration success message.
public class RegistrationSuccessPage extends BasePage {
private final By successMessage = By.cssSelector ("#content h1");
public RegistrationSuccessPage (final WebDriver driver) {
super (driver);
}
public String getSuccessMessage () {
return this.driver.findElement (this.successMessage)
.getText ();
}
}
The RegistrationSuccessPage class follows the same pattern, separating the locators and methods.
Test Scenario 2:
Implementation:
Add a new testLogin() method in the LambdaTestECommerceTests class. This method implements the test scenario for user login.
@Test
public void testLogin () {
this.driver.navigate ()
.to ("https://ecommerce-playground.lambdatest.io/index.php?route=account/login");
final LoginPage loginPage = new LoginPage (this.driver);
final MyAccountPage myAccountPage = loginPage.performLogin (EMAIL, PASSWORD);
assertEquals (myAccountPage.getPageTitle (), "My Account");
}
The LoginPage class is created as the page object class for the Account Login page and holds its locators and interaction methods.
public class LoginPage extends BasePage {
private final By emailField = By.id ("input-email");
private final By passwordField = By.id ("input-password");
private final By loginButton = By.cssSelector ("input[type="submit"]");
public LoginPage (final WebDriver driver) {
super (driver);
}
public MyAccountPage performLogin (final String email, final String password) {
this.driver.findElement (this.emailField)
.clear ();
this.driver.findElement (this.emailField)
.sendKeys (email);
this.driver.findElement (this.passwordField)
.clear ();
this.driver.findElement (this.passwordField)
.sendKeys (password);
this.driver.findElement (this.loginButton)
.click ();
return new MyAccountPage (this.driver);
}
}
The performLogin() method accepts the email and password as parameters, performs necessary actions for user login, and returns a new instance of the My Account page.
Likewise, the MyAccountPage class contains all the page objects of the My Account page.
public class MyAccountPage extends BasePage {
private final By pageTitle = By.cssSelector ("#content h2");
public MyAccountPage (final WebDriver driver) {
super (driver);
}
public String getPageTitle () {
return this.driver.findElement (this.pageTitle)
.getText ();
}
}
We need the title of this page to verify that the user has logged in successfully. The getPageTitle() method returns the title of the page. This method is used in the test to check that the correct page title, “My Account”, is displayed.
Test Execution:
Create a new testng.xml file named “testng-pageobjectmodel.xml“ to run the tests. The tests will be executed on the latest version of the Chrome browser.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd">
<suite name="LambdaTest ECommerce Website Test Suite">
<test name="LambdaTest ECommerce Playground - Registration Test">
<classes>
<class name="io.github.mfaisalkhatri.pageobjectmodeldemo.tests.LambdaTestECommerceTests">
<methods>
<include name="testRegisterUser"/>
</methods>
</class>
</classes>
</test>
<test name="LambdaTest ECommerce Playground - Login Test">
<classes>
<class name="io.github.mfaisalkhatri.pageobjectmodeldemo.tests.LambdaTestECommerceTests">
<methods>
<include name="testLogin"/>
</methods>
</class>
</classes>
</test>
</suite>
Run the following command to execute the tests:
mvn clean install -Dsuite-xml=test-suites/testng-pageobjectmodel.xml
The following screenshot from IntelliJ IDE shows that the tests were executed successfully:
LambdaTest is a cloud-based testing platform that enables cross-browser and cross-platform testing on multiple operating systems and devices. It eliminates the need for local infrastructure, allows tests to run in parallel at scale, and ensures faster, more reliable validation across diverse environments.
To get started, you can check out this documentation on Selenium testing with LambdaTest.
To run the tests on the LambdaTest platform, we need to define specific capabilities for the platform, browser, and browser versions.
To simplify this, we can use the LambdaTest Automation Capabilities Generator. Next, create a BaseTest.java class file to hold all the configuration details.
public class BaseTest {
protected RemoteWebDriver driver;
private static final String LT_USERNAME = System.getenv ("LT_USERNAME");
private static final String LT_ACCESS_KEY = System.getenv ("LT_ACCESS_KEY");
private static final String GRID_URL = "@hub.lambdatest.com/wd/hub";
The LambdaTest cloud grid can be connected using the LambdaTest Username, Access Key, and Grid URL. The Username and Access Key can be found in the Account Settings under Password & Security.
Once we have these, we can write the test script to handle different browsers and start their respective sessions.
@BeforeClass
public void setup () {
try {
this.driver = new RemoteWebDriver (new URL ("https://" + LT_USERNAME + ":" + LT_ACCESS_KEY + GRID_URL),
getChromeOptions ());
} catch (final MalformedURLException e) {
System.out.println ("Could not start the remote session on LambdaTest cloud grid");
}
this.driver.manage ()
.timeouts ()
.implicitlyWait (Duration.ofSeconds (10));
}
The setup() method will instantiate a new RemoteWebDriver session on the LambdaTest cloud grid. Using the configurations set in the getChromeOptions() method, it will start a new Chrome browser session.
private ChromeOptions getChromeOptions () {
final var browserOptions = new ChromeOptions ();
browserOptions.setPlatformName ("Windows 10");
browserOptions.setBrowserVersion ("latest");
final HashMap<String, Object> ltOptions = new HashMap<> ();
ltOptions.put ("project", "POM demo With Selenium");
ltOptions.put ("build", "LambdaTest ECommerce Playground");
ltOptions.put ("name", "Registration and Login Tests");
ltOptions.put ("w3c", true);
ltOptions.put ("plugin", "java-testNG");
browserOptions.setCapability ("LT:Options", ltOptions);
return browserOptions;
}
Using the getChromeOptions() method, the capabilities for the Chrome browser and the underlying platform, build, and test names will be set. It will also enable the Java-testNG plugin in the LambdaTest cloud environment. This method will finally return the ChromeOptions to be used while instantiating the RemoteWebDriver session.
Test Execution:
Run the following command to execute the tests on LambdaTest.
mvn clean install -Dsuite-xml=test-suites/testng-pageobjectmodel.xml
Below is the screenshot of the test details of the user registration test executed. LambdaTest provides detailed test information such as the browser name, version, platform, execution logs, along with video recordings and screenshots.
While the Page Object Model provides a robust foundation for maintaining clean and reusable test scripts, there’s an advanced implementation called Page Factory that streamlines element initialization and interaction.
In traditional POM, locators are defined explicitly in the page classes and initialized inside constructors or methods. Page Factory simplifies this by using annotations like @FindBy, which automatically locate and initialize elements when the page object is created.
public class LoginPage {
WebDriver driver;
@FindBy(id = "input-email")
private WebElement emailField;
@FindBy(id = "input-password")
private WebElement passwordField;
@FindBy(css = "input[type='submit']")
private WebElement loginButton;
public LoginPage(WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
public void performLogin(String email, String password) {
emailField.clear();
emailField.sendKeys(email);
passwordField.clear();
passwordField.sendKeys(password);
loginButton.click();
}
}
This approach not only improves readability but also supports lazy initialization, meaning elements are only looked up when they are first used. In complex frameworks with multiple shared pages and frequent UI updates, Page Factory reduces boilerplate code and prevents errors caused by manual element initialization.
To get started with Page Factory for web automation, head over to this blog on Page Factory in Selenium.
The Page Object Model is a foundational design pattern that drives effective test automation. It helps in building maintainable, scalable, and robust test automation frameworks with Selenium.
By separating the web elements, actions, and locators into page classes, an abstraction layer is created that keeps test scripts clean and focused on test logic. This reduces code duplication, improves readability, and makes it easier to update tests when the UI changes.
Using platforms like LambdaTest in your testing strategy takes the benefits of POM even further, allowing smooth cross-browser and cross-device testing on real environments. This not only makes test automation more reliable but also helps accelerate release cycles for modern web applications.
Explore how the Page Object Model can be implemented across different frameworks and languages with these detailed resources:
On This Page
Did you find this page helpful?
More Related Hubs
Start your journey with LambdaTest
Get 100 minutes of automation test minutes FREE!!