Hey, we're Apify. The Apify platform gives you access to 2,000+ web scraping and automation tools. Or you can build your own. Check us out.
What is the Page Object Model (POM)?
At its core, the Page Object Model (POM) is a design pattern used in Selenium automation to represent a web application's web pages or components as objects in code. Each web page is associated with a Page Object, and this object encapsulates the page's structure, elements, interactions, and intricacies.

Why is POM essential for Selenium automation?
Imagine a scenario where you have a sizable Selenium test suite. Web pages change, elements get updated, and your tests require frequent adjustments. Without POM, managing this can become a nightmare. Test scripts often get cluttered with web element locators and actions, making them difficult to read and maintain. POM addresses these challenges by introducing the concept of Page Objects.
Think of a Page Object as a blueprint for a web page. It contains methods and properties that allow you to interact with the page's elements (e.g., buttons, text fields, links) and perform actions (e.g., clicking, typing) on them. By creating Page Objects, you achieve a clear separation of concerns: your test scripts focus on test logic, while the Page Objects handle the web page's details.
Advantages of using POM
- Maintainability: In large-scale automation projects, web pages often change. Elements get updated, added, or removed. Without a structured approach like POM, maintaining your test scripts becomes a nightmare. POM allows you to isolate changes to Page Objects, making updates more manageable.
- Readability: POM promotes readable and maintainable test scripts. With Page Objects, your tests become more expressive, as you interact with elements using descriptive method names. This improves the overall clarity of your test cases.
- Reusability: Page Objects are reusable components. When multiple tests interact with the same page, you can use the same Page Object in each test. If the page's structure changes, you only need to update the Page Object, not every test case.
- Scalability: POM scales well with the size of your automation project. As you add more test cases and pages, the structured approach provided by POM keeps your codebase organized and maintainable.
Setting up your environment
Before we dive into implementing the Page Object Model (POM) in Selenium, it's crucial to ensure your development environment is properly configured. In this section, we'll cover the necessary prerequisites and guide you through creating a Python project for Selenium automation.
Prerequisites
To get started with Selenium and the Page Object Model, you'll need the following:
Python: Make sure you have Python installed on your system. You can download the latest version from the official Python website.
Selenium: Install the Selenium WebDriver library using Python's package manager, pip, by running the following command:
Creating a Selenium Project
Once you have the prerequisites in place, you can create a new Python project for your Selenium automation work by following the steps below, or clone the GitHub repository with the final code for this tutorial.
-
Create a Project Directory: Create a directory in your desired location to store your Selenium project, and then navigate into that directory.
mkdir selenium-pom cd selenium-pom
-
Initialize a Python Virtual Environment (Optional): It's a good practice to work within a virtual environment to isolate your project's dependencies. Inside the project directory we created in the previous step, create a virtual environment using the following command:
python -m venv venv
Activate the virtual environment:
- On Windows:
venv\\Scripts\\activate
- On macOS and Linux:
source venv/bin/activate
- On Windows:
-
Install Selenium: Inside your virtual environment, install Selenium by running the following command:
pip install selenium
-
WebDrivers: Selenium requires WebDriver executables for different browsers (e.g., Chrome, Firefox). You'll need to download the WebDriver for your preferred browser and ensure it's accessible from your system's PATH. You can find WebDriver downloads and installation instructions on the official Selenium website.
-
Create Python Files and organize your project: To organize our Selenium project, we will create Python files for Page Objects, test scripts, and any additional utilities we might require. We can structure our project by creating directories to categorize these components. This will help us keep our code base clean, easy to understand, and maintainable. As an example, here is the directory structure of the project we will work on during this article:

Great, your environment is now set up and ready for Selenium automation with the Page Object Model. In the upcoming sections, we'll take a deeper look into the practical implementation of POM, starting with creating Page Objects to represent web pages.
Creating Page Objects
What is a Page Object?
A Page Object is a Python class that represents a specific web page or a component of a web page. It encapsulates the structure and behavior of that page, including the web elements (e.g., buttons, input fields) and the actions you can perform on them (e.g., clicking, typing). Page Objects promote code reusability and maintainability by providing a clean and organized way to interact with web elements.
So letβs create our first Page Object:
Step 1: Define the Page Object class
Create a Python class for the web page you want to represent. Give it a meaningful name, typically ending with "Page," to indicate its purpose.
In this example, we've created a LoginPage
class.
Our goal will be to implement tests for a dummy login page (thanks to Dmitry Shyshkin for the website). We will create tests for three distinct scenarios:
- Login successful: User entered valid credentials.
- Invalid username: User entered an invalid username.
- Invalid password: User entered an invalid password.

Step 2: Define web elements and actions
Now we need a way to access the web elements and actions from within the Page Object class. To keep things organized, we created a separate file under the utils
directory to house all the locators we need:
Here, we've defined the elements USERNAME
, PASSWORD
, SUBMIT
and ERROR_MESSAGE
based on the elementsβ IDs found on the target website.
Once this is done, we have to import locator.py
and its contents into the login_page.py
file.
Step 3: Implement methods
Still, within the login_page.py
file, our task is to define methods that represent the interactions we want to happen on the web page.
All three previously discussed test cases involve attempting to log into an account. This login process essentially involves entering the username, and password, and then clicking the "Submit" button.
With these requirements in mind, we can design methods that precisely execute these actions. For example, the enter_username
method locates the username input field and inputs the provided username using the send_keys
function. The other methods in this class follow the same idea:
You might have noticed that the three last methods are a little different. These methods use the high-level login method we defined to perform the login action with the specified username and password combinations. We will soon employ these methods to run tests to evaluate our test cases.
Writing test cases with POM
With the Page Object in place, we can now incorporate it into our test scripts. But first, in the pursuit of maintaining modularity and organization within our code, letβs create a base_test.py
file.
The purpose of this file is to serve as a repository for all the shared logic used across our tests. By centralizing this logic, we establish a convenient reference point whenever we need to generate new test files.
Test case 1: Logging in with valid user credentials
Now that our base test is set up, we can begin developing the logic for our login test.
The defined method test_login_with_valid_user
serves as a test for our initial scenario: logging in using valid user credentials. For the test to succeed, we should see the text "logged-in-successfully" in the URL of the webpage right after submitting our credentials. If thatβs the case, a positive test feedback message will be printed in our terminal.
To run the method, type the following command in your terminal:


Test case 2: Logging in with an invalid username
With the method for our first test case out of the way, letβs move on to the second scenario: logging in with an invalid username.
The method test_login_with_invalid_username
tests for the second scenario: trying to log in using an invalid username. For the test to succeed, we should see the error message "Your username is invalid!" displayed on the screen right after clicking the βSubmitβ button. If thatβs the case, the test passes.
To run the method, type the following command in your terminal:

Test case 3: Logging in with an invalid password
Similar to the previous method, the method checks for a particular error message that should be displayed when the user enters a valid username together with an invalid password. The logic is almost the same, except that, this time, we should expect a different error message to be displayed.
The method test_login_with_invalid_password
tests for the third scenario: trying to log in using an invalid password. For the test to be successful, we should see the error message "Your password is invalid!" displayed on the screen immediately after clicking the "Submit" button. If this message appears, it signifies a passing test.
To run the method, type the following command in your terminal:

Running all tests
Now that we have all three methods ready, we may want to execute them all together to test all of our test cases simultaneously. Here is the complete code:
To run all methods in the TestLogin
class at once, type the following command in your terminal:
After a few seconds, you should see a similar message displayed on your terminal:

Handling page navigation and dynamic elements
In web testing and automation, it's common to encounter scenarios where web pages have dynamic elements, or your test cases require navigation between different pages. The Page Object Model (POM) provides an organized way to handle these challenges.
Handling dynamic elements
Dynamic elements are elements on a web page that may load or change after the initial page load. Examples include elements that appear after a delay, elements generated via JavaScript, or elements with dynamic IDs or attributes.
To handle dynamic elements with POM:
- Include Dynamic Elements in Page Objects: In your Page Object class, include dynamic elements as attributes. You can locate these elements using Selenium locators just like any other element.
- Use Explicit Waits: To ensure that dynamic elements are fully loaded before interacting with them, use Selenium's explicit waits. Explicit waits allow you to wait for specific conditions to be met before proceeding with the test.
Here's an example of how we used an explicit wait within our login Page Object to enhance the reliability of the tests we've just created:
In this example, the wait_for_element
method waits for the element to be present using an explicit wait before running the rest of the code.
Handling page navigation
With POM, you can encapsulate page navigation within Page Objects, making your test scripts more modular.
To handle page navigation with POM:
Step 1: Include Navigation Methods in Page Objects: Create methods within your Page Objects for navigating to other pages. For example, you can have a go_to_dashboard
method in a HomePage
Page Object.
Step 2: Reuse Page Objects: After navigating to a new page, you can create an instance of the corresponding Page Object to continue interacting with that page. This promotes code reusability and maintains a clear structure.
Here's an example of navigating from the login page to the dashboard page using Page Objects:
By encapsulating page navigation and dynamic element handling within Page Objects, you maintain a structured and organized approach to your Selenium automation, making your test scripts more robust and maintainable.
Running tests and reporting
Running your Selenium tests and generating reports are essential steps in any automation project. So far, weβve been using running our tests using unittest
. While Selenium test runners provide basic feedback, it's helpful to generate more informative test reports. We can achieve this by integrating test reporting libraries or frameworks.
For example, we can use pytest
and the pytest-html
plugin to create basic HTML test reports for better visibility into our automation results.
Generating Basic Test Reports
Install pytest-html
:
Run Tests with pytest
and Generate HTML Report:
This command will run your tests and generate an HTML report named report.html
.
View the HTML report:
Open the generated HTML report in a web browser to see detailed test results, including passed and failed test cases, error messages, and timestamps.

This basic reporting setup provides a visual representation of our test execution, making it easier to identify issues and share results with our team.
Remember that there are more advanced reporting and test management tools available that you can integrate into your automation framework for more comprehensive reporting, such as Allure, TestNG, or ExtentReports. But thatβs a topic for another article π
Read more about Selenium
In this tutorial, we've explored the Page Object Model (POM) and how it can make our Selenium automation projects more scalable, readable, and, overall, more professional. But there's much more to Selenium and web automation, so check out our other Selenium posts:
π Playwright vs. Selenium: which one to use for web scraping?
π Puppeteer vs. Selenium for automation
π Cypress vs. Selenium: choosing the right web testing and automation framework
π Selenium Webdriver: how to handle popups
π Selenium Webdriver: how to handle iframes
π Selenium Grid: what it is and how to set it up
π Web scraping with Selenium