CSS selectors are a good fit when a Selenium test needs to target a web element by stable attributes, hierarchy, or component state. A selector that is too broad can return the wrong node, while a selector tied to generated classes can break after a harmless frontend build, so the lookup should prove the exact element and value the test needs.

In Python, By.CSS_SELECTOR sends the selector to the browser and returns a WebElement from the current browsing context. Pairing it with WebDriverWait keeps the lookup from racing JavaScript-rendered content, and scoping child lookups from a parent element keeps repeated components from matching the wrong card, row, or form.

Prefer selectors built from stable id values, data-testid attributes, roles, names, or durable component attributes. CSS selectors search only the current page, frame, or shadow root, so switch into an iframe or shadow root before searching when the element is not in the top-level document.

Steps to find Selenium elements with CSS selectors:

  1. Create selenium-element-find-css.py with a local demo page and a CSS selector wait.
    selenium-element-find-css.py
    from pathlib import Path
    from tempfile import TemporaryDirectory
    import os
    import shutil
     
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver.chrome.service import Service
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.ui import WebDriverWait
     
     
    HTML = """<!doctype html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>Selenium CSS selector demo</title>
      </head>
      <body>
        <main id="pricing">
          <article class="plan-card" data-testid="pricing-plan" data-plan="starter" data-active="true">
            <h2 class="plan-title">Starter</h2>
            <p class="plan-price">$9</p>
          </article>
          <article class="plan-card featured" data-testid="pricing-plan" data-plan="pro" data-active="true">
            <h2 class="plan-title">Pro</h2>
            <p class="plan-price">$29</p>
          </article>
          <article class="plan-card" data-testid="pricing-plan" data-plan="archive" data-active="false" hidden>
            <h2 class="plan-title">Archive</h2>
            <p class="plan-price">$0</p>
          </article>
        </main>
      </body>
    </html>
    """
     
     
    options = Options()
    options.add_argument("--headless=new")
    options.add_argument("--disable-dev-shm-usage")
    options.add_argument("--window-size=1280,720")
     
    if hasattr(os, "geteuid") and os.geteuid() == 0:
        options.add_argument("--no-sandbox")
     
    for browser_name in ("google-chrome", "chromium", "chromium-browser"):
        browser_path = shutil.which(browser_name)
        if browser_path:
            options.binary_location = browser_path
            break
     
    driver_path = shutil.which("chromedriver")
    service = Service(driver_path) if driver_path else Service()
     
    with TemporaryDirectory() as tmpdir:
        page = Path(tmpdir) / "pricing.html"
        page.write_text(HTML, encoding="utf-8")
     
        driver = webdriver.Chrome(service=service, options=options)
        try:
            driver.get(page.as_uri())
     
            wait = WebDriverWait(driver, 10)
            pro_plan = wait.until(
                EC.visibility_of_element_located(
                    (By.CSS_SELECTOR, "[data-testid='pricing-plan'][data-plan='pro']")
                )
            )
            price = pro_plan.find_element(By.CSS_SELECTOR, ".plan-price").text
            active_plans = driver.find_elements(
                By.CSS_SELECTOR, "[data-testid='pricing-plan'][data-active='true']"
            )
     
            print(f"title: {driver.title}")
            print(f"selected_plan: {pro_plan.find_element(By.CSS_SELECTOR, '.plan-title').text}")
            print(f"selected_price: {price}")
            print(f"active_plan_count: {len(active_plans)}")
        finally:
            driver.quit()

    The root-only --no-sandbox branch is for disposable containers that run Chrome as root. Omit that branch when local policy already runs the browser as an unprivileged user.
    Related: How to configure ChromeDriver for Selenium

  2. Run the script and confirm Selenium finds the Pro plan.
    $ python3 selenium-element-find-css.py
    title: Selenium CSS selector demo
    selected_plan: Pro
    selected_price: $29
    active_plan_count: 2
  3. Use a stable CSS selector in the real test.
    pro_plan = wait.until(
        EC.visibility_of_element_located(
            (By.CSS_SELECTOR, "[data-testid='pricing-plan'][data-plan='pro']")
        )
    )

    Prefer a stable id or data-* attribute when the application provides one. Generated class names and full DOM paths often change without changing the feature behavior.

  4. Search inside the matched parent when repeated components share child class names.
    price = pro_plan.find_element(By.CSS_SELECTOR, ".plan-price").text

    The child selector runs from pro_plan, so .plan-price does not accidentally read a price from another card.

  5. Use find_elements() when zero or multiple matches are valid.
    visible_errors = driver.find_elements(By.CSS_SELECTOR, ".field-error[role='alert']")
    if visible_errors:
        print(visible_errors[0].text)

    find_element() raises an exception when nothing matches. find_elements() returns an empty list, which is easier to handle for optional messages, repeated rows, or validation errors.

  6. Re-locate the element after a redraw, route change, or component refresh.
    pro_plan = wait.until(
        EC.visibility_of_element_located(
            (By.CSS_SELECTOR, "[data-testid='pricing-plan'][data-plan='pro']")
        )
    )

    A saved WebElement can become stale after JavaScript replaces the node. Keep the selector and wait for a fresh element before reading or clicking it again.
    Related: How to troubleshoot stale element errors in Selenium

  7. Remove the demo script after moving the selector pattern into the real test.
    $ rm selenium-element-find-css.py