Selenium Find Element Python: Complete Guide (2026)

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:

  1. Use By.ID when elements have unique IDs — fastest locator
  2. Use By.CSS_SELECTOR for everything else — readable and versatile
  3. Use By.XPATH only when CSS can't do the job — text matching, parent traversal
  4. Always use WebDriverWait before find_element on dynamic pages — prevents NoSuchElementException
  5. Use find_elements to 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).

Read more