Selenium interviews test your understanding of browser automation fundamentals, locator strategies, synchronisation, test design patterns, and CI/CD integration. This guide covers the 50 most common questions — with concise answers and real examples.
Quick reference
| Topic | Most asked questions |
|---|---|
| Architecture | WebDriver, Selenium Grid, RC vs WebDriver |
| Locators | XPath vs CSS, strategies, dynamic elements |
| Waits | Implicit vs explicit vs fluent, timing issues |
| Actions | Keyboard/mouse, frames, windows, alerts |
| Page Object Model | Design pattern, implementation, benefits |
| Cross-browser | Chrome/Firefox/Edge, headless, remote |
| Assertions | JUnit/TestNG/pytest, soft vs hard |
| CI/CD | Jenkins, GitHub Actions, Docker Selenium |
| Advanced | Shadow DOM, JavaScript executor, listeners |
| Performance | Parallel execution, test optimisation |
Architecture & Fundamentals
1. What is Selenium and what are its components?
Selenium is an open-source suite for browser automation and web testing:
| Component | Purpose |
|---|---|
| Selenium WebDriver | Direct browser communication via W3C WebDriver protocol |
| Selenium IDE | Browser extension for record-and-playback |
| Selenium Grid | Parallel/distributed test execution across machines |
Selenium WebDriver is the most widely used component in modern automation.
2. Explain the Selenium WebDriver architecture.
Test Script (Python/Java/C#)
│ W3C WebDriver Protocol (HTTP/JSON Wire)
▼
Browser Driver (ChromeDriver / GeckoDriver / EdgeDriver)
│ Native browser commands
▼
Browser (Chrome / Firefox / Edge)
The test script sends REST API commands to the browser driver. The driver translates them to native browser instructions. The result travels back the same way.
3. What is the difference between Selenium RC and Selenium WebDriver?
| Aspect | Selenium RC | Selenium WebDriver |
|---|---|---|
| Architecture | Server intermediary required | Direct browser communication |
| Speed | Slower (server hop) | Faster |
| API | Complex (RC commands) | Clean OOP API |
| Multi-window | Limited | Full support |
| Maintenance | Deprecated | Actively maintained |
| Standard | Proprietary | W3C standard |
Selenium RC is deprecated. Always use WebDriver (Selenium 3/4).
4. What is WebDriverManager and why use it?
WebDriverManager (Bonigarcía) auto-downloads and configures browser drivers, removing manual driver management:
# Without WebDriverManager — manual path needed
from selenium import webdriver
driver = webdriver.Chrome(executable_path='/usr/bin/chromedriver')
# With WebDriverManager — automatic
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()))
Note: Selenium 4.6+ includes built-in driver manager — no third-party library needed.
5. What is Selenium Grid and when do you use it?
Selenium Grid distributes tests across multiple machines and browsers in parallel:
┌─────────────┐
│ Hub │ ← receives test requests
└──────┬──────┘
┌────────┼────────┐
┌────▼───┐ ┌──▼────┐ ┌─▼────┐
│Node 1 │ │Node 2 │ │Node 3│
│Chrome │ │Firefox│ │Edge │
└────────┘ └───────┘ └──────┘
Use cases: cross-browser testing, parallelising large test suites, reducing total execution time.
Selenium Grid 4 uses standalone / hub-node / fully distributed modes and supports Docker out of the box.
Locators
6. What locator strategies does Selenium support?
from selenium.webdriver.common.by import By
driver.find_element(By.ID, "username")
driver.find_element(By.NAME, "email")
driver.find_element(By.CLASS_NAME, "btn-primary")
driver.find_element(By.TAG_NAME, "input")
driver.find_element(By.LINK_TEXT, "Sign In")
driver.find_element(By.PARTIAL_LINK_TEXT, "Sign")
driver.find_element(By.CSS_SELECTOR, "#login-form input[type='email']")
driver.find_element(By.XPATH, "//button[contains(text(),'Submit')]")
Priority (best to worst): ID → CSS Selector → XPath
7. XPath vs CSS Selector — which is better?
| Aspect | XPath | CSS Selector |
|---|---|---|
| Speed | Slower | Faster |
| Readability | Verbose | Cleaner |
| Traverse up (parent) | Yes (parent::, ..) |
No |
| Text matching | Yes (contains(text(),'x')) |
Limited |
| Partial attribute | Yes (contains(@id,'x')) |
Yes ([id*='x']) |
| Browser support | All | All |
Recommendation: Use CSS Selectors by default. Use XPath when you need to traverse upward or match by text content.
8. Write XPath for an element with dynamic ID.
# Dynamic ID like "row_12345_name" — avoid exact match
# ❌ Bad
driver.find_element(By.XPATH, "//input[@id='row_12345_name']")
# ✅ Good — starts-with
driver.find_element(By.XPATH, "//input[starts-with(@id,'row_')]")
# ✅ Good — contains
driver.find_element(By.XPATH, "//input[contains(@id,'name')]")
# ✅ Good — sibling/ancestor relationship
driver.find_element(By.XPATH, "//label[text()='Full Name']/following-sibling::input")
9. What is the difference between find_element and find_elements?
# find_element — returns first match, raises NoSuchElementException if not found
element = driver.find_element(By.CLASS_NAME, "item")
# find_elements — returns list, empty list if not found (no exception)
items = driver.find_elements(By.CLASS_NAME, "item")
if items:
print(f"Found {len(items)} items")
10. How do you locate elements inside an iframe?
# Switch to iframe first, then locate elements inside it
driver.switch_to.frame("iframe-id") # by id/name
driver.switch_to.frame(0) # by index
driver.switch_to.frame(driver.find_element(By.TAG_NAME, "iframe")) # by element
element = driver.find_element(By.ID, "inner-element")
# Switch back to main document
driver.switch_to.default_content()
# Switch to parent frame (one level up)
driver.switch_to.parent_frame()
Waits
11. What are the three types of waits in Selenium?
| Wait Type | Scope | Configurable? | Exception |
|---|---|---|---|
| Implicit | All find_element calls globally | timeout only | NoSuchElementException |
| Explicit | Specific condition, specific element | timeout + polling | TimeoutException |
| Fluent | Like explicit but custom polling + ignore exceptions | timeout + poll interval + exceptions | TimeoutException |
12. What is implicit wait and what are its drawbacks?
driver.implicitly_wait(10) # waits up to 10 seconds for element to appear
element = driver.find_element(By.ID, "username")
Drawbacks:
- Applies globally — can slow down tests where elements don't exist
- Interacts badly with explicit waits (unpredictable behaviour)
- Cannot wait for element state (visible, clickable, etc.)
- Best practice: avoid implicit waits, use explicit waits.
13. How do you use WebDriverWait (explicit wait)?
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
wait = WebDriverWait(driver, timeout=10)
# Wait for element to be visible
element = wait.until(EC.visibility_of_element_located((By.ID, "result")))
# Wait for element to be clickable
button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".submit-btn")))
# Wait for URL to contain a string
wait.until(EC.url_contains("/dashboard"))
# Wait for text to be present
wait.until(EC.text_to_be_present_in_element((By.ID, "status"), "Complete"))
# Wait for element to disappear
wait.until(EC.invisibility_of_element_located((By.ID, "loading-spinner")))
14. What is FluentWait and how does it differ from WebDriverWait?
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import NoSuchElementException
import time
# FluentWait: custom polling interval + ignore specific exceptions
wait = WebDriverWait(
driver,
timeout=30,
poll_frequency=2, # check every 2 seconds
ignored_exceptions=[NoSuchElementException]
)
element = wait.until(EC.presence_of_element_located((By.ID, "dynamic-content")))
Key difference: FluentWait allows custom polling interval and exception suppression. WebDriverWait uses 500ms polling by default.
15. What is a StaleElementReferenceException and how do you fix it?
Occurs when a previously found element is no longer attached to the DOM (page refreshed, DOM updated).
from selenium.common.exceptions import StaleElementReferenceException
# ✅ Fix 1: re-locate the element
def click_with_retry(driver, locator, retries=3):
for _ in range(retries):
try:
driver.find_element(*locator).click()
return
except StaleElementReferenceException:
time.sleep(0.5)
# ✅ Fix 2: use WebDriverWait to wait for re-appearance
wait.until(EC.staleness_of(old_element))
new_element = driver.find_element(By.ID, "element-id")
User Actions
16. How do you handle dropdowns in Selenium?
from selenium.webdriver.support.ui import Select
# HTML <select> element
select = Select(driver.find_element(By.ID, "country"))
select.select_by_visible_text("Montenegro")
select.select_by_value("ME")
select.select_by_index(5)
# Multi-select
select.select_by_visible_text("Option A")
select.select_by_visible_text("Option B")
# Deselect all
select.deselect_all()
print(select.all_selected_options)
print(select.options)
Note: Select class only works with <select> HTML elements — not custom dropdown components.
17. How do you perform mouse hover, drag-and-drop, and right-click?
from selenium.webdriver.common.action_chains import ActionChains
actions = ActionChains(driver)
# Mouse hover
hover_element = driver.find_element(By.ID, "menu-item")
actions.move_to_element(hover_element).perform()
# Right-click (context menu)
element = driver.find_element(By.ID, "target")
actions.context_click(element).perform()
# Drag and drop
source = driver.find_element(By.ID, "drag-source")
target = driver.find_element(By.ID, "drop-target")
actions.drag_and_drop(source, target).perform()
# Double-click
actions.double_click(element).perform()
# Click and hold, move, release
actions.click_and_hold(source).move_to_element(target).release().perform()
18. How do you handle browser alerts and pop-ups?
from selenium.webdriver.support import expected_conditions as EC
# Wait for alert
wait.until(EC.alert_is_present())
alert = driver.switch_to.alert
# Simple alert — only OK button
alert.accept()
# Confirm dialog — OK or Cancel
alert.accept() # click OK
alert.dismiss() # click Cancel
# Prompt dialog — text input
alert.send_keys("input text")
alert.accept()
# Get alert text
print(alert.text)
19. How do you handle multiple browser windows/tabs?
# Get current window handle
original_window = driver.current_window_handle
# Open new tab (Selenium 4)
driver.switch_to.new_window('tab')
# Or click a link that opens new window
driver.find_element(By.LINK_TEXT, "Open in new tab").click()
# Get all window handles
all_windows = driver.window_handles
# Switch to new window
for window in all_windows:
if window != original_window:
driver.switch_to.window(window)
break
# Do stuff in new window
print(driver.title)
# Switch back
driver.switch_to.window(original_window)
# Close current window
driver.close()
20. How do you upload a file in Selenium?
# For <input type="file"> — use send_keys with absolute file path
file_input = driver.find_element(By.CSS_SELECTOR, "input[type='file']")
file_input.send_keys("/absolute/path/to/file.pdf")
# Verify file name appears
print(file_input.get_attribute("value")) # C:\fakepath\file.pdf (browser security)
Note: For non-standard file upload components (drag-and-drop zones), use JavaScript executor or libraries like PyAutoGUI.
Page Object Model
21. What is the Page Object Model (POM)?
POM is a design pattern where each web page is represented as a class. Element locators and page actions are encapsulated within the class.
# 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://example.com/login"
# Locators
USERNAME_INPUT = (By.ID, "username")
PASSWORD_INPUT = (By.ID, "password")
LOGIN_BUTTON = (By.CSS_SELECTOR, "button[type='submit']")
ERROR_MESSAGE = (By.CLASS_NAME, "error-msg")
def __init__(self, driver):
self.driver = driver
self.wait = WebDriverWait(driver, 10)
def open(self):
self.driver.get(self.URL)
return self
def login(self, username, password):
self.wait.until(EC.visibility_of_element_located(self.USERNAME_INPUT))
self.driver.find_element(*self.USERNAME_INPUT).send_keys(username)
self.driver.find_element(*self.PASSWORD_INPUT).send_keys(password)
self.driver.find_element(*self.LOGIN_BUTTON).click()
return self
def get_error_message(self):
return self.wait.until(
EC.visibility_of_element_located(self.ERROR_MESSAGE)
).text
# test_login.py
def test_invalid_login(driver):
page = LoginPage(driver)
page.open().login("wrong@email.com", "wrongpass")
assert page.get_error_message() == "Invalid credentials"
22. What are the benefits of Page Object Model?
| Benefit | Explanation |
|---|---|
| Reusability | Page actions written once, used in many tests |
| Maintainability | Locator change in one place only |
| Readability | Tests describe intent, not browser mechanics |
| Separation | Test logic separated from UI details |
| Debugging | Errors point to page class, not test code |
23. What is the Page Factory pattern?
Page Factory (Java) uses @FindBy annotations and PageFactory.initElements():
// Java example
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
public class LoginPage {
@FindBy(id = "username")
private WebElement usernameInput;
@FindBy(css = "button[type='submit']")
private WebElement loginButton;
public LoginPage(WebDriver driver) {
PageFactory.initElements(driver, this);
}
public void login(String user, String pass) {
usernameInput.sendKeys(user);
loginButton.click();
}
}
Python equivalent: Python doesn't have Page Factory natively — use the POM pattern with tuples as shown in Q21.
24. How do you structure a Selenium test project?
project/
├── pages/
│ ├── base_page.py # common methods (wait, scroll, screenshot)
│ ├── login_page.py
│ ├── dashboard_page.py
│ └── checkout_page.py
├── tests/
│ ├── conftest.py # pytest fixtures (driver setup/teardown)
│ ├── test_login.py
│ └── test_checkout.py
├── utils/
│ ├── driver_factory.py # browser/environment config
│ └── helpers.py
├── data/
│ └── test_data.json
├── requirements.txt
└── pytest.ini
25. What is the difference between driver.close() and driver.quit()?
| Method | Effect |
|---|---|
driver.close() |
Closes the current browser window/tab |
driver.quit() |
Closes all windows and ends the WebDriver session |
Best practice: Always call driver.quit() in teardown — it releases the browser driver process. Failure to call quit leaks processes.
Cross-Browser Testing
26. How do you run tests in headless mode?
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless=new") # Chrome headless
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
options.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=options)
# Firefox headless
from selenium.webdriver.firefox.options import Options as FirefoxOptions
ff_options = FirefoxOptions()
ff_options.add_argument("--headless")
driver = webdriver.Firefox(options=ff_options)
27. How do you run the same test in multiple browsers?
# pytest parametrize approach
import pytest
from selenium import webdriver
@pytest.fixture(params=["chrome", "firefox", "edge"])
def driver(request):
browser = request.param
if browser == "chrome":
d = webdriver.Chrome()
elif browser == "firefox":
d = webdriver.Firefox()
elif browser == "edge":
d = webdriver.Edge()
yield d
d.quit()
Or use Selenium Grid — register nodes with different browsers and use RemoteWebDriver:
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
capabilities = {"browserName": "firefox", "browserVersion": "latest"}
driver = RemoteWebDriver(
command_executor="http://localhost:4444/wd/hub",
desired_capabilities=capabilities
)
28. What is WebDriver BiDi and Selenium 4 improvements?
Selenium 4 key improvements:
| Feature | Selenium 3 | Selenium 4 |
|---|---|---|
| Protocol | JSON Wire Protocol | W3C WebDriver standard |
| CDP | Not built-in | Chrome DevTools Protocol support |
| Grid | Hub-Node only | Standalone/Hub-Node/Fully Distributed |
| New Window | Not standardised | driver.switch_to.new_window('tab') |
| Relative Locators | ❌ | near(), above(), below(), to_left_of() |
| BiDi | ❌ | Bidirectional communication (event-driven) |
Assertions & Reporting
29. How do you take screenshots in Selenium?
# Full page screenshot
driver.save_screenshot("screenshot.png")
# Element screenshot (Selenium 4)
element = driver.find_element(By.ID, "chart")
element.screenshot("chart.png")
# In-memory (for pytest / custom reporting)
import base64
screenshot_base64 = driver.get_screenshot_as_base64()
screenshot_bytes = driver.get_screenshot_as_png()
Best practice: take screenshot on test failure using pytest hooks:
# conftest.py
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
if rep.when == "call" and rep.failed:
driver = item.funcargs.get("driver")
if driver:
driver.save_screenshot(f"screenshots/{item.name}.png")
30. What is the difference between hard and soft assertions?
# Hard assertion — test stops on first failure
def test_hard():
assert driver.title == "Expected Title" # stops here if wrong
assert driver.current_url == "expected/url"
# Soft assertion — collects all failures, reports at end
# Use pytest-check or softest library
import pytest_check as check
def test_soft():
check.equal(driver.title, "Expected Title")
check.is_in("/dashboard", driver.current_url)
# test continues even if first check fails
JavaScript Executor
31. When and how do you use JavaScript Executor?
from selenium.webdriver.common.by import By
# 1. Click hidden/obscured element
driver.execute_script("arguments[0].click();", element)
# 2. Scroll to element
driver.execute_script("arguments[0].scrollIntoView(true);", element)
# 3. Scroll page
driver.execute_script("window.scrollBy(0, 500);")
driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
# 4. Set input value (bypasses sendKeys limitations)
driver.execute_script("arguments[0].value = 'test@email.com';", input_el)
# 5. Get inner text
text = driver.execute_script("return arguments[0].innerText;", element)
# 6. Remove readonly attribute
driver.execute_script("arguments[0].removeAttribute('readonly');", el)
# 7. Return value from script
page_title = driver.execute_script("return document.title;")
32. How do you handle Shadow DOM elements?
Shadow DOM encapsulates elements — normal locators can't pierce it.
# Selenium 4 — use CSS selector with shadow root
shadow_host = driver.find_element(By.CSS_SELECTOR, "my-component")
shadow_root = shadow_host.shadow_root
inner_element = shadow_root.find_element(By.CSS_SELECTOR, "input.search")
inner_element.send_keys("test")
# JavaScript approach (older Selenium / deeper shadow roots)
shadow_root = driver.execute_script(
"return arguments[0].shadowRoot", shadow_host
)
inner = driver.execute_script(
"return arguments[0].querySelector('input.search')", shadow_root
)
Advanced Topics
33. How do you handle cookies in Selenium?
# Get all cookies
cookies = driver.get_cookies()
# Get specific cookie
session_cookie = driver.get_cookie("session_id")
# Add cookie (must be on the domain first)
driver.get("https://example.com")
driver.add_cookie({"name": "auth_token", "value": "abc123"})
# Delete cookie
driver.delete_cookie("session_id")
driver.delete_all_cookies()
# Cookie injection pattern (bypass login in tests)
def inject_session(driver, token):
driver.get("https://example.com")
driver.add_cookie({"name": "session", "value": token, "path": "/"})
driver.refresh()
34. How do you handle AJAX / dynamic content?
# Wait for AJAX spinner to disappear
wait.until(EC.invisibility_of_element_located((By.ID, "loading")))
# Wait for network idle via JavaScript
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
# Wait for jQuery AJAX (if site uses jQuery)
wait.until(lambda d: d.execute_script("return jQuery.active") == 0)
# Wait for element count to change
initial_count = len(driver.find_elements(By.CLASS_NAME, "result-item"))
driver.find_element(By.ID, "load-more").click()
wait.until(lambda d: len(d.find_elements(By.CLASS_NAME, "result-item")) > initial_count)
35. How do you capture network traffic / intercept requests?
# Selenium 4 with CDP (Chrome DevTools Protocol)
from selenium.webdriver.common.log import Log
driver.execute_cdp_cmd("Network.enable", {})
driver.execute_cdp_cmd("Network.setExtraHTTPHeaders", {
"headers": {"Authorization": "Bearer token123"}
})
# Capture requests via CDP (requires threading)
requests_log = []
driver.execute_cdp_cmd("Network.requestWillBeSent", {})
# More complete implementation uses selenium-wire library:
# pip install selenium-wire
from seleniumwire import webdriver as wire_webdriver
driver = wire_webdriver.Chrome()
driver.get("https://example.com")
for request in driver.requests:
if request.response:
print(request.url, request.response.status_code)
36. What are Selenium Listeners / Event Firing WebDriver?
// Java — WebDriverEventListener
public class TestListener implements WebDriverEventListener {
@Override
public void beforeClickOn(WebElement element, WebDriver driver) {
System.out.println("Before click: " + element.getText());
}
@Override
public void onException(Throwable throwable, WebDriver driver) {
driver.findElement(By.tagName("body")); // save screenshot here
}
}
WebDriver driver = new ChromeDriver();
EventFiringWebDriver efwd = new EventFiringWebDriver(driver);
efwd.register(new TestListener());
Python alternative: subclass WebDriver or use pytest fixtures/hooks.
Test Configuration & CI/CD
37. How do you structure pytest fixtures for Selenium?
# conftest.py
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
@pytest.fixture(scope="session")
def driver():
options = Options()
options.add_argument("--headless=new")
options.add_argument("--window-size=1920,1080")
d = webdriver.Chrome(options=options)
d.implicitly_wait(0) # use explicit waits
yield d
d.quit()
@pytest.fixture(autouse=True)
def navigate_to_home(driver):
driver.get("https://example.com")
yield
Scope: function (default) — new browser per test; session — one browser for all tests (faster but shared state).
38. How do you run Selenium tests in GitHub Actions?
# .github/workflows/selenium.yml
name: Selenium Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install dependencies
run: pip install selenium pytest webdriver-manager
- name: Run tests
run: pytest tests/ --headless -v
- name: Upload screenshots on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: screenshots
path: screenshots/
39. How do you run Selenium in Docker?
# docker-compose.yml — Selenium Grid
services:
selenium-hub:
image: selenium/hub:4
ports:
- "4444:4444"
chrome:
image: selenium/node-chrome:4
depends_on:
- selenium-hub
environment:
- SE_EVENT_BUS_HOST=selenium-hub
- SE_NODE_MAX_SESSIONS=5
tests:
build: .
depends_on:
- chrome
environment:
- SELENIUM_HUB=http://selenium-hub:4444
command: pytest tests/ -v
# driver_factory.py
import os
from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
hub = os.getenv("SELENIUM_HUB", "http://localhost:4444")
driver = RemoteWebDriver(
command_executor=f"{hub}/wd/hub",
desired_capabilities=DesiredCapabilities.CHROME,
)
40. How do you run tests in parallel with pytest-xdist?
pip install pytest-xdist
# Run with 4 workers
pytest tests/ -n 4
# Run one worker per CPU core
pytest tests/ -n auto
# Each worker needs its own browser — use function-scoped fixtures
@pytest.fixture(scope="function")
def driver():
d = webdriver.Chrome()
yield d
d.quit()
Note: session-scoped fixtures are shared per worker, not across all workers.
Common Debugging & Patterns
41. How do you debug a flaky test?
Flaky tests pass sometimes, fail others. Common causes and fixes:
| Cause | Fix |
|---|---|
| Race condition | Use explicit waits, not time.sleep() |
| Stale element | Re-locate element after DOM update |
| Overlapping element | Scroll into view / wait for overlay to close |
| Network latency | Increase timeout / wait for network idle |
| Test order dependency | Isolate tests, use fresh state per test |
| Browser version mismatch | Pin driver + browser version |
42. How do you handle ElementNotInteractableException?
from selenium.common.exceptions import ElementNotInteractableException
# Element exists but not visible/enabled
# Fix 1: Wait until clickable
wait.until(EC.element_to_be_clickable((By.ID, "submit")))
# Fix 2: Scroll into view
driver.execute_script("arguments[0].scrollIntoView(true);", element)
element.click()
# Fix 3: JavaScript click (last resort)
driver.execute_script("arguments[0].click();", element)
# Fix 4: Move to element first
ActionChains(driver).move_to_element(element).click().perform()
43. How do you verify an element is NOT present?
# find_elements returns empty list — no exception
elements = driver.find_elements(By.ID, "error-message")
assert len(elements) == 0, "Error message should not be visible"
# Check visibility
def is_element_visible(driver, locator):
elements = driver.find_elements(*locator)
return len(elements) > 0 and elements[0].is_displayed()
# Wait for element to be invisible
wait.until(EC.invisibility_of_element_located((By.ID, "spinner")))
44. How do you get browser logs / console errors?
# Chrome browser logs (requires logging capability)
from selenium.webdriver.chrome.options import Options
options = Options()
options.set_capability("goog:loggingPrefs", {"browser": "ALL"})
driver = webdriver.Chrome(options=options)
driver.get("https://example.com")
logs = driver.get_log("browser")
errors = [log for log in logs if log["level"] == "SEVERE"]
print(errors)
45. How do you simulate keyboard shortcuts?
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
# Send keys to element
input_el.send_keys(Keys.RETURN) # press Enter
input_el.send_keys(Keys.TAB) # Tab
input_el.send_keys(Keys.BACKSPACE)
input_el.send_keys(Keys.CONTROL, "a") # Ctrl+A (select all)
# Clear and type
input_el.send_keys(Keys.CONTROL + "a")
input_el.send_keys(Keys.DELETE)
input_el.send_keys("new text")
# ActionChains for combinations
actions = ActionChains(driver)
actions.key_down(Keys.CONTROL).send_keys("c").key_up(Keys.CONTROL).perform()
Performance & Best Practices
46. What are best practices for writing maintainable Selenium tests?
| Practice | Detail |
|---|---|
| Use POM | Separate locators from test logic |
| Explicit waits only | Never time.sleep(), avoid implicit waits |
| Meaningful locators | Prefer IDs, data-testid attributes |
| Test isolation | Each test independent, own setup/teardown |
| Screenshot on failure | Built-in in conftest fixture |
| Environment config | URLs, credentials from env vars, not hardcoded |
| Small, focused tests | One concept per test |
| Retry flaky tests | pytest-rerunfailures for known-unstable flows |
47. How do you add data-testid attributes for better selectors?
<!-- In production code, add data-testid attributes -->
<button data-testid="checkout-submit">Pay Now</button>
<input data-testid="email-input" type="email" />
# Stable locators that won't break on CSS/text changes
driver.find_element(By.CSS_SELECTOR, "[data-testid='checkout-submit']")
driver.find_element(By.CSS_SELECTOR, "[data-testid='email-input']")
48. Selenium vs Playwright vs Cypress — when to use which?
| Aspect | Selenium | Playwright | Cypress |
|---|---|---|---|
| Language support | Python/Java/C#/JS/Ruby | Python/JS/Java/.NET | JavaScript only |
| Speed | Slower | Fast | Fast |
| Shadow DOM | CDP support | Native | Limited |
| Network interception | selenium-wire | Built-in | Built-in |
| Cross-browser | Chrome/Firefox/Edge/Safari | Chrome/Firefox/WebKit | Chrome/Firefox/Edge |
| Mobile | Limited | Android support | Limited |
| Learning curve | Moderate | Low-Moderate | Low |
| Best for | Legacy, multi-language teams | Modern automation | Frontend dev TDD |
| iframe support | Yes | Yes | Complex |
| CI/Docker | Selenium Grid | Docker image | Docker image |
Choose Selenium when you need multi-language support, Java team, or must test Safari and IE legacy.
49. How do you test file downloads?
import os
from selenium.webdriver.chrome.options import Options
# Configure download directory
download_dir = "/tmp/downloads"
os.makedirs(download_dir, exist_ok=True)
options = Options()
prefs = {
"download.default_directory": download_dir,
"download.prompt_for_download": False,
"download.directory_upgrade": True,
"safebrowsing.enabled": True
}
options.add_experimental_option("prefs", prefs)
driver = webdriver.Chrome(options=options)
# Click download button
driver.find_element(By.ID, "download-report").click()
# Wait for file to appear
import time
timeout = 30
start = time.time()
while time.time() - start < timeout:
files = os.listdir(download_dir)
if files and not any(f.endswith(".crdownload") for f in files):
break
time.sleep(1)
assert any(f.endswith(".csv") for f in os.listdir(download_dir))
50. What are the most common Selenium anti-patterns?
| Anti-pattern | Problem | Fix |
|---|---|---|
time.sleep() everywhere |
Slow, brittle | Explicit WebDriverWait |
XPath with position /tr[1] |
Breaks on reorder | Use unique attribute |
driver.implicitly_wait() mixed with explicit |
Unpredictable behaviour | Use explicit waits only |
| No POM / all locators in tests | Hard to maintain | Implement Page Object Model |
| Session-scope driver with state | Tests interfere | Function-scope or clean state |
find_element in assertions |
StaleElementReference | Re-locate inside assertion |
| Hardcoded URLs and credentials | Not portable | Environment variables |
| No screenshot on failure | Hard to debug failures | pytest hookimpl screenshot |
Comparison tables
Selenium vs alternatives
| Feature | Selenium 4 | Playwright | Cypress | Puppeteer |
|---|---|---|---|---|
| Protocol | W3C WebDriver | CDP/WebSocket | CDP | CDP |
| Languages | Python/Java/C#/Ruby/JS | Python/JS/Java/.NET | JS/TS | JS/TS |
| Safari | Yes (limited) | WebKit | No | No |
| Parallel | Grid / pytest-xdist | Built-in | Dashboard (paid) | Manual |
| Auto-wait | ❌ (manual) | ✅ | ✅ | ❌ |
| Network mock | selenium-wire | ✅ | ✅ | ✅ |
| Trace/video | ❌ | ✅ | ✅ | ❌ |
| Mobile emulation | Limited | ✅ | ❌ | ✅ |
Common wait conditions
| Condition | expected_conditions method |
|---|---|
| Element present in DOM | presence_of_element_located |
| Element visible | visibility_of_element_located |
| Element clickable | element_to_be_clickable |
| Element invisible | invisibility_of_element_located |
| Text in element | text_to_be_present_in_element |
| URL contains | url_contains |
| Title contains | title_contains |
| Alert present | alert_is_present |
| Frame available | frame_to_be_available_and_switch_to_it |
| Number of windows | number_of_windows_to_be |
FAQ
Q: Is Selenium 3 still usable?
Selenium 3 works but is in maintenance-only mode. Selenium 4 brings W3C standardisation, CDP support, Selenium Grid 4, and relative locators — upgrade when possible.
Q: Can Selenium test mobile apps?
Not natively. Use Appium (built on the WebDriver protocol) for iOS and Android app automation. Appium reuses Selenium client libraries, so the API is familiar.
Q: How do I handle CAPTCHAs in Selenium?
CAPTCHAs are designed to block automation. Options: disable CAPTCHA in test environments, use test API keys from reCAPTCHA (v2 test keys), or use third-party solving services. Never try to "bypass" production CAPTCHAs.
Q: What is desired_capabilities and is it still used?DesiredCapabilities was the old way to configure browsers. Selenium 4 replaces it with Options classes (ChromeOptions, FirefoxOptions) and W3C capabilities. DesiredCapabilities still works but is deprecated.
Q: How do I avoid the NoSuchElementException?
Always use explicit waits (WebDriverWait + EC) instead of finding elements immediately after navigation. Pages load asynchronously — the element may not exist yet when your code runs.
Q: Selenium vs selenium-wire vs undetected-chromedriver?
- selenium-wire — extends Selenium to capture/modify network requests
- undetected-chromedriver — patches ChromeDriver to avoid bot detection on some sites
- Use these only when standard Selenium is insufficient; standard Selenium should be the default.