Post thumbnail
SOFTWARE AUTOMATION & TESTING

From Zero to Hero: Python Selenium Page Object Model Explained

By Suman Gangopadhyay

Have you ever found yourself struggling to maintain automation scripts as your test suite grows? As web applications evolve, so does the complexity of test automation, making it essential to adopt a structured approach.

This is where the Page Object Model (POM) comes into play—a design pattern that enhances maintainability, readability, and scalability in Selenium automation.

In this article, we’ll explore how Python and Selenium work together with Page Object Model to create a clean and efficient test automation framework. By the end, you’ll understand how to implement Page Object Model and elevate your automation practices.

Table of contents


  1. What is the Page Object Model?
  2. Benefits of the Page Object Model
  3. Why Use the Page Object Model in Selenium with Python?
  4. Implementing Page Object Model in Python Selenium
    • Components
    • Components
    • Components
    • Components
    • Steps in the Fixture
  5. Conclusion

What is the Page Object Model?

The Page Object Model is a design pattern used in automation testing to represent web pages as objects. Each web page in the application under test is modeled as a class, with the class containing the web elements (such as buttons, text fields, and links) and the methods that perform actions on those elements (such as clicking a button or entering text).

The primary purpose of Page Object Model is to separate the test logic from the UI structure. This separation makes the test code more readable and maintainable. When the UI changes, you only need to update the page object class, not the individual test cases.

For example, consider a login page with username and password fields and a submit button. In POM, you would create a LoginPage class that contains locators for these elements and methods like enter_username(), enter_password(), and click_submit(). Your test cases would then use these methods instead of directly interacting with the web elements.

Benefits of the Page Object Model

The Page Object Model offers several compelling advantages:-

  1. Improved Maintainability: When the UI changes, you only need to update the page object class, not the test cases. This reduces the effort required to keep your test suite up-to-date.
  2. Reduced Code Duplication: By encapsulating common actions in methods, you avoid repeating the same code in multiple test cases.
  3. Enhanced Readability: Test cases become more readable and easier to understand because they focus on the test logic rather than the details of the UI interactions.
  4. Reusability: Page objects can be reused across multiple test cases, promoting code reuse and consistency.
  5. Better Organization: Page Object Model encourages a structured approach to organizing your test code, making it easier to manage large test suites.

These benefits make Page Object Model an essential tool for anyone looking to build a robust and scalable test automation framework.

Why Use the Page Object Model in Selenium with Python?

Python’s simplicity and readability make it an excellent choice for writing test automation scripts. When paired with Selenium, it allows for powerful and flexible automation. However, as your test suite expands, managing the code can become cumbersome without a proper structure. Page Object Model addresses this challenge by providing a clear and organized way to structure your test code. It allows you to:- 

  • Abstract UI Details: By hiding the locators and implementation details within page objects, your test cases can focus on the test scenarios rather than the specifics of the UI.
  • Facilitate Collaboration: POM makes it easier for team members to understand and contribute to the test automation code, even if they are not deeply familiar with the UI.
  • Support Parallel Development: Developers can work on the application while testers work on the automation scripts, with minimal conflicts, as long as the page objects are kept in sync with the UI changes. In the context of Selenium with Python, Page Object Model leverages Python’s object-oriented features to create a clean and efficient automation framework.

Implementing Page Object Model in Python Selenium

To illustrate the implementation of POM, let’s consider a simple example. The Project structure is given as below :- 

Implementing Page Object Model in Python Selenium

The project structure is designed to adhere to the principles of the Page Object Model and best practices for test automation. It separates concerns into distinct categories, ensuring a clean, modular, and maintainable framework:

  • Page Objects (in PageObjects): Encapsulate the UI structure and interactions, making the code reusable and easier to maintain when the UI changes. Each page object represents a specific page or component of the application, abstracting its locators and interactions.
  • Tests (in Tests): Focus on the test logic and assertions, using the page objects to interact with the application. This separation ensures that tests are concise and focused on verification, not UI manipulation.
  • Common Utilities (in common.py): Provide shared logic or helper functions that are not specific to any page or test, such as Selenium helper methods or logging utilities.
  • Pytest Configuration (in conftest.py): Centralize the setup and configuration for the testing framework, such as fixtures for WebDriver management and hooks for customizing test execution.


Modularity and Reusability: The use of directories and __init__.py files allows for modular imports, making it easy to extend the framework by adding new page objects and test cases. For example, if the application grows to include additional pages (e.g., a dashboard or settings page), new page objects can be added to the PageObjects directory, and corresponding tests can be added to the Tests directory.

Scalability: As the application grows, new page objects and test files can be added to the respective directories without disrupting the existing structure. This ensures that the framework remains maintainable and scalable, even for large-scale test automation projects.

Root Directory:

  • common.py for shared utilities.
  • conftest.py for Pytest fixtures and configuration.

PageObjects Directory:

  • base_page.py for common page object functionality.
  • login_page.py for login-specific page interactions.
  • __init__.py to enable package imports.

Tests Directory:

  • test_login.py for login-related test cases.
  • __init__.py to enable package imports.

Now, let us see the code and understand its working:- 

# PageObjects/base_page.py

"""

The BasePage Class includes common methods like find elements, clicks and interact with UI

"""

from selenium.webdriver.support.ui import WebDriverWait

from selenium.webdriver.support import expected_conditions as EC

from selenium.common.exceptions import TimeoutException

from selenium.common.exceptions import ElementNotVisibleException

from selenium.common.exceptions import NoSuchElementException

from common import Config

class BasePage:

   def __init__(self, driver):

       self.driver = driver

       self.timeout = Config().TIMEOUT

   def find_element(self, locator):

       try:

           web_element = WebDriverWait(self.driver, self.timeout).until(EC.presence_of_element_located(locator))

           return web_element

       except (NoSuchElementException, ElementNotVisibleException, TimeoutException):

           raise Exception(f"Element {locator} not found within {self.timeout} seconds")

   def is_visible(self, locator):

       try:

           web_element = WebDriverWait(self.driver, self.timeout).until(EC.visibility_of_element_located(locator))

           return web_element

       except (NoSuchElementException, ElementNotVisibleException, TimeoutException):

           return False

   def is_enabled(self, locator):

       try:

           web_element = WebDriverWait(self.driver, self.timeout).until(EC.element_to_be_clickable(locator))

           return web_element

       except (NoSuchElementException, ElementNotVisibleException, TimeoutException):

           return False

   def click(self, locator):

       element = self.find_element(locator) and self.is_visible(locator) and self.is_enabled(locator)

       element.click()

   def enter_text(self, locator, text):

       element = self.find_element(locator)

       element.click()

       element.send_keys(text)

This code defines a BasePage class, which includes several common methods used to interact with web elements during automated browser testing using the Selenium library. These methods handle tasks such as finding elements, checking visibility and enablement, clicking elements, and entering text.

MDN

Components

  1. Imports:
    • WebDriverWait, expected_conditions (EC): For waiting until certain conditions are met.
    • TimeoutException, ElementNotVisibleException, NoSuchElementException: Handle exceptions related to element interactions.
    • Config: Custom configuration class.
  2. BasePage Class:
    • Constructor:
      • __init__(self, driver): Initializes the class with a web driver instance and sets a timeout value from the configuration.
    • Methods:
      • find_element(self, locator): Waits for an element to be present and returns it. Raises an exception if the element is not found within the timeout period.
      • is_visible(self, locator): Checks if an element is visible and returns the element if it is. Returns False if the element is not found or not visible within the timeout period.
      • is_enabled(self, locator): Checks if an element is clickable and returns the element if it is. Returns False if the element is not found or not clickable within the timeout period.
      • click(self, locator): Clicks on an element after ensuring it is present, visible, and enabled.
      • enter_text(self, locator, text): Finds an element, clicks on it, and enters the specified text.

This code defines a LoginPage class that inherits from both BasePage and Locators classes. The class provides methods to interact with the login page elements, such as entering a username, entering a password, and clicking the login button. The Locators class contains the locators for the input fields and the submit button.

Components

  1. Imports:
    • By: For specifying the type of locator (e.g., name, XPath).
    • BasePage: Base class for page objects containing common methods.
  2. Locators Class:
    • Attributes:
      • USERNAME_INPUT_BOX: Locator for the username input box.
      • PASSWORD_INPUT_BOX: Locator for the password input box.
      • SUBMIT_BUTTON: Locator for the submit button.
  3. LoginPage Class:
    • Attributes:
      • USERNAME_INPUT: Tuple containing the locator strategy and locator value for the username input field.
      • PASSWORD_INPUT: Tuple containing the locator strategy and locator value for the password input field.
      • LOGIN_BUTTON: Tuple containing the locator strategy and locator value for the login button.
    • Methods:
      • enter_username(self, username): Enters the specified username in the username input field by calling the enter_text method from the BasePage class.
      • enter_password(self, password): Enters the specified password in the password input field by calling the enter_text method from the BasePage class.
      • click_login(self): Clicks the login button by calling the click method from the BasePage class.
# common.py

"""

Common/Basic Configuration file for the web application

"""

class Config:

   BASE_URL = "https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"

   DASHBOARD_URL = "https://opensource-demo.orangehrmlive.com/web/index.php/dashboard/index"

   TIMEOUT = 10

   USERNAME = "Admin"

   PASSWORD = "admin123"

This code defines a Config class that holds common configuration settings for a web application. These configurations include URLs, timeout values, and credentials used for logging into the web application.

Components

  1. Config Class:
    • Attributes:
      • BASE_URL: The base URL for the login page of the web application.
      • DASHBOARD_URL: The URL for the dashboard page of the web application.
      • TIMEOUT: The time (in seconds) to wait for a web element to become available before timing out.
      • USERNAME: The username used for logging into the web application.
      • PASSWORD: The password used for logging into the web application.
# conftest.py

"""

Use conftest.py is used to Manage the WebDriver setup and teardown

Centralizes WebDriver setup making it reusable across your tests

"""

import pytest

from selenium import webdriver

from selenium.webdriver.chrome.service import Service

from webdriver_manager.chrome import ChromeDriverManager

@pytest.fixture(scope="function")

def driver():

   """Setup WebDriver"""

   driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))

   driver.maximize_window()

   """Test Executes Here"""

   yield driver

   """Cleanup after Test"""

   driver.quit()

This code manages the setup and teardown of the WebDriver for browser automation testing. By using conftest.py, you centralize the WebDriver setup, making it reusable across multiple test cases in your test suite. This is achieved using the pytest framework’s fixture mechanism.

Components

  1. Imports:
    • pytest: For defining fixtures used in tests.
    • webdriver: Selenium WebDriver for browser automation.
    • Service: Service class to manage the browser driver.
    • ChromeDriverManager: Automatically manages the installation and setup of the ChromeDriver.
  2. Fixture:
    • driver(): A pytest fixture that sets up the WebDriver, maximizes the browser window, executes the test, and then quits the WebDriver after the test is completed.

Steps in the Fixture

  1. Setup WebDriver:
    • The WebDriver for Chrome is initialized using the ChromeDriverManager to automatically handle the setup.
    • The browser window is maximized for the test.
  2. Yield Statement:
    • The yield statement temporarily returns control back to the test case where the fixture is being used.
    • The test case will execute after the yield statement.
  3. Cleanup after Test:
    • After the test case execution, control returns to the fixture, and the WebDriver is quit to clean up resources.
# Tests/test_login.py

"""

Create test using Pytest and Page Object Model

Focuses purely on Test Logic leveraging the Structured Page Objects

Commands :

pytest -v test_login.py

"""

import pytest

from PageObjects.login_page import LoginPage

from common import Config

from conftest import driver

def test_valid_login(driver):

   driver.get(Config().BASE_URL)

   login_page = LoginPage(driver)

   login_page.enter_username(Config().USERNAME)

   login_page.enter_password(Config().PASSWORD)

   login_page.click_login()

   # Assert some Post-Login Behavior

   assert Config().DASHBOARD_URL == driver.current_url

   print(f"SUCCESS : Login Success with {Config().USERNAME} and {Config().PASSWORD}")

This code creates a test case for the login functionality using the Pytest framework and the Page Object Model (POM) design pattern. The test is designed to validate a successful login to the web application.

Components

  1. Imports:
    • pytest: For defining and running tests.
    • LoginPage: Page Object class representing the login page.
    • Config: Configuration settings for the web application.
    • driver: Pytest fixture that sets up the WebDriver, defined in conftest.py.
  2. Test Function:
    • test_valid_login(driver): This function performs the following steps:
      1. Navigate to the Base URL:
        • driver.get(Config().BASE_URL): Opens the login page using the base URL from the configuration.
      2. Initialize LoginPage Object:
        • login_page = LoginPage(driver): Creates an instance of the LoginPage class, which provides methods to interact with the login page elements.
      3. Enter Username and Password:
        • login_page.enter_username(Config().USERNAME): Enters the username defined in the configuration.
        • login_page.enter_password(Config().PASSWORD): Enters the password defined in the configuration.
      4. Click the Login Button:
        • login_page.click_login(): Clicks the login button to submit the login form.
      5. Assert Post-Login Behavior:
        • assert Config().DASHBOARD_URL == driver.current_url: Verifies that the current URL matches the expected dashboard URL after a successful login.
        • print(f”SUCCESS : Login Success with {Config().USERNAME} and {Config().PASSWORD}”): Prints a success message to the console

In case you want to learn more about Python Selenium in Automation Testing, consider enrolling for GUVI’s Selenium Automation Testing Course which teaches you everything from scratch by providing an industry-grade certificate!

MDN

Conclusion

In conclusion, the Page Object Model is a powerful design pattern that can significantly improve the quality and maintainability of your Selenium test automation scripts. By encapsulating the UI elements and actions within page object classes, you create a clear separation between test logic and UI interactions.

This leads to cleaner, more readable, and more maintainable test code. In this introduction, we’ve explored what POM is, its benefits, and how to implement it in Python with Selenium. We’ve also provided a simple example to illustrate the concept.

As you delve deeper into test automation, adopting the Page Object Model will help you manage complexity and scale your test suite effectively.

Career transition

Did you enjoy this article?

Schedule 1:1 free counselling

Similar Articles

Loading...
Share logo Copy link
Power Packed Webinars
Free Webinar Icon
Power Packed Webinars
Subscribe now for FREE! 🔔
close
Webinar ad
Table of contents Table of contents
Table of contents Articles
Close button

  1. What is the Page Object Model?
  2. Benefits of the Page Object Model
  3. Why Use the Page Object Model in Selenium with Python?
  4. Implementing Page Object Model in Python Selenium
    • Components
    • Components
    • Components
    • Components
    • Steps in the Fixture
  5. Conclusion