Popular Searches
Popular Course Categories
Popular Courses

POM Best Practices

Page Object Model

POM Best Practices in Selenium

Page Object Model (POM) is a widely used design pattern in Selenium automation that helps organize web application automation code by separating page-specific elements and actions from test cases. Following POM best practices makes Selenium frameworks more readable, maintainable, reusable, scalable, and easier to debug.

In a well-designed POM framework, each important application page is represented by a dedicated Java class. The page class contains locators and methods for interacting with that page, while test classes focus mainly on test scenarios, test data, and assertions.

POM becomes especially valuable in large Selenium projects where the same pages, elements, and workflows are used across many test cases.

Course Resource: Selenium Training | Register for Course Demo


1. What is Page Object Model?

Page Object Model is a design pattern in Selenium where web pages or significant sections of an application are represented as separate classes. These classes contain the locators and methods required to interact with the corresponding page.

Instead of placing Selenium locators and WebDriver commands directly inside every test case, the page-specific operations are encapsulated inside page classes.

Test Class

    |

    v

Page Object

    |

    +-- Locators

    |

    +-- Page Actions

    |

    v

WebDriver

    |

    v

Web Application


2. Why are POM Best Practices Important?

Simply using POM does not automatically create a good automation framework. The page classes must be designed carefully so that they remain easy to maintain as the application grows.

  • Improves code readability.
  • Reduces duplicate Selenium code.
  • Centralizes page locators.
  • Makes application changes easier to manage.
  • Improves test-case maintainability.
  • Encourages reusable page actions.
  • Separates test logic from UI interaction logic.
  • Makes debugging easier.
  • Supports scalable automation frameworks.
  • Works effectively with TestNG, Maven, CI/CD, and reporting tools.


3. Basic POM Architecture

A typical Selenium POM framework separates tests, page classes, utilities, test data, and configuration.

Automation Framework

        |

        +-- Tests

        |

        +-- Pages

        |

        +-- Components

        |

        +-- Utilities

        |

        +-- Test Data

        |

        +-- Configuration

        |

        +-- Reports

        |

        +-- Drivers


4. Best Practice: One Page Class for One Page

A common POM best practice is to create a dedicated class for each major application page.

For example, an e-commerce application may contain:

LoginPage.java

HomePage.java

ProductPage.java

CartPage.java

CheckoutPage.java

PaymentPage.java

Each class should primarily represent the behavior and elements of its corresponding page.


5. Keep Locators Inside Page Classes

Locators should generally be maintained inside the page object instead of being scattered throughout test classes.

Incorrect approach:

@Test

public void loginTest() {

    driver.findElement(By.id("username")).sendKeys("admin");

    driver.findElement(By.id("password")).sendKeys("admin123");

    driver.findElement(By.id("loginButton")).click();

}

Better POM approach:

public class LoginPage {

 

    private WebDriver driver;

 

    private By username = By.id("username");

    private By password = By.id("password");

    private By loginButton = By.id("loginButton");

 

    public LoginPage(WebDriver driver) {

        this.driver = driver;

    }

 

    public void login(String user, String pass) {

        driver.findElement(username).sendKeys(user);

        driver.findElement(password).sendKeys(pass);

        driver.findElement(loginButton).click();

    }

}


6. Use Meaningful Locator Names

Locator names should clearly communicate which application element they represent.

Good examples:

private By usernameField = By.id("username");

private By passwordField = By.id("password");

private By loginButton = By.id("loginButton");

private By searchBox = By.id("search");

private By cartIcon = By.id("cart");

Avoid vague names such as:

private By element1;

private By locator2;

private By testObject;


7. Keep Locators Private

Page-specific locators should generally be private. Tests should interact with the page through meaningful methods rather than directly accessing internal locators.

public class LoginPage {

 

    private By usernameField = By.id("username");

    private By passwordField = By.id("password");

    private By loginButton = By.id("loginButton");

 

    public void login(String username, String password) {

        driver.findElement(usernameField).sendKeys(username);

        driver.findElement(passwordField).sendKeys(password);

        driver.findElement(loginButton).click();

    }

}

This provides encapsulation and prevents test classes from depending directly on page implementation details.


8. Use a Constructor to Initialize WebDriver

A page object normally receives the WebDriver instance through its constructor.

public class LoginPage {

 

    private WebDriver driver;

 

    public LoginPage(WebDriver driver) {

        this.driver = driver;

    }

}

This allows the same browser session to be used by the page object and test class.


9. Keep Page Actions Inside Page Classes

Page classes should expose meaningful business-level actions rather than requiring tests to perform individual Selenium commands.

For example, instead of writing:

driver.findElement(By.id("username")).sendKeys("admin");

driver.findElement(By.id("password")).sendKeys("admin123");

driver.findElement(By.id("loginButton")).click();

the test should be able to call:

loginPage.login("admin", "admin123");

This keeps the test readable and makes the page implementation reusable.


10. Use Business-Level Methods

POM methods should represent meaningful user actions whenever practical.

For example:

login()

searchProduct()

addProductToCart()

removeProductFromCart()

proceedToCheckout()

enterShippingDetails()

placeOrder()

These methods make test cases easier to understand because they describe what the user is doing instead of exposing low-level Selenium commands.


11. Avoid Putting Assertions Everywhere in Page Classes

Assertions are generally best kept in test classes or dedicated verification/helper layers rather than being scattered throughout page methods.

For example, instead of:

public void login(String username, String password) {

    // Login actions

    Assert.assertTrue(driver.getTitle().contains("Dashboard"));

}

A cleaner approach is:

loginPage.login(username, password);

 

Assert.assertTrue(

    homePage.isDashboardDisplayed()

);

This keeps the page object focused on page behavior while the test controls the verification.


12. Return the Next Page Object When Appropriate

When an action navigates to another page, the page method can return the corresponding page object.

public HomePage login(String username, String password) {

 

    driver.findElement(usernameField).sendKeys(username);

    driver.findElement(passwordField).sendKeys(password);

    driver.findElement(loginButton).click();

 

    return new HomePage(driver);

}

The test can then follow the application flow naturally.

HomePage homePage = loginPage.login("admin", "admin123");

homePage.verifyDashboard();


13. Avoid Hard-Coding Test Data in Page Classes

Page classes should normally contain UI interaction logic rather than test-specific data.

Avoid:

public void login() {

    usernameField.sendKeys("admin");

    passwordField.sendKeys("admin123");

}

Prefer:

public void login(String username, String password) {

    usernameField.sendKeys(username);

    passwordField.sendKeys(password);

}

The test, Data Provider, configuration, or secure test-data mechanism can supply the actual values.


14. Separate Test Data from Page Logic

Test data should be maintained separately from page interaction logic whenever practical.

Test Data

    |

    +-- Username

    +-- Password

    +-- Product

    +-- Search Keyword

    +-- Expected Result

          |

          v

       Test Class

          |

          v

      Page Object

This separation allows the same page methods to be reused with different data sets.


15. Use Data Providers with POM

TestNG Data Providers work well with POM because the Data Provider can supply test data while the page object handles UI interactions.

@DataProvider(name = "loginData")

public Object[][] loginData() {

    return new Object[][] {

        {"admin", "admin123"},

        {"manager", "manager123"},

        {"employee", "employee123"}

    };

}

 

@Test(dataProvider = "loginData")

public void loginTest(String username, String password) {

    loginPage.login(username, password);

}


16. Use Explicit Waits Carefully

Dynamic web applications often require synchronization. Explicit waits should be implemented in a controlled and reusable manner.

WebDriverWait wait =

    new WebDriverWait(driver, Duration.ofSeconds(10));

 

wait.until(

    ExpectedConditions.visibilityOfElementLocated(usernameField)

);

Avoid adding arbitrary sleeps throughout page classes.


17. Avoid Excessive Thread.sleep()

Thread.sleep() pauses execution for a fixed amount of time regardless of whether the application is ready.

Avoid:

Thread.sleep(5000);

Prefer condition-based waits:

wait.until(

    ExpectedConditions.elementToBeClickable(loginButton)

);

Explicit waits generally provide better synchronization because the test waits for a meaningful condition.


18. Create Reusable Wait Utilities

Large frameworks can centralize commonly used wait operations in utility classes.

public class WaitUtils {

 

    private WebDriver driver;

    private WebDriverWait wait;

 

    public WaitUtils(WebDriver driver) {

        this.driver = driver;

        this.wait = new WebDriverWait(

            driver,

            Duration.ofSeconds(10)

        );

    }

 

    public void waitForVisible(By locator) {

        wait.until(

            ExpectedConditions.visibilityOfElementLocated(locator)

        );

    }

 

    public void waitForClickable(By locator) {

        wait.until(

            ExpectedConditions.elementToBeClickable(locator)

        );

    }

}


19. Use Stable Locators

Locator stability is important for maintainable Selenium frameworks. Prefer attributes that are stable and meaningful.

Common locator preference can include:

id

name

stable data attributes

accessible attributes

CSS selectors

XPath when appropriate

Avoid fragile locators that depend heavily on changing CSS classes or deeply nested DOM structures.


20. Avoid Overly Complex XPath

Long XPath expressions can become fragile when the application's DOM changes.

Avoid unnecessarily complex expressions such as:

/html/body/div[2]/div[1]/div[3]/div[2]/button

Prefer a stable attribute where available:

By.id("loginButton")

or:

By.cssSelector("[data-testid='login-button']")


21. Use Page Components for Reusable UI Sections

Not every UI element needs to belong directly to a complete page class. Reusable components such as navigation bars, headers, menus, product cards, and dialogs can be represented as component objects.

HomePage

    |

    +-- HeaderComponent

    +-- NavigationComponent

    +-- ProductCard

    +-- FooterComponent

This approach can reduce duplication when the same component appears on multiple pages.


22. Create Component Classes for Repeated Elements

Suppose an application contains product cards with the same structure. Instead of duplicating product-card locators on every page, create a reusable component.

public class ProductCard {

 

    private WebElement root;

 

    public ProductCard(WebElement root) {

        this.root = root;

    }

 

    public String getProductName() {

        return root.findElement(

            By.cssSelector(".product-name")

        ).getText();

    }

 

    public void addToCart() {

        root.findElement(

            By.cssSelector(".add-to-cart")

        ).click();

    }

}


23. Keep Page Classes Focused

A page class should not become a large collection of unrelated functionality. Each class should have a clear responsibility.

For example:

LoginPage

    -> Login-related actions

 

SearchPage

    -> Search-related actions

 

CartPage

    -> Cart-related actions

 

CheckoutPage

    -> Checkout-related actions

This makes classes easier to understand and maintain.


24. Avoid Giant Page Classes

A common POM problem is creating one enormous class containing every locator and action in the application.

Instead of:

ApplicationPage.java

    -> Login

    -> Search

    -> Products

    -> Cart

    -> Checkout

    -> Payment

    -> Profile

    -> Reports

prefer smaller, focused page and component classes.


25. Use Encapsulation

Encapsulation means hiding implementation details and exposing only the operations required by the test.

public class LoginPage {

 

    private By usernameField = By.id("username");

    private By passwordField = By.id("password");

    private By loginButton = By.id("loginButton");

 

    public void login(String username, String password) {

        driver.findElement(usernameField).sendKeys(username);

        driver.findElement(passwordField).sendKeys(password);

        driver.findElement(loginButton).click();

    }

}

The test does not need to know how the username field is located.


26. Use Clear Method Names

Method names should describe the action or behavior clearly.

Good examples:

login()

searchProduct()

selectCategory()

addProductToCart()

removeProduct()

proceedToCheckout()

enterAddress()

placeOrder()

Avoid vague method names such as:

doAction()

click()

execute()

process()

unless the context genuinely makes the meaning clear.


27. Keep Methods Small and Focused

A method should ideally perform one meaningful operation or workflow step.

For example:

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();

}

For a business-level workflow, these can also be combined:

public void login(String username, String password) {

    enterUsername(username);

    enterPassword(password);

    clickLogin();

}


28. Avoid Duplicate Methods

If the same UI interaction appears in several places, consider creating a reusable method or component.

Instead of repeating:

driver.findElement(By.id("search")).clear();

driver.findElement(By.id("search")).sendKeys(keyword);

driver.findElement(By.id("searchButton")).click();

create:

public void search(String keyword) {

    searchBox.clear();

    searchBox.sendKeys(keyword);

    searchButton.click();

}


29. Separate Navigation from Verification

Navigation methods should generally perform navigation, while verification methods should communicate whether an expected page or element is present.

public void openLoginPage() {

    driver.get(baseUrl + "/login");

}

 

public boolean isLoginPageDisplayed() {

    return driver.findElement(loginButton).isDisplayed();

}


30. Use Return Values Where Useful

Page methods can return useful information when the test needs to verify a value.

public String getPageTitle() {

    return driver.getTitle();

}

 

public String getUsername() {

    return driver.findElement(usernameLabel).getText();

}

The test can then perform assertions:

Assert.assertEquals(

    homePage.getPageTitle(),

    "Dashboard"

);


31. Keep Assertions Meaningful

Assertions should verify business expectations rather than simply confirming that Selenium commands executed without throwing an exception.

Examples include:

Assert.assertEquals(

    checkoutPage.getOrderStatus(),

    "Order Confirmed"

);

 

Assert.assertTrue(

    homePage.isDashboardDisplayed()

);


32. Use a Base Page Carefully

A BasePage can contain genuinely common functionality such as WebDriver access, common waits, navigation helpers, and reusable browser operations.

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();

    }

}


33. Do Not Put Everything in BasePage

A BasePage should not become another giant utility class containing application-specific actions.

Good:

BasePage

    -> click()

    -> type()

    -> waitForVisible()

    -> getTitle()

Bad:

BasePage

    -> login()

    -> addProduct()

    -> checkout()

    -> createUser()

    -> generateInvoice()

    -> approveOrder()

Application-specific operations should normally remain in their relevant page or component classes.


34. Use Page Factory Carefully

Selenium projects may use Page Factory-style element initialization, but teams should choose a consistent approach that fits their framework.

public class LoginPage {

 

    private WebDriver driver;

 

    @FindBy(id = "username")

    private WebElement usernameField;

 

    @FindBy(id = "password")

    private WebElement passwordField;

 

    @FindBy(id = "loginButton")

    private WebElement loginButton;

 

    public LoginPage(WebDriver driver) {

        this.driver = driver;

        PageFactory.initElements(driver, this);

    }

}

Whether using By locators or @FindBy, consistency and maintainability are more important than mixing patterns unnecessarily.


35. Prefer Readable Page Objects

A page class should be understandable by another automation engineer without requiring extensive investigation.

A readable class generally contains:

  • Clear class name.
  • Clearly named locators.
  • Constructor.
  • Meaningful page actions.
  • Small helper methods.
  • Minimal duplication.
  • Clear return types.


36. Use Constants for Stable Configuration

Configuration values such as environment-specific base URLs should generally not be scattered throughout page classes.

Instead, configuration can be centralized:

public class Config {

 

    public static final String QA_URL =

        "https://qa.example.com";

 

    public static final String STAGE_URL =

        "https://stage.example.com";

}

For larger projects, external configuration is usually preferable to hard-coding environment-specific values in Java source.


37. Separate Environment Configuration

Environment values such as QA, staging, and production URLs should be managed independently from page behavior.

Environment

    |

    +-- QA URL

    +-- Stage URL

    +-- Production URL

          |

          v

       Config

          |

          v

      WebDriver

          |

          v

      Page Objects


38. Use a Driver Factory

A DriverFactory can centralize browser creation and make the framework easier to extend.

public class DriverFactory {

 

    public static WebDriver createDriver(String browser) {

 

        if (browser.equalsIgnoreCase("chrome")) {

            return new ChromeDriver();

        }

 

        if (browser.equalsIgnoreCase("firefox")) {

            return new FirefoxDriver();

        }

 

        if (browser.equalsIgnoreCase("edge")) {

            return new EdgeDriver();

        }

 

        throw new IllegalArgumentException(

            "Unsupported browser: " + browser

        );

    }

}


39. Combine Driver Factory with POM

A scalable framework can use a DriverFactory to create the browser and then pass the driver to page objects.

WebDriver driver =

    DriverFactory.createDriver("chrome");

 

LoginPage loginPage =

    new LoginPage(driver);

 

loginPage.login("admin", "admin123");


40. Use ThreadLocal for Parallel Execution

When Selenium tests execute concurrently, sharing one WebDriver instance between multiple threads can cause test interference. A common framework technique is to maintain a driver instance per thread.

public class DriverManager {

 

    private static ThreadLocal<WebDriver> driver =

        new ThreadLocal<>();

 

    public static void setDriver(WebDriver webDriver) {

        driver.set(webDriver);

    }

 

    public static WebDriver getDriver() {

        return driver.get();

    }

 

    public static void removeDriver() {

        driver.remove();

    }

}

The exact driver-management design should match the framework's execution model.


41. POM with TestNG

TestNG can manage setup, test execution, Data Providers, groups, dependencies, and assertions while POM manages page interaction.

@BeforeMethod

public void setup() {

    driver = DriverFactory.createDriver("chrome");

    loginPage = new LoginPage(driver);

}

 

@Test

public void validLoginTest() {

    loginPage.login("admin", "admin123");

    Assert.assertTrue(homePage.isDashboardDisplayed());

}

 

@AfterMethod

public void tearDown() {

    if (driver != null) {

        driver.quit();

    }

}


42. POM with Data Provider

@DataProvider(name = "users")

public Object[][] users() {

    return new Object[][] {

        {"admin", "admin123"},

        {"manager", "manager123"},

        {"employee", "employee123"}

    };

}

 

@Test(dataProvider = "users")

public void loginTest(String username, String password) {

    loginPage.login(username, password);

}

This keeps the test data, test execution, and page interaction responsibilities separated.


43. Avoid Hard-Coded Browser Creation in Every Test

Creating WebDriver independently in every test class can lead to duplication.

A centralized DriverFactory or driver-management layer makes browser configuration easier to maintain.

Test

 |

 v

DriverFactory

 |

 v

WebDriver

 |

 v

Page Object

 |

 v

Application


44. Keep Test Classes Focused on Test Scenarios

A test class should describe what is being tested rather than contain every Selenium implementation detail.

Example:

@Test

public void validLoginTest() {

 

    loginPage.login("admin", "admin123");

 

    Assert.assertTrue(

        homePage.isDashboardDisplayed()

    );

}

This is easier to understand than a test containing dozens of direct Selenium commands.


45. Avoid UI Logic Inside Test Classes

Large blocks of WebDriver commands inside test methods reduce maintainability.

Instead of:

driver.findElement(By.id("username")).sendKeys("admin");

driver.findElement(By.id("password")).sendKeys("admin123");

driver.findElement(By.id("loginButton")).click();

driver.findElement(By.id("search")).sendKeys("Laptop");

prefer:

loginPage.login("admin", "admin123");

homePage.searchProduct("Laptop");


46. Keep Page Classes Independent from Test Framework Logic

Page objects should ideally focus on application interaction rather than becoming tightly coupled to TestNG-specific test execution behavior.

This makes the page layer easier to reuse and maintain.


47. Avoid Test-Specific Method Names in Page Objects

Page methods should describe application actions rather than individual test-case names.

Avoid:

executeTestCaseTC001()

executeLoginTest()

runAdminTest()

Prefer:

login()

logout()

searchProduct()

addToCart()


48. Keep Page Object APIs Stable

Tests should interact with page objects through stable methods. If a locator changes, ideally only the page class needs modification.

Test

 |

 +-- loginPage.login()

 |

 +-- loginPage.isErrorDisplayed()

 |

 +-- homePage.searchProduct()

The test does not need to know whether the underlying locator uses an ID, CSS selector, or XPath.


49. Use Reusable Utility Classes

Common technical operations can be extracted into utilities when they are genuinely reusable.

Examples include:

  • WaitUtils
  • ScreenshotUtils
  • ConfigReader
  • ExcelReader
  • JSONReader
  • DriverFactory
  • DateUtils
  • FileUtils


50. Keep Utilities Separate from Page Objects

A utility class and a page object serve different purposes.

Page ObjectUtility
Represents application UIProvides reusable technical functionality
Contains page locatorsUsually does not contain page-specific locators
Contains business-level page actionsContains generic helper operations
Example: LoginPageExample: ExcelReader


51. Use Meaningful Packages

Package organization becomes important as the framework grows.

src/test/java

|

+-- tests

|

+-- pages

|

+-- components

|

+-- utilities

|

+-- drivers

|

+-- data

|

+-- listeners


52. Suggested POM Project Structure

selenium-automation

|

+-- src

|   +-- test

|       +-- java

|           +-- tests

|           |   +-- LoginTest.java

|           |   +-- SearchTest.java

|           |   +-- CheckoutTest.java

|           |

|           +-- pages

|           |   +-- LoginPage.java

|           |   +-- HomePage.java

|           |   +-- ProductPage.java

|           |   +-- CartPage.java

|           |   +-- CheckoutPage.java

|           |

|           +-- components

|           |   +-- HeaderComponent.java

|           |   +-- ProductCard.java

|           |

|           +-- utilities

|           |   +-- WaitUtils.java

|           |   +-- ConfigReader.java

|           |   +-- ExcelReader.java

|           |

|           +-- drivers

|           |   +-- DriverFactory.java

|           |

|           +-- data

|               +-- LoginDataProvider.java

|

+-- pom.xml


53. Use Naming Conventions

Consistent naming makes automation frameworks easier to understand.

ItemExample
Page ClassLoginPage
Test ClassLoginTest
ComponentHeaderComponent
LocatorloginButton
Action Methodlogin()
Verification MethodisDashboardDisplayed()
UtilityWaitUtils


54. Keep Comments Useful

Comments should explain non-obvious decisions rather than describe every obvious line of code.

Avoid:

// Click login button

loginButton.click();

Prefer comments that explain why something unusual is required:

// Wait for the asynchronous dashboard request to complete

// before validating the page content.


55. Avoid Duplicate Locators

If the same UI element is required by multiple methods within a page class, define the locator once and reuse it.

private By searchBox = By.id("search");

 

public void search(String keyword) {

    driver.findElement(searchBox).clear();

    driver.findElement(searchBox).sendKeys(keyword);

}

 

public void clearSearch() {

    driver.findElement(searchBox).clear();

}


56. Use Reusable Login Methods

Login is often used by many tests. A reusable login page method can prevent repeated UI code.

public void login(String username, String password) {

    driver.findElement(usernameField).sendKeys(username);

    driver.findElement(passwordField).sendKeys(password);

    driver.findElement(loginButton).click();

}

Tests can then simply call:

loginPage.login(username, password);


57. Handle Page Navigation Clearly

If an action navigates to another page, clearly represent that transition in the page object design.

public HomePage login(String username, String password) {

 

    enterUsername(username);

    enterPassword(password);

    clickLogin();

 

    return new HomePage(driver);

}


58. Avoid Returning Incorrect Page Objects

A page method should return the page object that actually represents the resulting application state. If a login action fails and remains on the login page, blindly returning a HomePage can make tests misleading.

Page transitions should reflect the actual application workflow.


59. Handle Dynamic Elements Carefully

Modern web applications frequently contain dynamic IDs, asynchronous elements, overlays, and changing content.

Good POM design should combine:

  • Stable locators.
  • Explicit waits.
  • Reusable interaction methods.
  • Proper handling of dynamic elements.
  • Meaningful verification methods.


60. Avoid Stale Element Problems

Elements can become stale when the DOM is refreshed or replaced. For dynamic pages, storing WebElement references for long periods can increase the chance of stale element problems.

Where appropriate, using a By locator and locating the element when the action is performed can be more resilient.

private By refreshButton = By.id("refresh");

 

public void clickRefresh() {

    driver.findElement(refreshButton).click();

}


61. Keep Page Objects Reusable

A page object should not be designed around only one test case.

For example, a login method should support different credentials:

loginPage.login("admin", "admin123");

loginPage.login("manager", "manager123");

loginPage.login("employee", "employee123");

This allows the same page functionality to support many tests.


62. Use Parameterized Page Methods

When the same action can be performed with different values, accept those values as method parameters.

public void searchProduct(String productName) {

    searchBox.clear();

    searchBox.sendKeys(productName);

    searchButton.click();

}

This is more reusable than creating separate methods for every possible product.


63. Avoid Excessive Abstraction

POM should simplify automation rather than make simple actions unnecessarily complicated.

Do not create multiple layers of wrappers when a straightforward page method would be clearer.

Test

  |

  v

Page Method

  |

  v

WebDriver

A framework should introduce additional abstraction only when it provides meaningful reuse, consistency, or maintainability.


64. Use a Consistent Coding Style

All page classes should follow a consistent structure.

public class LoginPage {

 

    // WebDriver

    private WebDriver driver;

 

    // Locators

    private By usernameField = By.id("username");

    private By passwordField = By.id("password");

    private By loginButton = By.id("loginButton");

 

    // Constructor

    public LoginPage(WebDriver driver) {

        this.driver = driver;

    }

 

    // Actions

    public void login(String username, String password) {

        driver.findElement(usernameField).sendKeys(username);

        driver.findElement(passwordField).sendKeys(password);

        driver.findElement(loginButton).click();

    }

 

    // Verification

    public boolean isLoginButtonDisplayed() {

        return driver.findElement(loginButton).isDisplayed();

    }

}


65. Use Code Reviews for POM

As automation frameworks grow, code reviews can help identify poor locator choices, duplicated methods, excessive page responsibilities, synchronization problems, and unsafe parallel-execution patterns.

Useful review questions include:

  • Is the locator stable?
  • Is the method reusable?
  • Does the page class have a clear responsibility?
  • Is test data separated from UI logic?
  • Are waits handled appropriately?
  • Is there unnecessary duplication?
  • Could this component be reused?


66. POM and Test Reports

Page methods should provide meaningful failures so that test reports are useful. Clear method names and appropriate exceptions can make failures easier to understand.

loginPage.login(username, password);

 

Assert.assertTrue(

    homePage.isDashboardDisplayed(),

    "Dashboard was not displayed after login"

);

Good test descriptions and meaningful assertions help connect reported failures to business scenarios.


67. POM and Screenshots

For failed Selenium tests, frameworks often capture screenshots. Screenshot handling can be implemented in listeners or utilities instead of duplicating screenshot code throughout page classes.

Test Failure

    |

    v

TestNG Listener

    |

    v

Screenshot Utility

    |

    v

Screenshot File

    |

    v

Test Report


68. POM and Logging

Logging can help diagnose failures without placing excessive print statements throughout the framework.

Useful logging may include:

Starting login

Entering username

Clicking login button

Navigating to dashboard

Validating dashboard

Sensitive values such as passwords and tokens should not be logged.


69. POM and CI/CD

A clean POM framework integrates well with CI/CD systems because page logic, test logic, configuration, test data, and driver management are separated.

Developer Commit

       |

       v

CI/CD Pipeline

       |

       v

Maven Build

       |

       v

TestNG

       |

       v

Selenium

       |

       v

POM

       |

       v

Application

       |

       v

Reports


70. POM and Maven

Maven can manage Selenium, TestNG, reporting, Apache POI, logging, and other project dependencies.

mvn clean test

A well-organized POM framework can therefore be integrated into repeatable local and CI test execution.


71. Common POM Mistakes

  • Creating one huge page class for the entire application.
  • Duplicating locators across multiple classes.
  • Using unstable locators.
  • Using excessive XPath.
  • Putting test data inside page classes.
  • Putting assertions everywhere inside page methods.
  • Using Thread.sleep() unnecessarily.
  • Sharing WebDriver unsafely during parallel execution.
  • Creating duplicate utility methods.
  • Mixing page logic and framework infrastructure.
  • Hard-coding environment-specific URLs.
  • Hard-coding sensitive credentials.
  • Creating overly complex abstraction layers.
  • Using unclear method and variable names.
  • Ignoring synchronization problems.


72. POM Best Practices Checklist

PracticeRecommendation
Page ClassesCreate focused classes for pages or meaningful UI components.
LocatorsKeep locators centralized and use stable selectors.
EncapsulationKeep implementation details private.
MethodsExpose meaningful business-level actions.
Test DataKeep data separate from page logic.
AssertionsKeep important test verification in test or verification layers.
WaitsPrefer condition-based waits over arbitrary sleeps.
DriverCentralize browser creation and manage parallel execution safely.
ComponentsExtract repeated UI sections into reusable components.
ConfigurationKeep environment configuration outside page classes.
SecurityDo not expose passwords, tokens, or secrets in logs or source control.
MaintenanceKeep classes small, readable, and focused.


73. POM Before and After

Without POM

@Test

public void loginTest() {

 

    driver.findElement(By.id("username"))

          .sendKeys("admin");

 

    driver.findElement(By.id("password"))

          .sendKeys("admin123");

 

    driver.findElement(By.id("loginButton"))

          .click();

 

    Assert.assertTrue(

        driver.getTitle().contains("Dashboard")

    );

}

With POM

@Test

public void loginTest() {

 

    loginPage.login("admin", "admin123");

 

    Assert.assertTrue(

        homePage.isDashboardDisplayed()

    );

}

The second approach separates page interaction from the test scenario and is generally easier to maintain as the application changes.


74. Complete POM Example

LoginPage.java

import org.openqa.selenium.By;

import org.openqa.selenium.WebDriver;

 

public class LoginPage {

 

    private WebDriver driver;

 

    private By usernameField =

        By.id("username");

 

    private By passwordField =

        By.id("password");

 

    private By loginButton =

        By.id("loginButton");

 

    public LoginPage(WebDriver driver) {

        this.driver = driver;

    }

 

    public void login(String username, String password) {

        driver.findElement(usernameField)

              .sendKeys(username);

 

        driver.findElement(passwordField)

              .sendKeys(password);

 

        driver.findElement(loginButton)

              .click();

    }

}

HomePage.java

import org.openqa.selenium.By;

import org.openqa.selenium.WebDriver;

 

public class HomePage {

 

    private WebDriver driver;

 

    private By dashboard =

        By.id("dashboard");

 

    public HomePage(WebDriver driver) {

        this.driver = driver;

    }

 

    public boolean isDashboardDisplayed() {

        return driver.findElement(dashboard)

                    .isDisplayed();

    }

}

LoginTest.java

import org.openqa.selenium.WebDriver;

import org.openqa.selenium.chrome.ChromeDriver;

import org.testng.Assert;

import org.testng.annotations.AfterMethod;

import org.testng.annotations.BeforeMethod;

import org.testng.annotations.Test;

 

public class LoginTest {

 

    private WebDriver driver;

    private LoginPage loginPage;

    private HomePage homePage;

 

    @BeforeMethod

    public void setup() {

        driver = new ChromeDriver();

        driver.manage().window().maximize();

        driver.get("https://example.com/login");

 

        loginPage = new LoginPage(driver);

        homePage = new HomePage(driver);

    }

 

    @Test

    public void validLoginTest() {

 

        loginPage.login(

            "admin",

            "admin123"

        );

 

        Assert.assertTrue(

            homePage.isDashboardDisplayed(),

            "Dashboard was not displayed"

        );

    }

 

    @AfterMethod

    public void tearDown() {

        if (driver != null) {

            driver.quit();

        }

    }

}


75. Real-World POM Architecture

                    Test Classes

                         |

                         v

                +----------------+

                | Page Objects   |

                +----------------+

                   |     |     |

                   v     v     v

                Login  Home  Checkout

                   |

                   v

             Page Components

                   |

                   v

             Selenium WebDriver

                   |

                   v

              Web Application

 

Supporting Layers:

-------------------

Driver Factory

Config Reader

Wait Utilities

Test Data

Data Providers

Screenshot Utility

Reporting

Logging

CI/CD


76. POM Design Principles

A strong POM framework generally follows several important design principles.

  • Single Responsibility: Keep classes focused on a clear responsibility.
  • Encapsulation: Hide implementation details behind meaningful methods.
  • Reusability: Build actions that can be reused across tests.
  • Maintainability: Centralize frequently changing UI information.
  • Separation of Concerns: Keep tests, pages, utilities, data, and configuration logically separated.
  • Scalability: Design the framework so that new pages and tests can be added without excessive duplication.


77. POM Learning Roadmap

  1. Understand Selenium WebDriver.
  2. Learn the concept of design patterns.
  3. Understand Page Object Model.
  4. Create a basic LoginPage class.
  5. Move locators into page classes.
  6. Create reusable page action methods.
  7. Use constructors for WebDriver.
  8. Create multiple page classes.
  9. Use POM with TestNG.
  10. Use POM with Data Providers.
  11. Create reusable components.
  12. Introduce DriverFactory.
  13. Introduce configuration management.
  14. Add waits and synchronization utilities.
  15. Add screenshots and reporting.
  16. Support parallel execution safely.
  17. Integrate Maven and CI/CD.
  18. Build a complete scalable Selenium framework.


78. Practical Exercises

  1. Create a LoginPage using POM.
  2. Create a HomePage class.
  3. Create a ProductPage class.
  4. Create a CartPage class.
  5. Create a CheckoutPage class.
  6. Move all locators from tests into page classes.
  7. Create reusable login and logout methods.
  8. Create a reusable search method.
  9. Use TestNG DataProvider with POM.
  10. Create a DriverFactory.
  11. Create a WaitUtils class.
  12. Create a HeaderComponent.
  13. Create reusable ProductCard components.
  14. Integrate screenshots with test failure handling.
  15. Run the POM framework using Maven.
  16. Integrate the framework with CI/CD.


79. Interview Questions on POM Best Practices

1. What is Page Object Model?

POM is a design pattern that represents application pages as classes containing their locators and interactions.

2. Why is POM used in Selenium?

POM helps separate UI interaction logic from test logic and improves maintainability and reusability.

3. Should locators be stored in test classes?

In a POM design, page-specific locators are generally maintained inside the relevant page classes.

4. Why should locators be private?

Private locators provide encapsulation and prevent tests from depending directly on page implementation details.

5. What should a page class contain?

A page class generally contains page-specific locators, a WebDriver reference, constructor, and meaningful page interaction methods.

6. Should test data be stored inside page classes?

Test data should generally be separated from page interaction logic so that page methods remain reusable.

7. Should assertions be placed inside page classes?

Important test assertions are generally better maintained in test or verification layers, although page objects can expose methods that return values or state for assertions.

8. What is a BasePage?

A BasePage is a common parent class that can provide genuinely shared browser operations such as waits and common interactions.

9. What is a DriverFactory?

A DriverFactory centralizes WebDriver creation and can simplify browser selection and driver configuration.

10. What is a component object?

A component object represents a reusable UI section such as a header, menu, product card, or dialog.

11. Why should Thread.sleep() generally be avoided?

It uses a fixed delay rather than waiting for an application condition and can make tests slower or less reliable.

12. How can POM support parallel execution?

POM can support parallel execution when the underlying framework manages independent WebDriver instances and test data safely for each thread.

13. Can POM be used with TestNG DataProvider?

Yes. DataProvider can supply test data while POM handles the corresponding UI interactions.

14. How does POM reduce maintenance?

When a page locator changes, the relevant page class can often be updated without modifying every test that uses that page.

15. What is a common POM mistake?

Common mistakes include creating giant page classes, duplicating locators, mixing test data with UI logic, and creating excessive abstraction.


80. Quick Reference Table

ConceptBest Practice
Page ObjectRepresent a page or meaningful UI component.
LocatorsKeep them centralized and stable.
MethodsUse meaningful business-level actions.
WebDriverPass it through constructors or a controlled driver-management layer.
Test DataKeep it separate from page implementation.
AssertionsPerform meaningful verification in the test/verification layer.
WaitsUse condition-based synchronization.
ComponentsExtract repeated UI sections.
DriverFactoryCentralize browser creation.
UtilitiesKeep generic technical helpers separate from page classes.
Parallel ExecutionUse isolated, thread-safe browser sessions.
SecurityProtect passwords, tokens, and other secrets.


81. Summary

Page Object Model is an important design pattern for building maintainable Selenium automation frameworks. The main idea is to represent application pages and reusable UI components as classes and keep their locators and interaction methods organized inside those classes.

Following POM best practices means using stable locators, meaningful methods, encapsulation, reusable components, proper synchronization, separated test data, centralized driver management, and clear project organization.

A well-designed POM framework should keep test cases focused on business scenarios while page classes handle Selenium interactions. TestNG Data Providers can be used for multiple data sets, DriverFactory can manage browsers, utilities can handle common framework operations, and reporting/listeners can handle execution results.

The ultimate goal is not simply to create page classes, but to create an automation framework that remains readable, reusable, reliable, and maintainable as the application and test suite grow.


82. Course Resources

Learn more about Selenium automation and professional testing practices:

Final Takeaway: Good POM practices keep Selenium automation organized by separating page interaction, test scenarios, test data, configuration, driver management, and reusable framework utilities. A clean POM structure reduces duplication and makes UI changes easier to manage across a growing automation suite.

whatsapp