Locators: ID, CSS, XPath, Relative Locators, and Resilient Selection: Configuration, Design Patterns, and Trade-Offs
Choose locator contracts deliberately by comparing test IDs, semantic attributes, IDs, CSS, XPath, scoped searches, readable selectors, and geometry-based relative locators across maintainability and CI trade-offs.
Learning objectives
- Choose between production semantics and dedicated test attributes without treating either as universally correct.
- Compare ID, CSS, XPath, and relative locators by readability, stability, portability, diagnostic quality, and cost.
- Prefer scoped component searches over globally clever selectors when they better express ownership and uniqueness.
- Separate Selenium locator configuration from AUT markup contracts, browser policy, framework lifecycle, and CI settings.
- Use a decision table to justify locator choices with observable behavior rather than selector fashion.
- Document a locator contract that a UI team and test team can review when markup evolves.
1. From selectors that work to locator contracts that survive
Lesson 2 proved that several locator forms can target the same control. Production engineering begins when the team decides which of those forms is allowed to be stable. A locator policy is not a list of favorite APIs; it is an agreement between tests and the UI about identity, scope, localization, component ownership, and permitted markup refactoring.
Treat locator changes like API changes at the test boundary. If a UI refactor preserves behavior but breaks dozens of tests, either the refactor violated an intentional locator contract or the tests were coupled to details that were never promised.
2. Dedicated test attributes versus production semantics
Production semantics can be excellent locator inputs: stable IDs,
form names, domain keys, accessible labels, and durable navigation
targets. They keep tests close to user-visible behavior. Dedicated
attributes such as data-testid create a clearer
test-only contract and can be especially useful for component roots
or controls whose visual text is localized.
| Choice | Strength | Risk | Good governance |
|---|---|---|---|
| Stable production ID/attribute | No separate test namespace; often clear identity | May be owned by unrelated code or generated by framework | Document which IDs/attributes are API-like and stable |
| Accessible/semantic attribute | Aligns tests with user-facing semantics | Accessible name or content may legitimately change/localize | Use when the semantic text itself is part of the requirement |
Dedicated data-testid |
Explicit automation contract, localization-resistant | Can become meaningless test litter if unmanaged | Name by domain/component intent and review removals |
| Styling class | Convenient when already present | Presentation/build lifecycle; often generated or reused | Do not treat as stable unless explicitly promoted to contract |
3. ID versus CSS versus XPath
When a unique predictable ID exists, official Selenium guidance recommends it. CSS is usually the next practical choice because it is compact, readable, and naturally expresses attributes and descendant scope. XPath is appropriate when its relationship model adds clarity—for example, locating a control relative to a domain-identified ancestor or expressing text/structure that CSS cannot state directly.
| Question | Prefer | Reason |
|---|---|---|
| One durable unique ID? | ID | Shortest direct identity contract |
| Attribute-based component/action contract? | CSS | Compact and readable |
| Need meaningful ancestor/descendant or text relationship? | XPath | Relationship can be explicit when CSS is awkward/impossible |
| Need “third wrapper under second panel”? | Neither—improve the contract | DOM position is incidental implementation detail |
| Need visual position relative to another control? | Relative locator only if geometry is the requirement | Rendered layout, not semantic identity, is the contract |
Do not reduce this to performance folklore. On modern pages, maintainability and diagnosability usually dominate micro-optimizing selector evaluation. Narrow search scope and readable contracts improve both engineering cost and the amount of DOM work the browser must perform.
4. Compact selectors versus deep traversal
The following example makes the Compact selectors versus deep traversal behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
Prefer: [data-testid="product-card"][data-sku="SKU-42"]
Then: card.find_element([data-action="add"])
Avoid: main > div:nth-child(2) > section > article:nth-child(1) > div.actions > button.primary
Avoid: /html/body/main/div[2]/section/article[1]/div[3]/button[1]
The compact/scoped form names the domain object and action. The deep forms encode wrapper count, sibling position, CSS class, and nesting depth. Deep traversal can pass for months and then fail across an innocuous design-system update, creating CI noise with no business regression.
5. Global search versus scoped component search
Global search is appropriate for truly page-global controls: one checkout link, one primary heading, one application shell. Repeated components are different. A product card, row, dialog, or table body should often become a search context before locating its internal controls.
PRODUCT = '[data-testid="product-card"][data-sku="SKU-42"]'
ADD = '[data-action="add"]'
card = driver.find_element(By.CSS_SELECTOR, PRODUCT)
add = card.find_element(By.CSS_SELECTOR, ADD)
This pattern also gives future page/component abstractions a natural boundary without forcing Chapter 13’s full Page Object discussion into locator code. The locator contract stays about identity and scope; framework lifecycle and architecture remain separate concerns.
6. Readable locators beat clever selectors
A selector can be technically concise and still obscure intent. Regex-like XPath expressions, positional pseudo-classes, chained sibling tricks, and selectors that depend on several unrelated classes increase the cognitive load of every failure. Review locators as code: a maintainer should be able to answer “what behavior or identity is this promising?” without reverse-engineering the entire DOM.
7. Relative position versus stable identity
Relative locators can improve readability when the relationship
“input below this label” or “button right of Cancel” is actually
meaningful and stable. They are a poor substitute for an existing
data-testid="save-settings". Geometry adds dependencies
on viewport, fonts, translations, zoom, responsive breakpoints, and
browser layout.
In Selenium Python 4.47.0, use locate_with. Treat the
relative relationship as a current binding/browser capability and
verify behavior on the browsers/viewports that matter if geometry is
part of the test contract.
8. Keep locator policy separate from neighboring configuration domains
The following table organizes the key choices and evidence for Keep locator policy separate from neighboring configuration domains. Use it together with the surrounding prose so the rows serve as a comparison aid rather than standalone rules.
| Domain | Example | Why it is separate |
|---|---|---|
| Selenium locator | By.CSS_SELECTOR + selector value |
How this session asks the browser to find a node |
| Test framework | pytest fixture lifetime | When sessions/tests are created and destroyed |
| AUT contract | data-testid="checkout" |
Markup identity the product promises to expose |
| Browser policy/profile | extensions, download policy | Browser environment, not locator syntax |
| Proxy/TLS/identity | corporate proxy, certificate trust, SSO | Network/security boundary |
| CI/container/cloud | runner image, browser container, Grid | Execution infrastructure |
Keeping these domains separate improves diagnosis. A CSS selector should not be rewritten because a proxy blocks the page, and a test-framework retry should not hide an ambiguous locator.
9. Worked decision: checkout flow across desktop and localized UI
Scenario: the checkout control appears on desktop and mobile, its
visible text is localized, the app team guarantees a stable
id="checkout", and CSS layout differs by viewport.
Which locator should be the primary contract?
| Candidate | Desktop | Mobile | Localization | Decision |
|---|---|---|---|---|
By.ID, "checkout" |
Stable | Stable | Independent of text | Primary |
By.LINK_TEXT, "Checkout" |
Works in English | Works if same text | Breaks by locale | Use only if English text is the requirement |
| Deep CSS path | Matches current layout | Likely changes | Independent of text | Reject: layout coupling |
| Relative to cart count | May work | Geometry can move | Independent of text | Reject unless geometry is the requirement |
The decision is justified by observable contracts: the same ID survives both viewports and locales. This is more useful than an abstract rule that “ID is always best.” If the ID becomes generated, the evidence changes and the decision should be revisited.
10. A small locator policy you can actually enforce
The following example makes the A small locator policy you can actually enforce behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.
Locator contract
1. Prefer explicit stable identity: durable ID or reviewed data-testid/data-* contract.
2. For repeated components, locate the component root, then search within it.
3. Prefer compact readable CSS for attribute-based selection.
4. Use XPath when it clearly expresses a real relationship; reject absolute DOM paths.
5. Prove uniqueness when uniqueness is part of the requirement.
6. Treat visible text as a contract only when content/localization is intentionally under test.
7. Use relative locators only when geometry is meaningful and tested across relevant layouts.
8. Never use generated styling classes/IDs unless they are explicitly promoted to a stable contract.
9. Preserve first-failure evidence before changing selectors.
10. Review locator-contract changes with the owning UI/component team.
Knowledge check
When can a dedicated data-testid be better than visible text?
When the control identity must survive localization/content edits and the team explicitly governs the test attribute as a stable contract.
Why is a deep selector a maintenance problem even if it is unique?
Uniqueness says nothing about stability. Deep selectors couple to wrappers, sibling positions, and structure that can change without changing user behavior.
What is the main diagnostic benefit of component scoping?
It separates failure to identify the component from failure to identify a control inside that component, reducing the failing layer.
Should relative locators replace a stable ID?
Usually no. Relative locators encode geometry; a stable ID encodes identity. Use geometry when geometry is the actual requirement.
A team says “always use XPath because it is powerful.” What is missing?
A contract-based decision. Power does not establish stability, readability, scope, localization behavior, or diagnostic quality.
Official references and version notes
- Selenium downloads — current supported-binding and Selenium Server release baseline.
- Locator strategies — current Selenium documentation for the traditional locator set and relative-locator examples.
- Finding web elements — first-match, nested-search, and multiple-element behavior.
- Tips on working with locators — Selenium project guidance on IDs, compact CSS, XPath, readability, and narrowed search scope.
-
Python relative-locator API 4.47.0
— current
locate_with/RelativeByAPI;with_tag_nameis deprecated.
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this
address.