DS DevShelfHub Projects · AI tools
Cheatsheets / Selenium
Cheatsheet · Dev tooling

Selenium: WebDriver, Locators, Waits, Actions and Grid Reference Guide

By DevShelfHub

WebDriver, locators, waits, Selenium 4 relative locators, Grid, actions, frames, alerts — the W3C WebDriver standard as a one-page reference.

104 items 8 min WebDriver Waits Grid

Start hereQuick start · 6 you’ll reach for daily

Driverwebdriver.Chrome(options=opts)
Find elementdriver.find_element(By.CSS_SELECTOR, "...")
Explicit waitWebDriverWait(d, 10).until(EC...)
Typeel.send_keys("hello")
Action chainActionChains(d).move_to_element(el)...
Quitdriver.quit()

Target versions · paceVersions

Targets: selenium (py) ≥ 4.15 selenium-java ≥ 4.20 WebDriver W3C spec

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

Install · drivers · GridSetup

bash
# Python — Selenium 4 (W3C WebDriver)
pip install "selenium>=4.15"
# Drivers are auto-managed by Selenium Manager — no chromedriver download needed

# Java — Maven
# 
#   org.seleniumhq.selenium
#   selenium-java
#   4.20.0
# 

# Node / TS
npm install selenium-webdriver

# Selenium Grid — multi-node parallel runner (Docker)
docker network create grid
docker run -d -p 4442-4444:4442-4444 --net grid --name selenium-hub selenium/hub:4
docker run -d --net grid -e SE_EVENT_BUS_HOST=selenium-hub selenium/node-chrome:4
docker run -d --net grid -e SE_EVENT_BUS_HOST=selenium-hub selenium/node-firefox:4
# Console → http://localhost:4444/grid/console

# Standalone (single node + hub in one container)
docker run -d -p 4444:4444 --shm-size=2g selenium/standalone-chrome:4

Where things liveCommon imports

from selenium import webdriverBrowser launchers: Chrome, Firefox, Safari, Edge, Remote.
from selenium.webdriver.chrome.options import OptionsPer-browser options. Firefox / Edge / Safari mirror.
from selenium.webdriver.chrome.service import ServiceCustom driver path / args. Optional with Selenium Manager.
from selenium.webdriver.common.by import ByLocator strategies enum.
from selenium.webdriver.common.keys import KeysSpecial keys: ENTER, TAB, CONTROL, etc.
from selenium.webdriver.common.action_chains import ActionChainsLow-level mouse / keyboard sequences.
from selenium.webdriver.support.ui import WebDriverWait, SelectWaits + native <select> helper.
from selenium.webdriver.support import expected_conditions as ECPre-baked wait predicates.
from selenium.webdriver.support.relative_locator import locate_withSelenium 4 relative locators (above / near / etc.).
from selenium.common.exceptions import TimeoutException, StaleElementReferenceExceptionCommon exceptions to handle.

Options · headless · RemoteDriver setup

webdriver.Chrome(options=opts)Local Chrome. Selenium Manager fetches the driver automatically.
webdriver.Firefox(options=opts)Local Firefox. Same shape across browsers.
webdriver.Remote(command_executor="http://hub:4444", options=opts)Send to a Grid hub.
opts.add_argument("--headless=new")New Chrome headless. Preferred over old --headless.
opts.add_argument("--window-size=1280,800")Pin viewport.
opts.add_argument("--no-sandbox") / --disable-dev-shm-usageCommon CI / Docker flags.
opts.add_experimental_option("prefs", {"...": ...})Chrome prefs (downloads dir, popups, etc.).
opts.set_capability("acceptInsecureCerts", True)Skip self-signed cert errors.
opts.binary_location = "/path/to/chrome"Pick a specific browser binary.
driver.set_page_load_timeout(30)Per-navigation timeout.
driver.set_script_timeout(30)Async-JS execute timeout.
driver.quit() vs driver.close()quit kills the whole session; close shuts one tab. Always quit in finally.

By.* · relative locatorsLocators

driver.find_element(By.ID, "email")Single hit. Throws NoSuchElementException if missing.
driver.find_elements(By.CSS_SELECTOR, ".row")List (empty if none). Preferred for "may or may not exist".
By.ID, By.NAME, By.TAG_NAME, By.CLASS_NAMEDirect attribute matches.
By.CSS_SELECTORPreferred general-purpose selector.
By.XPATHLast resort — brittle to DOM changes.
By.LINK_TEXT / By.PARTIAL_LINK_TEXTAnchor-text matches.
el.find_element(By.CSS_SELECTOR, ".price")Scoped to a parent element.
locate_with(By.TAG_NAME, "input").above({btn})Relative locator: above / below / to_left_of / to_right_of / near.
locate_with(By.TAG_NAME, "li").near(label, 50)Within 50px of label. Visual-spatial matching.
el.shadow_root.find_element(By.CSS_SELECTOR, ".btn")Pierce open Shadow DOM (4.5+, Chrome / Edge).
Selenium 4 removed every find_element_by_* helper (find_element_by_id, find_element_by_xpath, ...). They all go through By.* now.

click · send_keys · selectsElement actions

el.click()Click. Fails if not interactable — pair with explicit wait.
el.send_keys("hello")Type. Pass Keys.RETURN to submit.
el.send_keys(Keys.CONTROL, 'a')Modifier + key combo.
el.clear()Empty an input. Some React inputs need a separate send_keys(Keys.BACKSPACE).
el.submit()Submit the containing form.
el.is_displayed() / is_enabled() / is_selected()State checks — cheap, but don’t race.
el.get_attribute("value") / get_property("value")Attribute (HTML) vs property (DOM).
el.textVisible text. Strips leading / trailing whitespace.
el.screenshot("./button.png")Per-element screenshot (4+).
Select(el).select_by_value("us")Native <select> helper. Also by_visible_text / by_index.
driver.execute_script("arguments[0].click()", el)JS click — bypass overlay / interactability check. Use sparingly.

Explicit > implicitWaits

WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "x")))Explicit wait. Preferred.
EC.visibility_of_element_located(locator)In DOM + visible.
EC.element_to_be_clickable(locator)Visible + enabled + interactable.
EC.text_to_be_present_in_element(locator, "Paid")Inner text contains.
EC.staleness_of(el)Wait for an element to detach (e.g. SPA route change).
EC.url_contains("/checkout") / EC.title_contains("Pay")Page-level conditions.
EC.frame_to_be_available_and_switch_to_it(locator)Wait + switch in one shot.
EC.alert_is_present()For native dialogs.
driver.implicitly_wait(0)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)
driver.get("https://example.com")Navigate. Blocks until load event.
driver.back() / forward() / refresh()History.
driver.current_url / title / page_sourceRead page state.
driver.switch_to.new_window('tab' | 'window')Open + switch in one call (Selenium 4).
driver.window_handles / driver.switch_to.window(h)Move between tabs.
driver.set_window_size(1280, 800) / fullscreen_window()Viewport.
driver.get_cookies() / add_cookie({"name","value"})Cookies for the current domain.
driver.delete_all_cookies()Reset session state.
driver.execute_script("return document.title")Run JS in the page. Returns the value.
driver.save_screenshot("page.png")Full-page (top-fold) PNG.

switch_to · iframes · dialogsFrames, alerts, dialogs

driver.switch_to.frame(el) / frame("name") / frame(0)Cross into an iframe by element, name, or index.
driver.switch_to.parent_frame()Up one level.
driver.switch_to.default_content()Back to the top document.
alert = driver.switch_to.alertGrab a native dialog.
alert.accept() / dismiss() / send_keys("...")OK / Cancel / fill prompt.
alert.textRead message body.
driver.switch_to.active_elementElement currently focused.

Low-level inputActionChains

ActionChains(driver).move_to_element(el).perform()Hover.
.click(el) / .double_click(el) / .context_click(el)Click variants on a specific element.
.click_and_hold(el).move_to_element(dst).release()Drag-and-drop building blocks.
.drag_and_drop(src, dst)Convenience — same as the above.
.key_down(Keys.SHIFT).send_keys("a").key_up(Keys.SHIFT)Modifier + key combos.
.scroll_to_element(el) / .scroll_by_amount(x, y)Native scroll wheel events (Selenium 4).
.pause(seconds)Tiny dwell — useful between physically separate actions.
.perform()Required — nothing runs until you call it.
python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains

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

src = driver.find_element(By.ID, "tile")
dst = driver.find_element(By.ID, "slot")

# Drag-and-drop via low-level actions
ActionChains(driver)\
    .move_to_element(src)\
    .click_and_hold()\
    .pause(0.2)\
    .move_to_element(dst)\
    .release()\
    .perform()

# Right-click context menu
ActionChains(driver).context_click(src).perform()

# Keyboard shortcut
ActionChains(driver).key_down(Keys.CONTROL).send_keys('s').key_up(Keys.CONTROL).perform()

# Hover, then click a tooltip that appears
ActionChains(driver).move_to_element(src).pause(0.1).perform()
driver.find_element(By.CSS_SELECTOR, ".tooltip-action").click()

# Scroll into view (Selenium 4 native scroll origin)
ActionChains(driver).scroll_to_element(dst).perform()
ActionChains(driver).scroll_by_amount(0, 500).perform()

Hub · nodes · capabilitiesSelenium Grid

webdriver.Remote(command_executor=URL, options=opts)Send a session to Grid. URL ends in /wd/hub.
opts.set_capability("browserVersion", "stable")Ask Grid for a specific version. latest, beta, stable.
opts.set_capability("platformName", "linux")OS hint — matches against node tags.
opts.set_capability("se:name", "checkout test")Display name in Grid’s session list.
opts.set_capability("se:recordVideo", True)Record session video (with the right node image).
selenium-standalone vs hub + nodeStandalone = one box, all-in-one. Hub+node = scale-out across machines.
SE_NODE_MAX_SESSIONS=4How many concurrent browsers per node container.
http://hub:4444/grid/consoleWeb console — live sessions, queue depth.
--config (toml) / selenium.tomlPersist Grid config (relay, distributor, router).

Network · console · authBiDi & CDP

driver.execute_cdp_cmd("Network.enable", {})Enable Chrome DevTools Protocol domains (Chrome / Edge only).
driver.execute_cdp_cmd("Network.setExtraHTTPHeaders", {"headers":{}})Inject headers on every request.
driver.execute_cdp_cmd("Network.emulateNetworkConditions", {...})Throttle network (e.g. 3G).
driver.execute_cdp_cmd("Emulation.setGeolocationOverride", {...})Fake geolocation.
driver.register("auth.required", handler)BiDi: handle HTTP basic-auth prompts cross-browser.
driver.script.add_console_message_handler(fn)BiDi: stream console.log from the page.
driver.bidi_connection()Open the BiDi WebSocket session manually.
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.

Go deeperSee also

Selenium FAQ

What is Selenium and what is it used for?

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.