Early access The free plan is live — unlimited tests, no card, yours to keep Get started →
Self-healing

How self-healing test automation actually works

"Self-healing" gets attached to almost every test tool now. Most of them mean one of two things: try a second selector, or ask a model to guess a third. Here's what actually happens when a locator breaks — the full ordered chain, and the parts of it that are designed to refuse rather than guess.

What breaks, and what it costs

A locator is a bet on how you'll find the same element again tomorrow. Record a click on a button today, and the test stores something like an ID, a class, or the button's visible text. Ship a release where a developer renames that ID, swaps the button for a styled <div>, or the build pipeline regenerates a class name — and the bet is void. The test doesn't know the button moved; it knows the string it was looking for no longer exists.

The traditional answer is a person: someone opens the failing test, finds the new selector by hand, and updates it — for every test that touched that element, every time the UI changes. That maintenance loop, not writing the first test, is the real ongoing cost of browser automation. It's also exactly the work "self-healing" claims to remove.

What the phrase usually hides

Two common implementations sit under that one label. The first tries a stored backup selector when the primary fails — useful, but only if a working backup was ever captured. The second sends the current page to a language model and asks it to point at whatever looks closest to the original — which works often enough to feel magic, and fails in a specific, dangerous way: silently. A model that's 80% confident in the wrong element still returns an answer, the click still lands somewhere, and the step still reports green. A false pass is worse than a red one, because nobody goes looking for a test that says everything's fine.

The cascade, in order

QAright records more than one locator per element to begin with — up to fourteen ranked candidates, not a single string with nothing behind it. When a step's primary locator fails, it works down a fixed chain. Each phase is logged with what it tried and what it decided, so a repair is something you can read back and disagree with, not a black box that quietly did something.

  1. Primary locator

    The highest-scoring candidate from record time — and the score isn't just "ID beats class beats text." An attribute stability check runs first: an ID like login-btn and one like a7x9f2 are both "an ID," but only one of them will still be there next week. The generated one is scored down before it's ever trusted as primary.

  2. Fallback candidates

    The remaining ranked candidates, tried in order — each one re-checked for both presence and uniqueness against the live page. A candidate that now matches three elements instead of one isn't "found," it's ambiguous, and gets skipped rather than clicked on a guess.

  3. App model lookup

    The element is matched against a model built by crawling your app, not just the single page it happened to be recorded on — useful when the same control exists elsewhere with the DOM structured slightly differently.

  4. Semantic scan

    Every interactive element currently on the page is scored against the DOM snapshot captured at record time — tag, text, ARIA role, label, on-screen position, element type. This phase is held to a deliberately higher bar than a human-reviewed suggestion would need, for one reason: nobody is reviewing it in the moment. A wrong match here produces a passing step against the wrong element, which is a worse outcome than a failing one.

  5. AI-assisted repair

    Optional, off by default, and last — not first. If you enable it, the snapshot goes to a model you configure, only after every deterministic phase above has already failed.

The order is the point. A tool that reaches for an AI guess before it's tried the locators it already recorded isn't healing a test, it's replacing your test with a live guess on every run.

What it refuses to do

Most flaky automation doesn't come from a tool being unable to find an element. It comes from a tool guessing confidently when it shouldn't. The cascade above is deliberately conservative in four specific ways:

When the cascade itself can't tell

Sometimes none of the five phases resolve cleanly, and the honest answer is "a person should look at this." That's a different job from in-run healing, which has milliseconds to decide and has to be conservative enough to run unattended. A repair agent that runs after the fact can take longer and do more: replay the test up to the failing step — so the app is actually in the state that step expects, not wherever the recorded URL happens to load, which for anything behind a login is nowhere useful — diagnose using the failure the run already classified, propose a candidate from the live DOM, and then prove it by executing both the failing step and the one after it. "Found an element" and "found the right element" look identical right up until something downstream depends on the difference.

The actual feature is what you can check

None of the above is worth much if it can't be inspected. Every healing event — step, attempt number, strategy used, outcome — is logged and feeds a report you can read, not a pass/fail summary that hides how the pass happened. If a fallback is doing the work every run, that's visible, and it's promoted openly instead of quietly carrying the test forever.

Keep reading. Healing starts with the locators recorded in the first place; the order worth choosing them in is choosing locators that survive UI changes. A test that fails for a reason healing cannot fix is often a timing problem, covered in flaky tests, and test drift detection reports a changed page before any test has to heal around it.

See the cascade run against your own app, on the free plan — no card, no time limit.

Start on Free See how it works