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 self only 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. Use WebDriverWait in every interaction method to keep tests fast and deterministic.