Self-healing selectors
How self-healing selectors keep recorded tests running when an id or class changes: up to four fallback locators, flagged in results, warn or fail modes.
Last updated
Recorded steps keep backup locators, so a scenario keeps running when a page's id or class changes, flags the healed step and lets you update the scenario in one click.
Why it exists
Pages change. An id gets renamed and a recorded #submit-btn stops matching. Without fallbacks the step fails and someone has to find the new selector by hand. Self-healing tries a small set of more stable locators for the same element, and reports what it did so that the change is reviewed rather than hidden. The same idea is also called self-healing locators.
Fallbacks
When the recorder captures a click, fill, select, check, uncheck or upload step, it also stores up to four fallbacks for that element, most stable first:
| Fallback | Example |
|---|---|
| Test ID | data-testid, data-test or data-qa attribute |
| Role and accessible name | A button named "Submit" |
| Label text | The <label> associated with a field |
| Placeholder | A field's placeholder |
| Visible text | Short text of a button or link |
| CSS path | A structural CSS selector |
Fallbacks never contain field values. Password fields and data-qa-sensitive elements are not recorded at all. The editor shows how many fallbacks each step has. For a step inside a frame, the fallbacks are looked up inside that frame.
Scenarios saved before this feature have no fallbacks and behave as before.
How healing works during a run
- The primary selector is tried first.
- Only when it matches no element at all does the runner try the fallbacks in order.
- A fallback is accepted only when it matches exactly one element (and, for a role fallback, an element with that role).
Rules that keep healing honest:
- An element that exists but is hidden or disabled is a real failure and never heals.
- The primary selector and the fallbacks share the step timeout: the primary gets the first 60% to appear, the fallbacks the rest, so healing never makes a step longer.
- Assertions, checks and navigation never heal. Healing an expected-result check could hide a real bug.
Modes
Choose the behaviour with Self-healing in the scenario editor:
| Mode | Behaviour |
|---|---|
| Off | Fallbacks are ignored; a selector that matches nothing fails the step |
| Warn (default) | The step runs on the fallback and passes; the case is flagged Healed in Results and reports |
| Fail | The step fails without acting, and the message names the suggested replacement selector |
Review and update a scenario
In Results, a healed step shows a Healed badge (or Healing blocked in fail mode) with the original and the suggested selector.
- Review the step's screenshot to confirm the right element was used.
- Click Update selector in scenario.
The suggested selector becomes the step's primary selector, and the old one is kept as its first CSS fallback. The update uses the saved scenario's own fallback and is refused if the scenario changed since that run.
Suggested selectors for roles, labels and text use Playwright's exact internal:role, internal:label and internal:text selectors; test IDs and placeholders use plain CSS.
In reports and CI
- JSON reports include each healed step's original selector, the fallback used and the suggested selector, plus the batch's
healedStepscount. - JUnit adds a
healedproperty and asystem-outline per healed step. - HTML shows a Healed badge.
The CI runner accepts --healing off|warn|fail to override every scenario in a manifest; without it, each scenario's own mode applies (warn when unset). --healing fail is useful in CI when any healed selector should block a merge until the scenario is updated:
npm run qa -- --config scenario.json --healing fail --output qa-resultsThe GitHub Action exposes the same option as the healing input, and the MCP server as the healing argument of run_check and run_manifest.