Synthetic monitoring, without the brittle part
A self-healing monitoring tool that stores no selector
Most synthetic monitors record a CSS or XPath locator when you set them up and replay it forever. When your front end ships a change, the locator misses and the tool tries to patch the saved string, which is itself a new brittle string. There is a version of this that does not drift at all: a monitor that never memorized a locator in the first place.
Direct answer (verified 2026-06-22)
A self-healing monitoring tool re-runs end-to-end checks against your live app on a schedule and adapts to UI changes automatically, instead of false-alarming the moment a button is renamed or a form is restructured. Assrt does this by keeping each check as plain English and resolving it against a fresh accessibility tree on every run, so there is no stored selector to rot between runs. The output is standard Playwright, open source and free, and you can monitor on five cadences from Run Once to Weekly. Verified against the product source at assrt.ai.
What is actually wired together
A self-healing monitor is three things connected: something that decides when to run, something that drives the browser and reads the page, and something that turns the result into a signal you can act on. The healing lives in the middle box, because that is where the page gets read fresh each time instead of replayed from a saved locator.
One monitoring run, end to end
Why this one cannot drift between runs
The brittleness in classic monitoring comes from a single design choice: the locator is captured once and stored. From then on, every run is a bet that the DOM still matches the string you saved months ago. Self-healing tools soften the bet by fuzzy-matching a sibling and rewriting the saved locator when it misses, then logging "healed." That patch is a new brittle string, and the cycle repeats.
Assrt removes the stored locator entirely. The check is prose. On every scheduled run, the runner calls snapshot to read the current accessibility tree, finds the element that satisfies the step's intent, and acts. A renamed label or a new wrapper div is simply a different tree to read. There is no saved string to miss, so there is nothing to patch.
#Case Login still works 1. Go to the app and open the sign-in page 2. Type the test account email into the email field 3. Type the password into the password field 4. Click the sign-in button 5. Assert the dashboard loads and shows the account name # No css selector. No xpath. No data-testid. # The runner reads the live accessibility tree each run # and finds the element that matches the intent.
The anchor: the failure path in src/core/agent.ts (lines 930 to 933)
When any action throws during a run, the runner does not abort and does not reach for a stored fallback chain. It fetches a fresh snapshot of the page and hands the model an error message ending in: "Please call snapshot and try a different approach." The healing is not a separate subsystem bolted on next to the monitor. It is the default control loop: read the page, act, and on any miss, read the page again. That is the uncopyable part, because it is the architecture, not a feature flag.
One scheduled run, when the UI shifted overnight
Say the team renamed the "Log in" button to "Sign in" and wrapped the form in a new container. A recorded monitor would miss its stored button locator and either fail loudly or heal into a fresh guess. Here is the same morning on a monitor that reads the tree fresh.
Scheduler fires, the page changed, the run adapts
The same run, watched as it happens, looks like a short narrative rather than a red light. Nothing in the plan named the button by a locator, so the rename never registered as a break.
What the monitor does when the page moved
09:00 — trigger
The cadences you can monitor on
Five options live in the dashboard, defined in src/app/app/test/page.tsx lines 1256 to 1262. You pick from a dropdown; there is no cron string to hand-write.
schedule cadences, Run Once to Weekly
fresh accessibility tree read per run
stored CSS or XPath locators to drift
open source and free, self-hosted
Standing one up against your app
The runner is a single command. Point it at a URL, let it discover scenarios, and the prose plan plus the generated Playwright land on disk. From there you schedule it and the same plan re-resolves on every run.
Because the output is standard Playwright in your repo, monitoring and CI are the same artifact. The check that pages you at 3am is the same file a teammate reads in a pull request, which is the opposite of a closed synthetic flow you cannot inspect or move.
Where reading-the-tree-fresh is not magic
Resolving against a live tree removes one whole class of false alerts, the cosmetic ones. It does not remove the cost of a flow that is genuinely ambiguous: if two buttons could both satisfy "sign in," the runner has to pick, and a vague step invites a wrong pick. The fix is the same discipline good tests always wanted, write the step the way a person would describe the goal, not the way a machine would address a node. That is cheaper than maintaining locators, but it is not free.
And healing is scoped to how an element is reached, never to whether the assertion holds. A monitor that quietly "healed" a broken checkout into a green check would be worse than useless. When the feature is actually down, the run fails with screenshots and the event log, the way a monitor is supposed to.
Want a monitor that survives your next front-end ship?
Bring your app and a flaky check; we will set up a self-healing monitoring run that reads the tree fresh and keeps the Playwright in your repo.
Frequently asked questions
What is a self-healing monitoring tool, in one sentence?
It is a synthetic monitor that re-runs end-to-end checks against your live app on a schedule and adapts to UI changes automatically, so a renamed button or a restructured form gets handled on the next run instead of firing a false alert. The healing part is what separates it from a recorded flow that breaks the moment your front end ships.
How does Assrt heal without a stored selector?
There is nothing to heal because nothing is stored. A classic monitor records a CSS or XPath locator at setup time and replays it; when the DOM shifts, the locator misses and the tool tries to patch the saved string. Assrt keeps the check as plain English (a #Case block) and resolves it fresh on every run. The agent calls snapshot to read the current accessibility tree, finds the element that matches the intent, and acts. The plan never names a brittle locator, so a UI change is just a different tree to read, not a broken string to repair. You can see the failure path in src/core/agent.ts lines 930 to 933: when an action throws, the runner grabs a fresh snapshot and tells the model to call snapshot and try a different approach.
How is this different from uptime monitoring or a status-page ping?
An uptime check confirms the homepage returned a 200. It cannot tell you that login silently swallows the password after a state-setter typo, or that checkout returns 200 but the Stripe redirect never fires. A self-healing monitor drives the real flow: it types into the field, clicks the button, waits for the post-login URL, and asserts what a user would assert. The trade is more setup for catching everything between a 200 and a working feature.
What schedules can I run the monitor on?
Five cadences, defined in the dashboard at src/app/app/test/page.tsx lines 1256 to 1262: Run Once (Now), Every Hour, Every 6 Hours, Daily at 9am, and Weekly (Monday 9am). There is no cron-string field; you pick from the dropdown and the runner translates it. If you need a custom cadence, wrap the CLI in your own GitHub Actions workflow or a systemd timer, since the underlying runner is just npx @m13v/assrt against your URL and plan.
Does self-healing mean the monitor will hide a real regression?
No, and this is the line that matters. Healing adapts to how an element is reached (a renamed label, a moved button, a new wrapper div), not to whether the assertion passes. If your login flow genuinely breaks, the monitor re-reads the page, fails to satisfy the intent, and reports a failure with screenshots and the event log. The point of resolving against a fresh tree is to stop false alerts on cosmetic drift, not to paper over a broken feature.
Is it open source, and do I keep the tests?
Yes. Assrt is open source and free, and the output is standard Playwright that lives in your repo and runs in your CI. There is no proprietary YAML and no closed cloud format to export from later. That matters for a monitor specifically: a synthetic check you cannot read or move is a liability the day you want to leave the vendor. Here the check is prose plus generated Playwright, both yours.
Where do I see what a monitoring run actually produced?
Every run writes /tmp/assrt/results/latest.json plus a per-run file, with a top-level passed and failed count and an array of scenarios, each carrying its assertions and per-assertion evidence (screenshots, video, event log). Point a tail or a notify hook at latest.json to fan results into your own alerting, or read the run in the dashboard. The plan that produced it sits in /tmp/assrt/scenario.md as editable Markdown.
Keep reading
Self-healing tests guide
Why there is no locator to heal when the test is prose resolved against a fresh accessibility tree.
Continuous monitoring for web apps
The scheduled-run loop, a watched markdown plan, and five cadences from the dashboard.
Self-healing Playwright tests guide
What a stored CSS or XPath locator costs you over time, and what resolving against a fresh tree changes.
Comments (••)
Leave a comment to see what others are saying.Public and anonymous. No signup.