Playwright locators

tldr: Locators are the single largest source of flaky tests in Playwright. The framework gives you better tools than any predecessor. Role-based selectors, text-based locators, auto-waiting, and web-first assertions. They reduce the flake tax. They do not eliminate it. The locator strategy your team picks on day one quietly determines how much time you will spend on test maintenance forever.


Every Playwright test does three things. Find an element. Act on it. Assert something is true. The find step is where almost all the value and almost all the pain lives.

A test that uses a brittle selector will break the next time a designer changes a class name or an engineer reorders a div. Multiply that across a thousand tests and a year of product changes and you have a maintenance treadmill that quietly consumes a senior engineer's time.

This is the strategic view of Playwright locators. Not a syntax reference. The decisions that determine whether your test suite ages well or rots.

What a locator actually is in Playwright

In older browser-automation tools, a selector was a string that resolved to a DOM node once. If the DOM changed before you acted, the reference was stale and the test failed.

In Playwright, a locator is a description of how to find an element, not a reference to the element itself. The locator is resolved fresh every time you act on it. Between two lines of the same test, the DOM can re-render, the React tree can swap nodes, and the locator still finds the right element on the next interaction.

This is the single most important architectural detail in Playwright's testing model. It is the reason the framework feels less flaky than its predecessors, and it is the reason modern locator strategies work at all.

The locator strategies Playwright gives you

Playwright supports six common ways to find elements, in roughly the order you should reach for them.

The first is getByRole. It finds elements by their ARIA role, the same way assistive technology and accessibility tooling does. getByRole('button', { name: 'Submit' }). This is the recommended default.

The second is getByLabel for form inputs. It finds the input associated with a given label, the way a user would visually pair them.

The third is getByPlaceholder, getByText, and getByTitle for elements identified by their visible content. These are user-facing properties, which means they are stable as long as the user-visible UI is stable.

The fourth is getByTestId. A data attribute (data-testid by convention) that the application code explicitly exposes for tests. Brittle to no one because nothing else in the app depends on it.

The fifth is CSS selectors. .submit-button or [type="submit"]. They work and you will use them occasionally. They are the most likely to break when classes or attributes change.

The sixth is XPath. Avoid it unless you have no other option. XPath selectors are the most brittle, the hardest to read, and the slowest to evaluate.

Playwright's recommendation, and ours, is to default to getByRole and the other user-facing locators.

Two reasons. First, they map to what the user actually sees and interacts with. A test that finds the "Submit" button by its role and accessible name will keep working as long as the user can still find the Submit button. That is the right semantic anchor for a test.

Second, writing tests this way is how you discover accessibility bugs. If getByRole('button', { name: 'Submit' }) cannot find your submit button, neither can a screen reader. Test code that doubles as accessibility tooling is rare, and locators are the place Playwright delivers it.

The data-testid debate

data-testid is the second-best locator strategy and a fierce religious debate in some teams.

The argument for: testids are explicit. They never change accidentally. They survive arbitrary refactoring. They are the most stable locator your application can produce.

The argument against: they are dead weight in production HTML, they encourage tests that do not catch accessibility regressions, and they hide the fact that the component is not properly accessible by giving you a back door around it.

The right answer for most teams is a hybrid. Default to getByRole and the user-facing locators. Reach for getByTestId when the role-based query is genuinely ambiguous, when the component is intentionally not user-facing (a wrapper div, a developer-only widget), or when the team has a stable testid convention that survives refactors.

Avoid the third pattern, which is getByTestId on everything as a matter of policy. That pattern works in the short term and quietly degrades the suite's value as accessibility coverage forever.

Locators and the auto-waiting model

Locators are how you specify what to find. Auto-waiting is how Playwright decides when to act.

Every action on a locator (click, fill, check) waits for a set of "actionability" conditions before firing. The element has to be attached to the DOM, visible, stable (not animating), and enabled. If those conditions are not met within the configured timeout, the action fails with a clear error.

Web-first assertions extend the same pattern. expect(locator).toBeVisible() retries until the locator is visible or the timeout expires. You almost never need a manual waitForSelector or setTimeout in Playwright. The framework is doing it for you.

The locator strategy and the auto-waiting model only work together. A test that uses brittle locators will fail under auto-waiting in confusing ways, because the locator will time out trying to find something that has since moved or been renamed. Picking stable locators is a precondition for getting the full value of Playwright's flake-reduction features.

The flake tax is the right thing to fix

Most "Playwright is flaky" complaints are locator complaints.

A test that uses .btn-primary will fail the day a designer renames the class. A test that uses xpath=//div[3]/button[2] will fail the day someone adds a new component above it. A test that uses getByRole('button', { name: 'Submit' }) will keep working until the day someone removes the Submit button entirely, which is exactly what a test should care about.

The right time to invest in locator strategy is at the start, before the suite is large enough that fixing it is expensive. Teams that wait until they have a thousand brittle tests rarely catch up. This is the same dynamic we describe in our broader take on test optimization.

An alternative path with Bug0

Locator strategy is one of the most consequential things a QA team owns and one of the easiest to get wrong. The right policy, applied from day one, is the difference between a test suite engineers trust and a treadmill they quietly stop running.

Bug0 does this work for you. Our AI agents generate Playwright tests with role-based locators by default, maintain them as the application changes, and verify every result through a human expert layer before it reaches your engineers. When a locator drifts, the AI updates it. When the AI is wrong, the human catches it. The framework underneath is still Playwright. The locator-maintenance treadmill is not yours.

Conclusion

Locators are the foundation of every Playwright test. The framework gives you the right defaults. Whether your team uses them is a discipline question, not a tooling question.

Pick a stable strategy on day one. Default to role-based and user-facing locators. Use testids sparingly. Avoid XPath. The flake tax will still exist, but it will be a tax you can pay rather than one that compounds quietly into a year of lost engineering time.

FAQs

What is a locator in Playwright?

A locator is a description of how to find an element, evaluated fresh every time you act on it. Unlike older selectors that returned stale references, a Playwright locator survives DOM re-renders. This is the architectural foundation for Playwright's auto-waiting and flake-reduction features.

What is the best locator strategy in Playwright?

Default to role-based locators (getByRole, getByLabel, getByText) for user-facing elements. Use getByTestId for non-accessible or genuinely ambiguous cases. Avoid CSS class-based selectors and XPath unless nothing else works.

Should I use data-testid in Playwright tests?

Use it sparingly. It is the second-best strategy after role-based locators, useful for wrappers and developer-only components, but it bypasses accessibility coverage. Default to getByRole and reach for testids when the role-based query is ambiguous.

What is the difference between locator and selector in Playwright?

A selector is a string. A locator is an object that re-resolves the selector every time you act on it. Locators are the modern API and almost always the right choice. The string-based page.click('css=...') style is older and more flake-prone.

Why are my Playwright locators failing intermittently?

Almost always because the locator is matching the wrong element when the page is in a transient state, or because the element is briefly hidden or animating. Use role-based locators with accessible names, add visibility assertions, and check that no other element with the same role is on the page during transitions.

Can Playwright handle dynamic class names from CSS-in-JS?

Yes, by avoiding class-based selectors entirely. Use role-based or text-based locators, which are immune to hashed class names. This is one of the strongest arguments for the user-facing locator strategy.

How does Bug0 handle locator maintenance?

Bug0's AI agents generate role-based locators by default and update them automatically when the application changes. Every change is verified by a human QA expert before it reaches your team. You stop owning the locator-maintenance treadmill.

Ship every deploy with confidence.

Bug0 gives you a dedicated AI QA engineer that tests every critical flow, on every PR, with zero test code to maintain. 200+ engineering teams already made the switch.

From $2,500/mo. Full coverage in 7 days.

Go on vacation. Bug0 never sleeps. - Your AI QA engineer runs 24/7

Go on vacation.
Bug0 never sleeps.

Your AI QA engineer runs 24/7 — on every commit, every deploy, every schedule. Full coverage while you're off the grid.