Docs/SDK/Jest-Style Assertions

Jest-Style Assertions

Updated 2026-01-15 — Comprehensive guide to Predicate assertions: predicates, state-aware checks, retries, confidence gating, and optional vision fallback.

Think Jest for AI web agents. Take a browser snapshot (rendered DOM + layout), then evaluate predicates that return pass/fail with structured details.


Introduction

What is an assertion in Predicate?

In Predicate, an assertion is a predicate:

  • Input: AssertContext (current snapshot, current url, and stepId)
  • Output: AssertOutcome (passed, reason, details)

Two layers

Predicates are pure functions — they read snapshot state and return AssertOutcome. Runtime execution decides how and when to evaluate predicates (once vs retry, confidence gating, fallback, tracing).


Getting Started

Install

pip install sentienceapi

Quick start: assert that text exists

from predicate import AsyncPredicateBrowser
from predicate.agent_runtime import AgentRuntime
from predicate.verification import exists
from predicate.tracing import Tracer, JsonlTraceSink

async with AsyncPredicateBrowser() as browser:

Python keyword

assert is a Python keyword, so the Python API uses assert_ instead.


Using Assertions

Basic predicates

These predicates are for simple checks:

URL predicates:

  • Python: url_matches, url_contains
  • TypeScript: urlMatches, urlContains

Element predicates:

  • Python: exists, not_exists, element_count
  • TypeScript: exists, notExists, elementCount

Composition:

  • Python: all_of, any_of, custom
  • TypeScript: allOf, anyOf, custom

Composed predicate example

from predicate.verification import all_of, exists, url_contains

on_checkout = all_of(
    url_contains("/checkout"),
    exists("text~'Order summary'")
)
runtime.assert_(on_checkout, label="checkout_loaded"

State-Aware Assertions

These predicates are deterministic checks against element state extracted from the live DOM:

  • is_enabled / isEnabled — enabled/disabled
  • is_checked / isChecked — checked/unchecked
  • value_contains / valueContains — value equals/contains
  • is_expanded / isExpanded — expanded/collapsed
from predicate.verification import is_enabled, is_checked, value_contains

runtime.assert_(is_enabled("role=button text~'Continue'"), label="continue_enabled", required=True)
runtime.assert_(is_checked("role=checkbox name~'Accept terms'"

Assertion DSL (E, expect)

The DSL gives you a more expressive, Jest-like API while staying deterministic: DSL expressions compile to predicates you can pass into runtime.assert/assert_ and runtime.check(...).eventually().

Not a separate system

The DSL is just a nicer way to build predicates. The runtime still decides when to snapshot, retry, and emit trace events.

Import

from predicate.asserts import E, expect, in_dominant_list

Element Query Builder: E()

Build element queries by semantic attributes (role/text/name/href) plus visibility state:

# By role / text
E(role="button")
E(role="button", text_contains="Submit")

# By href
E(role="link", href_contains="/checkout")

# Visibility filters

Expectation Builder: expect()

Use expect(...) to create matchers like toExist() / toBeVisible():

# Element exists
runtime.assert_(
    expect(E(role="button", text_contains="Save")).to_exist(),
    label="save_button_exists",
    required=True,

Global Text Assertions

runtime.assert_(
    expect.text_present("Welcome back"),
    label="user_logged_in",
)

runtime.assert_(
    expect.no_text("Error"),
    label="no_errors",

Dominant List Queries: in_dominant_list() / inDominantList()

For “click the first / third / top‑N result” tasks, query the dominant group as an ordered list:

runtime.assert_(
    expect(in_dominant_list().nth(0)).to_have_text_contains("Show HN"),
    label="first_item_is_show_hn",
)

top_5 = in_dominant_list().

Retrying Assertions with .eventually()

Predicate provides a fluent assertion handle:

  • .once() — evaluates a predicate once (same semantics as assert/assert_)
  • .eventually() — retries with fresh snapshots until success, timeout, or exhaustion
ok = await runtime.check(
    exists("role=button text~'Continue'"),
    label="continue_visible",
    required=True,
).eventually(timeout_s=10, poll_s=0.25

Snapshot confidence gating + exhaustion

If you pass minConfidence / min_confidence, .eventually() can treat low-confidence snapshots as failures and resnapshot.

ok = await runtime.check(
    exists("text~'Payment method'"),
    label="payment_ready",
    required=True,
).eventually(
    timeout_s=15,
    poll_s=

Snapshot exhaustion

If confidence stays below the threshold for maxSnapshotAttempts, the assertion ends with reason_code = "snapshot_exhausted" and includes snapshot.diagnostics for debugging.

Expanded deterministic verifications: adaptive resnapshotting

On long / virtualized pages, a small snapshot limit can miss the target element and cause a false failure. You can make .eventually() more reliable by growing the snapshot element limit across retries.

from predicate.verification import exists

ok = await runtime.check(
    exists("text~'Checkout'"),
    label="checkout_visible",
    required=True,
).eventually(
    timeout_s

More verification primitives

See Verifications for scroll verification and other “expanded verification” patterns.

Optional vision fallback (last resort)

After snapshot_exhausted, you can optionally provide a vision-capable LLMProvider and let Predicate ask a strict YES/NO verifier question using a screenshot.

ok = await runtime.check(
    exists("text~'Order confirmed'"),
    label="order_confirmed",
    required=True,
).eventually(
    min_confidence=0.8,
    max_snapshot_attempts

Vision is last resort

The verifier question is derived from the assertion label. Vision is only used after structured snapshot attempts are exhausted — never by default.


Setup and Teardown

Jest uses beforeAll/beforeEach/afterEach/afterAll. In Predicate, the idea is the same: create a browser + runtime once, snapshot as needed, then close.

# pytest-asyncio example
import pytest
from predicate import AsyncPredicateBrowser
from predicate.agent_runtime import AgentRuntime
from predicate.tracing import Tracer, JsonlTraceSink
from predicate.verification import exists

@pytest.fixture

Guides

Use AgentRuntime

AgentRuntime is the recommended place to run assertions because it:

  • keeps the latest snapshot as assertion context
  • emits trace events (useful for debugging / Studio)
  • provides .check().eventually() for robust "wait until true" verification

When to use basic vs state-aware assertions

  • Use basic assertions for "presence/absence/count/url" checks.
  • Use state-aware assertions when you need deterministic checks of element state (enabled/checked/value/expanded) that commonly changes in SPAs.

How to read failures (reason codes + suggestions)

When an assertion fails, Predicate includes structured details:

  • reason_code (e.g. no_match, state_mismatch, snapshot_low_confidence, snapshot_exhausted)
  • nearest_matches (best-effort suggestions when a selector fails)

FAQ

Jest also has snapshot testing — how is that different from a Predicate snapshot?

Short answer

Jest snapshots freeze output. Predicate snapshots model interactive state. They solve different problems, even though they share the word "snapshot".

What Jest snapshot testing is

Jest snapshots are essentially golden files:

  • serialized React component trees
  • HTML strings, JSON responses, text output
  • captures a static representation
  • answers: "Did this output change?"

Jest snapshot characteristics:

  • Static: captures a representation at one point in time
  • Text/JSON-based: stored as serialized snapshots
  • Comparison-oriented: meant to diff over time
  • Great for regression detection
  • Not interaction-aware: no notion of timing, user actions, or success

What a Predicate snapshot is

A Predicate snapshot is a runtime perception frame of a live browser:

  • rendered DOM after hydration
  • semantic roles + names
  • UI state (enabled, checked, expanded, value)
  • geometry + ordering
  • grouping (lists, cards, feeds)
  • stability diagnostics (confidence, quiet time)

Predicate snapshot characteristics:

  • Live: gathered from a running page, not static HTML
  • Semantic: role/name/text-focused, not raw HTML strings
  • Spatial/ordinal-aware: uses layout and ordering
  • Repeated during execution
  • Designed for verification: evaluate assertions, not diff against golden files

Key difference

AspectJest snapshotPredicate snapshot
PurposeDetect output changesEnable reasoning & verification
LifecycleStored & diffedEphemeral, per-step
InputRender output / stringsLive browser state
StabilityAssumes determinismMeasures instability
Used forRegression testingAgent execution + QA verification
Handles SPAsPoorlyNatively
Geometry / ordinalityNoYes

Do I need .eventually() for every assertion?

No. A good rule of thumb:

  • Use single-shot assert/assert_ for stable, immediate checks (URL checks, "element exists", etc.)
  • Use .check(...).eventually() for anything that depends on async UI timing (SPAs, loading states, transitions, network-backed components)

Why does .eventually() sometimes fail with snapshot_low_confidence / snapshot_exhausted?

If you set minConfidence / min_confidence, the runtime treats low-confidence snapshots as unreliable and will resnapshot. After maxSnapshotAttempts / max_snapshot_attempts, it stops with snapshot_exhausted to avoid infinite retries and surfaces snapshot.diagnostics in details to help you debug instability.


  • Agent Runtime — Runtime verification support for agent loops
  • AI-Driven QA — Enterprise QA workflows with Predicate assertions