Getting Started

The Core Loop

To drive the browser through a task, the rhythm is almost always these four steps:open the page → see what's on it → act on the @ref the snapshot gave you → snapshot again the moment the page changes. Get this loop flowing, then understand when @ref goes stale and when it self-heals, and you've got ninety percent of chrome-use's day-to-day usage.

The four-step loop

Each step decides based on the result of the last: snapshot -i gives you a structured view containing only interactive elements, each carrying a short @ref; you take a ref to click / type; the moment the page changes, snapshot again to get fresh refs.

core loop
$ chrome-use open <url>        # 1. open a page
$ chrome-use snapshot -i       # 2. see what's on the page (interactive elements only)
$ chrome-use click @e3         # 3. act on the ref the snapshot gave you
$ chrome-use snapshot -i       # 4. the page changed, so snapshot again

The browser stays running between commands, so these feel like one session. When you're done, wrap up with chrome-use close (or close --all). To read the page, find elements, or interact in more detail, see Reading the Page, Finding Elements, and Interacting.

ℹ️ A complete little example
Search, click a result, screenshot — that's just the loop running:
search → click → capture
$ chrome-use open https://duckduckgo.com
$ chrome-use snapshot -i                      # find the search box's ref
$ chrome-use fill @e1 "chrome-use cli"
$ chrome-use press Enter
$ chrome-use wait --load networkidle
$ chrome-use snapshot -i                      # the refs now reflect the search results
$ chrome-use click @e5                        # click a result
$ chrome-use screenshot result.png

@ref goes stale: snapshot again the moment the page changes

Refs (@e1, @e2, …) are reassigned on every snapshot. They go stale the moment the page changes — a navigating click, a form submit, a dynamic re-render, or a modal opening can all invalidate them. So after a navigation or a structural change, snapshot again before your next ref-based interaction.

⚠️ "Ref not found" / "Element not found: @eN"
This is almost always the same cause: the page changed after the snapshot. Don't guess — just chrome-use snapshot -i again and use the new ref.

@ref also self-heals: you don't have to re-snapshot for every tiny DOM change

Each ref records a fingerprint — role + accessible name + ancestor path. When you use it, if the original node is gone, chrome-use automatically re-locates the matching element on the current page and carries on. So after a React / Vue list re-render (labels unchanged), click @e3 still hits the right element.

If the element is genuinely gone, it fails loudly rather than clicking the wrong node — it never silently mis-targets. So: you only need to re-snapshot when you've navigated, or when the labels / structure genuinely changed.

✅ When you must re-snapshot
Navigated to a new page, switched tabs, structure changed significantly after a form submit, a popup / dialog opened — always snapshot -i again after these. A plain list re-render (labels unchanged) doesn't need it; let ref self-healing handle it.

Hard rule: snapshot-first, never locate by screenshot

For form fields and buttons, always snapshot -i first, then act on the ref / selector. Do not use screenshot + coordinate clicks to find or hit elements.

snapshot -i now pierces cross-origin iframes (embedded Google Payments / Stripe / checkout / KYC forms), listing their elements — along with their input values — by @ref, so click @e / type @e / fill @e reach straight into the iframe. Only use coordinates for canvas / WebGL, or when snapshot genuinely returns nothing for your target.

⚠️ A screenshot is output, not input
Screenshots are for visual verification you report to the user, never as the agent's own input. And a full-page screenshot of a real retina browser is often too large for an image reader to take in. If you feel you "need a screenshot to read state or find an element," that's a bug — please file an issue. Driving by pixels over the relay carries an extra risk: a coordinate event can drift to the user's foreground tab, whereas a ref never does.

Two different intents — only one is discouraged

The rule above targets screenshot-to-locate (using an image to find / hit an element) — that's the bug. Whereas screenshot-to-capture (saving a region or element to a file as reusable image assets — maps, charts, og images, visual-regression baselines, report illustrations) is fully supported and encouraged:

capture is the right use
# save a rendered region as a reusable image asset (not for locating)
$ chrome-use screenshot [selector] --clip x,y,w,h <file>

# want to click something a ref can't hit? box gives the element's CSS-pixel box + center
$ chrome-use box @ref
$ chrome-use click <centerX> <centerY>

Screenshots auto-downsample to a longest edge of ≤ 2000px so image readers can take them in and so pixels line up with click x y; override with --max-width / --max-height / --scale. To click an element a ref can't hit, box @ref gives its CSS-pixel box and centerX/centerY, which you feed straight to click <centerX> <centerY> — no screenshot needed. For canvas / WebGL coordinate tricks, see Screenshots · Recording · Viewport and the canvas-related sections.


Two driving paths — and when to switch to eval

What you have in hand is a real Chrome with the user's real DOM. There are two layers, and you can mix them freely:

🧩 The structured path

snapshot + @ref, find, typed actions. Convenient and readable, good for straightforward forms and navigation. But the accessibility view is lossy and fragile: refs go stale on any change, hidden inputs never appear, and overlays block coordinate clicks.

🛠️ The eval-first path

chrome-use eval "<js>" — your eyes and hands on the real DOM: read hidden inputs, reach into Shadow DOM / iframes, inspect form.elements and .validity, extract the exact structure you want, or just el.click().

✅ The moment the structured path fights you, switch to eval
The moment the structured path starts fighting you, switch to eval instead of retrying it over and over — it's the fastest way to find out "why it failed" (e.g. a hidden point_choice=none the UI never exposes). Don't try a flaky structured locator three times; drop straight to eval and act on the DOM.
what's actually in this form / why won't it submit?
# list every field's name / type / value / checked in the form
$ chrome-use eval "[...document.forms[0].elements].map(e=>[e.name,e.type,e.value,e.checked])"

# read a hidden field the UI never exposes
$ chrome-use eval "document.querySelector('[name=point_choice]')?.value"

# find every field that fails validation and its error message
$ chrome-use eval "[...document.forms[0].elements].filter(e=>!e.validity.valid).map(e=>e.name+': '+e.validationMessage)"

# click directly via the DOM, bypassing overlays
$ chrome-use eval "document.querySelector('#stubborn').click()"

The escalation ladder is: for straightforward pages, snapshot + @eN is fastest → when you don't want to snapshot, use find role/text/label → native CSS selectors → the moment any of the above fights you, go to eval and act on the DOM directly. For more locating techniques, see Finding Elements.

ℹ️ eval runs in the page's MAIN world
eval runs by default in the top document's main world, and state persists across calls — so a top-level const x / let x will collide with the next call (Identifier 'x' has already been declared). Use unique names, attach to window.x, or wrap the body in an IIFE. For quotes / non-ASCII / large scripts, use eval --stdin (heredoc), eval --file <path>, or eval -b <base64> instead of an inline form the shell will mangle.

Next steps

Once the core loop is flowing, dive deeper as needed: