Testing Guide

Playwright Locators and getByRole: Building Resilient Tests

getByRole is not just another locator method. It fundamentally changes how you think about targeting elements by forcing your tests to reflect how users actually interact with your application, not how your DOM happens to be structured.

70%

After switching to role-based locators as the default strategy, teams report approximately 70% fewer tests breaking during UI redesigns compared to CSS selector or data-testid approaches.

Locator strategy analysis

1. The Evolution of Locator Strategies

Test locator strategies have evolved through several generations. The first generation used CSS selectors and XPath expressions to target elements by their position in the DOM tree. This approach is precise but extraordinarily fragile. A single wrapper div added by a CSS refactor can break dozens of XPath selectors. A renamed class in a design system update can cascade into test failures across the entire suite.

The second generation introduced data-testid attributes. The idea was sound: decouple test selectors from styling and structure by adding purpose-built attributes that exist solely for testing. This was a major improvement over CSS selectors because data-testid values are intentional and survive style changes. But they still break when components are refactored, when elements are replaced with different implementations, or when the testid naming conventions change across teams.

The third generation is role-based locators, embodied by Playwright's getByRole(), getByText(), and getByLabel() methods. These locators target elements by their semantic function: "the button named Submit," "the textbox labeled Email," "the navigation region." Because they target the element's purpose rather than its implementation, they survive structural changes that would break CSS selectors and even many data-testid approaches.

2. How getByRole Changes Your Mindset

The most important effect of adopting getByRole is not technical. It is psychological. When you write page.getByRole('button', { name: 'Add to cart' }), you are describing the element the way a user would describe it: "the Add to Cart button." When you write page.locator('[data-testid="add-to-cart-btn"]') , you are describing an implementation detail that means nothing to the user.

This shift in perspective has cascading effects on test quality. When you think about elements in terms of their user-facing role, you naturally write tests that verify user-facing behavior. Instead of checking that a specific div has a specific class, you check that a button is visible and clickable. Instead of verifying that a text node exists at a specific DOM position, you verify that a heading with specific content is present. The tests become descriptions of user expectations rather than descriptions of DOM structure.

This mindset also catches accessibility bugs during test authoring. If you try to write getByRole('button') for an element that is actually a styled div with an onClick handler, the locator will not find it. This immediately reveals that the element is not accessible to screen readers either. The act of writing tests with role-based locators becomes a passive accessibility audit for every page your tests touch.

Teams that adopt this mindset report that their test suites become easier to read, easier to maintain, and more valuable as documentation of expected behavior. A new team member can read a test file using getByRole locators and understand the user flow without ever looking at the application's HTML source.

Try Assrt for free

Open-source AI testing framework. No signup required.

Get Started

3. The Resilience Data: 70% Fewer Breakages

The claim that role-based locators reduce UI redesign breakage by approximately 70% comes from teams that tracked their test failure rates before and after migrating their locator strategy. The pattern is consistent across different application types and team sizes. The reason is straightforward: most UI redesigns change the structure and styling of elements while preserving their semantic purpose.

Consider a common redesign scenario: converting a traditional navigation bar into a hamburger menu on mobile. With CSS selectors, every test that clicks a nav link breaks because the elements are now inside a mobile menu drawer instead of a visible nav bar. With data-testid, the tests might survive if the testid values were preserved, but often they are not because the component is rewritten. With getByRole, the test that clicks getByRole('link', { name: 'Products' }) continues to work because the link still exists with the same name, just in a different container.

The 30% of breakages that still occur with role-based locators come from genuine functional changes. When a button is removed or renamed, when a form field changes its label, or when a navigation link is restructured, the role-based locator correctly fails because the element the test expects no longer exists in the same semantic form. These are failures you want to see because they represent actual changes to user-facing behavior that need test updates.

This means role-based locators have a higher signal-to-noise ratio than other strategies. When a test with getByRole breaks, there is a much higher probability that the failure represents a real change worth investigating. When a test with CSS selectors breaks, the most likely cause is a styling refactor that did not change any functionality. Reducing noise in test failures saves significant triage time across the team.

4. data-testid as Fallback, Not First Choice

data-testid is not a bad practice. It is a fallback for situations where role-based locators are insufficient. The Playwright documentation itself recommends using getByRole as the primary strategy and falling back to other methods when necessary. The problem arises when teams use data-testid as their default because it is familiar and straightforward, without first attempting a role-based approach.

Legitimate use cases for data-testid include: elements that have no meaningful ARIA role (a generic container that groups related content), elements that share the same role and name on a page (multiple "Delete" buttons in a list where each needs distinct targeting), and third-party components that do not expose proper accessibility attributes. In these cases, data-testid provides a stable, intentional selector that is better than falling back to CSS.

The recommended priority for Playwright locators is: getByRole first, then getByText or getByLabel for form elements, then getByTestId as a fallback, and CSS/XPath selectors only as a last resort. This priority ensures that your tests are as resilient as possible while still allowing you to target any element when the semantic approach falls short.

A practical rule of thumb: if more than 30% of your locators use data-testid, your application likely has accessibility issues worth investigating. Well-structured HTML with proper ARIA roles, labels, and landmarks should make role-based locators viable for the majority of interactive elements. When role-based locators do not work, the underlying HTML is often inaccessible to assistive technologies as well, making the locator limitation a useful diagnostic signal.

5. Accessibility Benefits of Role-Based Testing

The connection between role-based locators and accessibility is not incidental. getByRole queries the same accessibility tree that screen readers use. When your test can find and interact with an element via getByRole, it means a screen reader user can find and interact with that element too. When getByRole cannot find an element, it often means the element is invisible to assistive technologies.

This creates a powerful feedback loop. Every test you write with getByRole becomes a lightweight accessibility check. Over time, as you cover more pages with role-based tests, you build an implicit accessibility test suite that runs on every CI build. Developers learn to add proper ARIA attributes to make their elements testable, which simultaneously makes them accessible. The incentive alignment is natural: developers want tests to work, and making tests work with getByRole requires accessible HTML.

Some teams have formalized this relationship by requiring all new test locators to use getByRole unless a documented exception applies. The exception process forces developers to justify why a role-based locator cannot work, which often leads to fixing the underlying accessibility issue rather than accepting the data-testid workaround.

Assrt generates tests that use role-based locators by default, falling back to other strategies only when the accessibility tree does not provide sufficient information to uniquely identify an element. This means the generated test suite doubles as an accessibility baseline, flagging elements that lack proper roles and labels as part of the normal test generation process.

6. Migrating Your Selectors to Role-Based Locators

Migrating an existing test suite from CSS selectors or data-testid to role-based locators is best done incrementally. Attempting a big-bang migration of hundreds of locators is risky and time-consuming. Instead, adopt a policy of updating locators to role-based alternatives whenever you touch a test file for any reason. Over the course of a few months, the majority of actively maintained tests will migrate naturally.

For each locator you migrate, the process is: identify the current selector, determine the element's ARIA role and accessible name, write the getByRole equivalent, and verify that it uniquely identifies the intended element. The Playwright Inspector (launched with npx playwright codegen) can suggest role-based locators interactively, which accelerates the migration.

When you encounter elements that cannot be targeted with getByRole, decide whether the right fix is to add a data-testid or to improve the element's HTML semantics. If the element is a clickable div, converting it to a button element solves both the locator problem and the accessibility issue. If the element is a custom component without ARIA attributes, adding role and aria-label attributes makes it targetable and accessible.

Track your migration progress by counting locator types across your test suite. A simple script that parses test files and counts getByRole, getByTestId, and CSS selector usage gives you a dashboard of migration status. Target having at least 60% to 70% of your locators use role-based methods. The remaining 30% to 40% using data-testid or other methods is normal for applications with complex custom components or third-party integrations.

Ready to automate your testing?

Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.

$npm install @assrt/sdk