Example runs and specs
These are real runs of two specs against the demo app in examples/, captured with the CLI in this repo. Apart from replacing absolute local paths with run-dir or repo-relative paths, nothing here is hand-edited: the reports are the files cairn wrote.
- A failing run:
examples/flows/demos/_06-drifted-link.yml, which clicks a link by its old name, "Dashboard", after the page renamed it to "Open dashboard". - A passing run:
examples/flows/01-dashboard-nav.yml, which follows "Open dashboard" to the dashboard.
Reports and agent context
Failing run (drifted_dashboard_nav):
Passing run (dashboard_nav):
The trace file of the failing run (about 12 MB) is not published here, so the Trace link inside that report does not resolve.
Setup used
The demo app serves on http://localhost:8787 and needs the demo Postgres from examples/docker-compose.yaml (port 5433) seeded with bun examples/demo-app/db/seed.ts. examples/cairntrace.config.yml can start both for you (webServer and services). The runs below were started with the app and database already up, so they used --no-services --no-web-server.
bun examples/demo-app/server.ts
./bin/cairn run examples/flows/demos/_06-drifted-link.yml --cold-start --no-services --no-web-server
./bin/cairn run examples/flows/01-dashboard-nav.yml --cold-start --no-services --no-web-server
./bin/cairn spec heal examples/flows/demos/_06-drifted-link.ymlspec heal was run without --apply, so the example spec was not changed.
Failing run: the spec and its output
The spec, copied from examples/flows/demos/_06-drifted-link.yml:
# Cairntrace example spec — selector drift, healed by `cairn spec heal`.
#
# This spec is INTENTIONALLY broken: it was written when the link was named
# "Dashboard", and the page has since renamed it to "Open dashboard". Running it
# fails with a locator-not-found error.
#
# Then run:
# ./bin/cairn spec heal examples/flows/demos/_06-drifted-link.yml
# to see Cairntrace inspect the snapshot and propose:
# replace /steps/1/click/name from "Dashboard" to "Open dashboard"
#
# Add --apply to write the fix back to the file (comments preserved), then re-run
# with `cairn run examples/flows/demos/_06-drifted-link.yml` to confirm it passes.
#
version: 1
name: drifted_dashboard_nav
intent: navigate to dashboard via a renamed link (simulates UI drift)
environment: local
preconditions:
commands:
- run: "echo demo-app must be running on :8787"
outcomes:
- id: url_is_dashboard
description: after clicking, URL ends with /dashboard.html
verify:
url:
endsWith: /dashboard.html
steps:
- id: open_home
open: http://localhost:8787/
- id: click_link
click:
by: role
role: link
name: Dashboardcairn run output (narration on stdout, then the Markdown summary):
[07:50:43] run start: drifted_dashboard_nav (env=local, backend=agent-browser)
[07:50:43] run dir: run-dir
[07:50:43] precondition precondition[0] started (budget 2m 0s)
[07:50:43] precondition precondition[0] ok 9ms
[07:50:49] step open_home passed 339ms
[07:50:59] step click_link failed 10.7s
element not found: could not resolve role=link name="Dashboard" for click within 10000ms
matching candidates (6):
- link "Open dashboard" ref=e3
- link "Products" ref=e4
- link "Documents" ref=…
[07:51:03] outcomes: evaluating 1
[07:51:03] outcome url_is_dashboard verifying…
[07:51:03] outcome url_is_dashboard failed
expected: URL endsWith "/dashboard.html"
actual: URL was "http://localhost:8787/"
# Run: drifted_dashboard_nav — FAILED
- env: local | backend: agent-browser | cold-start: yes
- duration: 20.2s | outcomes: 0/1 passed
- run id: 2026-10-14T07-50-43-854Z_drifted_dashboard_nav_675ce7
- reason: element not found: could not resolve role=link name="Dashboard" for click within 10000ms matching candidates (6): - link "Open dashboard" ref=e3 - link "Products" ref=e4 - link "Documents" ref=e5 - link "Form controls" ref=e6 - link "Vendor profile" ref=e7 - link "Sign in" ref=e8
## Outcomes
- ✗ url_is_dashboard → outcomes/url_is_dashboard.md
## Steps
- ✓ open_home (339ms)
- ✗ click_link (10.7s) — element not found: could not resolve role=link name="Dashboard" for click within 10000ms
matching candidates (6):
- li…
## Reproduce
```bash
cairn run examples/flows/demos/_06-drifted-link.yml --env local
```
Run dir: run-dir
Agent context: run-dir/agent_context.mdThe reproduce line is shortened to a repo-relative path from the checkout root.
cairn spec heal on the same spec proposes the rename (no --apply, trimmed to the proposal):
proposed 1 op(s) on examples/flows/demos/_06-drifted-link.yml; pass --apply to write
## Proposed ops (1)
- **replace** `/steps/1/click/name`
- from: "Dashboard"
- to: "Open dashboard"
- why: snapshot shows role=link with name="Open dashboard" (6 candidates)cairn spec heal --verify applies the same op to a temporary copy, reruns it from a cold browser and writes the fix only when the rerun passes (verified: yes). The contract hash covers intent and outcomes, so a heal can change the click target but never the definition of done.
Passing run: the spec and its output
The spec, copied from examples/flows/01-dashboard-nav.yml:
# Cairntrace example spec — navigation + URL/text outcomes
#
# Demonstrates: open → click → wait → URL outcome + text outcome.
# Run against the demo app served at http://localhost:8787 (see examples/README.md).
#
version: 1
name: dashboard_nav
intent: from the home page, follow "Open dashboard" and land on a working dashboard
environment: local
preconditions:
commands:
- run: "echo demo-app must be running on :8787"
outcomes:
- id: url_is_dashboard
description: after clicking the link, URL ends with /dashboard.html
verify:
url:
endsWith: /dashboard.html
- id: dashboard_heading_visible
description: the page shows the "Dashboard" heading
verify:
text:
contains: Dashboard
- id: no_console_errors
description: the navigation produced no console errors
verify:
console:
errorsMax: 0
steps:
- id: open_home
open: http://localhost:8787/
- id: click_open_dashboard
click:
by: role
role: link
name: Open dashboard
- id: wait_for_dashboard
wait:
text: Dashboard
timeoutMs: 5000cairn run output:
[06:23:38] run start: dashboard_nav (env=local, backend=agent-browser)
[06:23:38] run dir: run-dir
[06:23:38] precondition precondition[0] started (budget 2m 0s)
[06:23:38] precondition precondition[0] ok 4ms
[06:23:39] step open_home passed 247ms
[06:23:40] step click_open_dashboard passed 875ms
[06:23:41] step wait_for_dashboard passed 201ms
[06:23:41] outcomes: evaluating 3
[06:23:41] outcome url_is_dashboard verifying…
[06:23:42] outcome url_is_dashboard passed
[06:23:42] outcome dashboard_heading_visible verifying…
[06:23:42] outcome dashboard_heading_visible passed
[06:23:42] outcome no_console_errors verifying…
[06:23:42] outcome no_console_errors passed
# Run: dashboard_nav — PASSED
- env: local | backend: agent-browser | cold-start: yes
- duration: 4.2s | outcomes: 3/3 passed
- run id: 2026-10-14T06-23-38-052Z_dashboard_nav_68a219
## Outcomes
- ✓ url_is_dashboard → outcomes/url_is_dashboard.md
- ✓ dashboard_heading_visible → outcomes/dashboard_heading_visible.md
- ✓ no_console_errors → outcomes/no_console_errors.md
## Steps
- ✓ open_home (247ms)
- ✓ click_open_dashboard (875ms)
- ✓ wait_for_dashboard (201ms)Other example flows
Every flow in examples/flows/ runs against the demo app. Files starting with _ (in demos/) are intentionally failing and are not part of the passing set.
02-row-count.yml: counts the inventory table rows (exactly 3) and checks that no request failed.03-network.yml: checks that the page calls/api/inventoryand gets a 2xx response.04-script.yml: uses a page-evaluated script to assert that exactly 3[data-item]elements exist after a fetch.07-config-driven.yml: opens the dashboard through relative paths resolved from the environmentbaseUrl.08-conditional-step.yml: control flow withif,repeatwithuntil, optional waits andwhengates.10-artifact-xlsx.yml: downloads a workbook template, checks it with thexlsxverifier, and rejects a transformed invalid upload.11-batch-hover-click.yml: runs a hover and a click as onebatchstep so a hover-revealed button is acted on in one invocation.12-focus-value.yml: focuses a control, types into it and waits for its exact live value, withouteval.13-widgets-form.yml: fills custom form controls on/widgets.htmlwith the widget steps (form,choose,check, optional clicks).demos/_05-detects-broken-api.yml: fails by design; itsnoFailedRequestsoutcome is meant to surface the 500 from/api/broken.demos/_09-imported-drift.yml: fails by design; a stale locator sits in the importedexamples/actions/open_dashboard.yml, so heal should patch the action file, not the spec.platform/20-login-journey.ymltoplatform/33-export-workbook.yml: the platform flows against the Postgres-backed app: sign-in, catalog, product create and duplicate SKU, document upload and preview, CSV/XLSX exports, API session and API login, form controls, auth redirect, async restock job,run:/teardown:provisioning and the workbookxlsxcheck.