Write clean, scalable, and reliable Selenium tests using the power of Page Object Model in Java
“Why is our test failing again?!”
Been there? Yeah, me too – 3 AM, CI pipeline redder than my eyes.
The Page Object Model (POM) in Selenium can save your butt…
If – and that’s a big IF – you don’t mess it up like I once did.
Let’s break this down like I’m training you over chai in the office pantry, not like a dry-as-dust doc written by AI bots.
We’ll cover what works, what absolutely sucks, and how to build a clean, badass POM structure with Java.
You ever open a Selenium test file and feel your brain melt?
I once inherited a UI test suite where every damn click, sendKeys, and assert lived in the test method itself. 1,000+ lines of pure soup. Zero reuse. I cried.
That’s why POM exists. It’s about:
Reusability
Readability
Maintainability
Write once. Use everywhere.
You move all the web elements and UI actions to separate page classes. Test logic stays clean. Maintenance becomes less painful than debugging JavaScript in IE8 (true story).
Just because it’s called “Page Object” doesn’t mean you need one class per page. Nope.
I once saw a POM class with 147 methods. It had logic for login, signup, forgot password, search, checkout, feedback form – you name it. All. In. One. File. 🤦♂️
POM isn’t a license to dump stuff randomly. It’s not your digital junk drawer.
Wait – I almost forgot… This also includes teams that go full-on OOP overkill:
AbstractLoginBaseFactoryServiceImpl.java – yes, that was real.
Let’s break down what actually makes a good one.
Use verbs that reflect behaviour:
loginPage.enterUsername("admin");
loginPage.enterPassword("1234");
loginPage.clickLogin();
Not :
typeText1();
setFieldA();
clickButton1();
PageFactory sounds fancy. And yes, it was useful once. But here’s the tea:
@FindBy
looks nice)@FindBy
as a hammer for every nail (and that’s dangerous)Slightly more verbose
Real Advice?
Use PageFactory for static pages. Stick with classic POM for anything dynamic, flaky, or AJAX-heavy.
Also, don’t mix both. It’s like wearing Crocs to a wedding. Just don’t. For a more in-depth understanding of automation testing tools, check out Selenium’s official documentation.
Let me paint a nightmare:
driver.findElement
all over the place
What we did:
Audit & Group: Collected repeated locators and grouped them into page-specific classes.
Extract Actions: Converted inline code like driver.findElement(...).click()
into readable methods like homePage.clickLoginButton();
Add Base Page: Common wait methods, logging, and utility functions moved to BasePage
.
Refactor Tests: Test methods went from 50+ lines to 5–10 lines of business logic.
Results: From 20+ flaky tests/day to zero flakiness. Devs started trusting automation again.
Lesson?
Spaghetti code is delicious in Italy. Not in test automation.
Want to keep your sanity when your suite grows to 500+ tests?
Here’s your go-to checklist:
LoginPage
, not loginpg
clickSubmitButton()
, not btnclk()
driver.findElement
in test classesBasePage
for waits, logging, helpersHeader
, Sidebar
, Footer
)Write tests like user stories
LoginPage login = new LoginPage(driver);
login.enterUsername("admin");
login.enterPassword("secret");
login.clickLogin();
Print this. Stick it on your desk. Thank me later.
Now, let’s talk about refactoring—really refactoring. The kind of refactor that makes you go from headaches to high fives.
I remember one project where I was tasked with refactoring an absolute mess. The code was like a runaway train: unstructured, untestable, and overly complex. Locators and test steps were mashed together, making it impossible to add new tests without breaking the existing ones. Every new test seemed like a patch to the failing code rather than a well-thought-out addition.
Here’s the key to refactoring your test suite: start by breaking your pages down into sections. Don’t just create a massive monolithic page object for each entire page. Separate out logical chunks like headers, footers, sidebars, and modals.
Once we broke things into manageable chunks, everything clicked. We could test small, isolated components, reuse code, and ensure consistency across tests. The result? A clean, scalable framework that didn’t feel like a ticking time bomb.
public class LoginPage extends BasePage {
private By usernameField = By.id("username");
private By passwordField = By.id("password");
private By loginButton = By.id("loginBtn");
public LoginPage(WebDriver driver) {
super(driver);
}
public void enterUsername(String username) {
driver.findElement(usernameField).sendKeys(username);
}
public void enterPassword(String password) {
driver.findElement(passwordField).sendKeys(password);
}
public void clickLogin() {
driver.findElement(loginButton).click();
}
}
Thread.sleep()
unless you enjoy chaosThe Page Object Model (POM) is a design pattern that is commonly used in Selenium-based automation frameworks. The main idea is to create separate classes for each page in your web application. Each page class contains methods representing the actions that can be performed on that page, as well as locators for the page elements. This approach separates test logic from UI interaction, improving maintainability and scalability.
POM brings several benefits to test automation:
Maintainability: Changes in the UI (such as element locators) only need to be made in one place — the corresponding page object class.
Readability: Your tests become more readable because they reflect the user flow (e.g., loginPage.enterUsername()
), making it easier to understand.
Reusability: Once you create a page object for a page, it can be reused in multiple test cases, reducing code duplication.
PageFactory
is a useful tool that automates the initialization of elements in the page object class using annotations like @FindBy
. It can reduce boilerplate code, but it has some limitations, such as being prone to lazy loading and not playing well with dynamic content.
It’s better to use PageFactory
for static pages with relatively simple structures. For more complex, dynamic pages, a classic approach where elements are initialized explicitly (e.g., driver.findElement()
) might be more reliable.
Dynamic elements can be tricky, especially when their IDs or XPaths change frequently. To handle this:
Use dynamic locators based on the text or other attributes (like CSS classes).
Implement custom wait strategies to wait for elements to appear or become clickable.
Consider using explicit waits to handle elements that take time to load or change.
Yes, you can. Even if your project is small, POM will help you maintain clear separation between your test logic and UI interactions. While it might seem like overkill for a simple test, starting with POM ensures that your code is scalable and easier to maintain as your project grows.
Here are some common mistakes to avoid when using POM:
Overloading page objects: Trying to put everything related to a page in a single class, making it overly complex and hard to maintain.
Including test logic in page objects: Your page object should only contain methods for interacting with the UI, not assertions or test-specific logic.
Not separating base actions: Common actions, like clicking a button or typing text, should be abstracted into a base class for easier reuse.
The main difference lies in how the elements are initialized:
Classic POM: You initialize the elements explicitly in the constructor or method (using driver.findElement()
).
PageFactory: You use annotations (@FindBy
) to initialize elements automatically when the page object is created.
While PageFactory
can be convenient, it can lead to issues with lazy loading and dynamic elements. In contrast, the classic POM approach gives you more control over when and how elements are loaded.
In such cases, you can break your page object into smaller objects representing these sections. For example, instead of having one huge HomePage
class, you could have separate classes like Header
, Footer
, and Sidebar
that are used in the HomePage
class. This makes your framework more modular and easier to maintain.
To improve performance:
Minimize element searches: Avoid searching for elements multiple times. Cache them in the page object class if needed.
Use waits efficiently: Use explicit waits where possible, rather than relying on Thread.sleep()
.
Reuse page objects: Instead of creating new instances of page objects repeatedly, use a singleton pattern to initialize them once and reuse across tests.
Yes, the Page Object Model is compatible with both JUnit and TestNG. It is a design pattern, not tied to a specific testing framework, so you can use it with any testing framework you prefer. The only requirement is that your test framework supports Selenium WebDriver.
Look, I’ve seen it all — from page objects that were longer than my tax returns to tests that failed if you blinked wrong.
You don’t need to be perfect. You need to be clean. Predictable. Scalable.
POM is like that one tool in your QA belt that just works when done right.
So yeah — build it right the first time. Or refactor before it becomes a monster.
Also Don’t forget to check our blogs.
Designed by ScriptNG
One Response