ScreenshotNeo

BlogGuides

Selenium Locators Cheat Sheet

A practical guide to all eight Selenium locator strategies, Selenium 4 relative locators, choosing stable selectors, and avoiding ambiguous matches.

By the ScreenshotNeo team4 October 20266 min read

Selenium locators tell WebDriver which page element to find. Prefer a unique, stable id when the page has one; otherwise, Selenium recommends a well-written CSS selector. Use XPath when its flexibility helps express the relationship you need. A single-element lookup returns the first match, so it does not prove your locator is unique.

This cheat sheet covers Selenium’s eight traditional locator strategies, Selenium 4 relative locators, Python examples, scoped searches, and common locator failures. The examples use the current Selenium Python API: from selenium.webdriver.common.by import By and driver.find_element(By.ID, "..." ). See Selenium’s documentation for locator strategies, locator guidance, and finding elements.

1. The eight Selenium locator strategies

Strategy Example Use it when Watch for
By.ID By.ID, "email" The element has a unique, stable ID. IDs that change between page loads or are duplicated.
By.NAME By.NAME, "q" A useful name attribute identifies the element. Several controls can share the same name.
By.CLASS_NAME By.CLASS_NAME, "submit" A single class is a meaningful locator. Pass one class only, not a space-separated class list.
By.CSS_SELECTOR By.CSS_SELECTOR, "form#login input[name='email']" You need a compact selector using attributes, hierarchy, or relationships. Keep it readable and specific enough to identify the intended element.
By.XPATH By.XPATH, "//button[@type='submit']" You need XPath’s relationship or text-matching flexibility. Complex XPath can be harder to read and debug.
By.LINK_TEXT By.LINK_TEXT, "Documentation" An anchor’s visible text is stable and should match exactly. Works only for links (<a> elements).
By.PARTIAL_LINK_TEXT By.PARTIAL_LINK_TEXT, "Doc" A partial anchor label is appropriate and unambiguous. Also link-only; partial matches can select the wrong link.
By.TAG_NAME By.TAG_NAME, "button" You want elements by tag, often as a collection. Common tags usually match many elements.

Selenium’s guidance is to prefer IDs when they are available, unique, and consistently predictable. When a suitable ID is unavailable, it recommends a well-written CSS selector. XPath remains supported and useful, but its syntax is more complicated and often harder to debug.

2. Choosing a locator that will hold up

  1. Check for a stable unique ID. If the target has one, use By.ID. Avoid IDs generated anew on each render.
  2. Use a concise CSS selector next. Combine a stable container, tag, and attribute only as needed, such as form#login input[name='email'].
  3. Choose XPath for a relationship or matching condition CSS does not express as clearly. Keep the expression as short as the task allows.
  4. Use link text only for anchors. Exact text is specific when copy is stable. Partial text tolerates some copy changes but can match multiple links.
  5. Use class or tag lookups with care. Classes and tags commonly occur more than once. Narrow the search or deliberately retrieve a collection.
  6. Check how many elements match. A successful singular lookup alone does not establish uniqueness.

CSS versus XPath is not a universal speed contest. Choose based on clarity and the match you need. Selenium notes that a nested search can take two browser commands; a CSS or XPath selector that expresses the same search in one command may improve performance slightly. Keep that selector understandable.

3. Runnable Python example

Install Selenium with python -m pip install selenium. Selenium Manager can manage a compatible browser driver for supported setups. You still need a browser installed. Save this as locators.py and run python locators.py; it opens a public example page and demonstrates lookups and collection handling.

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

url = "https://www.selenium.dev/selenium/web/web-form.html"
driver = webdriver.Chrome()

try:
    driver.get(url)
    wait = WebDriverWait(driver, 10)

    # Wait for the form field, then locate it by its stable name.
    text_input = wait.until(
        EC.visibility_of_element_located((By.NAME, "my-text"))
    )
    text_input.send_keys("Selenium locator example")

    # Locate the submit control by CSS and click it.
    submit = driver.find_element(By.CSS_SELECTOR, "button")
    submit.click()

    # Find the result by ID after navigation/rendering.
    result = wait.until(EC.visibility_of_element_located((By.ID, "message")))
    print("Result:", result.text)

    # Collection lookup: inspect every matching element.
    links = driver.find_elements(By.TAG_NAME, "a")
    print("Link count:", len(links))
    for link in links:
        print(link.text, link.get_attribute("href"))
finally:
    driver.quit()

The example uses the public Selenium demo page. For your application, replace the URL and locator values with selectors from its DOM. If a page loads content asynchronously, wait for the desired state instead of assuming that a fixed pause will always be enough.

4. Single-element lookup, collections, and ambiguity

driver.find_element(...) returns the first matching element in the current search context. If multiple elements match, it does not raise an error just because the selector was ambiguous. The returned first match may not be the one your test intended.

# One match is expected: make the selector specific and assert the result.
submit = driver.find_element(By.CSS_SELECTOR, "form#checkout button[type='submit']")

# A collection is expected: inspect or filter all matches.
buttons = driver.find_elements(By.TAG_NAME, "button")
for button in buttons:
    print(button.text, button.is_displayed(), button.is_enabled())

# Explicitly check uniqueness when the test depends on one match.
matches = driver.find_elements(By.ID, "email")
if len(matches) != 1:
    raise AssertionError(f"Expected one #email element; found {len(matches)}")

An empty result from find_elements is an empty list. A missing match from find_element raises NoSuchElementException. If the page updates after lookup, a previously found element can become stale; locate it again after the update.

5. CSS and XPath patterns

Goal CSS selector XPath
Element with ID #account //*[@id='account']
Input by name input[name='email'] //input[@name='email']
Button with type button[type='submit'] //button[@type='submit']
Element inside a form form#login input[name='email'] //form[@id='login']//input[@name='email']
Element with both classes .button.primary //*[contains(concat(' ', normalize-space(@class), ' '), ' button ') and contains(concat(' ', normalize-space(@class), ' '), ' primary ')]
Anchor with exact text CSS does not select by rendered text //a[normalize-space(.)='Continue']

In Python, use By.CSS_SELECTOR or By.XPATH with the corresponding expression. Avoid building selectors from unescaped user input. If a value contains quotes or special characters, construct the selector carefully or locate a broader stable element and compare its attributes in code.

  • Compound class names: By.CLASS_NAME, "button primary" is invalid because class-name lookup accepts one class name. Use By.CSS_SELECTOR, ".button.primary" to match both classes.
  • Link text: By.LINK_TEXT and By.PARTIAL_LINK_TEXT apply to links only. They are not general text locators for buttons or other elements.
  • Whitespace and changing copy: visible link text may change or include whitespace. Prefer a stable ID, attribute, or CSS selector when text is not a reliable contract.
  • Generic tags: By.TAG_NAME, "div" is likely broad. Use find_elements when collecting, or narrow by a stable ancestor and attribute.

7. Selenium 4 relative locators

Relative locators find an element based on where it appears in relation to an easier-to-identify element: above, below, to the left, to the right, or near. Selenium uses element bounding rectangles to determine size and position. They are useful when the spatial arrangement is meaningful and the anchor element is stable.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.relative_locator import locate_with

email = driver.find_element(By.ID, "email")
password_below_email = driver.find_element(
    locate_with(By.TAG_NAME, "input").below(email)
)

# Other spatial relations include .above(), .to_left_of(), .to_right_of(),
# and .near(). Supply a stable anchor element to each relation.

Relative locators are a spatial description, not a substitute for a stable semantic identifier in every layout. Responsive reflow, added content, or overlapping elements can change what is geometrically near or above another element. Use them only when that relationship fits the page and verify the match in the relevant viewport.

8. Search within a parent or shadow root

A nested search scopes the child lookup to a previously located parent. It is easy to understand, but may require two browser commands. If a concise CSS or XPath selector expresses the same relationship clearly, a single lookup can be slightly more efficient.

# Scoped search within a located container
panel = driver.find_element(By.ID, "settings-panel")
save_button = panel.find_element(By.CSS_SELECTOR, "button.save")

# Equivalent single lookup when the DOM relationship is suitable
save_button = driver.find_element(
    By.CSS_SELECTOR, "#settings-panel button.save"
)

# Selenium 4+: search inside a shadow root
host = driver.find_element(By.CSS_SELECTOR, "user-card")
shadow_root = host.shadow_root
name = shadow_root.find_element(By.CSS_SELECTOR, ".name")
print(name.text)

Shadow-root search requires Selenium 4 or later. First locate the shadow host, then obtain its shadow root and search within that context. Elements inside the shadow root are not found by searching the ordinary document context.

9. Wait for the right condition

A locator answers which element to find; a wait answers when to look for it or when it is ready. For dynamic pages, explicit waits make the expected condition clear. Common conditions include presence, visibility, clickability, and a particular title or URL.

from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

wait = WebDriverWait(driver, 10)
# In the DOM, whether or not visible
item = wait.until(EC.presence_of_element_located((By.ID, "result")))
# Visible to the user
item = wait.until(EC.visibility_of_element_located((By.ID, "result")))
# Visible and enabled for interaction
button = wait.until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, "button[type='submit']"))
)

Choose the condition that matches the next action. Presence does not guarantee visibility or clickability. Avoid mixing implicit and explicit waits without understanding their interaction, since the combined waiting behavior can be difficult to predict.

10. Troubleshooting locator errors

Symptom Likely cause What to do
NoSuchElementException The selector does not match in the current context, the page has not rendered the element, or the element is in a frame or shadow root. Check the current page and selector; wait for the right condition; switch to the correct frame or search the shadow root.
InvalidSelectorException Malformed CSS/XPath, or a compound string passed to By.CLASS_NAME. Validate the selector syntax; use one class name or a CSS selector such as .button.primary.
The wrong element is clicked A broad selector matched several elements and singular lookup returned the first. Scope it to a stable parent, add a distinguishing attribute, or inspect all matches with find_elements.
StaleElementReferenceException The DOM changed after the element was found. Wait for the update, then locate the element again instead of reusing the stale reference.
Element found but not interactable It is hidden, covered, disabled, or not yet ready. Wait for visibility or clickability and check whether an overlay or state change is blocking interaction.
Link-text lookup finds nothing The target is not an anchor, the text differs, or the link text is unstable. Confirm it is an <a>; use a stable attribute or CSS/XPath selector if appropriate.
Shadow DOM element is missing The search is being performed in the document instead of the host’s shadow root. Locate the host, access host.shadow_root, and search there (Selenium 4+).
Relative locator selects a different item on another viewport Responsive layout or changed geometry altered the spatial relationship. Use a stable semantic locator, or validate the relative locator in each supported layout.

11. Reliability, performance, and maintenance

  • Reliability: Prefer selectors based on stable IDs or attributes that represent the element’s role. Avoid selectors coupled to incidental layout depth or generated class names.
  • Uniqueness: Check match counts during development, especially for controls whose accidental mis-selection could submit the wrong form or change data.
  • Performance: Selenium notes that nested lookups can take two commands; a readable single CSS or XPath locator may save a command and improve performance slightly. Do not trade away maintainability for a speculative speed gain.
  • Waits: Use condition-based waits around asynchronous rendering. A fixed delay can be unnecessarily slow on a fast run and insufficient on a slow one.
  • Cost: Selenium itself is open-source software. Operational costs depend on where browsers and test infrastructure run; this locator guide makes no benchmark or cost claim for a particular hosting setup.

12. Or skip the browser setup

If your goal is to inspect a page visually rather than interact with its DOM, ScreenshotNeo can return a screenshot or PDF from one GET request. It is a website screenshot API and MCP server from Yorker Media. The examples below use https://stripe.com; replace it with the page you need. See the ScreenshotNeo API documentation for request 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,
)
r.raise_for_status()
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}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
  • Cookie and consent banners are accepted before capture; 60+ known consent platforms, newsletter popups, and chat widgets are removed. Each step can be turned off.
  • Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing. Response headers report the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 shots; yearly billing gives two months free. Every feature is available on every plan.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

13. FAQ

What is the most reliable Selenium locator?

A unique, stable ID is Selenium’s preferred choice when available. Otherwise, use a clear CSS selector that identifies the intended element.

Does find_element require exactly one match?

No. It returns the first match in the current context. Use find_elements and check the count if uniqueness matters.

No. Link-text strategies target anchor elements. Use an appropriate button locator such as an ID, CSS selector, or XPath.

Are relative locators available in Selenium 3?

The documented relative-locator feature is part of Selenium 4. Shadow-root search methods also require Selenium 4 or greater.