Selenium find_element By.ID: Python Examples (2026)

Selenium find_element By.ID: Python Examples (2026)

In Selenium Python, find an element by ID with driver.find_element(By.ID, "element-id"). This is the fastest locator strategy — IDs are direct DOM lookups. Always use explicit waits on dynamic pages: WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, "element-id"))).

Key Takeaways

By.ID is the fastest Selenium locator. ID attributes are indexed by browsers for fast lookup. Prefer By.ID over CSS selectors or XPath when elements have reliable IDs.

IDs must be unique on the page. If a page has duplicate IDs (it happens in poorly written HTML), find_element(By.ID, ...) returns the first match. Use find_elements to check.

Dynamic IDs change on every page load. React, Angular, and Vue often generate IDs like input-abc123 or :r1:. Don't use these — find elements by name, data-testid, or CSS class instead.

NoSuchElementException means the element isn't in the DOM yet. Add WebDriverWait with presence_of_element_located to wait for it. Don't use time.sleep().

By.CSS_SELECTOR with #id is equivalent to By.ID. driver.find_element(By.CSS_SELECTOR, "#submit") works the same as By.ID, "submit". Use whichever is more readable in context.

Basic Usage: find_element By.ID

from selenium import webdriver
from selenium.webdriver.common.by import By

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

# Find element by ID
username_field = driver.find_element(By.ID, "username")
username_field.send_keys("testuser")

password_field = driver.find_element(By.ID, "password")
password_field.send_keys("secretpassword")

submit_button = driver.find_element(By.ID, "login-submit")
submit_button.click()

By.ID directly maps to document.getElementById() in the browser — the most efficient DOM lookup method.

With Explicit Waits (Required for Dynamic Pages)

On pages that load content via JavaScript (React, Vue, Angular), the element may not exist in the DOM immediately after driver.get():

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/dashboard")

wait = WebDriverWait(driver, 10)  # Wait up to 10 seconds

# Wait until element exists in DOM
element = wait.until(
    EC.presence_of_element_located((By.ID, "user-profile"))
)

# Wait until element is visible
element = wait.until(
    EC.visibility_of_element_located((By.ID, "user-profile"))
)

# Wait until element is clickable (visible + enabled)
button = wait.until(
    EC.element_to_be_clickable((By.ID, "save-button"))
)
button.click()

Differences:

  • presence_of_element_located: Element is in DOM (may be hidden)
  • visibility_of_element_located: Element is in DOM and visible
  • element_to_be_clickable: Element is visible and not disabled

By.ID vs By.CSS_SELECTOR vs By.XPATH

All three can find elements by ID:

# By.ID — most readable, direct lookup
element = driver.find_element(By.ID, "submit-button")

# By.CSS_SELECTOR — same performance, useful when combining with other selectors
element = driver.find_element(By.CSS_SELECTOR, "#submit-button")

# By.XPATH — verbose, same result
element = driver.find_element(By.XPATH, '//*[@id="submit-button"]')
element = driver.find_element(By.XPATH, '//button[@id="submit-button"]')

When to use each:

  • By.ID — when you just need the ID, cleanest syntax
  • By.CSS_SELECTOR — when you need ID + type or ID + class: button#submit-button
  • By.XPATH — when you need the parent element with an ID, or combining with text: //div[@id="container"]//button[text()="Save"]

CSS Selector with ID Specifics

You can combine CSS ID selectors with other attributes:

# ID + element type
button = driver.find_element(By.CSS_SELECTOR, "button#submit")

# ID + class
element = driver.find_element(By.CSS_SELECTOR, "#sidebar.active")

# ID + attribute
input_field = driver.find_element(By.CSS_SELECTOR, "#username[required]")

# Parent ID + child element
link = driver.find_element(By.CSS_SELECTOR, "#main-nav a.active")

Handling NoSuchElementException

from selenium.common.exceptions import NoSuchElementException

# Option 1: Try/except
try:
    element = driver.find_element(By.ID, "optional-banner")
    element.is_displayed()  # Banner exists
except NoSuchElementException:
    pass  # Banner not present, continue

# Option 2: find_elements returns empty list (no exception)
elements = driver.find_elements(By.ID, "optional-banner")
if elements:
    elements[0].is_displayed()

# Option 3: Custom helper function
def find_by_id_or_none(driver, element_id):
    elements = driver.find_elements(By.ID, element_id)
    return elements[0] if elements else None

banner = find_by_id_or_none(driver, "promo-banner")
if banner:
    banner.click()

Dynamic IDs: What to Do Instead

Many JavaScript frameworks generate IDs dynamically:

<!-- React generates: -->
<input id=":r1:" name="email" />
<input id=":r2:" name="password" />

<!-- Angular generates: -->
<input id="mat-input-0" />
<input id="mat-input-1" />

Never use these IDs in tests — they change on every render or after DOM updates.

Better alternatives:

# Use name attribute
username = driver.find_element(By.NAME, "username")

# Use data-testid (ask devs to add these)
button = driver.find_element(By.CSS_SELECTOR, "[data-testid='submit-button']")

# Use aria-label
button = driver.find_element(By.CSS_SELECTOR, "[aria-label='Submit form']")

# Use stable class names
button = driver.find_element(By.CSS_SELECTOR, ".submit-btn")

# Use form + input type combination
submit = driver.find_element(By.CSS_SELECTOR, "form input[type='submit']")

# Use XPath with text content
button = driver.find_element(By.XPATH, "//button[text()='Save Changes']")

Best practice: Ask developers to add data-testid attributes to key interactive elements:

<button data-testid="checkout-submit">Place Order</button>

This is stable, not used for styling, and clearly marks elements for testing.

Getting Element Properties After Finding

element = driver.find_element(By.ID, "username-field")

# Get text content
text = element.text

# Get input value
value = element.get_attribute("value")

# Get any attribute
placeholder = element.get_attribute("placeholder")
aria_label = element.get_attribute("aria-label")
data_value = element.get_attribute("data-value")

# Check state
is_enabled = element.is_enabled()
is_selected = element.is_selected()
is_displayed = element.is_displayed()

# Get CSS property
color = element.value_of_css_property("color")
font_size = element.value_of_css_property("font-size")

Find Child Elements with ID as Scope

# Find a container by ID, then search within it
container = driver.find_element(By.ID, "user-form")

# Find elements within the container
name_input = container.find_element(By.NAME, "full-name")
email_input = container.find_element(By.NAME, "email")
submit = container.find_element(By.CSS_SELECTOR, "button[type='submit']")

This scopes the search and makes tests more maintainable.

Using IDs in Page Object Model

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:
    # Locators
    USERNAME_INPUT = (By.ID, "username")
    PASSWORD_INPUT = (By.ID, "password")
    SUBMIT_BUTTON = (By.ID, "login-submit")
    ERROR_MESSAGE = (By.ID, "login-error")

    def __init__(self, driver):
        self.driver = driver
        self.wait = WebDriverWait(driver, 10)

    def login(self, username: str, password: str):
        username_field = self.wait.until(
            EC.visibility_of_element_located(self.USERNAME_INPUT)
        )
        username_field.clear()
        username_field.send_keys(username)

        password_field = self.driver.find_element(*self.PASSWORD_INPUT)
        password_field.send_keys(password)

        submit = self.wait.until(
            EC.element_to_be_clickable(self.SUBMIT_BUTTON)
        )
        submit.click()

    def get_error_message(self) -> str:
        try:
            error = self.wait.until(
                EC.visibility_of_element_located(self.ERROR_MESSAGE)
            )
            return error.text
        except:
            return ""


# Usage
driver = webdriver.Chrome()
driver.get("https://example.com/login")

login_page = LoginPage(driver)
login_page.login("user@example.com", "wrongpassword")

error = login_page.get_error_message()
assert error == "Invalid email or password"

Performance: By.ID vs Other Locators

Rough performance ranking (fastest to slowest):

Locator Relative Speed Notes
By.ID ⭐⭐⭐⭐⭐ Direct getElementById()
By.CSS_SELECTOR ⭐⭐⭐⭐ Native browser CSS engine
By.NAME ⭐⭐⭐⭐ Direct attribute lookup
By.CLASS_NAME ⭐⭐⭐ getElementsByClassName()
By.TAG_NAME ⭐⭐⭐ getElementsByTagName()
By.XPATH ⭐⭐ XPath traversal, slowest

For test automation speed at scale, prefer By.ID and By.CSS_SELECTOR over By.XPATH.

Complete Login Test Example

import pytest
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


@pytest.fixture
def driver():
    driver = webdriver.Chrome()
    driver.implicitly_wait(0)  # Disable implicit waits, use explicit
    yield driver
    driver.quit()


def test_successful_login(driver):
    driver.get("https://example.com/login")
    wait = WebDriverWait(driver, 10)

    # Find by ID — fast and readable
    username = wait.until(EC.visibility_of_element_located((By.ID, "username")))
    username.send_keys("testuser@example.com")

    password = driver.find_element(By.ID, "password")
    password.send_keys("password123")

    submit = wait.until(EC.element_to_be_clickable((By.ID, "submit-btn")))
    submit.click()

    # Verify successful login by checking for dashboard element
    dashboard = wait.until(EC.visibility_of_element_located((By.ID, "user-dashboard")))
    assert dashboard.is_displayed()


def test_invalid_login_shows_error(driver):
    driver.get("https://example.com/login")
    wait = WebDriverWait(driver, 10)

    username = wait.until(EC.visibility_of_element_located((By.ID, "username")))
    username.send_keys("wrong@example.com")

    driver.find_element(By.ID, "password").send_keys("wrongpassword")
    wait.until(EC.element_to_be_clickable((By.ID, "submit-btn"))).click()

    error = wait.until(EC.visibility_of_element_located((By.ID, "error-message")))
    assert error.text == "Invalid credentials"

The Alternative: Natural Language Selectors

Selenium's By.ID requires you to know the exact HTML structure. When IDs change or aren't available, tests break.

HelpMeTest finds elements using AI — no selectors needed:

Go to https://example.com/login
Type "testuser@example.com" in the email field
Type "password123" in the password field
Click the "Sign In" button
Verify the dashboard page loads

The AI identifies elements by their visual appearance and context — the same way a human would. When a developer renames #login-submit to #btn-signin, your test doesn't break.

When By.ID is the right choice:

  • IDs are stable and meaningful (not auto-generated)
  • Working with server-rendered HTML (Django, Rails, Laravel)
  • Large existing Selenium test suite
  • Performance-critical test suites

When to consider alternatives:

  • Single-page apps with dynamic IDs
  • Teams spending significant time on locator maintenance
  • Non-technical team members writing tests

Summary

By.ID is Selenium's fastest and most direct locator:

# Basic
element = driver.find_element(By.ID, "element-id")

# With wait (recommended for dynamic pages)
element = WebDriverWait(driver, 10).until(
    EC.presence_of_element_located((By.ID, "element-id"))
)

# Safe find (no exception if missing)
elements = driver.find_elements(By.ID, "element-id")
element = elements[0] if elements else None

Use By.ID when elements have stable, unique, meaningful IDs. Avoid it when IDs are auto-generated by JavaScript frameworks.

Read more