Self-heal & root-cause
Two behaviors set Tripwire apart inside a run: it self-heals through UI change, and it root-causes genuine failures — down to the backend error — into deduped issues. This page explains both at the engine level.
Self-healing
A conventional test pins itself to a locator — button.btn-primary[data-id="create"] — and dies the moment that locator goes stale. Tripwire carries no stored locators. Each step describes intent, and the engine re-reads the live DOM on every run.
How it re-grounds
intent: "Click 'Create account'"
│
├─ read_dom ──► title, url, visible text, numbered interactive elements (#ref)
│
└─ act ──────► click the element whose live label/role/text matches the intentThe model resolves the target fresh, at that instant, from the current page. For a scoped assertion (visible_text with a selector_hint, or element_visible) the element is likewise resolved live. So the kinds of changes that wreck a traditional suite don't register:
| UI change | Conventional suite | Tripwire |
|---|---|---|
| Button class renamed | 🔴 breaks | 🟢 heals |
| Element moved in the DOM | 🔴 breaks | 🟢 heals |
| Label text tweaked | 🔴 breaks | 🟢 heals (same intent) |
| Behavior actually regressed | 🔴 fails | 🔴 fails — correctly |
The goal: only fail when the behavior is wrong, never when the markup merely moved.
Root-cause capture: from UI symptom to backend cause
When an assertion genuinely fails, the engine doesn't stop at "step 3 red." It already captured every failed network response during the case — including the traceparent header, from which it extracts a trace_id. The chain is:
1. assertion fails ──► e.g. "order summary shows USD, not EUR"
2. driver.root_cause() ──► the failing request + its trace_id
3. trace_id + a configured log backend ──► fetch matching server log lines
4. Claude synthesizes ──► { category, server_error, suspected_cause, suggested_fix, confidence, evidence }
5. attach to the report + the filed issueIf no trace_id is present, or no log backend is configured, you still get the in-browser root cause (the failing request and a summary) — the server-side enrichment is best-effort and additive. See Connecting server logs for the backends (Loki, Datadog, Elasticsearch, a custom HTTP endpoint, or a file) and how to wire one up.
Why this matters
A German checkout that silently shows USD instead of EUR returns a healthy HTTP 200 — nothing throws in the browser. An absent_text: ["USD"] check fails the run, the failing POST /api/checkout?cc=DE carries a trace_id, and the log backend resolves it to the real 500 GeoLookupError line on the server. Claude turns that into "backend error → cause → suggested fix." That's the seam no UI-only tester can reach.
From failure to deduped issue
reports.py builds a provider-agnostic issue from the case (including the server diagnosis), keyed by a stable fingerprint:
tw:<suite_id>:<case_id>:<assertion_id>:<failure_class>- First occurrence → a new issue is opened (built-in tracker, GitHub, GitLab, Jira).
- Re-occurrence → Tripwire searches the tracker for the fingerprint and comments on the existing issue ("↻ Reproduced again") instead of opening a duplicate.
The issue body carries: what failed (expected vs. observed), what worked, the root cause (failing request + trace_id), the server diagnosis (error, suspected cause, suggested fix, log evidence), severity (driven by p0/smoke tags), the run URL, and the commit.
So a flaky environment or a long-standing regression produces one living issue that accrues occurrences — not a pile of duplicates every nightly run.
Putting it together
A run against staging will:
- Self-heal through cosmetic and structural UI churn.
- Fail precisely when behavior regresses, capturing the failing request and
trace_id. - Root-cause it to the backend when logs are connected.
- File one deduped, root-caused issue to your tracker — and comment on it on subsequent runs.
Configure destinations in Filing issues and log backends in Connecting server logs. For the full engine picture, see Architecture.