Yesterday I asked you to think about a failing test nobody could explain, and the explanation usually starts here, with how the test finds things. A locator is how your test points at a thing on the page. When a test says “click Add to order”, the framework needs a way to pick that exact button out of the thousands of elements on the page. Suites live or die on whether those pointers still find the right thing after the designer restyles everything.
The kinds you will meet, worst to best
- XPath by position, like //div[3]/button[2]. The moment somebody adds a div, the test breaks. Avoid.
- Deep CSS chains, like .grid > .card:nth-child(2) > button. Tied to layout, breaks with it.
- Class names, like .btn-primary. Design systems rename these constantly.
- IDs, like #login-button. Fine when they exist and are stable. Many apps have neither.
- Role and accessible name, like getByRole(‘button’, { name: ‘Add to order’ }). It matches how a user, and a screen reader, finds the button, so your test doubles as an accessibility check.
- Test IDs, like data-testid=“add-to-order”. An agreed name between the dev and the test. The design can change completely and the pointer holds.
Here is the same Tumble Kitchen button, located three ways, so you can feel the difference:
// Fragile: breaks when the layout changes
page.locator('//ul/li[2]/button')
// Good: matches what the user sees
page.getByRole('button', { name: 'Add to order' })
// Most stable: an agreed name design cannot touch
page.getByTestId('add-to-order')
Locators break because they encode how the page is built. Good locators encode what the user is looking for. Write them the way a user would describe the thing, not the way the developer wrote the div. If you want the long history of these trade-offs, my introduction to element locator strategies is where the series on my blog starts.
Habits that save you weeks
- Ask the devs for test IDs on the elements you interact with. Most are happy to add them; it is a one-line change on their side.
- When a locator breaks, do not just patch it. Ask why. Was it a refactor (the developer tidied code without changing behaviour), or did the behaviour actually change? One answer means update the pointer; the other means you may have caught a bug.
Do this today
Two exercises, twenty minutes:
- Open Tumble Kitchen, right-click the search box, and choose Inspect. Write down every locator that could point at it: its position, its id, its label, its role. Decide which one survives a redesign. Do the same for one Add to order button and the cart total.
- Then open my Element Locator Cheat Sheet. Beyond the reference tables, it has a playground with 34 locator challenges against live mini-apps: you type a selector, see what it matches, and get graded on whether it would survive change. Do the first tier today. That is deliberate practice, and it is worth more than any amount of reading.
Tomorrow you install the tool that will use what you just wrote.
