Self-Healing Tests Framework: Why Patching CSS Selectors Is Solving the Wrong Problem
Every self-healing test framework on the market does the same thing: when a CSS selector breaks, it searches through a ranked list of backup locators until one matches. The underlying assumption is that selectors are the unit of healing. Assrt takes a different approach. It does not store selectors at all. Before every browser action, it captures a fresh accessibility tree snapshot and uses element refs that are regenerated on every interaction. When a test breaks beyond step-level recovery, it regenerates the entire test case through a diagnostic model. The healing unit is the test case, not the locator.
“Open-source, self-hosted, generates real Playwright code. No proprietary YAML, no vendor lock-in, no monthly bill.”
Assrt vs $7.5K/mo self-healing tools
1. How Every Self-Healing Framework Works Today
The standard self-healing model, used by testRigor, Mabl, Testim, Ranorex, and nearly every commercial test automation platform, works like this: when you record or write a test, the tool stores multiple locator strategies for each element. A button might have a CSS selector, an XPath, a text match, an ARIA label, and a data-testid attribute all recorded simultaneously.
When a test runs and the primary locator fails, the tool tries the second locator, then the third, and so on down the list. If one of the backup locators matches an element, the tool updates its stored primary locator and marks the test as healed. Some tools use machine learning to rank which backup locator is most likely correct. Others use heuristic scoring based on element attributes.
This approach was designed for a world where tests are scripts that reference specific DOM elements. A Selenium test that clicks #submit-btn breaks when a developer renames that ID. The self-healing layer intercepts the failure and finds the button by other means. The model assumes that tests are brittle scripts that need a resilience layer on top.
2. Why Selector Patching Is the Wrong Abstraction
Selector patching solves the symptom (a locator broke) without addressing the root cause (the test is coupled to implementation details of the DOM). This creates several problems that every team using self-healing tools eventually hits.
Silent mishealing
When a selector-patching tool heals a broken locator, it picks the element that best matches the backup locator criteria. But "best match" is not always "correct match." If a developer removes a submit button and adds a cancel button in the same position, the self-healing layer might latch onto the cancel button because it has similar attributes. The test passes. The bug goes undetected. The test suite now has a false positive that is harder to find than a simple failure.
Locator database drift
Self-healing tools maintain a database of locator strategies for every element in every test. Over months of healing, this database drifts from reality. Locators that were once accurate get replaced with approximations. The tool becomes confident about locators that are technically wrong but happen to work. When the page eventually changes enough to break even the backup locators, the accumulated drift makes debugging significantly harder than it would have been with a simple, honest test failure.
Vendor lock-in through locator storage
The locator database is proprietary to each tool. TestRigor stores its locator intelligence in its cloud. Mabl stores it in theirs. Ranorex stores it in its project files using a proprietary RanoreXPath format. When you want to switch tools, you lose all the healing history. Your tests are technically portable (they are Selenium or Playwright scripts), but the self-healing layer that makes them maintainable is not. This is how test tools create lock-in without it being obvious at purchase time.
Skip the locator database entirely
Assrt uses live accessibility tree snapshots instead of stored selectors. No locator database to maintain, no healing history to lose when you switch tools.
Get Started →3. Snapshot Per Action: How Assrt Eliminates Stored Selectors
Assrt's test agent does not store locators. Instead, it follows a strict protocol defined in its system prompt: before every single browser interaction, call snapshot() to get the current accessibility tree with element refs.
The accessibility tree is a structured representation of every interactive element on the page, with each element assigned a temporary ref ID like ref="e5" or ref="e12". These refs are not CSS selectors. They are not XPath expressions. They are ephemeral identifiers that only exist for the duration of that snapshot. The next snapshot generates new refs for whatever elements exist on the page at that moment.
This is the critical architectural difference. Traditional self-healing tools try to maintain a stable mapping between a test step and a DOM element across multiple test runs. Assrt does not try to maintain that mapping at all. Every action starts fresh by reading the page as it currently exists. If a button was renamed, moved, or replaced, the agent sees the current state and acts on it. There is no stale locator to patch because there is no stored locator in the first place.
The system prompt for the test agent (defined in agent.ts) enforces this pattern explicitly: "ALWAYS call snapshot FIRST to get the accessibility tree with element refs. Use the ref IDs from snapshots when clicking or typing. If a ref is stale (action fails), call snapshot again to get fresh refs."
4. What Happens When an Action Fails
Even with fresh snapshots, actions can fail. An element might disappear between the snapshot and the click. A page might navigate unexpectedly. A modal might appear and block the target element. When this happens, the error handler in the agent does something specific: it catches the error, immediately takes a fresh snapshot of the current page state, and returns both the error message and the new snapshot to the AI agent.
The response the agent sees is: "Error: [description]. The action failed. Current page accessibility tree: [fresh snapshot]. Please call snapshot and try a different approach." This gives the agent enough context to understand what went wrong and what the page looks like now, then decide on a different strategy. It might click a different element. It might wait for a loading state to resolve. It might navigate back and try a different path to the same goal.
This is fundamentally different from selector-patching. A selector-patching tool that fails on a click tries to find the same element by a different locator. It assumes the intent was to click that specific element and the only problem is finding it. Assrt's agent understands the intent of the test step (for example, "submit the form") and can choose a completely different path to achieve the same outcome.
Fallback text matching
When a ref-based click fails, the browser layer also implements a fuzzy text-matching fallback. It queries all interactive elements on the page (buttons, links, inputs, anything with a click handler) and scores them by text similarity to the target description. Exact text matches win immediately. Substring matches score next. Partial word matches are scored proportionally. This means that even if the accessibility tree ref is stale, the system can still find a "Submit" button by its visible text without any stored locator.
5. Case-Level Healing: Regenerating the Test, Not the Locator
Step-level recovery handles transient failures: a loading spinner that takes longer than expected, a tooltip that blocks a button momentarily, a page that navigates to a slightly different URL. But sometimes a test fails because the application genuinely changed. A flow was redesigned. Steps were reordered. A feature was removed. In these cases, patching individual locators does not help. The test itself needs to be rewritten.
This is where Assrt's assrt_diagnose tool comes in. When a test case fails, you pass the URL, the failing scenario text, and the error to the diagnostic endpoint. A specialized model analyzes the failure and produces a structured response with three parts: root cause analysis (is this an application bug, a flawed test, or an environment issue?), a detailed explanation of what went wrong, and a corrected test case in the same#Case markdown format.
The corrected case is not a patched version of the old case with updated selectors. It is a regenerated case that accounts for how the application currently behaves. If the checkout flow now has three steps instead of two, the corrected case has three steps. If a button label changed from "Buy Now" to "Add to Cart," the corrected case uses the new label. The entire test case is the healing unit.
You paste the corrected case into /tmp/assrt/scenario.md, which auto-syncs to persistent storage via a file watcher that debounces changes and pushes updates after one second of stability. Then you re-run. The feedback loop from failure to corrected test to successful re-run happens in a single interaction.
See case-level healing in action
Run a test. If it fails, call assrt_diagnose. Get a corrected test case in seconds, not a patched selector. Open-source and free.
Get Started →6. Adaptive Waiting Instead of Fixed Sleeps
A common source of test flakiness is timing. Traditional test scripts use fixed sleep() calls or static timeouts to wait for dynamic content. If the page loads faster than the sleep, you waste time. If it loads slower, the test fails. Self-healing frameworks typically do not address this; they heal broken locators, not broken timing.
Assrt includes a wait_for_stable tool that takes a different approach. It injects a MutationObserver into the page that counts DOM changes (child additions, removals, text updates) across the entire document. Then it polls at 500ms intervals. When the mutation count stops changing for a configurable duration, the page is considered stable.
This means the wait adapts to actual page behavior. A page that finishes loading in 200ms gets waited on for 200ms plus the stability window. A page that takes 4 seconds to finish an API call and render the results gets waited on for 4 seconds plus the stability window. No configuration per page. No hardcoded timeouts. The same wait logic works on fast static pages and slow API-driven dashboards.
7. How This Compares to testRigor, Mabl, Testim, and Ranorex
Every commercial self-healing tool uses the locator-patching model described in section 1. The differences between them are in how they rank backup locators, not in whether they use them. Here is how each compares to the snapshot-per-action approach.
| Dimension | Assrt | testRigor / Mabl / Testim | Ranorex |
|---|---|---|---|
| Healing model | Snapshot per action + case regeneration | Locator patching with ML ranking | RanoreXPath with fuzzy matching |
| Stored locators | None (ephemeral refs) | 3 to 6 per element | Proprietary RanoreXPath per element |
| Silent mishealing risk | Low (agent understands intent) | Moderate (best-match heuristic) | Moderate (fuzzy path matching) |
| Test format | Markdown (#Case), drives Playwright | Plain English DSL / proprietary | C# or proprietary recording |
| Price | Free, open-source | $5K to $15K/mo (enterprise) | $3K+ one-time (per seat) |
| Self-hosted | Yes | No (cloud SaaS) | Yes (desktop app) |
| Vendor lock-in | None (standard Playwright) | High (proprietary format + healing DB) | Medium (RanoreXPath dependency) |
The pricing gap is the most visible difference, but the architectural difference matters more long-term. With locator-patching tools, your test maintenance decreases initially but plateaus as the locator database accumulates drift. With snapshot-per-action, there is no database to drift. Each run starts from the current state of the application, and the overhead stays constant regardless of how many times the application has changed since the tests were written.
8. Getting Started
Assrt runs locally. There is no account to create and no API key to provision for the test runner itself. Here is the workflow:
- 1.Generate a test plan. Run
assrt_planwith your application URL. Assrt navigates to the page, takes screenshots at multiple scroll positions, extracts the accessibility tree, and generates 5 to 8 test cases. - 2.Run the tests. Execute
assrt_testwith the URL and your plan. The agent drives a real Playwright browser, taking a fresh accessibility tree snapshot before every action. - 3.If a test fails, diagnose it. Run
assrt_diagnosewith the failing scenario and error. Get a corrected test case in the same #Case format. - 4.Edit and re-run. Paste the corrected case into
/tmp/assrt/scenario.mdand re-run. Changes auto-sync to persistent storage.
Test plans are saved locally. Results go to /tmp/assrt/results/latest.json. Scenario metadata (ID, name, URL) is at /tmp/assrt/scenario.json. Each run is auto-saved with a UUID so you can re-run the same scenario later by passing scenarioId to assrt_test.
9. Frequently Asked Questions
How does Assrt's self-healing differ from selector-patching tools?
Traditional self-healing frameworks store 3 to 6 backup locators per element and cycle through them when the primary selector breaks. Assrt does not use stored selectors at all. Before every browser action, it calls snapshot() to get the current accessibility tree with fresh element refs (e.g., ref="e5"). If an action fails, the catch block captures a new snapshot and the agent tries a different approach. The healing unit is the action, not the locator.
What happens when an Assrt test case breaks completely?
When a test case fails beyond step-level recovery, you call assrt_diagnose with the URL, the failing scenario, and the error. A dedicated diagnostic model analyzes whether the failure is an application bug, a flawed test, or an environment issue, then returns a corrected #Case in the same markdown format. You paste it into /tmp/assrt/scenario.md and re-run. The healing happens at the test-case level, not the selector level.
Does Assrt generate Playwright code or use a proprietary format?
Test plans are written in a human-readable #Case markdown format. When Assrt executes these plans, it drives a real Playwright browser session. The test artifacts (screenshots, videos, accessibility trees) are standard files you can use with any tool. There is no proprietary YAML, no locked-in DSL, and no vendor-specific runner required.
How does Assrt handle pages with dynamic content or loading states?
The agent uses a wait_for_stable tool that injects a MutationObserver into the page, counts DOM changes, and waits until mutations stop for a configurable period. This adapts to actual page load speed instead of relying on fixed sleep() delays.
What does Assrt cost compared to other self-healing test frameworks?
Assrt is open-source and free. It runs locally with no cloud dependency, no page limits, and no crawl quotas. Comparable self-healing tools like testRigor, Mabl, and Testim cost between $5,000 and $15,000 per month for enterprise usage. Your tests are standard Playwright code you own.
Can I self-host Assrt and keep test data private?
Yes. Assrt runs entirely on your machine or your infrastructure. Test results, screenshots, and videos are written to local files (/tmp/assrt/). Scenario storage uses a local cache at ~/.assrt/scenarios/ with optional cloud sync that you can disable. No data leaves your environment unless you choose to enable it.
Self-healing without the locator database
Assrt takes a fresh accessibility tree snapshot before every action. No stored selectors to drift. No proprietary healing database to lose. Open-source, self-hosted, free.