Docs/SDK/Jest-Style Assertions

Jest-Style Assertions

Updated 2026-01-15 — Comprehensive guide to Sentience 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 Sentience?

In Sentience, an assertion is a predicate:

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 sentience import AsyncSentienceBrowser
from sentience.agent_runtime import AgentRuntime
from sentience.verification import exists
from sentience.tracing import Tracer, JsonlTraceSink

async with AsyncSentienceBrowser() as browser:
    page = await browser.new_page()
    await page.goto("https://example.com")

    tracer = Tracer(run_id="demo-run", sink=JsonlTraceSink("trace.jsonl"))
    runtime = await AgentRuntime.from_sentience_browser(browser=browser, page=page, tracer=tracer)

    await runtime.snapshot()
    runtime.assert_(exists("text~'Example Domain'"), label="example_domain_visible", required=True)

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:

Element predicates:

Composition:

Composed predicate example

from sentience.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", required=True)

State-Aware Assertions

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

from sentience.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'"), label="terms_checked", required=True)
runtime.assert_(value_contains("role=textbox name~'Email'", "@"), label="email_has_at_symbol")

Retrying Assertions with .eventually()

Sentience provides a fluent assertion handle:

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=0.25,
    min_confidence=0.7,
    max_snapshot_attempts=3,
)

Snapshot exhaustion

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

Optional vision fallback (last resort)

After snapshot_exhausted, you can optionally provide a vision-capable LLMProvider and let Sentience 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=2,
    vision_provider=llm,  # must supports_vision() and generate_with_image(...)
)

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 Sentience, the idea is the same: create a browser + runtime once, snapshot as needed, then close.

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

@pytest.fixture
async def runtime():
    tracer = Tracer(run_id="pytest-run", sink=JsonlTraceSink("trace.jsonl"))
    async with AsyncSentienceBrowser() as browser:
        page = await browser.new_page()
        rt = await AgentRuntime.from_sentience_browser(browser=browser, page=page, tracer=tracer)
        yield rt, page

@pytest.mark.asyncio
async def test_homepage_renders(runtime):
    rt, page = runtime
    await page.goto("https://example.com")
    await rt.snapshot()
    rt.assert_(exists("text~'Example Domain'"), label="example_domain_visible", required=True)

Guides

Use AgentRuntime

AgentRuntime is the recommended place to run assertions because it:

When to use basic vs state-aware assertions

How to read failures (reason codes + suggestions)

When an assertion fails, Sentience includes structured details:


FAQ

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

Short answer

Jest snapshots freeze output. Sentience 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:

Jest snapshot characteristics:

What a Sentience snapshot is

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

Sentience snapshot characteristics:

Key difference

AspectJest snapshotSentience 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:

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.