Resilient Locators: Best Practices for Stable Monitors
Learn how to use Playwright locators effectively to build synthetic monitors that survive UI changes. Master getByRole, getByLabel, and data-testid.
The most common cause of flaky synthetic monitors is brittle selectors. If your test relies on CSS classes like .btn-blue-large or complex XPath strings, it will likely break when your frontend team does a minor refactor.
Playwright's Locators are designed to be resilient, semantic, and closer to how users interact with the page.
The Priority Hierarchy
When choosing a locator, follow this priority list to ensure maximum stability:
1. page.getByRole() (Highly Recommended)
This is the gold standard. It reflects how assistive technology (screen readers) perceives the page.
// ✅ Resilient: Even if the styling changes, it's still a "Sign In" button.
await page.getByRole('button', { name: 'Sign In' }).click();2. page.getByLabel()
Ideal for form fields. It ensures your labels are correctly associated with inputs.
await page.getByLabel('Email Address').fill('user@example.com');3. page.getByTestId()
Use this when semantic locators aren't enough or when the UI is highly dynamic. You should add data-testid attributes to your application code.
// In your React/HTML: <button data-testid="submit-order">...</button>
await page.getByTestId('submit-order').click();Locators to Avoid
To maintain a Premium Emerald standard of reliability, avoid these patterns:
- Brittle CSS Classes:
page.locator('.css-1v9j8p3')— These change every time you rebuild your styles (Tailwind, Styled Components). - Deep XPaths:
page.locator('//div[3]/div/ul/li[2]/button')— Any structural change in the DOM will break this. - Arbitrary Tag Names:
page.locator('button')— Fails if you add a second button to the page.
Chaining & Filtering
Often, you need to find an element inside another. Use chaining to narrow down the scope.
// Find the 'Delete' button specifically inside the 'Settings' card
const settingsCard = page.locator('.settings-card');
await settingsCard.getByRole('button', { name: 'Delete' }).click();You can also filter by text or another locator:
await page.getByRole('listitem')
.filter({ hasText: 'Product A' })
.getByRole('button', { name: 'Add to Cart' })
.click();Pro-Tip: Visible Only
Synthetic monitors sometimes fail because Playwright finds multiple elements, some of which are hidden. Use the visible filter:
await page.getByRole('button', { name: 'Submit' }).and(page.locator(':visible')).click();Next Steps
Complete Playwright Guide for Synthetic Monitoring
Master Playwright test structure, navigation, forms, assertions, and advanced patterns like API mocking for reliable synthetic monitoring.
Network Mocking & Interception
Use Playwright's network interception to isolate your monitors from third-party flakes, simulate errors, and test edge cases without real API calls.