Anchoring to Stable Context
The technique behind every robust locator: start from a landmark that will not move, then walk a short, meaningful path to the target.
Anchoring to Stable Context
Every locator has an anchor (where the search starts) and a path (how it gets from the anchor to the target). Fragile locators have a weak anchor (/html/body) or a long path (div/div/div[2]/span). Robust locators have a strong anchor and a short path. This lesson is about choosing anchors.
What makes a good anchor
Ranked from best to worst:
- Test ids on a container:
//*[@data-testid='checkout-summary'] - Landmark roles:
//*[@role='dialog'],//nav,//main,//header,//footer,//aside,//form - Headings:
//h2[normalize-space()='Billing address']and the section around it - Semantic elements with meaning:
//table,//article,//fieldset[legend[...]] - Stable ids or names:
//form[@id='login'],//select[@name='country'] - Text:
//*[normalize-space()='Order #10422']
Layout containers (div.row, div.col-md-6, div.wrapper) are not anchors. They exist for CSS and change whenever the design does.
The pattern
ANCHOR // short path to TARGET
//*[@role='dialog']//button[normalize-space()='Confirm']
//form[@id='login']//input[@name='password']
//*[@data-testid='cart']//tr[td[normalize-space()='USB-C Hub']]//button[@aria-label='Remove']
//section[h2[normalize-space()='Shipping']]//input[@name='postcode']
Two hops is typical. Three is fine. If you need five, the anchor is wrong.
Anchoring on headings
Headings are written by humans, reviewed by product, and rarely change without a reason. The section containing a heading is usually its parent or an ancestor with a semantic tag:
//h2[normalize-space()='Payment']/ancestor::section[1]//input[@name='cardNumber']
//h2[normalize-space()='Payment']/following::input[@name='cardNumber'][1]
//section[h2[normalize-space()='Payment']]//input[@name='cardNumber']
The following:: form is the most tolerant of structure: it does not care whether the input is inside the same section, only that it comes after the heading. The [1] limits it to the nearest one.
Anchoring on dialogs and overlays
Modals duplicate page controls: the page has a Save button and so does the dialog. Anchor on the dialog:
//*[@role='dialog']//button[normalize-space()='Save']
//*[@role='dialog'][.//h2[normalize-space()='Edit profile']]//button[normalize-space()='Save']
//*[@aria-modal='true']//button[@type='submit']
Without the anchor, //button[normalize-space()='Save'] returns both and your click lands on the hidden one.
Anchoring on navigation
//nav[@aria-label='Main']//a[normalize-space()='Pricing']
//header//a[normalize-space()='Sign in']
//footer//a[normalize-space()='Privacy']
Sites often repeat links in header, footer and sidebar. The landmark makes the intent explicit.
Anchoring on a row or card
Covered in depth in Lists and Repeated Components and Working with Tables. The anchor is the unit, identified by its own content:
//tr[td[normalize-space()='Jane Doe']]//button[normalize-space()='Edit']
//article[.//h3[normalize-space()='Pro plan']]//a[normalize-space()='Choose']
Keep the path short and semantic
Each step in the path should mean something. Compare:
//div[@id=‘app’]/div/div[2]/main/div/form/div[3]/div/inputEight steps, seven of them layout. Any of them can change.
//main//form[@id=‘profile’]//input[@name=‘displayName’]Three steps, each a real thing. Layout can change freely.
Use // between steps so intermediate wrappers can come and go. Use / only when “direct child” is part of the meaning, as in table/thead/tr.
Measuring robustness
Ask three questions of any locator:
- If a wrapper div is added or removed anywhere, does it still work? (
//between steps) - If the item order changes, does it still work? (no indexes, or indexes only where order is the point)
- If the styling library is replaced, does it still work? (no utility or generated classes)
Three yeses is stable. The playground’s robustness score applies similar rules automatically.
Try It Yourself
Open in Playground →
Remove the //*[@role='dialog'] anchor and watch the match count double.
Next Steps
- Lists and Repeated Components - Content-based anchors
- Dynamic IDs and Generated Classes - What to avoid anchoring on
- Locator Strategy in 2026 - How this fits with role and test-id locators