Selenium Find Element Python: Complete Guide (2026)
In Selenium Python, use driver.find_element(By.CSS_SELECTOR, "selector") for single elements and driver.find_elements(By.CSS_SELECTOR, "selector") for multiple. CSS selectors are generally preferred over XPath for readability. Always use explicit waits (WebDriverWait) before finding elements to avoid NoSuchElementException on dynamic pages.
Key Takeaways
Use By.CSS_SELECTOR for most element lookups. CSS selectors are shorter, more readable, and faster than XPath for most cases. XPath is only better when you need text content matching or complex parent-child traversal.
find_element raises NoSuchElementException if not found. Unlike find_elements which returns an empty list, find_element throws an exception immediately. Always use explicit waits before calling find_element on dynamic content.
By.ID is the fastest and most reliable locator. When elements have unique IDs, use them. IDs are direct DOM lookups, faster than CSS/XPath traversal.
Never use By.XPATH with position-based selectors. XPath like //div[3]/span[2] breaks when any parent adds or removes a child. Always prefer attribute-based selectors.
The By class is the preferred import. While Selenium supports string locators in newer versions (driver.find_element("id", "...")), the By class is explicit, readable, and IDE-friendly.
Importing Selenium in 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
driver = webdriver.Chrome()
driver.get("https://example.com")
The By class contains all locator strategy constants. Import it explicitly for readable, IDE-autocomplete-friendly code.
find_element vs find_elements
Two core methods:
# find_element — returns single WebElement, raises NoSuchElementException if not found
element = driver.find_element(By.ID, "submit-button")
# find_elements — returns list of WebElements, returns empty list if not found
elements = driver.find_elements(By.CSS_SELECTOR, ".product-item")
# Check if element exists without exception
elements = driver.find_elements(By.ID, "optional-banner")
if elements:
print("Banner found:", elements[0].text)
Key differences:
| Method | Returns | If not found |
|---|---|---|
find_element |
Single WebElement | Raises NoSuchElementException |
find_elements |
List of WebElements | Returns empty list [] |
Locator Strategies: By.ID
The fastest and most reliable locator when available.
# HTML: <button id="submit-btn">Submit</button>
button = driver.find_element(By.ID, "submit-btn")
# HTML: <input id="email-field" type="email">
email_input = driver.find_element(By.ID, "email-field")
email_input.send_keys("test@example.com")
When to use: Always prefer IDs when elements have unique, stable IDs.
Limitation: IDs must be unique on the page. If an element doesn't have an ID, use CSS selector.
Locator Strategies: By.CSS_SELECTOR
The most versatile and recommended locator for most situations.
# Select by ID (same as By.ID but with CSS syntax)
element = driver.find_element(By.CSS_SELECTOR, "#submit-btn")
# Select by class name
element = driver.find_element(By.CSS_SELECTOR, ".primary-button")
# Select by tag name
elements = driver.find_elements(By.CSS_SELECTOR, "button")
# Select by attribute
element = driver.find_element(By.CSS_SELECTOR, "input[name='email']")
element = driver.find_element(By.CSS_SELECTOR, "a[href='/login']")
# Combine tag and class
element = driver.find_element(By.CSS_SELECTOR, "button.submit")
# Child selector
element = driver.find_element(By.CSS_SELECTOR, ".form-group > input")
# Descendant selector
element = driver.find_element(By.CSS_SELECTOR, ".nav-menu a.active")
# Multiple classes
element = driver.find_element(By.CSS_SELECTOR, ".btn.btn-primary")
# Attribute starts with
elements = driver.find_elements(By.CSS_SELECTOR, "a[href^='/product/']")
# Attribute contains
elements = driver.find_elements(By.CSS_SELECTOR, "div[class*='error']")
CSS Selector Cheat Sheet
| Selector | Matches |
|---|---|
#id |
Element with that ID |
.class |
Elements with that class |
tag |
All elements of that tag |
[attr="val"] |
Element with attribute=value |
[attr^="val"] |
Attribute starts with value |
[attr*="val"] |
Attribute contains value |
parent > child |
Direct child |
ancestor descendant |
Any descendant |
tag.class |
Tag with that class |
Locator Strategies: By.XPATH
XPath is more powerful than CSS selectors for some cases but generally harder to read.
# Select by ID
element = driver.find_element(By.XPATH, '//*[@id="submit-btn"]')
# Select by class
element = driver.find_element(By.XPATH, '//*[@class="primary-button"]')
# Select by text content
element = driver.find_element(By.XPATH, '//button[text()="Submit"]')
# Partial text match
element = driver.find_element(By.XPATH, '//button[contains(text(), "Submit")]')
# Attribute match
element = driver.find_element(By.XPATH, '//input[@name="email"]')
# Multiple attributes
element = driver.find_element(By.XPATH, '//input[@type="email" and @name="user_email"]')
# Parent traversal (XPath advantage over CSS)
# Find a table cell, then get its row
cell = driver.find_element(By.XPATH, '//td[text()="John Doe"]')
row = cell.find_element(By.XPATH, '..') # Gets parent <tr>
# Sibling traversal
element = driver.find_element(By.XPATH, '//label[text()="Email"]/following-sibling::input')
When to use XPath over CSS
Use XPath when you need to:
- Find elements by visible text content (
//button[text()="Login"]) - Traverse to parent elements (
..) - Find elements based on sibling relationships
- Use complex boolean conditions with
and/or
For everything else, CSS selectors are preferred.
Locator Strategies: By.NAME
Finds elements by the name HTML attribute. Common for form inputs.
# HTML: <input name="username" type="text">
username_input = driver.find_element(By.NAME, "username")
username_input.send_keys("testuser")
# HTML: <input name="password" type="password">
password_input = driver.find_element(By.NAME, "password")
password_input.send_keys("secret123")
Locator Strategies: By.CLASS_NAME
Finds elements by a single CSS class name. For multiple classes, use By.CSS_SELECTOR.
# HTML: <div class="error-message">Invalid input</div>
error = driver.find_element(By.CLASS_NAME, "error-message")
# Multiple elements
errors = driver.find_elements(By.CLASS_NAME, "form-error")
# LIMITATION: Cannot match multiple classes with By.CLASS_NAME
# This will FAIL: driver.find_element(By.CLASS_NAME, "btn btn-primary")
# Use CSS selector instead:
button = driver.find_element(By.CSS_SELECTOR, ".btn.btn-primary")
Locator Strategies: By.TAG_NAME
Finds elements by HTML tag name.
# Find all links on the page
links = driver.find_elements(By.TAG_NAME, "a")
for link in links:
print(link.get_attribute("href"))
# Find all input fields in a form
inputs = driver.find_elements(By.TAG_NAME, "input")
# Find a single element
heading = driver.find_element(By.TAG_NAME, "h1")
print(heading.text)
Locator Strategies: By.LINK_TEXT and By.PARTIAL_LINK_TEXT
For finding anchor elements by their visible text.
# HTML: <a href="/login">Sign In</a>
login_link = driver.find_element(By.LINK_TEXT, "Sign In")
login_link.click()
# Partial match
# HTML: <a href="/dashboard">Go to Dashboard</a>
dashboard_link = driver.find_element(By.PARTIAL_LINK_TEXT, "Dashboard")
Finding Elements Within Elements
You can call find_element on any WebElement, not just the driver:
# Find elements within a specific container
form = driver.find_element(By.CSS_SELECTOR, "#registration-form")
# Now search within the form, not the whole page
email_input = form.find_element(By.CSS_SELECTOR, "input[type='email']")
submit_button = form.find_element(By.CSS_SELECTOR, "button[type='submit']")
# Useful when multiple similar elements exist on the page
rows = driver.find_elements(By.CSS_SELECTOR, "table.data-table tr")
for row in rows:
cells = row.find_elements(By.TAG_NAME, "td")
if cells:
print([cell.text for cell in cells])
Handling Dynamic Elements: Explicit Waits
find_element fails immediately if the element doesn't exist yet. Use explicit waits for dynamic content.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver.get("https://example.com")
wait = WebDriverWait(driver, 10) # Wait up to 10 seconds
# Wait until element is present in DOM
element = wait.until(EC.presence_of_element_located((By.ID, "results")))
# Wait until element is visible (present AND visible)
element = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, ".data-table")))
# Wait until element is clickable (visible AND enabled)
button = wait.until(EC.element_to_be_clickable((By.ID, "submit-btn")))
button.click()
# Wait for element text to appear
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.CSS_SELECTOR, ".loading-spinner")))
Always use explicit waits instead of time.sleep():
# Wrong — arbitrary sleep
import time
time.sleep(3)
element = driver.find_element(By.ID, "results")
# Right — explicit wait for the specific condition
element = WebDriverWait(driver, 10).until(
EC.visibility_of_element_located((By.ID, "results"))
)
Common Errors and Fixes
NoSuchElementException
Error: NoSuchElementException: Message: no such element: Unable to locate element
Causes and fixes:
# Cause 1: Element not in DOM yet (page still loading)
# Fix: Add explicit wait
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.ID, "slow-element"))
)
# Cause 2: Wrong selector
# Debug: Print page source to see actual HTML
print(driver.page_source)
# Cause 3: Element in an iframe
# Fix: Switch to iframe first
driver.switch_to.frame("iframe-id")
element = driver.find_element(By.ID, "element-in-iframe")
driver.switch_to.default_content() # Switch back
# Cause 4: Element in shadow DOM
# Fix: Use JavaScript
shadow_host = driver.find_element(By.CSS_SELECTOR, "my-component")
shadow_root = driver.execute_script("return arguments[0].shadowRoot", shadow_host)
element = shadow_root.find_element(By.CSS_SELECTOR, ".shadow-element")
StaleElementReferenceException
Error: StaleElementReferenceException: Message: stale element reference
The element was found but then the DOM was updated, invalidating the reference.
# Wrong — element reference goes stale after page update
items = driver.find_elements(By.CSS_SELECTOR, ".item")
driver.find_element(By.ID, "filter").click() # Page updates
items[0].click() # StaleElementReferenceException!
# Fix 1 — Re-find after DOM update
driver.find_element(By.ID, "filter").click()
items = driver.find_elements(By.CSS_SELECTOR, ".item") # Re-find
items[0].click()
# Fix 2 — Use retry logic
from selenium.common.exceptions import StaleElementReferenceException
def click_first_item():
for attempt in range(3):
try:
driver.find_element(By.CSS_SELECTOR, ".item").click()
return
except StaleElementReferenceException:
if attempt == 2:
raise
ElementNotInteractableException
Error: ElementNotInteractableException: Message: element not interactable
# Cause: Element is hidden or disabled
# Fix: Use JavaScript click as workaround
element = driver.find_element(By.ID, "hidden-button")
driver.execute_script("arguments[0].click()", element)
# Or wait for the element to become clickable
element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable((By.ID, "button-id"))
)
element.click()
Practical Examples
Example 1: Fill and Submit a Login Form
driver.get("https://example.com/login")
# Wait for form to be ready
wait = WebDriverWait(driver, 10)
email_field = wait.until(EC.visibility_of_element_located((By.NAME, "email")))
email_field.send_keys("test@example.com")
driver.find_element(By.NAME, "password").send_keys("password123")
driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
# Wait for successful redirect
wait.until(EC.url_contains("/dashboard"))
print("Login successful")
Example 2: Extract Table Data
driver.get("https://example.com/users")
wait = WebDriverWait(driver, 10)
# Wait for table to load
wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "table.users-table tbody tr")))
rows = driver.find_elements(By.CSS_SELECTOR, "table.users-table tbody tr")
users = []
for row in rows:
cells = row.find_elements(By.TAG_NAME, "td")
if len(cells) >= 3:
users.append({
"name": cells[0].text,
"email": cells[1].text,
"role": cells[2].text
})
print(f"Found {len(users)} users")
for user in users:
print(user)
Example 3: Interact with a Dropdown
from selenium.webdriver.support.ui import Select
# For native <select> elements
select_element = driver.find_element(By.ID, "country-select")
select = Select(select_element)
# Select by visible text
select.select_by_visible_text("United States")
# Select by value attribute
select.select_by_value("US")
# Select by index
select.select_by_index(0)
# Get all options
options = [opt.text for opt in select.options]
print("Available options:", options)
Selenium vs Playwright: Finding Elements
| Feature | Selenium Python | Playwright Python |
|---|---|---|
| API | driver.find_element(By.X, "y") |
page.locator("selector") |
| Multiple elements | find_elements(...) |
locator.all() |
| Auto-waiting | No (need WebDriverWait) | Yes (built-in) |
| Stale elements | Common issue | Locators re-query |
| CSS selectors | Yes | Yes |
| XPath | Yes | Yes |
| Text matching | Via XPath only | locator.get_by_text() |
| Role-based | No | locator.get_by_role() |
Playwright's Locator API is more modern and handles many Selenium pain points automatically:
# Playwright equivalent — simpler, auto-waits
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
page.goto("https://example.com/login")
# Auto-waits for elements to be ready
page.locator("input[name='email']").fill("test@example.com")
page.locator("input[name='password']").fill("password123")
page.locator("button[type='submit']").click()
Beyond Manual Element Finding: AI Testing
Writing find_element calls for every test is repetitive. When the UI changes — a class name update, an ID rename, a structural refactor — every locator breaks and needs updating.
HelpMeTest generates and maintains these locators automatically. You describe the test in plain English:
Log in as a regular user
Steps:
1. Go to https://example.com/login
2. Enter email "test@example.com"
3. Enter password "password123"
4. Click the Sign In button
5. Verify the dashboard is shown
HelpMeTest converts this to Robot Framework + Playwright automation with locators, waits, and error handling. When the UI changes, it self-heals the broken selectors automatically — no manual locator maintenance needed.
For teams tired of constantly updating find_element calls after UI changes, this eliminates the maintenance burden entirely.
Conclusion
The recommended approach for finding elements in Selenium Python:
- Use
By.IDwhen elements have unique IDs — fastest locator - Use
By.CSS_SELECTORfor everything else — readable and versatile - Use
By.XPATHonly when CSS can't do the job — text matching, parent traversal - Always use
WebDriverWaitbeforefind_elementon dynamic pages — prevents NoSuchElementException - Use
find_elementsto check for optional elements — returns empty list instead of raising exception
The most common source of brittle Selenium tests is tight coupling to specific HTML structure via XPath. Prefer stable attributes (id, name, data-testid) over positional selectors like div:nth-child(3).