Selenium Cheatsheet
Page Object Model
Use this Selenium reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
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.