What is Page Object Model in Selenium Java?
The Page Object Model, commonly called POM, is one of the most widely used design patterns in Selenium automation. It helps organize automation code by separating page-related operations from test logic. Instead of writing locators, clicks, typing actions, waits, and page-specific behavior directly inside test classes, each page of the application is represented by a separate Java class called a page object.
POM is considered an industry standard because Selenium projects become difficult to maintain when tests grow. A small script with one login test can survive with direct driver.findElement() calls. A real framework with hundreds of tests cannot. When locators are duplicated across many tests, a small UI change can create a large maintenance problem. POM solves this by centralizing page knowledge in one place.
1. What Is Page Object Model?
Page Object Model is a design pattern in which every web page, major screen, or reusable UI component is represented as a Java class. This class contains the locators for that page and the methods that interact with those elements. The test class uses the page object methods instead of directly working with Selenium locators.
- A login page becomes
LoginPage.java. - A home page becomes
HomePage.java. - A search page becomes
SearchPage.java. - A cart page becomes
CartPage.java. - A checkout page becomes
CheckoutPage.java.
The page object is responsible for knowing how to interact with the page. The test is responsible for deciding what scenario to verify and what assertions to perform.
2. Why POM Was Introduced
Without POM, Selenium code is often written directly inside test methods. This creates duplication and makes tests harder to read. Consider a login workflow. If 100 tests perform login and every test has the username locator, password locator, and login button locator, a locator change requires updates in 100 places.
driver.findElement(By.id("username")).sendKeys("admin");
driver.findElement(By.id("password")).sendKeys("admin123");
driver.findElement(By.id("login")).click();
This code may look fine in one test. It becomes a maintenance issue when repeated across a framework. POM moves these details into a login page class. If the login button id changes, only LoginPage.java needs to be updated.
3. Traditional Selenium vs POM
In traditional Selenium scripting, the test class directly locates elements and performs browser actions. The test knows too much about the page structure. In POM, the test calls page methods that represent business actions. The page object handles the Selenium details.
Without POM:
Test Class
Locate username
Locate password
Locate login button
Click login
With POM:
Test Class
loginPage.login("admin", "admin123")
Page Object
Performs Selenium actions
The difference is important. POM does not remove Selenium. It places Selenium code in page classes where it belongs, so test classes can remain clean and scenario-focused.
4. Basic Project Structure
A typical Selenium Java framework using POM separates pages, tests, utilities, listeners, reports, and configuration. The exact folder names may vary, but the idea is the same: page classes live separately from test classes.
AutomationFramework
pages
LoginPage.java
HomePage.java
SearchPage.java
CartPage.java
tests
LoginTest.java
SearchTest.java
utilities
DriverFactory.java
WaitUtil.java
ScreenshotUtil.java
listeners
reports
testng.xml
This structure makes the framework easier to navigate. A tester looking for login locators knows to open LoginPage.java. A tester looking for login scenarios knows to open LoginTest.java.
5. Components of a Page Object
A page object usually contains a WebDriver reference, locators, a constructor, page action methods, and sometimes methods that return page state. For example, a login page object may contain username, password, login button, error message, and methods such as login(), getErrorMessage(), and isLoginButtonDisplayed().
The page object should describe what a user can do on that page. It should not contain unrelated workflows from other pages. Keeping page objects focused is one of the keys to maintainable POM design.
6. Login Without POM
Before understanding the POM version, it helps to see the problem. The test below mixes test flow, element location, browser interaction, and assertion in one method.
@Test
public void verifyLogin() {
driver.findElement(By.id("username")).sendKeys("admin");
driver.findElement(By.id("password")).sendKeys("admin123");
driver.findElement(By.id("login")).click();
Assert.assertEquals(driver.getTitle(), "Dashboard");
}
This test is small, but it is not scalable. If the login page changes, many tests may need updates. If waits are required, every test may need wait logic. If the login workflow changes, duplicate code must be fixed everywhere.
7. Login Page With POM
In POM, the login page gets its own class. Locators are stored inside the page class, and methods are created for page actions.
public class LoginPage {
private WebDriver driver;
private By username = By.id("username");
private By password = By.id("password");
private By loginButton = By.id("login");
public LoginPage(WebDriver driver) {
this.driver = driver;
}
public void enterUsername(String user) {
driver.findElement(username).sendKeys(user);
}
public void enterPassword(String pass) {
driver.findElement(password).sendKeys(pass);
}
public void clickLogin() {
driver.findElement(loginButton).click();
}
}
The test can now create a login page object and call its methods. The locators are no longer duplicated in the test class.
8. Test Using the Page Object
@Test
public void verifyLogin() {
LoginPage loginPage = new LoginPage(driver);
loginPage.enterUsername("admin");
loginPage.enterPassword("admin123");
loginPage.clickLogin();
}
This test is cleaner than the direct Selenium version. However, it can be improved further. Instead of exposing every low-level action to the test, the page object can provide one business method named login().
9. Business Methods in POM
A good page object should expose meaningful actions. A login page should have a login() method. A search page should have a searchProduct() method. A cart page should have an addToCart() or checkout() method. These methods make tests read like business workflows.
public void login(String user, String pass) {
driver.findElement(username).sendKeys(user);
driver.findElement(password).sendKeys(pass);
driver.findElement(loginButton).click();
}
@Test
public void verifyLogin() {
LoginPage loginPage = new LoginPage(driver);
loginPage.login("admin", "admin123");
}
This style improves readability and reduces test-level noise. The test no longer cares how login is performed internally.
10. Returning the Next Page Object
When an action navigates to another page, the method can return the next page object. For example, successful login may open the dashboard page.
public DashboardPage login(String user, String pass) {
driver.findElement(username).sendKeys(user);
driver.findElement(password).sendKeys(pass);
driver.findElement(loginButton).click();
return new DashboardPage(driver);
}
DashboardPage dashboard =
loginPage.login("admin", "admin123");
This pattern makes navigation explicit. It also helps test classes chain page transitions in a readable way.
11. Dashboard Page Example
public class DashboardPage {
private WebDriver driver;
private By heading = By.tagName("h1");
public DashboardPage(WebDriver driver) {
this.driver = driver;
}
public String getHeading() {
return driver.findElement(heading).getText();
}
}
The test can assert the dashboard heading without knowing the locator. This keeps verification logic in the test while keeping page knowledge in the page object.
12. Benefits of POM
- Reduces duplicate Selenium code.
- Centralizes locators in page classes.
- Improves test readability.
- Makes locator maintenance easier.
- Encourages reusable business methods.
- Improves debugging because page behavior has a clear owner.
- Supports larger automation frameworks.
- Separates page interaction from test validation.
13. What Happens When a Locator Changes?
Suppose the login button id changes from login to btnLogin. Without POM, every test that directly uses that locator must be updated. With POM, only the locator inside LoginPage.java changes.
private By loginButton = By.id("btnLogin");
This is the main maintenance advantage of POM. Locator changes are isolated to the page object that owns the page.
14. POM with Explicit Wait
Modern web applications are dynamic. Elements may appear after AJAX calls, animations, or page transitions. Page objects are a good place to use explicit waits because the page object knows when its elements are ready.
public void clickLogin() {
WebDriverWait wait =
new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(
ExpectedConditions.elementToBeClickable(loginButton)
).click();
}
The test does not need to know the waiting logic. It simply calls loginPage.clickLogin() or loginPage.login().
15. BasePage Class
Many frameworks create a BasePage class for common page actions such as click, type, get text, wait for visibility, and wait for clickability. Page classes then extend BasePage and reuse these helper methods.
public class BasePage {
protected WebDriver driver;
protected WebDriverWait wait;
public BasePage(WebDriver driver) {
this.driver = driver;
this.wait = new WebDriverWait(
driver,
Duration.ofSeconds(10)
);
}
protected void click(By locator) {
wait.until(
ExpectedConditions.elementToBeClickable(locator)
).click();
}
protected void type(By locator, String value) {
WebElement element =
wait.until(
ExpectedConditions.visibilityOfElementLocated(locator)
);
element.clear();
element.sendKeys(value);
}
}
This reduces repeated wait logic inside every page. It also makes page methods cleaner.
16. Login Page Extending BasePage
public class LoginPage extends BasePage {
private By username = By.id("username");
private By password = By.id("password");
private By loginButton = By.id("loginBtn");
public LoginPage(WebDriver driver) {
super(driver);
}
public DashboardPage login(String user, String pass) {
type(username, user);
type(password, pass);
click(loginButton);
return new DashboardPage(driver);
}
}
The page class is now focused on business action. The low-level waiting and interaction behavior is inherited from BasePage.
17. POM with PageFactory
PageFactory is a Selenium-supported way to initialize web elements using annotations such as @FindBy. It was very common in older frameworks.
public class LoginPage {
@FindBy(id = "username")
private WebElement username;
@FindBy(id = "password")
private WebElement password;
@FindBy(id = "login")
private WebElement loginButton;
public LoginPage(WebDriver driver) {
PageFactory.initElements(driver, this);
}
public void login(String user, String pass) {
username.sendKeys(user);
password.sendKeys(pass);
loginButton.click();
}
}
PageFactory is still seen in many projects, but many modern Selenium frameworks prefer By locators with explicit waits. By locators give clearer control over when the element is found and how synchronization is applied.
18. Assertions and Page Objects
A common debate is whether assertions should live inside page objects. In most frameworks, assertions should stay in test classes. Page objects should expose page state, and tests should decide what to verify.
// Better page object method
public String getErrorMessage() {
return driver.findElement(errorMessage).getText();
}
// Test class assertion
Assert.assertEquals(
loginPage.getErrorMessage(),
"Invalid credentials"
);
This keeps page objects reusable. The same page method can support different tests with different expected values.
19. Page Object vs Test Logic
Page objects should know how to interact with a page. Test classes should know the scenario and the expected result. This boundary keeps the framework understandable.
- Page object: enter username, click login, return error message.
- Test class: decide valid login should open dashboard.
- Page object: search for a product.
- Test class: assert search results contain the expected item.
If page objects contain too many assertions, they become test classes in disguise. If test classes contain too many locators, POM is not being used properly.
20. Component Objects
Not every reusable UI part is a full page. Modern applications have headers, sidebars, menus, modals, cards, filters, widgets, and tables. These can be modeled as component objects. A component object represents a reusable section of the UI.
For example, a navigation menu may appear on many pages. Instead of duplicating menu locators in every page object, create a NavigationMenu component. This keeps page classes smaller and improves reuse.
21. Real Project Workflow
In an e-commerce application, the test may move through login, home, search, product, cart, checkout, and payment pages. With POM, the test can read almost like a business workflow.
DashboardPage dashboard =
loginPage.login("admin", "admin123");
SearchResultsPage results =
dashboard.searchProduct("Laptop");
CartPage cart =
results.addFirstProductToCart();
CheckoutPage checkout =
cart.proceedToCheckout();
This is much easier to understand than a test containing dozens of raw Selenium calls.
22. Common Beginner Mistakes
- Putting all page locators in one large class.
- Writing assertions inside every page method.
- Exposing too many low-level click and type methods to tests.
- Repeating locators in test classes.
- Creating page objects without meaningful business methods.
- Using static WebDriver inside page objects.
- Making page objects too large instead of using component objects.
- Ignoring waits and synchronization inside page actions.
23. POM and Parallel Execution
In parallel execution, page objects must use the correct WebDriver instance for the current thread. Avoid static page objects and static drivers. Each test should create page objects using the driver assigned to that test execution.
If the framework uses ThreadLocal<WebDriver>, page objects should receive DriverManager.getDriver() or the resolved driver in the constructor. This prevents one test from interacting with another test's browser.
24. Advantages and Disadvantages
POM improves maintainability, readability, reuse, and scalability. It centralizes locators and makes tests more business-focused. The main disadvantage is that it requires more initial design. A small one-time script may not need POM, but any long-term Selenium framework usually does.
POM can also become messy if page classes grow without discipline. Large pages should be split into logical methods and reusable components. Good POM requires consistent design, not just creating classes with locators.
25. POM vs Traditional Selenium
- Traditional Selenium keeps locators inside tests; POM keeps locators inside page classes.
- Traditional Selenium often duplicates code; POM encourages reusable methods.
- Traditional Selenium becomes harder to scale; POM supports framework growth.
- Traditional Selenium mixes actions and assertions; POM separates page interaction from test validation.
26. Best Practices
- Use one page object for one page or one major screen.
- Use component objects for reusable UI sections.
- Keep locators inside page classes.
- Keep assertions in test classes.
- Use business methods such as
login(),searchProduct(), andcheckout(). - Use explicit waits inside page methods where appropriate.
- Prefer clear
Bylocators with wait helpers for dynamic pages. - Return the next page object when an action navigates to another page.
- Avoid static WebDriver in page objects.
- Keep page objects readable and focused.
27. Framework-Level Design
In an enterprise framework, POM works with other layers. TestNG controls execution. BaseTest handles driver setup and teardown. Page objects model application screens. Utilities handle waits, screenshots, data, and configuration. Listeners handle reporting and failure events. POM is not the entire framework, but it is the layer that keeps page interaction maintainable.
A clean design avoids pushing everything into page objects. Page objects should not read Excel files, control TestNG groups, start browsers, or write reports. Those responsibilities belong to other framework layers. POM works best when each layer has a clear purpose.
28. Designing Page Methods
The quality of a page object depends heavily on the quality of its methods. A weak page object simply exposes low-level methods such as clickUsername(), clickPassword(), and clickButton(). That still moves Selenium code out of the test, but it does not fully improve readability. A stronger page object exposes meaningful methods such as login(), searchProduct(), applyFilter(), addItemToCart(), and proceedToCheckout().
Business-level methods make tests easier to understand. A test should describe the scenario, not every browser-level action. When a method name clearly describes user intent, the test becomes readable even to someone who is not familiar with every locator on the page.
At the same time, page methods should not hide too much. A method named completePurchase() inside LoginPage would be wrong because the login page should not know the whole checkout flow. Keep methods meaningful, but keep them within the responsibility of that page or component.
29. Page Object Size Control
Large applications can have large pages. If one page object grows to hundreds or thousands of lines, it becomes difficult to maintain. This usually happens when a page contains many independent sections such as filters, tables, side menus, headers, cards, forms, and modals. Putting every locator and method into one page class technically follows POM, but it does not produce clean design.
The solution is to split reusable or complex sections into component objects. For example, a product listing page can use FilterPanel, SortDropdown, ProductCard, and PaginationComponent. The page object can coordinate those components without owning every detail directly.
This approach keeps classes smaller and makes reuse easier. If the same header appears across many pages, a HeaderComponent can handle profile menu, logout, cart icon, and navigation links. Each page can use that component instead of duplicating header locators.
30. POM and Test Data
Page objects should accept data, not own data. For example, a login method can accept username and password. The page object should not decide which username belongs to a smoke test or which password comes from Excel. That decision belongs to the test, DataProvider, or test data layer.
public DashboardPage login(String username, String password) {
type(this.username, username);
type(this.password, password);
click(loginButton);
return new DashboardPage(driver);
}
This design keeps page objects reusable. The same login() method can be used by positive login tests, negative login tests, role-based tests, and data-driven tests. The page object only performs the action; the test decides the data and expected result.
31. POM and Synchronization
Synchronization is one of the main reasons Selenium tests become unstable. Page objects are a natural place to handle page-specific waits because they know which element should be ready before an action happens. A login page knows the login button must be clickable. A search page knows the search results should be visible. A checkout page knows the payment form must be loaded.
However, waits should be implemented consistently. If every page object creates its own wait logic in a different way, the framework becomes inconsistent. A BasePage or WaitUtil can provide common wait methods, and page objects can call those methods. This keeps synchronization behavior uniform across the framework.
Avoid fixed sleeps in page objects. A method such as Thread.sleep(5000) makes tests slower and still may not be reliable. Explicit waits based on real conditions are better because they wait only as long as needed and fail with a meaningful timeout when the condition is not met.
32. POM and Negative Testing
POM is not only for successful workflows. It should also support negative scenarios. A login page should expose methods to submit invalid credentials and retrieve error messages. A registration page should expose methods to enter invalid email, leave required fields empty, and read validation messages. The test can then assert whether the application handled the invalid input correctly.
The key is to return observable page state. For example, the page object can provide getErrorMessage(), isSubmitButtonEnabled(), or isValidationDisplayed(). The test uses those methods to perform assertions. This keeps negative test validation clean without placing assertions inside the page class.
33. POM and Maintainability
The biggest value of POM appears over time. When the application UI changes, page objects isolate the impact. If a locator changes, update the page class. If a workflow changes, update the relevant page method. Test classes often remain unchanged because they call business methods rather than raw Selenium operations.
This does not mean POM eliminates all maintenance. Poorly designed POM can still become expensive. If page objects are too large, methods are poorly named, locators are duplicated, or responsibilities are mixed, maintenance becomes difficult again. POM is a pattern, not a guarantee. It must be applied with discipline.
Good maintainability also comes from naming. Page classes, methods, and locators should use names that describe intent. loginButton is clearer than btn1. enterShippingAddress() is clearer than fillForm(). Clear names reduce the time needed to understand and update tests.
34. POM in Agile Projects
In agile development, UI changes frequently. POM helps automation teams keep up with change because page-specific updates are localized. When a sprint changes the login layout, the login page object is the first place to update. When a new search filter is added, the search page or filter component can be extended without rewriting unrelated tests.
POM also improves collaboration. Testers, developers, and automation engineers can discuss page behavior in terms of page objects and business actions. A test named checkoutWithCreditCard() that uses cartPage.proceedToCheckout() and paymentPage.payByCard() is easier to review than a test filled with raw XPath expressions.
35. When POM Is Not Enough
POM solves page interaction organization, but it does not solve every automation design problem. A complete framework also needs driver management, configuration management, test data handling, reporting, logging, wait strategy, retry strategy, CI integration, and environment handling. POM should be one layer in a broader framework design.
For very complex applications, POM may be combined with other patterns such as component objects, fluent interfaces, service layers, workflow classes, or screen-play style abstractions. The goal is not to follow one pattern blindly. The goal is to keep tests readable, maintainable, and trustworthy.
36. Practical Review Checklist
- Does each important page or component have a clear owner class?
- Are locators kept out of test classes?
- Do page methods describe user actions clearly?
- Are assertions kept mainly in test classes?
- Are waits handled consistently through page methods or base utilities?
- Are large page objects split into smaller component objects?
- Are page objects free from test data file reading and report generation?
- Are page objects safe for parallel execution?
37. Step-by-Step Implementation Approach
When introducing POM into an existing Selenium project, do not try to redesign the whole framework in one commit. Start with one stable workflow, usually login or search. Create the first page object, move locators into that page class, add one or two meaningful methods, and update one test to use it. Once the pattern is proven, apply the same structure to nearby pages.
A practical first step is to remove direct driver.findElement() usage from test classes. Every time a test needs to interact with a page element, ask which page owns that element. Then move the locator and interaction into that page object. Over time, tests become cleaner and page classes become the single source of truth for UI interaction.
The next step is to introduce a BasePage only when there is real duplication. Do not create a large base class on day one. Start with repeated actions such as click, type, get text, and wait for visibility. Keep those methods simple and stable. A base class should reduce duplication, not become a place where every unrelated helper method is stored.
38. POM Refactoring Example
Suppose a test has repeated login steps in many classes. The first refactoring is to create LoginPage with username, password, and login button locators. The second refactoring is to add a login() method. The third refactoring is to make login() return DashboardPage after successful navigation. This gives the framework a clean page flow.
After that, negative login tests can reuse the same page object by calling a method such as loginExpectingError() or by calling login() and reading the error message from the same page, depending on the application behavior. The key idea is reuse without hiding the scenario's expected result.
This incremental refactoring is safer than rewriting every test at once. It allows the team to learn the pattern, improve naming, and adjust the base page design before the structure spreads across the whole suite.
39. POM Quality Signals
A good POM framework has tests that are easy to read without opening every page class. The test method should communicate the user journey. Page methods should communicate user intent. Locators should be centralized. Assertions should be clear. When a failure occurs, the stack trace and screenshot should point naturally to the page action that failed.
Poor POM has opposite signals. Tests still contain many raw locators. Page classes contain assertions, test data loading, driver setup, report writing, and unrelated workflows. Method names are vague. Large page classes contain hundreds of locators without organization. These signs mean the project has page classes, but not clean Page Object Model design.
The best test of POM quality is maintenance. If a UI locator changes and the fix is localized to one page class, the design is helping. If a small page change still requires many test edits, the POM boundary needs improvement.
40. Interview Perspective
A short interview answer is: Page Object Model is a Selenium design pattern where each web page is represented by a Java class containing locators and page-specific methods. It improves code reusability, readability, and maintainability.
A stronger real-time answer is: in my Selenium framework, I use POM to separate page interactions from test logic. Each page class contains its own locators and business methods such as login(), searchProduct(), or checkout(). Test classes call these page methods and perform assertions. This reduces duplicate locator code, centralizes maintenance, and makes the framework easier to scale as the application grows.
41. Key Takeaway
Page Object Model transforms Selenium scripts into a maintainable automation framework. Instead of interacting with web elements directly in every test, tests communicate with page objects that model the application's pages and business actions.
Use one page object per page or major screen, keep locators and page actions inside page classes, keep assertions in tests, use explicit waits where needed, and create meaningful business methods. POM does not make a framework good by itself, but it provides the structure needed to build clean, reusable, and scalable Selenium automation.