Core Usage
Interacting
Once you've read the page, it's time to act. This page covers every interaction verb for driving a
real browser—click, type, check, select, upload, scroll, drag.
The first argument to most commands is the @ref you got from
snapshot -i (e.g. @e42); first find the element,
then act on it with the commands here.
Clicking & pointer
The most-used group of verbs, all acting on a single @ref. click auto-scrolls the
element into the viewport, and if the coordinate click is occluded it falls back to the DOM's .click().
$ chrome-use click @e1 # click
$ chrome-use click @e1 --new-tab # open the link in a new tab instead of navigating the current one
$ chrome-use dblclick @e1 # double-click
$ chrome-use hover @e1 # hover
$ chrome-use focus @e1 # focus (often used before keyboard input)
<li> closes the moment the input loses focus.
Try again with a DOM-dispatched click:
AGENT_BROWSER_CLICK_MODE=dom chrome-use click @e1,
or just use eval to select that item in the page.
Typing text
fill clears first and then types, while type appends after the existing content.
The default path is insertText (fast), but many fields with autocomplete only react to
real keystrokes—that's when you add --key-events.
$ chrome-use fill @e2 "hello" # clear, then type
$ chrome-use type @e2 " world" # don't clear, just append
$ chrome-use type @e2 "hello" --clear # type can clear the field first too
$ chrome-use type @e2 "hello" --delay 80 # 80ms between keystrokes
# real keystrokes (not insertText)—for autocomplete/combobox fields that
# only respond to key events, e.g. auto-filling city from a postcode (Google Places, etc.)
$ chrome-use type @e5 "201-0001" --key-events
# real keystrokes plus Enter to submit the candidate (--enter implies --key-events)—
# for async autocomplete / tag controls, e.g. Juejin's "add tag"
$ chrome-use type @e6 "ChatGPT" --enter
--enter
in a single step. If you'd rather pick one from a candidate list, first run
type … --key-events to trigger the dropdown, then snapshot -i to find the candidate and
click @ref.
--clear lets type clear the field first too (no need to switch back to
fill); --delay <ms> inserts a fixed gap between keystrokes to mimic
a slower, more human typing cadence. Both are dedicated flags — neither leaks into the text
that actually gets typed.
Keyboard
press presses a single key on the current focus (down + up), and also supports key combos.
keydown/keyup split the press and release apart, and used as a pair they let you "hold".
$ chrome-use press Enter # press a key on the current focus
$ chrome-use press Control+a # key combo
$ chrome-use keydown d # hold a key down (no auto release)
$ chrome-use keyup d # release—use as a pair to "hold to move"
# in a game: keydown d; sleep; keyup d
press d --hold 800 is better—the hold duration is timed inside the daemon,
with ~250ms less jitter per cycle than keydown + shell sleep + keyup. See
Canvas & games.
Check, select and dropdowns
Checkboxes use check/uncheck. For dropdowns, both a native
<select> and a custom combobox (react-select / ARIA, etc.) work with
select — since v1.5.72 select falls through to portal-aware
logic on a non-native control (open → wait for options → click by visible text);
pick is the explicit alias for the same behavior.
$ chrome-use check @e3 # check a checkbox
$ chrome-use uncheck @e3 # uncheck
$ chrome-use select @e4 "Pageview" # native <select> OR a custom combobox (v1.5.72+)
$ chrome-use select @e4 "a" "b" # multi-select
# any combobox: open it, wait for the menu to appear (including portal-rendered),
# match by visible text, dispatch the right events; errors out if the option never
# appears (never silently no-ops)
$ chrome-use pick @e4 --option "Europe"
select used to "return ✓ but change nothing" on custom dropdowns (a silent
false success) — now fixed: non-native controls go through the portal-aware path and
error loudly (listing the visible options) when the option never appears, never silently.
fill is hardened the same way: it fills a module-scoped Monaco editor
(synthetic paste when window.monaco isn't reachable) and controlled inputs whose
@ref anchored a wrapper (retargets to the nested editable). Backends like
Cloudflare Zaraz (react-select + Monaco + shadow-DOM) now drive with native
select/fill — no hand-rolled eval.
Uploading files
upload feeds a file to a file <input> or to a drop/paste-style editor (like X's
composer). It also works over the extension relay.
$ chrome-use upload @e5 file1.pdf # upload a file
chrome.debugger forbids setFileInputFiles, so the file bytes are streamed into the page
and reconstructed there into a File (chunked to native-messaging's 1 MiB cap). That's why both file
<input> elements and drop/paste editors receive it.
Scrolling
scroll scrolls the page in a direction; scrollintoview scrolls a specific element into
the viewport. For content inside cross-origin iframes (payment / checkout / KYC widgets), ordinary page scrolling
can't reach it—use --at x,y to wheel at a pixel point, or --frame n to target a frame.
$ chrome-use scroll down 500 # scroll the page (up/down/left/right)
$ chrome-use scroll down 700 --at 640,400 # wheel at a pixel point—reaches cross-origin iframes
# (Stripe/checkout/KYC) that ordinary page scrolling can't
$ chrome-use scroll down 700 --frame 2 # scroll frame #2 from `chrome-use frames`
$ chrome-use scrollintoview @e1 # scroll the element into the viewport
Drag & slider CAPTCHAs
drag can both drop between two refs and drag a handle by a pixel offset (sliders / canvas).
For the NetEase Yidun slider CAPTCHA common to unattended logins, use solve-slider
to pass it automatically.
$ chrome-use drag @e1 @e2 # drag and drop
$ chrome-use drag @e1 60 # drag the handle +60px (slider/canvas); `+60,-3` means dx,dy
$ chrome-use solve-slider # auto-solve the NetEase Yidun slider CAPTCHA on the page
$ chrome-use solve-slider 5 # …retry up to 5 times (each failure refreshes the puzzle)
AGENT_BROWSER_HUMANIZE.
Cross-origin iframes: always act by ref
Embedded payment / checkout / KYC widgets (Google Payments, Stripe, etc.) are cross-process out-of-process iframes.
Drive them by ref, never by screenshot.
snapshot -i pierces these iframes and lists the elements inside them by @ref (including
input values); get text --all-frames reads their text. Once you have the ref, act on it as usual.
# snapshot -i lists elements inside cross-origin iframes, then:
$ chrome-use click @e
$ chrome-use type @e "…"
$ chrome-use hover @e
$ chrome-use dblclick @e
$ chrome-use drag @a @b
# postcode/autocomplete field inside the frame—use real keystrokes:
$ chrome-use type @e "201-0001" --key-events
scroll down N --at x,y (a pixel point on the frame) or --frame n.
find/selectors match the page DOM (querySelectorAll), so for these two kinds of elements
they report "Element not found"—even though snapshot -i can list them (it goes through the CDP
accessibility tree, which pierces both) and get text can read them. In that case locate by the
@ref from the snapshot instead of find; box @ref gives coordinates as a
fallback. Also, verify the exact label text before concluding an element "doesn't exist":
LinkedIn's "Save" button is actually labeled 收藏, not 保存, and
snapshot -i has always shown button "收藏" [ref=eN]—just click @eN.
Next
Interacting is just one link in the core loop; read it alongside these pages: