The most common reason test suites fail is not because bugs were introduced — it's because the UI changed and the test selectors became outdated. A developer renames a CSS class, restructures the DOM, or changes button text, and suddenly 20 tests are failing for the wrong reason.
Self-healing tests address this by automatically adapting to UI changes.
How Self-Healing Works
Self-healing tools (like Healenium, Applitools, and Reflect) work by storing multiple ways to find the same element. If the primary selector fails, the tool tries fallback selectors — using the element's position, nearby text, ARIA role, or other attributes to locate it.
When a fallback succeeds, the tool logs the selector change and suggests updating the primary selector. The test passes and a human reviews the suggested update later.
Implementing Selector Resilience Without Third-Party Tools
You can achieve most of the benefits of self-healing by writing resilient selectors in the first place.
Prefer role and label selectors:
// Fragile — breaks if the class changes
page.locator('.submit-btn-primary')
// Resilient — tied to the semantic role and visible label
page.getByRole('button', { name: 'Submit Order' })Use test IDs for complex elements:
When there's no good role or label to target, add a data-testid attribute to the element:
<div data-testid="checkout-summary">...</div>page.getByTestId('checkout-summary')Test IDs are stable by design — developers know not to change them without updating tests.
Use text selectors for content that is stable:
page.getByText('Your order has been confirmed')This is resilient to DOM restructuring as long as the text itself doesn't change.
Building a Selector Strategy
Adopt a selector priority order for your team:
getByRolewith name — most resilient, most semanticgetByLabel— for form fieldsgetByText— for stable text contentgetByTestId— when no semantic selector is available- CSS selector — last resort, most fragile
Document this in your team's testing guidelines so everyone follows the same approach.
Handling Legitimate UI Changes
When the UI changes intentionally (a button is renamed, a section is restructured), your tests should update to match. The goal of resilient selectors is to reduce false failures — not to hide legitimate changes.
When a test fails after a UI change:
- Verify it's a selector issue, not an actual bug
- Update the selector to match the new UI
- Commit the selector update with the UI change in the same PR
This keeps tests and UI in sync and makes the history readable.