ScreenshotNeo

BlogHow-to

How to Find Elements by CSS Selectors in Selenium

Use Selenium’s CSS selector locator to find one or many elements, wait for dynamic content, and troubleshoot selectors that stop matching.

By the ScreenshotNeo team29 September 202610 min read

How to Find Elements by CSS Selectors in Selenium

In Python Selenium, find one element with driver.find_element(By.CSS_SELECTOR, "#fname"). Find every match with driver.find_elements(By.CSS_SELECTOR, "table tbody tr"). For elements added or revealed asynchronously, combine the CSS selector with an explicit WebDriverWait condition instead of looking immediately.

CSS is one of WebDriver’s eight traditional locator strategies. Selenium describes the CSS strategy as locating elements that match a CSS selector. It works across Selenium language bindings, though each language spells the method and constant differently. This guide focuses on Python, then gives equivalent Java code and practical advice for choosing selectors, waiting, handling multiple matches, and diagnosing failures. [Selenium locator strategies](https://www.selenium.dev/documentation/webdriver/elements/locators/)

1. Set up Selenium and open a page

Install Selenium in the Python environment where the script will run:

python -m pip install selenium

Recent Selenium versions use Selenium Manager to help obtain a compatible browser driver when one is not already configured. Install a supported browser such as Chrome or Firefox, then use the corresponding WebDriver class. This minimal example opens a page and looks up an input by ID:

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

# Selenium Manager can manage the driver for supported local browsers.
driver = webdriver.Chrome()

try:
    driver.get("https://www.selenium.dev/selenium/web/web-form.html")
    name_input = driver.find_element(By.CSS_SELECTOR, "#my-text-id")
    print(name_input.get_attribute("placeholder"))
finally:
    driver.quit()

By.CSS_SELECTOR tells Selenium to interpret the second argument as a CSS selector. The selector is evaluated against the page’s current DOM. Selenium returns a WebElement for the first match or raises NoSuchElementException if none exists at lookup time.

2. Write CSS selectors for Selenium

Selenium passes a CSS selector to the browser; it does not use a special Selenium-only selector syntax. These common patterns cover most automation tasks:

A CSS selector identifies a matching node in the browser’s current DOM.
A CSS selector identifies a matching node in the browser’s current DOM.
Target Selector Example
ID #id #login
Class .class-name .error-message
Tag and class tag.class p.content
Attribute value [attribute='value'] input[name='email']
Descendant ancestor descendant form#login input[name='email']
Direct child parent > child ul.menu > li
Several classes on one node .class-a.class-b .card.featured
Position among siblings :nth-child(n) table tbody tr:nth-child(2)

Pass the selector as a Python string. Use double quotes around the Python string when the selector itself contains single quotes, as in "input[name='email']". A selector such as .card.featured matches a single element that has both classes; .card .featured instead matches a descendant with class featured inside an element with class card.

Prefer stable attributes

Selectors are only as reliable as the attributes and structure they depend on. Prefer a stable ID, name, or application-provided data attribute when available. For example, [data-testid='save'] can be clearer and less fragile than a long chain of layout classes if the application maintains that attribute as part of its testing contract.

Avoid depending on generated class names that change between builds, or long positional selectors that assume the page’s layout never changes. Use the shortest selector that identifies the intended element unambiguously. Validate it against the current DOM when the page changes or a lookup stops working.

3. Find one element or collect all matches

Use the singular method when the page should contain one matching element. Use the plural method when zero, one, or many matches are valid. The plural call returns a list; an empty list means no element matched at that moment.

from selenium.webdriver.common.by import By

first_name = driver.find_element(By.CSS_SELECTOR, "#fname")
content_paragraph = driver.find_element(By.CSS_SELECTOR, "p.content")

rows = driver.find_elements(By.CSS_SELECTOR, "table tbody tr")
for row in rows:
    print(row.text)

# A plural lookup is useful when no match is a normal outcome.
notices = driver.find_elements(By.CSS_SELECTOR, ".notice")
if not notices:
    print("No notices are displayed")

The singular method returns the first match in document order, not a guarantee that only one match exists. If uniqueness matters, explicitly check the plural result:

matches = driver.find_elements(By.CSS_SELECTOR, "button.save")
if len(matches) != 1:
    raise RuntimeError(f"Expected one save button, found {len(matches)}")
save_button = matches[0]

4. Wait for elements on dynamic pages

JavaScript applications often insert elements, reveal them, or enable controls after navigation. An immediate lookup only checks the DOM at that instant. Use an explicit wait to poll for the state the next action actually requires.

Wait for the state your next action needs: presence, visibility, or clickability.
Wait for the state your next action needs: presence, visibility, or clickability.

Import WebDriverWait and expected conditions, then wait for clickability before interacting:

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()
try:
    driver.get("https://example.com")
    wait = WebDriverWait(driver, 10)
    submit = wait.until(
        EC.element_to_be_clickable((By.CSS_SELECTOR, "button.submit"))
    )
    submit.click()
finally:
    driver.quit()

Remove the accidental leading space before driver = if copying this snippet exactly; Python code at the top level must not be indented. The corrected setup line is driver = webdriver.Chrome(). The condition receives a locator tuple: (By.CSS_SELECTOR, "button.submit").

Choose the wait condition based on what must be true:

Condition What it establishes Use it when
presence_of_element_located A matching node exists in the DOM. You need to inspect an attribute or DOM-backed state; visibility is not required.
visibility_of_element_located A matching node exists and is displayed. You need to read visible content or interact after it appears.
presence_of_all_elements_located At least one matching node exists; returns the current matches. You need a dynamically populated collection to appear.
element_to_be_clickable The element is visible and enabled. You are about to click or otherwise interact with an enabled control.

Example: wait until a results list appears, then collect its rows:

wait = WebDriverWait(driver, 10)
rows = wait.until(
    EC.presence_of_all_elements_located(
        (By.CSS_SELECTOR, "table.results tbody tr")
    )
)
for row in rows:
    print(row.text)

Presence does not mean the element is visible, and visibility does not mean it is enabled. Clickability combines visibility and enabled state. Waiting for the narrowest useful condition makes failures easier to interpret than adding a fixed sleep, which may be too short on a slow run and unnecessarily long on a fast one.

5. Complete example: wait, read, and handle missing results

This script opens a page, waits for a form field, fills it, submits, and waits for the result. Replace the URL and selectors with those from the application under test:

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
from selenium.common.exceptions import TimeoutException, NoSuchElementException

url = "https://example.com/search"
driver = webdriver.Chrome()
wait = WebDriverWait(driver, 12)

try:
    driver.get(url)

    query = wait.until(
        EC.visibility_of_element_located(
            (By.CSS_SELECTOR, "input[name='q']")
        )
    )
    query.send_keys("selenium")

    submit = wait.until(
        EC.element_to_be_clickable(
            (By.CSS_SELECTOR, "button[type='submit']")
        )
    )
    submit.click()

    results = wait.until(
        EC.presence_of_all_elements_located(
            (By.CSS_SELECTOR, "ul.results > li")
        )
    )
    for result in results:
        print(result.text)

except TimeoutException:
    print("The expected element or results did not appear before the wait expired")
except NoSuchElementException:
    print("A direct lookup found no matching element")
finally:
    driver.quit()

The exception handling is optional, but it makes a failed run easier to diagnose. In a test suite, you may prefer to let the exception fail the test so the failure is visible to the test runner.

6. Java equivalent

The same locator strategy is available in Java through By.cssSelector. The singular method returns one element; the plural method returns a list:

import java.time.Duration;
import java.util.List;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

WebDriver driver = new ChromeDriver();
try {
    driver.get("https://example.com");
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));

    WebElement button = wait.until(
        ExpectedConditions.elementToBeClickable(By.cssSelector("button.submit"))
    );
    button.click();

    List<WebElement> rows = driver.findElements(
        By.cssSelector("table tbody tr")
    );
    for (WebElement row : rows) {
        System.out.println(row.getText());
    }
} finally {
    driver.quit();
}

Other Selenium bindings use their own naming conventions, but the selector remains ordinary CSS. Check the binding’s API documentation for its exact method names.

7. CSS selectors compared with other locator strategies

CSS is concise for IDs, classes, attributes, and relationships in the DOM. ID and class-name locators can be convenient for a single simple attribute. XPath can express some relationships CSS cannot, including matching an element by its text content or navigating to certain ancestors. The best locator is the one that is readable, stable, and tied to the page’s intended contract.

Strategy Useful for Trade-off
CSS selector IDs, classes, attributes, descendants, children, and structural patterns. Does not select by rendered text content alone.
ID A unique, stable ID. Only works when the relevant ID is known and stable.
Class name A single class that identifies the target. Cannot express combinations and relationships as flexibly as CSS.
XPath Text-based queries and relationship navigation beyond CSS’s selector model. Can become harder to read when overused.

8. Troubleshooting CSS locator failures

Symptom Likely cause What to do
NoSuchElementException The selector does not match the current DOM, or the lookup happened before insertion. Inspect the live DOM, test the selector, and use an explicit wait if content is delayed.
TimeoutException from a wait The matching state never became true within the timeout. Confirm the selector and page state, then check whether the element is hidden, disabled, or in another browsing context.
Element found but not interactable The node exists but is hidden, covered, disabled, or not yet ready. Wait for visibility or clickability as appropriate; check overlays and enabled state.
More elements than expected The selector is broad or matches repeated components. Scope it beneath a stable container and inspect the plural result before acting.
Works locally but fails in CI Timing, browser size, environment, or browser version changes what is rendered or when. Use state-based waits, control the test environment, and verify the rendered DOM in the failing environment.
Element appears absent despite a correct selector It is inside an iframe or a shadow root. Switch into the iframe first; for shadow DOM, access the relevant shadow root using the support available in your Selenium version.

Debug in a deliberate order

  1. Inspect the current DOM and confirm the selector matches the intended node.
  2. Check whether the page has finished the action that inserts or reveals the element.
  3. Use an explicit wait for presence, visibility, or clickability according to the next step.
  4. Check iframe and shadow-root boundaries; a selector search is scoped to the current context.
  5. Use find_elements when multiple matches or zero matches are expected, and handle the returned collection intentionally.
  6. Replace generated classes or brittle positional paths with a stable ID, name, data attribute, or semantic relationship if the application exposes one.

9. Performance, reliability, and cost notes

A CSS lookup is a local browser query, but reliability depends more on selector quality and page state than on making the selector shorter. A stable, scoped selector is easier to maintain than a long chain tied to incidental layout. For collections, locate the shared container or the repeated items once and avoid repeatedly querying the same broad page region inside a loop.

Use explicit waits for state transitions and keep timeouts proportionate to the application’s expected behavior. A timeout is a maximum wait, not a delay imposed on every successful run. Avoid mixing implicit and explicit waits without understanding their interaction; it can make elapsed wait time less predictable. Always close the browser with quit(), including on exceptions, so the session and browser process do not linger.

Local Selenium’s direct costs depend on the browser, machine, and any hosted browser infrastructure you choose; the locator API itself does not define a per-selector fee. For a task whose output is a screenshot rather than an interactive test, a managed screenshot endpoint can avoid browser setup and maintenance.

10. Or skip the browser setup

If your task is to capture a page image rather than interact with its controls, ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. See the ScreenshotNeo API documentation for options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write("shot.webp", res);

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, and cache hits are never billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use screenshot tools. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.

Sign up for ScreenshotNeo and get 1,000 free screenshots a month with no card.

11. FAQ

Does find_element return every match?

No. It returns the first matching element. Use find_elements to get a list of all current matches.

Can a CSS selector match text such as “Continue”?

CSS selectors do not provide a general text-content selector. Use another strategy, such as XPath, when the visible text is the locator requirement.

Should I add a fixed sleep before every lookup?

No. A fixed sleep delays every run and may still be too short. Wait for the specific DOM, visibility, or interaction state your next step needs.

Can I use CSS selectors inside an iframe automatically?

No. First switch WebDriver to the iframe context, then locate elements within it. Switch back to the top-level document when finished.