Skip to main content
A predicate declares what should be true. It is the argument to until on reticle_act_and_wait, and to predicate on reticle_assert and reticle_wait_for. Every predicate is { kind, ...fields }.

Not all predicates prove the same thing

Reticle grades them, and it will tell you when you have chosen a weak one. This is a real response from a text assertion:
Take the advice. element and text are convenient and weak; signal, state and net are what make a green trustworthy.

The leaves

element

query accepts role, name, text, label, placeholder, testid, alt, scope. state is one of visible, hidden, enabled, disabled, checked, expanded, focused, present. Add "absent": true to assert something is gone. The predicate for a removal, a dismissed toast, or a regression check.

text

Both contains and value are accepted. absent: true asserts the text is not present.

net

The one that catches the expensive bug:
Exactly one. Double-submit fails at two, and nothing on screen would have told you.

route

contains also works.
route asserts the route changed. Asserting the route you are already on always fails, and the response says so: expected: "a route change to /", assertion: "route.changed".

console

“The flow completed and logged nothing.” Worth attaching to most actions. Plenty of features work while quietly throwing.

signal

The app emitted a named signal via reticle.signal(). dataMatches is shallow JSON matching, and * means “present, any value”. Nothing outranks this. A 200 proves the server was reachable; a rendered row proves React ran. A signal is the application itself saying the thing succeeded. See instrumentation for how to emit them.

state

Walks a dot-path, with numeric array indices. equals takes a literal, or an operator pattern: Omit equals entirely to assert presence. This is the predicate that catches a UI-versus-store desync. A deploy that only looks shipped. Deterministically, in one call, with no model involved. On a miss it names the real store value and the keys that were available, so a failure is legible rather than a blind “no”.

animation

Combinators

A combinator needs its own kind, and the children go in predicates. A bare { "allOf": [ … ] } does not parse, and neither does { "kind": "allOf", "allOf": [ … ] }.Reticle refuses the call rather than running half of it: “Nothing ran, the predicate was not evaluated, so no verdict was produced.” Better to be told than to get a green from an assertion that never executed.
allOf is the workhorse. “The signal fired and exactly one request went out and the console stayed clean” is a genuinely strong check, and it is one call. Each child reports its own evidence:

Timing and scoping

The since cursor comes from a prior reticle_act response. Use it whenever you are asserting something an action was supposed to cause. Without it, an assertion can pass on an event from two clicks ago.

Where predicates get used

Naming the consequence before the action is the whole point.
Last modified on August 14, 2026