Of course! Let's dive deep into Page Factory in Python.

What is Page Factory?
Page Factory is a design pattern in test automation that simplifies the creation and initialization of Page Objects. It's not a separate library you install but rather a pattern you implement, often using helper functions or a dedicated library like Selenium Page-Objects.
The core idea is to separate the test logic from the page's UI element locators. Instead of hardcoding find_element calls in your test methods, you define them in a structured way at the top of your Page Object class.
Think of it like a factory that manufactures your page objects, populating them with the necessary UI elements (locators) in one go.
The Problem Page Factory Solves
Consider a standard Page Object without the Page Factory pattern:

# LoginPage.py
from selenium.webdriver.common.by import By
class LoginPage:
def __init__(self, driver):
self.driver = driver
def enter_username(self, username):
# Hardcoded locator inside the method
self.driver.find_element(By.ID, "username").send_keys(username)
def enter_password(self, password):
# Another hardcoded locator
self.driver.find_element(By.ID, "password").send_keys(password)
def click_login(self):
# And another one...
self.driver.find_element(By.XPATH, "//button[@type='submit']").click()
Problems with this approach:
- Hardcoded Locators: The locators (
By.ID, "username") are mixed with the test logic. If a locator changes, you have to find and change it inside the method. - No Centralized Locators: All locators are scattered throughout the methods. There's no single place to see all the elements on a page.
- Repetitive Code: The
find_elementcall is repeated in every method. - No Type Hinting: It's hard to know what kind of element you're working with (e.g., is it a text input, a button, a dropdown?).
The Page Factory Solution
The Page Factory pattern addresses these issues by using a class-level attribute to define locators. The most common implementation uses the @FindBy annotation.
Here’s how the same LoginPage would look using the Page Factory pattern with the popular selenium-page-objects library.
Setup
First, you need to install the library:

pip install selenium-page-objects
Implementing the Page Object
Now, let's refactor our LoginPage.
# LoginPage_PF.py
from selenium.webdriver.common.by import By
from page_objects import PageObject, PageElement
class LoginPage_PF(PageObject):
# --- Define Page Elements using @FindBy ---
# This is the core of Page Factory.
# The library will find these elements and attach them to the class.
username = PageElement(id_="username")
password = PageElement(id_="password")
login_button = PageElement(xpath="//button[@type='submit']")
# You can also find lists of elements
error_messages = PageList(xpath="//div[@class='error-message']")
def __init__(self, driver):
super().__init__(driver, base_url="https://example.com/login")
def login(self, username, password):
"""A high-level action method that uses the defined elements."""
self.username = username # This uses the PageElement's __set__ method
self.password = password
self.login_button.click()
How it Works Under the Hood
The selenium-page-objects library (or any Page Factory implementation) does the following magic when you initialize the page object:
- It scans the class for attributes that are
PageElementinstances. - For each
PageElement, it uses the locator you provided (id_="username") to find the actual Selenium WebElement on the web page. - It attaches this found WebElement to the instance, so
self.usernamenow points directly to the web element.
Writing the Test
Your test code becomes incredibly clean and readable because it doesn't contain any find_element calls.
# test_login.py
import unittest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from LoginPage_PF import LoginPage_PF
class TestLogin(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
self.driver.maximize_window()
def test_successful_login(self):
# 1. Navigate to the page
self.driver.get("https://example.com/login")
# 2. Initialize the Page Object
# The Page Factory pattern kicks in here!
login_page = LoginPage_PF(self.driver)
# 3. Perform actions using the clean page object methods
login_page.login("standard_user", "secret_sauce")
# 4. Add an assertion (e.g., check if user is redirected)
# ... your assertion logic here ...
def tearDown(self):
if self.driver:
self.driver.quit()
if __name__ == "__main__":
unittest.main()
Key Benefits of Page Factory
- Readability: Tests read like a user story:
login_page.login("user", "pass"). It's clear what's happening. - Maintainability: If an ID changes from
usernametouser-name, you only need to change it in one place at the top of theLoginPage_PFclass. - Centralized Locators: All locators for a page are grouped together, making it easy to get an overview of the page's structure.
- Reduced Code Duplication: You don't write
find_elementover and over again. - Type Safety and Features: Libraries like
selenium-page-objectsprovidePageElementandPageListobjects that offer extra functionality, like waiting for elements to be clickable or visible, which you can configure in the@FindBydecorator.
Advanced Features of selenium-page-objects
The @FindBy decorator is powerful and supports many options:
from page_objects import PageElement, PageList
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class AdvancedPage(PageObject):
# Basic usage
search_box = PageElement(id_="q")
# Using a different locator strategy
submit_button = PageElement(name_="btnK")
# Waiting for the element to be present
results = PageList(
xpath="//div[@class='g']",
wait_for=EC.presence_of_all_elements_located,
timeout=10
)
# Chaining locators (find 'div' inside 'main#content')
article_title = PageElement(
locator=PageElement(css_selector="main#content"),
locator_strategy=By.XPATH,
value=".//h1"
)
Page Factory vs. Standard Page Object
| Feature | Standard Page Object | Page Factory |
|---|---|---|
| Locator Definition | Inside methods (e.g., find_element(...)) |
At the class level, using decorators (@FindBy) |
| Readability | Good, but locators are mixed with actions. | Excellent. Separates structure from actions. |
| Maintainability | Poor. Locators are scattered. | Excellent. All locators are in one place. |
| Initialization | Standard __init__ |
Requires a special initialization step (usually handled by the library). |
| Learning Curve | Simpler to understand at first. | Requires learning the specific Page Factory library's syntax. |
Conclusion
Page Factory is a best practice for building scalable and maintainable test frameworks in Python with Selenium. It enforces a clean separation of concerns, making your tests easier to write, read, and modify. While you can implement a basic version yourself, using a well-established library like selenium-page-objects is highly recommended as it provides robust features and handles the complex initialization logic for you.
