Playwright Selectors / Locators
Priority order — pick the highest one that works
Playwright's official recommendation: prefer how a human perceives the element. Drop down the list only when you must.
| # | Locator | Targets | Example | Tier |
|---|---|---|---|---|
| 1 | getByRole | Buttons, links, checkboxes, headings, inputs — by their accessibility role + name | getByRole('button', {name:'Subscribe'}) | prefer |
| 2 | getByLabel | Form fields, via their <label> text | getByLabel('Email address') | prefer |
| 3 | getByPlaceholder | Inputs with placeholder text (when no label) | getByPlaceholder('you@example.com') | ok |
| 4 | getByText | Non-interactive content: messages, paragraphs, spans | getByText('You are subscribed') | ok |
| 5 | getByAltText / getByTitle | Images by alt text; elements by title attribute | getByAltText('Company logo') | ok |
| 6 | getByTestId | Anything — via an explicit data-testid you add | getByTestId('signup-submit') | fallback |
| ✗ | CSS / XPath | Raw DOM paths like .card > form > button:nth-child(3) | locator('div.card button') | avoid |
Why this order? Role/label/text mirror what the user actually sees,
so they survive restyling and DOM restructuring. CSS/XPath are tied to structure — a redesign silently breaks them.
data-testid is the resilient escape hatch when nothing user-facing is unique.
Good vs. brittle — same button
✓ Resilient
getByRole('button',
{ name: 'Subscribe' })
Survives moving the button, restyling, wrapping it in new divs.
✗ Brittle
locator(
'div.card > form > button')
Breaks the instant someone adds a wrapper or renames a class.
Adding a test ID (the fallback)
When an element has no unique role/label/text, add an explicit hook in your markup:
<button type="submit" data-testid="signup-submit">Subscribe</button>
…then target it with getByTestId('signup-submit'). It's a contract: "tests
depend on this id — don't rename it casually."
The accessibility connection
Notice the top locators are all accessibility concepts (role, label, alt text).
That's not a coincidence — Playwright reads the same accessibility snapshot Claude uses.
Bonus: writing testable markup (real <label>s, proper button roles) also makes your app more
accessible to screen-reader users. Good tests and good a11y push the same direction.