Choosing the Right Locator Strategy in Playwright for Test Stability
Learn how to choose between Role, Text, and Test-ID locators in Playwright to reduce test flakiness and improve accessibility auditing.
01 Aug 2025, 12:33 UTC

The Fragility Problem in End-to-End Testing
The most common cause of "flaky" tests is the locator. When a test relies on a CSS class or a specific DOM hierarchy, a minor UI redesign or a styling update can break the test suite, even if the application's functionality remains intact. To build a maintainable suite, you must choose locators based on how the element is identified by the user or the accessibility tree, rather than how it is structured in the HTML.
Comparing Playwright Locator Strategies
Playwright provides several ways to find elements. The following table compares the most common strategies based on stability and intent.
| Locator Method | Primary Target | Stability | Best Use Case |
|---|---|---|---|
getByRole() |
ARIA roles (button, heading) | High | Interactive elements and accessibility validation. |
getByTestId() |
data-testid attribute |
Highest | Complex components where text or roles change frequently. |
getByText() |
Visible inner text | Medium | Static labels or unique confirmation messages. |
locator('css/xpath') |
DOM structure/classes | Low | Last resort for elements with no accessible attributes. |
Evaluating Trade-offs
User-Centric vs. Developer-Centric: getByRole and getByText simulate how a human interacts with the page. If a screen reader cannot find a button, your test will fail, which provides an implicit accessibility audit. However, these are vulnerable to internationalization (i18n) changes; if the "Submit" button becomes "Enviar," a text-based locator will break.
The Stability of Test IDs: getByTestId decouples the test from the UI. By adding a data-testid="submit-button" attribute to an element, you create a contract between the developer and the tester. The CSS can change from blue to red, and the text can change from English to Spanish, but the test remains green.
The Risk of CSS and XPath: Avoid auto-generated XPaths (e.g., /html/body/div[1]/div[2]/form/button). These are extremely fragile; adding a single wrapper div for styling purposes will invalidate the entire path.
Implementation Example: Stable vs. Fragile
Consider a login form. Below is a comparison of a fragile implementation versus a stable one using Playwright (v1.40+).
Fragile Approach:// ❌ Avoid: Relies on CSS classes and DOM position
await page.locator('.btn-primary').click();
await page.locator('div > form > input:nth-child(2)').fill('password123');
Stable Approach:
// ✅ Recommended: Relies on roles and dedicated test IDs
// Find by ARIA role (accessible name)
await page.getByRole('button', { name: 'Log in' }).click();
// Find by dedicated test ID for the password field
await page.getByTestId('login-password-input').fill('password123');
Configuration and Validation
To use getByTestId, Playwright looks for the data-testid attribute by default. If your project uses a different attribute (like data-cy or data-qa), configure it in the playwright.config.ts file:
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
testIdAttribute: 'data-cy', // Changes the target attribute for getByTestId()
},
});
Verifying Locator Resilience
To verify that your locator strategy is working as intended, perform these three checks:
- The Style Test: Change a CSS class name in your frontend code. If your test uses
getByRoleorgetByTestId, it should still pass. If it fails, you are relying on styling. - The Accessibility Check: Open Browser DevTools, go to the "Accessibility" tab, and ensure the element you are targeting with
getByRolehas the expected role (e.g.,button) and name. - The Codegen Audit: Run
npx playwright codegen [url]. Observe the locators Playwright suggests. The framework prioritizes role-based locators; if it suggests a complex CSS selector, it is a sign that the element needs a better ARIA role or adata-testid.
Limitations
While getByTestId is the most stable, overusing it can lead to "test-only" attributes cluttering the production DOM. Balance this by using getByRole for standard interactive elements to ensure the application remains accessible to all users.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.