Imagine you have 200 Selenium tests that all interact with the login page. Each test contains the same three locators โ username field, password field, login button โ hardcoded directly in the test method. Now a developer changes the username field’s ID from user-name to email. You must find and update that locator in all 200 files. Miss one, and a test breaks. This maintenance nightmare is exactly what the Page Object Model solves. POM is the most important design pattern in UI automation โ the line between amateur scripts and professional frameworks.
The Problem: Duplicated Locators and Fragile Tests
Without POM, locators and Selenium API calls are scattered across every test file. When the UI changes, the same fix must be applied in dozens of places. POM eliminates this duplication by centralising page knowledge in dedicated classes.
# โโ WITHOUT POM โ the maintenance nightmare โโ
# test_login_valid.py
def test_valid_login(browser):
browser.get("https://www.saucedemo.com")
browser.find_element(By.ID, "user-name").send_keys("standard_user") # locator here
browser.find_element(By.ID, "password").send_keys("secret_sauce") # locator here
browser.find_element(By.ID, "login-button").click() # locator here
assert "inventory" in browser.current_url
# test_login_invalid.py โ SAME locators duplicated
def test_invalid_login(browser):
browser.get("https://www.saucedemo.com")
browser.find_element(By.ID, "user-name").send_keys("wrong_user") # duplicated!
browser.find_element(By.ID, "password").send_keys("wrong_pass") # duplicated!
browser.find_element(By.ID, "login-button").click() # duplicated!
error = browser.find_element(By.CSS_SELECTOR, "[data-test='error']")
assert "do not match" in error.text.lower()
# test_login_empty.py โ SAME locators duplicated AGAIN
def test_empty_login(browser):
browser.get("https://www.saucedemo.com")
browser.find_element(By.ID, "login-button").click() # duplicated!
error = browser.find_element(By.CSS_SELECTOR, "[data-test='error']")
assert "username is required" in error.text.lower()
# Problem: "user-name" appears in 200 test files.
# If the ID changes to "email", you must update ALL 200 files.
# โโ WITH POM โ single source of truth โโ
# pages/login_page.py โ locators defined ONCE
class LoginPage:
URL = "https://www.saucedemo.com"
USERNAME = (By.ID, "user-name") # defined ONCE
PASSWORD = (By.ID, "password") # defined ONCE
LOGIN_BTN = (By.ID, "login-button") # defined ONCE
ERROR_MSG = (By.CSS_SELECTOR, "[data-test='error']")
def __init__(self, driver):
self.driver = driver
def open(self):
self.driver.get(self.URL)
return self
def login(self, username, password):
self.driver.find_element(*self.USERNAME).send_keys(username)
self.driver.find_element(*self.PASSWORD).send_keys(password)
self.driver.find_element(*self.LOGIN_BTN).click()
def get_error_message(self):
return self.driver.find_element(*self.ERROR_MSG).text
# If "user-name" changes to "email", update ONE line in LoginPage.
# All 200 tests continue to work without modification.
print("POM: Change locator in 1 place โ 200 tests stay green")
USERNAME = (By.ID, "user-name") โ and unpack them with the asterisk operator: self.driver.find_element(*self.USERNAME). This pattern keeps locator definitions clean, makes them easy to scan visually at the top of the class, and allows you to pass them to WebDriverWait expected conditions directly: WebDriverWait(driver, 10).until(EC.visibility_of_element_located(self.USERNAME)).find_element and click, you have not implemented POM โ you have created a locator repository with the same fragility problems in a different location.Common Mistakes
Mistake 1 โ Storing locators in page objects but not encapsulating actions
โ Wrong: Page object has locators only; test still calls login_page.driver.find_element(*LoginPage.USERNAME).send_keys("user").
โ
Correct: Page object has a login(username, password) method that encapsulates all three steps. Test calls login_page.login("user", "pass") โ one readable line.
Mistake 2 โ Creating one giant page object for the entire application
โ Wrong: A single AppPage class with 500 locators and 200 methods covering every page in the application.
โ
Correct: One page object per page or major component: LoginPage, InventoryPage, CartPage, CheckoutPage. Each class has 5โ20 locators and 5โ15 methods. Keep classes focused and manageable.