Selenium 4 is W3C-only — DesiredCapabilities is gone, replaced by browser-specific
Options. Selenium Manager auto-downloads matching drivers (since 4.6) —
no more chromedriver-binary / webdriver-manager.
BiDi (CDP-like cross-browser event API) is GA in 4.20+. find_element_by_* helpers were
removed in 4.3 — everything goes through By.*. This sheet pins to Selenium 4.15+.
Legacy — turn off; mixing with explicit waits compounds timeouts.
time.sleep(...)
Avoid — #1 cause of flake. Always prefer explicit waits.
python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException
driver = webdriver.Chrome()
wait = WebDriverWait(driver, timeout=10, poll_frequency=0.25,
ignored_exceptions=[TimeoutException])
# Explicit wait — most common pattern. Always prefer over time.sleep().
btn = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, "button#pay")))
btn.click()
# Custom condition — any callable that returns truthy when ready
def text_in(locator, fragment):
def _predicate(d):
return fragment in d.find_element(*locator).text
return _predicate
wait.until(text_in((By.ID, "status"), "Paid"))
# Fluent-wait shorthand — same WebDriverWait, just chained
WebDriverWait(driver, 5).until(EC.url_contains("/checkout/success"))
# Implicit wait — driver-wide default. Don't mix with explicit waits.
driver.implicitly_wait(0) # 0 = off — recommended
# Never do this — flaky and slow:
# import time; time.sleep(5)
BiDi is the cross-browser successor to CDP — Firefox and WebKit are landing implementations. CDP commands work everywhere Chrome runs, but lock you to Chromium.
Login flow · Page ObjectEnd-to-end · Login test
Headless Chrome, explicit waits, a thin Page Object class. Drops straight into pytest with no extra plumbing.
python
# End-to-end Selenium 4 — headless Chrome, explicit waits, Page Object lite.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
def make_driver():
opts = Options()
opts.add_argument("--headless=new")
opts.add_argument("--window-size=1280,800")
return webdriver.Chrome(options=opts) # Selenium Manager picks the driver
class LoginPage:
URL = "https://example.com/login"
def __init__(self, driver):
self.d, self.wait = driver, WebDriverWait(driver, 10)
def open(self):
self.d.get(self.URL); return self
def login(self, user, pw):
self.d.find_element(By.ID, "email").send_keys(user)
self.d.find_element(By.ID, "password").send_keys(pw)
self.d.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
self.wait.until(EC.url_contains("/dashboard"))
return self
def test_login():
d = make_driver()
try:
LoginPage(d).open().login("ana@x.com", "secret")
assert "Welcome" in d.find_element(By.TAG_NAME, "h1").text
finally:
d.quit() # always close — orphan procs leak
if __name__ == "__main__":
test_login()
Best practiceGood to know
Let Selenium Manager handle drivers.
Since 4.6, Selenium downloads + caches the matching driver on first use. Delete any chromedriver-binary / webdriver-manager dependency — they cause version drift.
Wrap every driver in try / finally driver.quit().
An exception before quit leaks the browser process + driver port. After a few thousand CI runs that’s a real OOM. pytest fixtures with yield + teardown solve this cleanly.
Page Object Model still pays off.
One class per page, locators + actions inside, tests stay declarative. Refactor once when the DOM changes — tests don’t.
Common trapsWatch out for
Don’t mix implicit + explicit waits.
Combined waits compound — a 10s implicit + a 10s explicit can hang for 20+ seconds per retry. Turn implicit waits to 0 and rely on explicit ones only.
StaleElementReferenceException after SPA re-renders.
Storing an element reference across a route change blows up. Re-find inside the wait, or use EC.staleness_of(old_el) before re-acquiring.
Time-based sleep() = flake.
The fastest way to make a test pass locally and fail on CI. Every time.sleep() is a code smell — replace with an explicit wait on the actual state change.
Selenium is an open-source browser automation framework. It is used for end-to-end testing of web applications, web scraping, and automating repetitive browser tasks. Selenium drives real browsers (Chrome, Firefox, Edge, Safari) through the WebDriver protocol. Selenium 4 introduced BiDi (bidirectional) support and Selenium Grid 4.
What is the difference between CSS selectors and XPath in Selenium?
CSS selectors (By.CSS_SELECTOR) use standard CSS syntax (#id, .class, tag[attr="val"]) — they are faster and more readable. XPath (By.XPATH) is more powerful — it can traverse up the DOM and use text content (//button[text()="Submit"]) but is slower and brittle. Prefer CSS selectors unless you need XPath's traversal capabilities.
What is the difference between implicit and explicit waits in Selenium?
Implicit waits set a global timeout for every find_element call — if the element is not found immediately, Selenium polls until the timeout. Explicit waits (WebDriverWait + expected_conditions) wait for a specific condition on a specific element. Explicit waits are preferred — implicit waits can mask flaky tests and slow down your suite.
How do I handle dropdowns and alerts in Selenium?
For native HTML select elements, wrap with Select(driver.find_element(...)) then call select_by_visible_text("Option") or select_by_value("val"). For browser alerts, use driver.switch_to.alert then alert.accept() or alert.dismiss(). For modal dialogs built with JavaScript, interact with them as regular elements using locators.
How do I run Selenium tests in parallel with Selenium Grid?
Selenium Grid lets you distribute test execution across multiple machines and browsers. Start a Grid node with java -jar selenium-server.jar node and a hub with java -jar selenium-server.jar hub. In your test, set the desired capabilities and point RemoteWebDriver to the hub URL. Selenium Grid 4 also supports Docker and Kubernetes deployments.
Is Selenium better than Playwright for browser testing?
Playwright is often faster, more reliable, and easier to set up for new projects because it auto-manages browser binaries, has built-in auto-wait, and supports async natively. Selenium has a larger ecosystem, supports more languages, and is the industry standard for enterprise teams. Use Playwright for greenfield projects; use Selenium when you need broad compatibility or have an existing suite.