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.
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:
$ 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:
- Wait for the specific element you expect to appear:
wait @reforwait --text "...". - Wait for the URL to change:
wait --url "**/new-page". - Wait for the network to go idle (the catch-all for SPA navigations):
wait --load networkidle.
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.
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.
$ 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:
- Element state:
visible|hidden|gone|present; - Count:
count <css> <op> <n>; - Text/value/attribute:
text|value|attr … equals|contains|matches; - URL:
url …; - Network request:
request <substr> [--status 2xx]; - Console:
no-errors.
--not inverts the check, and --no-wait checks once without polling.
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
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}.
$ chrome-use click @e8 --observe # one ~20-80 token reply shows the dialog/toast/new rows that popped up
&&, 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.
$ chrome-use form fill --map \
'{"Email":"a@b.com","Country":"US","Subscribe":true}' \
--submit "Sign up"
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.
$ chrome-use click ".cookie-accept" --if-present # dismiss a cookie banner that may not be there
$ chrome-use check "#opt-in" --if-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.