Skip to main content

Testing with @reticlehq/test — declarative, signal-bound specs

Driving Reticle interactively is reconnaissance. To turn it into a repeatable, CI-runnable suite, write declarative specs with @reticlehq/test. Specs bind to signals and testids — never DOM structure — so they inherit Reticle’s refactor-resistance.

The test context t

A thin, typed façade over Reticle’s tools — it resolves testids → refs for you, so specs never touch refs or DOM: Any failed matcher throws with the structured evidence (near-miss, failure reason) so the runner reports why.

Deterministic + honest

  • t.clock bakes reticle_clock into the spec, so time-gated UI (a 5s auto-dismiss, a 500ms hover dwell) is tested deterministically instead of racing real timers.
  • t.expectInputModeReal() — a hover/drag spec asserts native input is active; if it’s running synthetic (no CDP), the spec is skipped with a reason, never silently passing on a no-op. Enable real input headless with reticle drive (see usage §18).

Run a suite (headless, the same path CI uses)

bootSession launches a headless real-input browser at your app and gives the runner a programmatic tool invoker (no MCP/stdio):
Each spec reports pass | fail (with evidence) | skip (with reason). For CI, emit JUnit:

Flows become specs

.reticle/ flows (see Flows) can be executed directly as specs — replayed with their expect/success predicates and skipping dynamic (LLM-output) regions — so the recorded map and the suite can’t drift apart:

Authoring tip: record → prune → commit

You don’t have to hand-write steps. Drive the flow once (or record it via the panel), let Reticle emit the program, trim it, and commit it as a spec — the regression test is a byproduct of testing, not separate work.