Chapter 04Lesson 01~125 minutes

Locators: ID, CSS, XPath, Relative Locators, and Resilient Selection: Core Concepts and Mental Model

Build a precise locator mental model: search context, strategy and value, matching node set, WebElement references, semantic identity, scoped search, and the maintenance consequences of coupling selectors to incidental DOM structure.

Locator intentSearch contextWebElementCSS / XPathRelative locators

Learning objectives

  • Distinguish a DOM search context, locator strategy/value, matching node set, and returned WebElement reference.
  • Explain why locator intent should track stable UI semantics rather than incidental DOM depth, generated classes, or styling.
  • Compare Selenium’s traditional locator strategies and the failure behavior of find_element versus find_elements.
  • Use component scoping to narrow a search and make uniqueness an explicit test property.
  • Explain relative locators as geometry-based selection and why responsive layout can change their result.
  • Record session, URL, title, DOM-marker, and match-count evidence before changing a locator contract.

1. Why locator design is test engineering, not selector trivia

Chapter 03 established that every WebDriver command is scoped to a concrete session. The next problem is more local but equally important: which DOM node does a test mean? A browser can contain thousands of nodes, repeated components, generated classes, localized text, and markup that changes without changing user behavior. A locator is the contract that turns test intent into a node reference.

A weak locator says “the third button inside the second wrapper.” A resilient locator says “the checkout control,” “the product card for SKU-42,” or “the add action inside that card.” The second form survives harmless markup refactors because it couples to behavior or stable identity rather than incidental structure.

DevOps consequence: a locator failure in CI should signal a meaningful UI contract change or a real application problem. If selectors break whenever a wrapper or generated class changes, the pipeline reports noise instead of release evidence.

2. The locator pipeline: context → matches → element reference

A search context is the object within which Selenium searches. The driver searches the current document; a WebElement can itself become a narrower search context for descendant elements. Selenium combines that context with a locator strategy such as ID or CSS and a locator value such as checkout or [data-testid="checkout"]. The browser evaluates the locator and produces a set of matching DOM nodes. Selenium then returns one or more protocol-backed WebElement references.

Locator intent becomes a scoped element reference

The following diagram visualizes the relationships described in The locator pipeline: context → matches → element reference. Read the nodes in sequence and use the arrows to connect the conceptual state changes to the explanation around the diagram.

flowchart TD
I[Test intent] --> C[Search context]
C --> L[Locator strategy + value]
L --> M[Matching DOM node set]
M --> E[WebElement reference]
E --> O[Read or interact]
O --> V[Assertion + evidence]

Every arrow matters. Test intent chooses the semantic target. Search context defines where Selenium looks. The locator expresses how to identify the target. The match set determines whether the result is unique, absent, or ambiguous. The WebElement reference belongs to the current session and browsing context; it is not a copied DOM node. Later rerendering can replace that node and make the reference stale, a lifecycle issue explored in later chapters.

3. The standard locator strategies and what they actually couple to

Selenium currently exposes eight traditional WebDriver locator strategies. Their syntax differs, but the engineering question is always the same: what property are you promising will remain stable?

Strategy Typical expression What it couples to Use with care when…
ID By.ID, "checkout" Unique element identity IDs are generated or reused
Name By.NAME, "email" Form/control name contract Names exist for submission rather than stable test identity
CSS selector By.CSS_SELECTOR, "[data-testid=checkout]" Attributes, classes, hierarchy you choose Selector becomes a long DOM traversal
XPath //article[@data-sku="SKU-42"]//button Structure, attributes, text, relationships It becomes an absolute path or text/localization dependency
Class name By.CLASS_NAME, "card" One class token Class is generated/styling-only or not unique
Tag name By.TAG_NAME, "button" HTML element type Many nodes share the tag
Link text By.LINK_TEXT, "Help" Visible anchor text Content is localized or edited
Partial link text By.PARTIAL_LINK_TEXT, "Hel" Part of visible anchor text Multiple links share the text fragment

Official Selenium guidance prefers unique predictable IDs when available and otherwise favors compact readable CSS. XPath remains valid, especially when a true ancestor/descendant or text relationship is the clearest statement of intent; the problem is not XPath itself but unnecessary traversal and hidden coupling.

4. find_element and find_elements encode different evidence

find_element returns the first match in the chosen context. If nothing matches, Selenium raises NoSuchElementException. That is useful when the test contract requires “an element must exist,” but it can hide ambiguity when two elements accidentally match.

find_elements returns a list of all matches and returns an empty list when there are none. That makes it the better diagnostic tool when uniqueness or cardinality matters.

from selenium.webdriver.common.by import By

checkout = driver.find_element(By.ID, "checkout")
print(checkout.tag_name)

matches = driver.find_elements(By.CSS_SELECTOR, '[data-action="add"]')
print(f"add-button count={len(matches)}")
assert len(matches) == 2
Contract rule: if uniqueness matters, prove it. A singular API call does not prove that only one node matched; it proves that Selenium returned the first match.

5. Stable identity: production semantics and dedicated test attributes

The strongest locator is usually one backed by an explicit product contract. A stable id, semantic name, accessible label, or domain attribute such as data-sku may already express durable meaning. Teams may also define dedicated attributes such as data-testid when production semantics are insufficient or too volatile.

A test attribute is not automatically superior. It becomes valuable when the UI team treats it as a reviewed contract rather than disposable test-only markup. Conversely, a CSS class created by a build tool for styling is a poor test contract even if it looks unique today.

<!-- Durable identity contracts -->
<a id="checkout" href="/checkout.html">Checkout</a>
<article data-testid="product-card" data-sku="SKU-42">
  <button data-action="add" aria-label="Add Beacon to cart">Add</button>
</article>

<!-- Incidental styling/build output: do not promote this to a test contract -->
<button class="Button_root__x7F9a primary mt-2">Add</button>

6. Scope searches to the component that owns the behavior

A global selector often becomes complicated because it must distinguish one component from every similar component on the page. A component-scoped search turns the owning element into a new search context. The outer locator establishes domain identity; the inner locator describes the action within that component.

card = driver.find_element(
    By.CSS_SELECTOR,
    '[data-testid="product-card"][data-sku="SKU-42"]',
)
add_button = card.find_element(By.CSS_SELECTOR, '[data-action="add"]')
print(add_button.get_attribute("aria-label"))

This also improves diagnostics. If the product card is missing, the failure is at the domain-identity layer. If the card exists but its add action is missing, the failure is inside that component. One clever global selector would blur those two causes.

7. Relative locators are geometry, not identity

Relative locators ask Selenium to find an element spatially above, below, near, to the left of, or to the right of another element. In current Selenium Python, construct them with locate_with(...). The older with_tag_name(...) helper is deprecated in 4.47.0.

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

save = driver.find_element(
    locate_with(By.TAG_NAME, "button").to_right_of((By.ID, "cancel"))
)
print(save.text)

This is useful when the visual relationship is itself the test intent and stable identity is unavailable. It is also more layout-sensitive: responsive CSS can stack controls vertically, translations can change size, and viewport differences can change geometry without changing semantics. Prefer stable identity when you have it; use relative position deliberately, not as a replacement for an explicit contract.

8. Read-only state inspection before changing selectors

Before “fixing” a locator, prove the environment and page state. The following inspection reads session identity, browser capabilities, URL, title, a fixture build marker, and candidate match counts. It does not click, submit, alter storage, or reset data.

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

print("selenium", selenium.__version__)
print("session", driver.session_id)
print("browser", driver.capabilities.get("browserName"), driver.capabilities.get("browserVersion"))
print("url", driver.current_url)
print("title", driver.title)
print("fixture", driver.find_element(By.ID, "build-marker").get_attribute("data-build"))
print("checkout-id-count", len(driver.find_elements(By.ID, "checkout")))
print("checkout-testid-count", len(driver.find_elements(By.CSS_SELECTOR, '[data-testid="checkout"]')))

If the URL or fixture marker is wrong, changing the selector is the wrong first move. If the correct page is loaded but the match count is zero, inspect the DOM and context. If the count is greater than one, the locator contract is ambiguous.

9. Locator quality is CI signal quality

A browser test becomes release evidence only when its failure semantics are interpretable. Stable locator contracts reduce false negatives, scoped searches make incident ownership clearer, and explicit uniqueness checks reveal ambiguous markup before it becomes intermittent pipeline behavior. Locator review therefore belongs in the same engineering conversation as API compatibility and schema contracts.

The next lesson turns this model into a disposable local workflow and records concrete evidence for each locator choice.

Knowledge check

Why can a passing find_element call still hide a locator defect?

What is gained by searching from a product-card WebElement instead of the whole document?

When is XPath a reasonable choice?

Why is a generated CSS class risky even if it is unique today?

What special risk comes with relative locators?

A selector fails in CI. What should you confirm before editing it?

Next concept

Guided Hands-On Workflow

Create the loopback fixture, inspect it, build ID/CSS/XPath/scoped/relative locators progressively, and verify exactly what each search returns.

Official references and version notes

Version and compatibility note

Version-sensitive behavior was rechecked against current Selenium primary documentation on 2026-08-28. The mandatory examples pin Selenium Python 4.47.0, use Python 3.10+, a supported locally installed Chromium-family browser, Selenium Manager for ordinary local driver resolution, and loopback-only fixtures. Grid and WebDriver BiDi are not required in this chapter; remote execution uses the same locator semantics but has a distinct session/context and transport boundary.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.