Choosing Resilient Locators in Playwright: User-Facing vs. Technical Selectors
Learn how to choose between User-Facing, CSS, and XPath locators in Playwright to reduce test flakiness and ensure accessibility compliance.
05 Sept 2026, 21:00 UTC

The Cost of Brittle Selectors
The most common cause of flaky automated tests is the "brittle selector." When tests rely on the internal structure of the HTML (like deep CSS paths or specific div hierarchies), a simple design change—such as wrapping a button in a new container for styling—breaks the test, even though the application's functionality remains intact.
The goal is to decouple the test logic from the implementation details. The most resilient strategy is to locate elements based on their perceived role or visible text, mirroring how a human user identifies an element on the screen.
Comparing Locator Strategies
Playwright provides several ways to find elements. The following table compares the primary strategies based on stability, maintenance, and intent.
| Strategy | Example | Resilience | Primary Use Case |
|---|---|---|---|
| User-Facing | getByRole('button', { name: 'Submit' }) |
High | Standard UI interactions and accessibility validation. |
| CSS Selectors | .submit-btn-primary |
Medium | Targeting specific styles or unique IDs when roles are ambiguous. |
| XPath | //div[2]/span/button |
Low | Complex traversal (e.g., finding a parent based on a child's text). |
Trade-offs and Decision Logic
User-Facing Locators (The Gold Standard)
Locators like getByRole, getByText, and getByLabel interact with the Accessibility Tree rather than the raw DOM. If you use getByRole('button'), Playwright looks for any element with the ARIA role of "button," regardless of whether it is an <button> tag or a <div> with role="button".
Benefit: Your tests act as a continuous accessibility audit. If a test fails because it can't find a "button," it often means the element is missing the correct accessibility markers for screen readers.
CSS and XPath (The Fallbacks)
CSS selectors are fast and familiar, but they are tied to the developer's naming conventions. If a team switches from Bootstrap to Tailwind, class-based selectors will fail globally. XPath is powerful for bidirectional navigation (moving up to a parent element), but it is notoriously verbose and breaks easily when the DOM nesting changes.
Handling Repeated Elements
Avoid using indices like .nth(0). If a new item is prepended to a list, your test will interact with the wrong element. Instead, use the .filter() method to narrow down a list of identical roles by their unique inner text.
Implementation Example: Resilient Table Interaction
Consider a scenario where you must click a "Delete" button for a specific user in a data table. The table structure is dynamic, and classes change during build steps.
// Run this in a Playwright test file (e.g., tests/user-management.spec.ts)
// Requires: @playwright/test
import { test, expect } from '@playwright/test';
test('should delete a specific user from the list', async ({ page }) => {
await page.goto('https://example.com/users');
// BAD: Brittle CSS/XPath
// await page.locator('tr:nth-child(3) .btn-danger').click();
// GOOD: User-facing locator with filtering
// 1. Find the row that contains the specific user's name
// 2. Within that row, find the button with the role 'button' and name 'Delete'
const userRow = page.getByRole('row').filter({
hasText: 'Jane Doe'
});
const deleteButton = userRow.getByRole('button', { name: 'Delete' });
await deleteButton.click();
// Validation: Check for a success message
await expect(page.getByText('User deleted successfully')).toBeVisible();
});
Verification and Risks
To verify the resilience of your locators, perform a DOM Mutation Test: change a CSS class name or wrap a target element in a new <div>. If the test continues to pass, your locator is decoupled from the implementation.
Risks: Be cautious with getByText when the application supports multiple languages (i18n). In those cases, prefer getByRole with a name that matches a translation key or use data-testid attributes as a last resort for elements that have no stable visible text.
Rollback Strategy
Since locator changes only affect the test code and not the application state, no system rollback is required. If a new locator strategy causes false negatives, revert the specific locator line to the previous working version using your version control system (e.g., git checkout path/to/test.spec.ts).
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.