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 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.