Page Object Best Practices

Introduction

Creating Page Objects is only the first step toward building a maintainable automation framework. To get the maximum benefit from the Page Object Model (POM), it is important to follow a set of best practices while designing your page classes.

Well-designed Page Objects make your automation framework easier to read, maintain, and extend. They also reduce code duplication, improve reusability, and make test cases more understandable.

In this tutorial, you’ll learn the most important Page Object Best Practices in Selenium with Python, along with an example, real-world scenarios, common mistakes, and recommendations followed in professional automation frameworks.


Why Follow Page Object Best Practices?

Following best practices helps you:

  • Write clean and maintainable automation code.

  • Reduce duplication across test cases.

  • Keep test scripts simple and readable.

  • Make UI changes easier to manage.

  • Improve framework scalability.

  • Build reusable automation components.


Common Page Object Best Practices

Some widely accepted Page Object best practices include:

  • Keep locators inside the Page Class.

  • Write methods that represent user actions.

  • Keep assertions inside test cases, not Page Classes.

  • Return self when method chaining is useful.

  • Create one Page Class for each webpage.

  • Use meaningful and descriptive method names.

  • Keep Page Classes focused only on page interactions.

Following these practices results in a cleaner and more professional automation framework.


Example

from selenium import webdriver
from selenium.webdriver.common.by import By


# Topic: 44. Page Object Model (POM) - Page Object Best Practices
# Practice site: https://www.testmuai.com/selenium-playground/radiobutton-demo
# Run: pytest -s 44_examples/test_03_page_object_best_practices.py
#
# Best practices: no assertions in pages, return self for fluent actions, keep
# locators private to the page, and name methods from the user perspective.


class RadioButtonDemoPage:
    URL = "https://www.testmuai.com/selenium-playground/radiobutton-demo"
    _MALE = (By.CSS_SELECTOR, "input[value='Male']")

    def __init__(self, driver):
        self.driver = driver

    def open(self):
        self.driver.get(self.URL)
        return self

    def select_male(self):
        self.driver.find_element(*self._MALE).click()
        return self

    def is_male_selected(self):
        return self.driver.find_element(*self._MALE).is_selected()


def test_page_object_best_practices():
    driver = webdriver.Chrome()

    try:
        page = RadioButtonDemoPage(driver).open().select_male()
        assert page.is_male_selected()
    finally:
        driver.quit()

Understanding the Code

Import Required Libraries

from selenium import webdriver
from selenium.webdriver.common.by import By

These modules are required to launch the browser and locate web elements.


Create the Page Class

class RadioButtonDemoPage:

A Page Class is created to represent the Radio Button Demo webpage.

The class contains everything related to this page, including its locators and user actions.


Store the Page URL

URL = "https://www.testmuai.com/selenium-playground/radiobutton-demo"

The webpage URL is stored inside the Page Class.

This avoids hardcoding URLs in multiple test cases.


Keep Locators Private

_MALE = (
    By.CSS_SELECTOR,
    "input[value='Male']"
)

Notice the locator name begins with an underscore (_).

This indicates that the locator is intended for internal use within the Page Class and should not be accessed directly by test cases.

Keeping locators private improves encapsulation and prevents accidental misuse.


Initialize the Driver

def __init__(self, driver):
    self.driver = driver

The constructor stores the WebDriver instance so that all page methods can use it.


Open the Webpage

def open(self):
    self.driver.get(self.URL)
    return self

The open() method navigates to the webpage.

Returning self allows method chaining, making test code shorter and easier to read.

Example:

page.open().select_male()

Create User-Oriented Methods

def select_male(self):

Method names should describe what the user does, not how Selenium performs the action.

A name like select_male() clearly explains the action being performed.


Perform the Click Action

self.driver.find_element(
    *self._MALE
).click()

The method locates the radio button and clicks it.

The Selenium code remains inside the Page Class instead of being repeated in test cases.


Return the Current Page Object

return self

Returning self enables multiple page methods to be chained together.

This keeps test scripts concise and readable.


Provide a Getter Method

def is_male_selected(self):

Instead of exposing Selenium code to the test, the Page Class provides a method that returns whether the radio button is selected.


Keep Assertions in the Test

assert page.is_male_selected()

Notice that the assertion is written inside the test function, not inside the Page Class.

This follows one of the most important Page Object principles:

  • Page Classes perform actions.

  • Test cases perform validations.

Separating these responsibilities makes the framework easier to maintain.


Close the Browser

driver.quit()

Closes the browser and ends the WebDriver session.


Practical Example

Suppose an application has a Login Page.

The Page Class can contain methods such as:

  • enter_username()

  • enter_password()

  • click_login()

The test case performs the validation:

  • Verify successful login.

  • Verify error message.

  • Verify page title.

This keeps responsibilities clearly separated.


Automation Testing Example

Consider an online banking application.

The TransferPage class performs actions such as:

  • Enter account number.

  • Enter transfer amount.

  • Click Transfer.

The test case verifies:

  • Transfer completed successfully.

  • Confirmation message appears.

  • Updated account balance is displayed.


Real-World Example

Professional automation frameworks follow these Page Object best practices in:

  • Banking applications

  • E-commerce websites

  • Healthcare systems

  • Insurance portals

  • CRM applications

  • ERP software

  • Enterprise web applications

These practices make large automation projects easier to maintain as the application grows.


Advantages of Following Page Object Best Practices

  • Improves code readability.

  • Makes maintenance easier.

  • Reduces duplicate Selenium code.

  • Encourages code reuse.

  • Keeps responsibilities separate.

  • Supports scalable automation frameworks.

  • Makes tests easier to understand.


Common Mistakes Beginners Make

Writing Assertions Inside Page Classes

Page Classes should perform actions only.

Assertions belong in the test cases.


Making Locators Public

Avoid exposing locators outside the Page Class.

Keep them private whenever possible.


Writing Generic Method Names

Avoid names like:

click()

Instead use descriptive names such as:

select_male()
click_login()
search_product()

These names clearly describe the user’s action.


Mixing Multiple Pages in One Class

Each webpage should have its own dedicated Page Class.

Avoid creating large classes that represent multiple pages.


Writing Selenium Code Directly in Tests

Move page interactions into Page Classes instead of repeating Selenium code in every test.


Best Practices

  • Keep locators private inside the Page Class.

  • Create one Page Class per webpage.

  • Write methods from the user’s perspective.

  • Return self when method chaining improves readability.

  • Keep assertions inside test cases.

  • Keep Page Classes responsible only for page interactions.

  • Use clear and meaningful method names.


Conclusion

Following Page Object Best Practices helps create automation frameworks that are clean, reusable, and easy to maintain. By keeping locators private, writing user-focused methods, separating actions from assertions, and organizing each webpage into its own Page Class, you can build professional Selenium frameworks that scale well as applications grow.


Frequently Asked Questions (FAQs)

Why should assertions not be placed inside Page Classes?

Page Classes should only perform page interactions.

Assertions belong in test cases because they verify the application’s behavior.


Why are locators kept private?

Private locators prevent direct access from test cases and improve encapsulation.


Why return self from page methods?

Returning self enables method chaining, making test scripts shorter and easier to read.


Should one Page Class represent multiple webpages?

No.

Each webpage should have its own dedicated Page Class.


Why should method names describe user actions?

Methods such as click_login() or select_male() make the automation code easier to understand and closely reflect real user behavior.


Key Takeaways

  • Keep locators private inside the Page Class.

  • Create user-friendly methods that describe actions.

  • Keep assertions inside test cases.

  • Return self when method chaining is useful.

  • Create one Page Class for each webpage.

  • Follow these best practices to build clean, maintainable, and scalable Selenium automation frameworks.