// ── JavaScript/Node.js Setup ──
const { Builder, Browser, By, Key, until } = require('selenium-webdriver');
async function example() {
let driver = await new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(new chrome.Options().headless())
.build();
try {
await driver.get('https://www.example.com');
let title = await driver.getTitle();
console.log('Page title:', title);
} finally {
await driver.quit();
}
}
example();
Browser Drivers
Browser
Driver
Manager
Chrome
ChromeDriver
WebDriverManager.chromedriver()
Firefox
GeckoDriver
WebDriverManager.firefoxdriver()
Edge
EdgeDriver
WebDriverManager.edgedriver()
Safari
SafariDriver
Built-in (macOS)
Common Chrome Options
Option
Purpose
--headless=new
Headless mode (new)
--window-size=W,H
Set viewport size
--start-maximized
Maximize window
--no-sandbox
CI environment support
--disable-dev-shm-usage
Docker fix
--disable-gpu
GPU issues workaround
user-agent=...
Custom user agent
💡
Use webdriver-manager (Python) or WebDriverManager (Java) to auto-download and manage the correct driver version. No need to manually download chromedriver.exe.
🔍
Locators (By Strategies)
ELEMENTS
python_locators.py
# ── Selenium Locators (Python) ──
from selenium.webdriver.common.by import By
# ID — most reliable
driver.find_element(By.ID, "username")
driver.find_elements(By.ID, "item") # returns list
# Name
driver.find_element(By.NAME, "email")
driver.find_element(By.NAME, "search-query")
# Class Name (single class)
driver.find_element(By.CLASS_NAME, "btn-primary")
driver.find_elements(By.CLASS_NAME, "list-item")
# Tag Name
driver.find_element(By.TAG_NAME, "h1")
driver.find_elements(By.TAG_NAME, "a")
# CSS Selector — powerful
driver.find_element(By.CSS_SELECTOR, "#submit-btn")
driver.find_element(By.CSS_SELECTOR, ".card .title")
driver.find_element(By.CSS_SELECTOR, "input[name='email']")
driver.find_element(By.CSS_SELECTOR, "div > ul > li:first-child")
driver.find_element(By.CSS_SELECTOR, "[data-testid='login']")
# XPath — most flexible
driver.find_element(By.XPATH, "//input[@id='username']")
driver.find_element(By.XPATH, "//button[contains(text(), 'Submit')]")
driver.find_element(By.XPATH, "//div[@class='card']//h3")
driver.find_element(By.XPATH, "//input[starts-with(@name, 'user_')]")
driver.find_element(By.XPATH, "//table//tr[td[text()='Alice']]")
driver.find_element(By.XPATH, "(//div[@class='item'])[2]")
# Link Text (exact)
driver.find_element(By.LINK_TEXT, "Sign In")
# Partial Link Text
driver.find_element(By.PARTIAL_LINK_TEXT, "Sign")
Prefer By.ID or By.CSS_SELECTOR over XPath when possible. CSS selectors are faster and easier to read. Use XPath only when you need to traverse the DOM (parent, sibling, text-based selection).
👆
Actions & Interaction
ACTIONS
python_actions.py
# ── Basic Actions (Python) ──
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import Select
from selenium.webdriver.support.ui import WebDriverWait
# ── Clicking ──
element = driver.find_element(By.ID, "submit-btn")
element.click()
# or
driver.find_element(By.ID, "submit-btn").click()
# ── Typing / Send Keys ──
input_field = driver.find_element(By.ID, "username")
input_field.clear()
input_field.send_keys("alice@example.com")
input_field.send_keys(Keys.ENTER) # press Enter
input_field.send_keys(Keys.TAB) # press Tab
input_field.send_keys(Keys.CONTROL, "a") # Ctrl+A (select all)
input_field.send_keys(Keys.CONTROL, "c") # Ctrl+C (copy)
input_field.send_keys(Keys.BACKSPACE) # backspace
input_field.send_keys("hello" + Keys.TAB + "world") # chain keys
# ── Text Input Methods ──
input_field.send_keys("") # clear via send_keys (some drivers)
input_field.clear() # recommended clear
# ── Get Element Information ──
element.text # inner text
element.get_attribute("href") # attribute value
element.get_attribute("class") # class attribute
element.get_attribute("data-testid") # data attribute
element.tag_name # tag name
element.is_displayed() # visible?
element.is_enabled() # enabled?
element.is_selected() # selected (checkbox)?
element.get_property("value") # JavaScript property
element.size # {'width': 300, 'height': 40}
element.location # {'x': 100, 'y': 200}
element.rect # size + location combined
# ── Dropdowns (Select) ──
dropdown = Select(driver.find_element(By.ID, "country"))
dropdown.select_by_visible_text("United States")
dropdown.select_by_value("us")
dropdown.select_by_index(2)
dropdown.deselect_all() # multi-select only
dropdown.options # list of all options
dropdown.first_selected_option # currently selected
Always end ActionChains with .perform(). Actions are queued until perform() is called. Without it, nothing happens.
⏳
Waits
SYNCHRONIZATION
python_waits.py
# ── Implicit Wait ──
# Sets global timeout for find_element() — applies to ALL finds
# Only needs to be set ONCE per driver session
driver.implicitly_wait(10) # 10 seconds max
# Now any find_element will poll for up to 10 seconds
element = driver.find_element(By.ID, "slow-element") # retries for 10s
# ⚠️ Do NOT mix implicit and explicit waits!
# ── Explicit Wait (WebDriverWait) — RECOMMENDED ──
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 10) # 10 second timeout
# Wait for element to be present in DOM
element = wait.until(
EC.presence_of_element_located((By.ID, "dynamic-element"))
)
# Wait for element to be clickable
button = wait.until(
EC.element_to_be_clickable((By.CSS_SELECTOR, "#submit-btn"))
)
button.click()
# Wait for element to be visible
element = wait.until(
EC.visibility_of_element_located((By.CLASS_NAME, "notification"))
)
# Wait for element to be invisible (disappear)
wait.until(
EC.invisibility_of_element_located((By.ID, "loading-spinner"))
)
# Wait for text to be present
wait.until(
EC.text_to_be_present_in_element((By.ID, "status"), "Complete")
)
# Custom wait condition
def element_has_count(driver, locator, count):
elements = driver.find_elements(*locator)
return len(elements) == count
wait.until(lambda d: element_has_count(d, (By.CLASS_NAME, "item"), 5))
python_expected_conditions.py
# ── Expected Conditions Reference ──
from selenium.webdriver.support import expected_conditions as EC
# ── Element State ──
EC.presence_of_element_located(locator) # In DOM (not necessarily visible)
EC.visibility_of_element_located(locator) # Visible and has size > 0
EC.visibility_of(element) # Same but pass WebElement
EC.element_to_be_clickable(locator) # Visible + enabled
EC.invisibility_of_element_located(locator) # Hidden or not in DOM
EC.staleness_of(element) # Element is no longer attached to DOM
# ── Text & Value ──
EC.text_to_be_present_in_element(locator, text) # Inner text contains
EC.text_to_be_present_in_element_value(locator, text) # Input value contains
# ── Selection & Attributes ──
EC.element_to_be_selected(element) # Checkbox/radio is selected
EC.element_selection_state_to_be(element, is_selected)
# ── Frames ──
EC.frame_to_be_available_and_switch_to_it(locator)
# ── Alerts ──
EC.alert_is_present() # Alert dialog is present
# ── Number of Elements ──
def at_least_n_elements(locator, n):
def _predicate(driver):
return len(driver.find_elements(*locator)) >= n
return _predicate
wait.until(at_least_n_elements((By.CLASS_NAME, "row"), 10))
# ── Custom Expected Condition Class ──
class element_has_css_class:
def __init__(self, locator, css_class):
self.locator = locator
self.css_class = css_class
def __call__(self, driver):
element = driver.find_element(*self.locator)
if self.css_class in element.get_attribute("class"):
return element
return False
wait.until(element_has_css_class((By.ID, "card"), "active"))
java_waits.java
// ── Java FluentWait (Selenium 4+) ──
import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.Wait;
import java.time.Duration;
Wait<WebDriver> wait = new FluentWait<>(driver)
.withTimeout(Duration.ofSeconds(30)) // total timeout
.pollingEvery(Duration.ofMillis(500)) // poll interval
.ignoring(NoSuchElementException.class) // ignore specific exceptions
.ignoring(StaleElementReferenceException.class)
.withMessage("Element not found within timeout");
WebElement element = wait.until(d -> {
WebElement el = d.findElement(By.id("dynamic"));
return el.isDisplayed() ? el : null;
});
// ── Python FluentWait equivalent ──
from selenium.webdriver.support.ui import WebDriverWait
wait = WebDriverWait(
driver,
timeout=30,
poll_frequency=0.5, # poll every 0.5 seconds
ignored_exceptions=[NoSuchElementException, StaleElementReferenceException]
)
Wait Types Comparison
Type
Scope
Use Case
Implicit
Global, all finds
Simple cases (set once)
Explicit
Specific element
Waiting for conditions
FluentWait
Custom polling
Fine-grained control
Sleep
❌ Never use
Causes flaky tests
Common Expected Conditions
Condition
Description
presence_of_element_located
Element in DOM
visibility_of_element_located
Element visible
element_to_be_clickable
Clickable + visible
invisibility_of_element_located
Hidden or removed
text_to_be_present_in_element
Text appears in element
frame_to_be_available
Frame ready to switch
alert_is_present
Alert dialog present
staleness_of
Element detached from DOM
🚫
Never mix implicit and explicit waits. They interact unpredictably and can cause extremely long wait times. Use explicit waits (WebDriverWait) exclusively.
⚠️
Never use time.sleep() in Selenium tests. It makes tests slow and flaky. Always use WebDriverWait with expected_conditions for deterministic waits.
🪟
Frames & Windows
NAVIGATION
python_frames.py
# ── iframe Handling ──
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# Switch to iframe by name/id
driver.switch_to.frame("iframe-name")
# Switch to iframe by index
driver.switch_to.frame(0)
# Switch to iframe by WebElement
iframe_element = driver.find_element(By.CSS_SELECTOR, "iframe#my-frame")
driver.switch_to.frame(iframe_element)
# Now interact with elements inside the iframe
driver.find_element(By.ID, "inner-button").click()
# Switch back to main page
driver.switch_to.default_content()
# Switch to parent frame (nested iframes)
driver.switch_to.parent_frame()
# ── Wait for frame and switch ──
wait = WebDriverWait(driver, 10)
wait.until(EC.frame_to_be_available_and_switch_to_it(
(By.ID, "my-iframe")
))
python_windows.py
# ── Window / Tab Handling ──
# Open new window/tab
driver.execute_script("window.open('https://example.com', '_blank');")
# Get all window handles
handles = driver.window_handles
# handles[0] = original, handles[1] = new tab
# Switch to new window
driver.switch_to.window(handles[1])
# Switch back to original
driver.switch_to.window(handles[0])
# Close current window/tab
driver.close()
# Switch to the remaining window after close
driver.switch_to.window(driver.window_handles[0])
# ── Window Management ──
# Maximize window
driver.maximize_window()
# Minimize window
driver.minimize_window()
# Full screen
driver.fullscreen_window()
# Set window size
driver.set_window_size(1280, 720)
# Set window position
driver.set_window_position(100, 100)
# Get window size
size = driver.get_window_size()
print(f"Width: {size['width']}, Height: {size['height']}")
# Get window position
position = driver.get_window_position()
# ── Alert / Confirm / Prompt Dialogs ──
from selenium.webdriver.support import expected_conditions as EC
# Wait for alert
alert = wait.until(EC.alert_is_present())
# Get alert text
text = alert.text
# Accept (click OK)
alert.accept()
# Dismiss (click Cancel)
alert.dismiss()
# Type into prompt
alert.send_keys("Hello")
python_navigation.py
# ── Navigation Commands ──
driver.get("https://example.com") # Navigate to URL
driver.back() # Browser back
driver.forward() # Browser forward
driver.refresh() # Refresh page
# Get current URL
current_url = driver.current_url
# Get page title
title = driver.title
# Get page source (HTML)
source = driver.page_source
# ── JavaScript Scrolling ──
# Scroll to bottom
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# Scroll to element
element = driver.find_element(By.ID, "footer")
driver.execute_script("arguments[0].scrollIntoView({behavior: 'smooth', block: 'center'});", element)
# Scroll by pixels
driver.execute_script("window.scrollBy(0, 500);")
# Scroll to top
driver.execute_script("window.scrollTo(0, 0);")
Frame Switch Methods
Method
Description
switch_to.frame("name")
Switch by name or ID
switch_to.frame(0)
Switch by index
switch_to.frame(element)
Switch by WebElement
switch_to.default_content()
Back to main page
switch_to.parent_frame()
Up one frame level
Alert Methods
Method
Description
EC.alert_is_present()
Wait for alert
alert.text
Get alert message
alert.accept()
Click OK/Yes
alert.dismiss()
Click Cancel/No
alert.send_keys(text)
Enter prompt text
⚠️
Always switch back to default_content() after working inside an iframe. If you don't, subsequent find_element() calls will fail because Selenium is still focused inside the frame.
Use pytest with Selenium for cleaner tests. Pytest fixtures handle setup/teardown elegantly, and plain assert statements are more readable than self.assertEqual().
Use Docker Compose for Grid. It's the fastest way to get multi-browser, multi-node Selenium Grid running. Scale nodes with deploy.replicas in docker-compose.yml.
# ── Tests with POM ──
import pytest
@pytest.fixture
def driver():
d = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
d.maximize_window()
yield d
d.quit()
def test_login_success(driver):
login = LoginPage(driver).load()
dashboard = login.login("alice@test.com", "password123")
assert "Alice" in dashboard.get_welcome_text()
def test_login_failure(driver):
login = LoginPage(driver).load()
login.login("wrong@test.com", "wrongpass")
error = login.get_error()
assert "Invalid" in error or "incorrect" in error.lower()
def test_logout(driver):
login = LoginPage(driver).load()
dashboard = login.login("alice@test.com", "password123")
login_page = dashboard.logout()
assert login_page.is_visible(LoginPage.EMAIL)
python_patterns.py
# ── Utility & Best Practice Patterns ──
# 1. Screenshot on failure (pytest conftest.py)
# conftest.py
import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
report = outcome.get_result()
if report.when == "call" and report.failed:
driver = item.funcargs["driver"]
driver.save_screenshot(f"screenshots/{item.name}.png")
# 2. Take screenshot manually
driver.save_screenshot("screenshot.png")
element.screenshot("element.png")
# 3. Page Source on failure
with open("page_source.html", "w") as f:
f.write(driver.page_source)
# 4. Reusable wait helper
def wait_for_url(driver, url_pattern, timeout=10):
WebDriverWait(driver, timeout).until(
lambda d: url_pattern in d.current_url
)
# 5. Handle stale elements (retry pattern)
from selenium.common.exceptions import StaleElementReferenceException
def safe_click(driver, locator, retries=3):
for _ in range(retries):
try:
element = WebDriverWait(driver, 5).until(
EC.element_to_be_clickable(locator)
)
element.click()
return True
except StaleElementReferenceException:
continue
return False
# 6. Headless detection helper
def is_headless(driver):
return driver.execute_script("return navigator.webdriver")
POM Architecture
Layer
Responsibility
BasePage
Common methods (find, click, type)
Page Objects
Locators + page-specific actions
Tests
Orchestration + assertions only
Fixtures
Driver setup/teardown
conftest.py
Shared pytest hooks
Best Practices Checklist
Practice
Why
Use POM
Maintainable, reusable locators
Explicit waits only
No implicit waits mixing
No sleep()
Deterministic tests
Screenshot on failure
Easy debugging
Unique data-testid attrs
Stable selectors
Independent tests
No test ordering dependency
CI headless mode
Fast, reliable CI runs
Base page class
DRY common operations
💡
Page Object Model is the standard pattern. Keep all locators and page actions in Page classes. Tests should only orchestrate page interactions and make assertions. When the UI changes, update only the Page class.
⚠️
Handle StaleElementReferenceException with retry logic. Elements can become stale when the DOM is re-rendered. Wrap interactions in a try/except that re-finds the element.