Core Usage

Waiting & Asserting

When an agent goes off the rails, it's usually because it waited wrong, not because it picked the wrong element. After a click or a fill, the page is still moving — what you wait for and how you confirm the action really landed decides whether your script is solid or brittle. This page spells out how wait waits, how expect asserts, how --observe shows you what changed, and how --if-present keeps optional steps idempotent.

observed.status is complete, partial, or unavailable, independently of the action success value. Missing evidence can produce changed:null, never a fabricated empty page or URL. A missing baseline returns the available after-tree instead of a diff. Incomplete observations include errors and retryAction:false; inspect current state rather than replaying the action.

Pick the right wait condition — wait

After every action that changes the page, wait for a specific condition instead of blindly sleeping a fixed number of milliseconds. wait supports these forms:

bash
$ chrome-use wait @e1                     # until an element appears
$ chrome-use wait 2000                    # dumb sleep, milliseconds (last resort)
$ chrome-use wait --text "Success"        # until this text shows up on the page
$ chrome-use wait --url "**/dashboard"    # until the URL matches the pattern (glob)
$ chrome-use wait --load networkidle       # until the network goes idle (post-navigation settle)
$ chrome-use wait --load domcontentloaded  # until DOMContentLoaded
$ chrome-use wait --fn "window.myApp.ready === true"  # until a JS condition holds

After any action that changes the page, pick one of these three:

⚠️ Avoid bare wait 2000
Outside of debugging, don't use a fixed-millisecond wait 2000 — it makes scripts slow and brittle. The timeout defaults to 25 seconds, so waiting on a real condition never costs much, and it continues the instant the condition is met.

The read waits for you — settle

An observation is only worth what the page was doing when it was taken, so snapshot and --observe wait for the page to stop changing before they capture. You do not have to guess a sleep in front of them:

Whichever takes longest wins, bounded by a 1 second ceiling. A static page costs about 100ms; a click that fires an XHR waits for the response instead of returning the pre-response tree as though it were the result.

They differ in one way. A plain snapshot has no action to react to, so a still page is its answer and it returns as soon as everything is quiet. After a mutating action, --observe keeps watching for a first reaction for half the ceiling (500ms by default) before it is willing to report changed:false — otherwise a control that renders on a 300ms timer reads as "nothing happened". Only actions that really change nothing pay that; anything that reacts ends the window at once.

bash
$ chrome-use snapshot -i --settle-ms 3000   # raise the ceiling for a slow page
$ chrome-use snapshot -i --no-settle        # capture now, mid-flight
⚠️ A ceiling that expires says so
When the page is still moving at the ceiling, the reply carries Page had not settled after 1000ms (request in flight still active) — this capture may be mid-transition. Re-read to confirm, or raise the ceiling with AGENT_BROWSER_SETTLE_MS. That means the tree may be mid-transition — re-read before trusting it. A mid-transition capture passed off as the final one is worse than being slow, so this line is never swallowed.

Since the wait already happened, the pixels can ride along with it: --with-screenshot <path> makes snapshot — or an action with --observe — leave both the structure and an image from that same settled moment (two separately-waited captures would describe two different states, which is worse than not combining them). The tree still goes to stdout and the image to disk: a screenshot is an output here, not the way an agent reads a page.

AGENT_BROWSER_SETTLE_MS sets the ceiling globally (0 disables the wait) and AGENT_BROWSER_SETTLE_QUIET_MS sets the quiet window. Waiting for something specific is still wait's job: the settle knows the page stopped, not that what you wanted appeared.


Confirm the action landed — expect

Once an action is done, confirm the result with an assertion instead of pulling a snapshot and eyeballing it. expect is a pass/fail verb with an exit code (0 pass / 1 false / 2 can't tell), so it composes with && and chrome-use batch, and it costs ~1 line — no reading a whole snapshot.

bash — expect assertions
$ chrome-use click @e8 && chrome-use expect "#toast" visible      # did the toast pop up?
$ chrome-use expect count ".result" ">=" 1                        # did results load?
$ chrome-use expect text @e3 contains "Saved"                    # success message?
$ chrome-use expect url contains /dashboard                       # did navigation land?
$ chrome-use expect "#spinner" gone                               # finished loading?
$ chrome-use requests --clear && chrome-use click @save \
    && chrome-use expect request /api/save --status 2xx           # POST fired and 2xx?
$ chrome-use expect no-errors                                     # no console errors?

expect waits for you

expect polls until the condition holds within the timeout window, so you often don't need a separate wait. The supported conditions are:

--not inverts the check, and --no-wait checks once without polling.

ℹ️ Prerequisites for requests and console
expect request can only see requests captured after tracking started — run requests --clear first; no-errors needs console capture — run console first. See Troubleshooting for more.

See what an action changed — --observe

Requests are summarized to at most 20 entries of 256 UTF-8 bytes each. Data URLs include their media header and encoded payload size, not the payload. JSON includes requestsTotal, requestsOmitted, and requestsShortened. Use network requests --json for full captured details. Request summaries still appear with changed:false, which describes tree and URL changes only.

Add --observe to any action that changes the page (click / fill / type / select / check / press / eval) and you skip the manual "action → wait → snapshot → diff" dance: the result carries an observed delta — interactive rows added/removed (new toasts and validation hints come through the alert channel too), URL changes, and requests fired; if nothing moved, it returns {changed:false}.

bash
$ chrome-use click @e8 --observe   # read changes and bounded request context after the action
ℹ️ On no change, read the why: line under it first
When nothing changed after an action, the daemon probes the target and reports the first decisive finding: the control is disabled, not rendered, outside the viewport, or covered by another element (named). Fix that one thing and retry the same semantic action. When the note says none of those apply, the action reached a control that genuinely changes nothing visible: do not read an empty delta as failure, and do not repeat the action.
ℹ️ When a click replaced the page, you get the new tree
For a link click or a submit that navigated, --observe no longer emits the old tree as removals plus the new tree as additions. It prints the new page's tree under observed snapshot:, with one line saying how many lines of the old page are gone. The rule: both trees are page-sized (20+ lines) and at least 80% of each changed. Refs in that tree are live; use them directly.
✅ expect or --observe?
For a hard pass/fail gate (with exit codes, &&, batches) use expect; to see what actually happened and get a structured diff, use --observe.

Fill a whole form at once — form fill --map

Instead of writing N separate fill / select / check steps, pass a {label or selector: value} map: it resolves each field by <label>, aria-label, placeholder, name, then CSS, and auto-dispatches to the right control type (string → text/dropdown/radio, true/false → checkbox). It can also submit for you (--submit "<text|selector>") and returns {filled, submitted, errors} — where errors are the inline validation messages, so a rejected submit tells you why in the same call.

bash — form fill --map
$ chrome-use form fill --map \
    '{"Email":"a@b.com","Country":"US","Subscribe":true}' \
    --submit "Sign up"
ℹ️ Rich-text editors are the exception
For rich-text editors like DraftJS / Monaco / CodeMirror, fill them individually with fill — it handles them; form fill covers standard controls. More input detail in Interacting.

Optional steps — --if-present

Add --if-present (aliased --optional) to any selector-based action, and when the target doesn't exist it becomes a no-op that still succeeds (↷ skipped, exit code 0) instead of erroring — no need to read the page first to decide, and the flow can be re-run over and over.

bash
$ chrome-use click ".cookie-accept" --if-present   # dismiss a cookie banner that may not be there
$ chrome-use check "#opt-in" --if-present
⚠️ Only skips "element not present"
--if-present only lets a missing target through; a real failure (say, the element is there but won't click) still errors and is not silently swallowed.

Next