Chapter 04Lesson 03~135 minutes

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.

Locator contractTrade-offsTest IDsPortabilityCI reliability

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
Privacy note: locator attributes should not embed secrets, emails, account IDs, tokens, or other sensitive production data merely to make automation easier. Synthetic identifiers are enough for test fixtures.

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.

Review heuristic: if the selector explanation is longer than the product concept it identifies, first ask whether the AUT needs a better stable attribute or a narrower component scope.

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?

Why is a deep selector a maintenance problem even if it is unique?

What is the main diagnostic benefit of component scoping?

Should relative locators replace a stable ID?

A team says “always use XPath because it is powerful.” What is missing?

Next concept

Diagnostics, Failure Modes, and Production Practices

Use this policy to distinguish wrong context, timing, ambiguity, generated identifiers, localization changes, and layout changes instead of patching every failure with a new selector.

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.