Your locator is a contract, not a coordinate
Generated tests love CSS paths and XPath. Those selectors describe where a button sat on the day the code was written, not what the button is. Here is the vocabulary that survives a redesign.

page.locator('.panel > div:nth-child(3) > button.primary') works today. It works tomorrow. It keeps working until a designer moves the button one slot to the left, and then your suite fails on a feature nobody broke. You did not write a test. You wrote a photograph of the DOM.
What Playwright actually recommends
The Playwright documentation is unusually direct about this. Its locators guide recommends prioritizing user-facing attributes and explicit contracts: getByRole, getByLabel, getByText, getByPlaceholder, getByTestId. Role locators mirror how users and assistive technology perceive the page. Test ids create a separate explicit contract, at the price of being invisible to users. They remain stable only if developers preserve that contract. CSS and XPath selectors are documented as fragile because they are tied to DOM structure, and the DOM structure is what changes.
Two properties of the framework reward the good habit. Locators are re-resolved before every action, so a re-render between two steps does not leave you holding a stale element. And locators are strict: if your selector matches two elements, the action throws instead of silently clicking the first one. A strict failure in CI is annoying for an afternoon. A wrong click in the wrong dialog is a bug report with your name on it.
Why generators reach for the bad habit first
A generated suite can reach for a selector that resolves on the current page without considering what a redesign will change. That is a review risk, not evidence about any particular model's training data. Tests tied to layout can pass on day one and fail after a front-end change for reasons unrelated to product behavior.
The failure is expensive in a specific way: the suite cries wolf on every redesign, the team learns to ignore red builds, and the one real regression sails through a wall of familiar noise.
A fifteen-minute audit for any suite
Grep your tests for page.locator with CSS or XPath strings, and for getByText used on interactive elements. For each hit, ask one question: does this selector name what the element is, or where it sits? Replace coordinates with contracts:
- Buttons and links: getByRole with the accessible name.
- Form fields: getByLabel, or getByPlaceholder as a fallback.
- Repeated or dynamic content: a data-testid agreed with the developers.
- getByText: useful for text content; prefer a role and accessible name when clicking a control.
You will be left with a handful of genuinely ambiguous controls. Those are not a test problem. They are an accessibility problem the tests just found for free.
The strictness trap, and when first() is a confession
Playwright locators are strict: an action on a selector that matches two elements throws a violation instead of guessing. Treat every strict-mode error as a design question. If you need .first() to make the suite pass, one of two things is true: the page renders duplicate controls that should have distinct accessible names, or your selector is too wide. Both are findings, not inconveniences. The fix is to narrow with a named contract, filter by a stable parent, or ask for a test id. Reaching for .nth(1) because the second match happens to be the right one this week is how suites learn to click the wrong dialog after the next release.
There is one legitimate escape hatch. locator.or() exists for the cases where the page honestly shows one of two states, like a login form or an already-signed-in banner. Use it deliberately, with both alternatives named, and it reads as a branch. Use it to paper over ambiguity and it reads as a shrug.
Where agents fit
Let the agent explore and draft; the locators are where your review lives. A generated test with getByRole('button', { name: 'Save' }) tells you what it believes the page does. A generated test with div:nth-child(3) tells you nothing except that the DOM looked like that once. Keep the first kind. Rewrite the second before it reaches CI, because every brittle locator you accept is a future false alarm you have already paid for.
Common questions
What is the most reliable locator strategy in Playwright?
Playwright recommends user-facing attributes and explicit contracts: getByRole, getByLabel, getByText, and data-testid attributes. CSS and XPath selectors are documented as fragile because they couple tests to DOM structure.
Are data-testid attributes better than role locators?
They solve different problems. Test ids survive any visual or structural change but say nothing about what the user sees. Role locators double as a lightweight accessibility check. Most mature suites use roles and labels first, test ids for repeated or generated content.
Why do AI tools generate CSS selectors so often?
A generator may choose a selector that works on the current page without checking its stability under redesign. The reason depends on the tool; audit its output rather than assuming anything about training data.