Monday, 9 a.m. The nightly regression run has 47 tests in red. Nobody touched the business logic: someone on the frontend team renamed a CSS class and reordered two divs in the sign-up form. The app works perfectly. The tests don't.
Anyone who has maintained an automation suite knows this scene. And knows the real cost: it's not the 47 tests, it's someone on the team spending the whole morning updating selectors instead of testing what just shipped.
Self-healing promises to solve exactly this. The problem is that most implementations treat the symptom, not the cause.
In this article we explain how we approach it at Qualis Lab, and how we apply it in Tessa, our intelligent automation platform.
What self-healing is, and what it isn't
Self-healing is the ability of an automation framework to detect that a UI element has changed, find its new version and keep running the test without manual intervention.
What it isn't: a tool for making tests pass. The difference looks subtle, and it's the most important point in this whole article.
Badly designed self-healing is dangerous, because it can turn a real bug into a green test. If the "Confirm payment" button disappeared and the recovery mechanism finds "Cancel" in the same position, clicks it and moves on, the report says everything is fine. It isn't fine. You've just automated blindness.
That's why our ground rule is simple: self-healing can change how an element is found, never which element is being looked for or what the test does.
Why most approaches fall short
The most common way to "heal" a test is to keep a list of fallback locators. If the id fails, try the name; if the name fails, try the XPath; if the XPath fails, try the text.
It works for small changes. For everything else it doesn't, and the reason is structural: the framework only knows what you stored. If you recorded a single selector when you built the test, when that selector breaks you have no information to decide which of the twenty buttons on the screen is the one you were looking for.
The problem isn't in the execution. It's in what the framework knows about the element.
Our view: the framework has to know what it's looking for
When a person looks for a button on a screen that changed, they don't memorize its id. They know it's the confirm button, inside the payment modal, below the amount field, next to "Cancel", on step three of checkout. If the button moved or changed color, they still find it.
A framework with real self-healing has to reason the same way. And to do that, it needs the same information.
That's why, in the frameworks we build, every successful run stores much more than a selector. When a test passes, the framework records what the element looked like at that moment and uses it as a baseline:
- Page context: URL, title and where in the functional flow the action happens.
- Element context: its DOM structure, its hierarchy, its attributes, its visible text.
- Neighborhood context: which elements surround it, and which container, form or modal it lives in.
- Functional context: which scenario step performs the action, and on which page, modal or section it happens.
It doesn't matter how the test was written: by hand with Page Objects, with Screenplay or generated by a recorder. The context builds up run after run. And if the test is generated by recording a flow, that same capture can start the baseline from day one.
With that information, when an element changes, the question stops being "which fallback selector do I try?" and becomes "which of the elements on this screen is most likely the same one that worked in the last successful run?". That's a question you can answer with judgment.
Where that information lives matters
An architecture decision that looks minor and isn't: the context metadata lives inside the automation project itself, versioned alongside the code.
Tied to the element, not the step. The same field is used across many tests. If the metadata lives on the element, it gets healed once and the fix applies to every action that uses it.
Versioned in the repository. Every change self-healing makes is recorded in the history, with full traceability. You know what changed, when and why.
No dependency on external platforms. Many commercial solutions store this information in their own cloud. That works, until you switch tools, the service goes down or the client has security restrictions on where information about their applications can live. If the context is in your repo, it's yours.
How the cycle works
When a test can't find an element, the process goes like this:
- Detection. The primary locator fails.
- Candidate search. The elements on the current screen that could match the original are analyzed.
- Context scoring. Each candidate is compared with the baseline from the last successful run: element type, text, hierarchy, neighbors, container, position in the flow.
- Confidence-based decision:
- High confidence: the fix is applied, the test continues, the change is logged and the baseline is updated.
- Medium confidence: the test continues, but the fix is proposed for human review before it's consolidated.
- Low confidence: the test fails. As it should.
That third case is what separates useful self-healing from dangerous self-healing. A framework that never fails isn't robust: it's suspicious.
How we apply it in Tessa
Tessa is the intelligent automation platform built by Qualis Lab, and context-based self-healing is one of its core pieces. It brings together in a single flow three things that, in most teams, live apart:
AI-generated scenarios. Tessa generates functional scenarios in Gherkin, a language that business, development and QA all understand equally.
Its own CodeGen on top of Playwright. Instead of using the native recorder, Tessa has its own code generator. While it records a flow, it captures the full context of every element: DOM, hierarchy, neighbors, container, page and functional step. The self-healing baseline exists from the very first recording, without waiting for runs.
Its own execution runner. It runs scenarios manually or integrated into the CI/CD pipeline, collects evidence and sends the results back to the platform.
Tessa's AI is the bridge between the functional and the technical world. It maps each Gherkin step to the actions that implement it, reuses existing automation before creating new pieces and, when the UI changes, uses the context to propose or apply the fix depending on the confidence level.
All the context metadata lives in the project's repository, versioned alongside the code. Nothing is tied to a third-party cloud.
The result: the team defines what to test. Tessa makes sure the way it's tested doesn't break with every release.
AI as judgment, not as a shortcut
AI has a concrete role in this setup: interpreting context. Detecting that a field went from being called "Document no." to "ID number" is trivial for a model and almost impossible for a fixed rule. The same goes for a button that moved but is still, functionally, the same button.
What AI doesn't do is decide what the test should check. The functional intent is defined by the scenario, and self-healing always works subordinate to it.
What you need to measure
If you implement self-healing and don't measure it, you've lost half the value. The indicators we track:
- Heals per run. How many elements were recovered automatically.
- Acceptance rate. Of the proposed fixes, how many were approved and how many rejected. A lot of rejections means the confidence threshold is badly calibrated.
- Repeat offenders. An element that gets healed every release is a signal: either the UI is unstable or the original locator was poorly chosen. Either way, it's a conversation to have with development.
- Maintenance hours saved. The number the business cares about.
What self-healing doesn't replace
It doesn't replace good locator design practices. A development team that adds stable attributes like data-testid to critical elements saves work for any recovery mechanism.
It doesn't replace communication between QA and development. A full redesign of a screen isn't a self-healing case: it's a functional change that requires reviewing the scenarios.
And it doesn't replace the team's judgment. It's a tool so people stop doing repetitive work, not so they stop looking.
In short
Most tests don't break because the application fails. They break because the framework doesn't know enough about what it's looking for.
Self-healing that works doesn't start when the test fails. It starts much earlier, in every run that passes: if the framework stores context and not just selectors, it has something to reason with when the UI changes. And if that context lives in your repository, versioned and traceable, you stay in control.
Tessa is Qualis Lab's intelligent automation platform: AI-generated scenarios, its own CodeGen on top of Playwright and context-based self-healing, to cut maintenance without giving up confidence in the results.
If your team spends more time fixing tests than writing new ones, in a free 20-minute assessment we'll show you how Tessa would work on your application. Discover Tessa or book an assessment.