Selenium Cheatsheet
Page Object Model
Use this Selenium reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
What Is the Page Object Model
POM is a design pattern that models each page (or component) as a class. Locators and page interactions live in the class; test logic lives in the test. This decouples test assertions from DOM details, so a locator change requires editing one file.
Basic Page Object
# 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" # Locators as class-level tuples EMAIL_INPUT = (By.ID, "email") PASSWORD_INPUT = (By.ID, "password") SUBMIT_BTN = (By.CSS_SELECTOR, "button[type='submit']") ERROR_MSG = (By.CLASS_NAME, "error-banner") def __init__(self, driver): self.driver = driver self.wait = WebDriverWait(driver, 10) def open(self): self.driver.get(self.URL) return self def enter_email(self, email: str): el = self.wait.until(EC.visibility_of_element_located(self.EMAIL_INPUT)) el.clear() el.send_keys(email) return self def enter_password(self, password: str): el = self.wait.until(EC.visibility_of_element_located(self.PASSWORD_INPUT)) el.clear() el.send_keys(password) return self def submit(self): self.wait.until(EC.element_to_be_clickable(self.SUBMIT_BTN)).click() return self def login(self, email: str, password: str): """Convenience method: full login flow.""" return self.enter_email(email).enter_password(password).submit() def get_error_message(self) -> str: return self.wait.until( EC.visibility_of_element_located(self.ERROR_MSG) ).text
Base Page Class
Reduce duplication by putting shared helpers in a base class:
# pages/base_page.py from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class BasePage: def __init__(self, driver, timeout: int = 10): self.driver = driver self.wait = WebDriverWait(driver, timeout) def find(self, locator): return self.wait.until(EC.presence_of_element_located(locator)) def find_visible(self, locator): return self.wait.until(EC.visibility_of_element_located(locator)) def find_clickable(self, locator): return self.wait.until(EC.element_to_be_clickable(locator)) def click(self, locator): self.find_clickable(locator).click() return self def type_in(self, locator, text: str): el = self.find_visible(locator) el.clear() el.send_keys(text) return self def get_text(self, locator) -> str: return self.find_visible(locator).text def is_visible(self, locator) -> bool: try: return self.find_visible(locator).is_displayed() except Exception: return False def wait_for_url(self, partial: str): self.wait.until(EC.url_contains(partial)) return self
# pages/login_page.py (using BasePage) from selenium.webdriver.common.by import By from pages.base_page import BasePage from pages.dashboard_page import DashboardPage class LoginPage(BasePage): URL = "https://app.example.com/login" EMAIL = (By.ID, "email") PASSWORD = (By.ID, "password") SUBMIT = (By.CSS_SELECTOR, "button[type='submit']") ERROR = (By.CLASS_NAME, "error-banner") def open(self): self.driver.get(self.URL) return self def login(self, email: str, password: str): self.type_in(self.EMAIL, email) self.type_in(self.PASSWORD, password) self.click(self.SUBMIT) return DashboardPage(self.driver) def get_error(self) -> str: return self.get_text(self.ERROR)
Page Chaining (Fluent Interface)
Page methods return self (for continuing on the same page) or the next page object (after a navigation action):
# pages/dashboard_page.py from pages.base_page import BasePage from selenium.webdriver.common.by import By class DashboardPage(BasePage): WELCOME_MSG = (By.CSS_SELECTOR, "h1.welcome") LOGOUT_BTN = (By.ID, "logout") def get_welcome_text(self) -> str: return self.get_text(self.WELCOME_MSG) def logout(self): from pages.login_page import LoginPage self.click(self.LOGOUT_BTN) return LoginPage(self.driver)
Using POM in Tests (pytest)
# tests/test_auth.py import pytest from selenium import webdriver from pages.login_page import LoginPage @pytest.fixture def driver(): d = webdriver.Chrome() d.maximize_window() yield d d.quit() def test_successful_login(driver): dashboard = LoginPage(driver).open().login("alice@example.com", "correct-password") assert "Welcome" in dashboard.get_welcome_text() def test_invalid_login_shows_error(driver): login = LoginPage(driver).open() login.login("alice@example.com", "wrong-password") assert "Invalid credentials" in login.get_error() def test_logout_returns_to_login(driver): login_page = ( LoginPage(driver) .open() .login("alice@example.com", "correct-password") .logout() ) assert driver.current_url == LoginPage.URL
Component Objects (Reusable Sub-Components)
For repeated UI widgets (nav bar, data table, modal) extract a component class:
# components/data_table.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 DataTable: ROW_SELECTOR = "tbody tr" CELL_SELECTOR = "td" def __init__(self, driver, root_locator): self.driver = driver self.root = driver.find_element(*root_locator) def get_rows(self): return self.root.find_elements(By.CSS_SELECTOR, self.ROW_SELECTOR) def get_cell(self, row_index: int, col_index: int) -> str: rows = self.get_rows() cells = rows[row_index].find_elements(By.CSS_SELECTOR, self.CELL_SELECTOR) return cells[col_index].text def row_count(self) -> int: return len(self.get_rows())
# Usage inside a page object class UsersPage(BasePage): TABLE = (By.ID, "users-table") def get_users_table(self): return DataTable(self.driver, self.TABLE)
Project Structure
project/
├── pages/
│ ├── base_page.py
│ ├── login_page.py
│ ├── dashboard_page.py
│ └── settings_page.py
├── components/
│ ├── data_table.py
│ ├── modal.py
│ └── nav_bar.py
├── tests/
│ ├── conftest.py # driver fixture, shared setup
│ ├── test_auth.py
│ └── test_dashboard.py
└── conftest.py # root fixtures (browser type, base URL)Gotchas
Do not put assertions inside page objects. Assertions belong in tests; page objects return data or the next page object.
Locators should be class-level constants, not inline strings. This makes a locator change a one-line fix.
Return
selfonly when the action stays on the same page. Return a new page object instance when the action causes navigation; the driver context is shared.
Avoid
time.sleep()inside page objects. UseWebDriverWaitin every interaction method to keep tests fast and deterministic.