Skip to content

selenium-expect

Fluent expect() assertions with auto-retry for Selenium Python.

Why selenium-expect?

Writing Selenium tests traditionally means pairing every check with WebDriverWait and expected_conditions — verbose, repetitive, and easy to get wrong. selenium-expect replaces that pattern with a single fluent expect() call that auto-retries until the condition passes or the timeout expires.

Before (raw Selenium)

from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

# Verbose, repetitive, error-prone
wait = WebDriverWait(driver, 10)
wait.until(EC.title_is("Dashboard"))
wait.until(EC.visibility_of_element_located((By.ID, "welcome")))
element = driver.find_element(By.ID, "welcome")
assert element.text == "Welcome, Alice!"
wait.until(EC.element_to_be_clickable((By.ID, "submit")))

After (selenium-expect)

from selenium_expect import expect

# Concise, fluent, auto-retrying
expect(driver).to_have_title("Dashboard")
welcome = driver.find_element(By.ID, "welcome")
expect(welcome).to_be_visible().to_have_text("Welcome, Alice!")
expect(driver.find_element(By.ID, "submit")).to_be_clickable()

Features

  • Auto-retry: No more WebDriverWait + expected_conditions boilerplate. Every assertion polls until it passes or times out.
  • Fluent API: Chain assertions naturally — expect(element).to_be_visible().to_have_text("Hello")
  • Negation: Use .not_ to invert any assertion — expect(element).not_.to_be_disabled()
  • 190+ assertions across 10 categories: driver, element, list, alert, cookie, JS, shadow DOM, select, iframe, window
  • Soft assertions: Accumulate failures across multiple checks, then raise once at the end with assert_all()
  • Custom matchers: Extend expect() with your own assertion methods via the @extend decorator
  • Locator-based expect: Re-find elements on each poll cycle — eliminates StaleElementReferenceException
  • Configurable polling: Fixed interval or custom backoff schedule with polling_intervals
  • Descriptive errors: Timeline, element HTML snippet, actual vs. expected values, and custom messages
  • Composition assertions: Combine multiple conditions with to_satisfy_all, to_satisfy_any, to_satisfy_none
  • Poll function: Assert on arbitrary callables with expect.poll(fn).to_equal(value)
  • Zero framework dependency: Works with pytest, unittest, Behave, or standalone scripts
  • Type-safe: Full type hints, mypy --strict compatible
  • Python 3.11+: Modern Python features throughout

Installation

pip install selenium-expect

For development documentation:

pip install selenium-expect[docs]

Quick example

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium_expect import expect

driver = webdriver.Chrome()
driver.get("https://example.com")

# Driver / page assertions
expect(driver).to_have_title("Example Domain")
expect(driver).to_have_url("https://example.com/")

# Element assertions — chainable
h1 = driver.find_element(By.TAG_NAME, "h1")
expect(h1).to_be_visible().to_have_text("Example Domain")

# Negation
expect(h1).not_.to_be_hidden()

# Locator-based — re-finds on each poll, no StaleElementReferenceException
expect(driver, by=By.ID, value="submit").to_be_clickable(timeout=15)

# Custom timeout and polling per assertion
expect(h1).to_have_text_contains("Example", timeout=5, polling=0.25)

More examples

Soft assertions

Collect all failures and raise once:

from selenium_expect import expect, assert_all

# Each assertion records failures without raising
expect(driver, soft=True).to_have_title("Wrong Title")
expect(driver.find_element(By.ID, "status"), soft=True).to_have_text("Ready")

# Raise if any soft assertions failed
assert_all()

Custom matchers

Add your own assertion methods:

from selenium_expect import expect, extend

@extend("to_have_color")
def to_have_color(element, color: str):
    """Assert element has a specific CSS color."""
    actual = element.value_of_css_property("color")
    return (actual == color, actual)

# Now use it like any built-in assertion
expect(element).to_have_color("rgba(255, 0, 0, 1)")
expect(element).not_.to_have_color("rgba(0, 0, 0, 1)")

Polling arbitrary functions

from selenium_expect import expect

# Assert on any callable — auto-retries until it matches
expect.poll(lambda: driver.execute_script("return document.readyState")).to_equal("complete")

# Works with any comparison
expect.poll(lambda: len(driver.find_elements(By.CLASS_NAME, "item"))).to_be_greater_than(5)

Pre-configured expect variants

from selenium_expect import expect

# Create a fast variant for quick checks
fast_expect = expect.configure(timeout=1.0, polling=0.1)
fast_expect(element).to_be_visible()

# Create a patient variant for slow pages
patient_expect = expect.configure(timeout=30.0, polling=0.5)
patient_expect(driver).to_have_title("Loaded")

Comparison with other tools

Feature selenium-expect WebDriverWait + EC
Auto-retry Yes Yes (manual)
Fluent chaining Yes No
Negation .not_ Manual
Soft assertions Yes No
Custom matchers Yes No
Locator-based re-find Yes Via EC
Configurable backoff Yes No
Descriptive errors Timeline + HTML Minimal
Framework dependency None None
Browser support Any Selenium driver Any Selenium driver

License

MIT — see LICENSE for details.