The Page Object Model (POM) wraps each page (or component) of your app in a Python class. Tests interact with the page through that class rather than calling Selenium directly — so when the UI changes, you update one place, not every test.
Without POM (Fragile)
def test_login_success(driver):
driver.get("https://app.example.com/login")
driver.find_element(By.ID, "email").send_keys("user@test.com")
driver.find_element(By.ID, "password").send_keys("secret")
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
assert driver.current_url.endswith("/dashboard")
def test_login_wrong_password(driver):
driver.get("https://app.example.com/login")
driver.find_element(By.ID, "email").send_keys("user@test.com")
driver.find_element(By.ID, "password").send_keys("wrong")
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
error = driver.find_element(By.CSS_SELECTOR, ".error-msg")
assert "Invalid" in error.textIf the submit button's selector changes, you fix it in both tests (and every other test that logs in).
With POM (Maintainable)
# pages/login_page.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class LoginPage:
URL = "https://app.example.com/login"
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
def open(self):
self.driver.get(self.URL)
return self
def login(self, email, password):
self.driver.find_element(By.ID, "email").send_keys(email)
self.driver.find_element(By.ID, "password").send_keys(password)
self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
return self
def error_message(self):
el = self.wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ".error-msg")))
return el.text# tests/test_login.py
from pages.login_page import LoginPage
def test_login_success(driver):
page = LoginPage(driver).open()
page.login("user@test.com", "secret")
assert driver.current_url.endswith("/dashboard")
def test_login_wrong_password(driver):
page = LoginPage(driver).open()
page.login("user@test.com", "wrong")
assert "Invalid" in page.error_message()Project Structure
tests/
├── conftest.py # shared fixtures (driver setup/teardown)
├── pages/
│ ├── login_page.py
│ ├── dashboard_page.py
│ └── checkout_page.py
└── tests/
├── test_login.py
├── test_checkout.py
└── test_search.pyPage Object Pattern Visualiser
POM structure
Ctrl+Enter
HTML
CSS
JS
Preview