How navigera works
The three ways to run navigera; what it does between commands: waits, settling, tabs, dialogs.
navigera drives a real Chrome over the DevTools Protocol from one native binary. There are three ways to run it:
| mode | how | for |
|---|---|---|
| session | navigera -s <name> start, then one command per process, then quit | agents’ shell tools, people at a terminal |
| serve | navigera serve; JSON commands on stdin, one JSON response per line | programs holding a pipe open |
| one-shot | navigera <command> … | a single action (launches and closes a browser) |
The agent-oriented guide is the skill,
also printed by navigera skill. This page is the full reference.
Behaviour
- Snapshots wait for fetched data. Before
axandscreenshot, navigera waits until the tab has had nofetch/XHR in flight for 500 ms (the usual network-idle window), at most 3 s; requests open longer than 5 s (long polls, streams) don’t count. A page that has been quiet, as it usually is while an agent thinks, is read at once. Sogoto … && … axshows a single-page app’s list, not its “Loading…” placeholder. A request to a URL the page has already fetched twice at a steady interval is a poll and doesn’t count, so a dashboard that refreshes every few hundred milliseconds is read at once. After an action whose own requests have started (a click that fetches), the snapshot waits for those requests and 100 ms more, not the whole window: a click that loads data for 200 ms is read about 300 ms later, with the data. A window set withset network-quiet <ms>holds after actions too.--no-wait(onax,screenshotandax --grep) skips the wait. - Clicks are trusted mouse events at the element’s centre after
scrolling it into view, once the element has stopped moving: CSS
animations and transitions running on it or an ancestor (a dialog sliding
in) finish first, as a user waits for them, and pages often wire a
dialog’s buttons only once it has opened. With nothing animating this
costs nothing. navigera checks that the press reached the page:
Chrome occasionally acknowledges mouse events it never delivers (seen
right after a tab switch), and then the click is sent once more, or as
DOM events if it still doesn’t arrive (
synthetic_clicksays so). Same-origin iframe offsets are added in the page; inside a cross-origin iframe (refs only) the box comes fromDOM.getContentQuads.- A styled checkbox or radio (the
<input>hidden or covered by its own label’s box) is clicked through its label, as a user would. - An element that never holds still (a perpetual drift) gets a second in all to stop, then is clicked where it was last read.
- If something else covers the element’s centre (a toast, a sticky
header, an overlay), the element is tried at the edges of the viewport,
then the click waits for the cover to go (up to the element wait). If it
stays covered the click fails and names the covering element and its
controls: a user would have clicked that instead. A cover that stays put
with controls of its own (a cookie banner, a dialog) is reported after a
second, unless the page is still fetching (a loading overlay with a
Cancel button goes when its data arrives).
click --forcesends DOM events to the element regardless (synthetic_click).filllikewise scrolls a field out from under a panel before setting it. - A disabled control is waited for (up to the element wait) and then
refused;
filldoes the same for a disabled or read-only field. - An element a user can’t see where it is (zero size, clipped, or under
its own wrapper, like the real input of a styled radio card) fails at
once, naming what a click there would hit and, where there is one, what
stands for it (its label, or the clickable card around it).
--forcesends DOM events to it. - If the click starts a navigation, the command returns once the new document is parsed.
- A click that changes nothing says so. From just before the press,
navigera watches the page’s DOM (the document, the element’s frame and
every open shadow root), events a DOM observer can’t see (input,
change, submit, toggle, invalid, copy, a scroll, a navigation
starting), clipboard writes, focus moving elsewhere, form values and
the URL a script set, and what only the browser sees (navigations,
dialogs, new tabs, downloads, requests). The first sign ends the
watch, usually within the round trip that already checked delivery.
Requests that are the page reporting on itself don’t count: beacons
and well-known analytics services are ignored, and a first-party
request that reads like reporting (
/event/…,/collect,/log) is waited for (up to 1.5 s) and judged by what the page does with the answer. With nothing after 200 ms (or after that answer), the result carriesno_effect, naming any reporting requests: often a control the page hasn’t wired up yet, so check for what the click should do before clicking again. Clicks on fields,<select>s and right or middle clicks aren’t judged, nor pages with same-origin frames it doesn’t watch.NAVIGERA_TRACE_EFFECT=1onstartadds aneffect_traceto click results (what was seen). - A click before the page wired the element up is sent again. On a
page whose document started loading less than 10 s ago, the click
also counts what listens for it (click, mouse, pointer and touch
listeners, handlers set as properties or attributes included,
navigera’s own left out) on the element, its ancestors, its document
and its window. When nothing did and the click changed nothing, no
script of the page ran for it: navigera watches the page while it has
scripts or data requests in flight, and until 500 ms after the click
without any (never past the element wait, at most 5 s), and if the
page adds such a listener there (a script after load, hydration of a
container or of the whole document), it clicks once more; the result
carries
clicked_againwith how long the page took. A click anything heard is never sent again: analytics on the document, or a script that records early clicks to replay them once the page is wired, may act on it later where navigera can’t see. A dead button on a page that just loaded reportsno_effectabout 300 ms later than before. Not covered: a page that listens on the document from the start (no_effectas before), an element inside a frame, a handler wired more than 500 ms after the click with nothing loading. - A click the page moved away from says so. A page that answers the
press itself, by moving the element (a banner put in above it on
pointerdown) or replacing it (a button rendered anew on mousedown),
has the release land elsewhere: the click event goes to the parent
the press and the release share, or the browser sends none, and the
element’s own handler never runs. The result carries
click_missedwith where the click went. The page saw the press, so navigera doesn’t press again: check what the click should do first.
- A styled checkbox or radio (the
- Page errors are reported. An uncaught exception while a command ran
adds one line to its
notes(the page threw TypeError: …; \errors` lists them), so a click whose handler crashed doesn't look like a click that did nothing.console,errorsandnetwork` keep the last 1000 entries per tab. - A page replaced mid-op: when a navigation replaces the page while an op is still looking for its element (a step that saves with a request and only then reloads, a client-side redirect), the op looks again on the new page within its wait.
- Navigation settling: every command that reads or acts on the page
first waits for any navigation an earlier action started, such as a JS
redirect, so it never reads the previous page. The wait is bounded by the
op’s own
timeout_msand never fails the op. Once the new document has committed, it waits at most 5 s forDOMContentLoaded: a page that keeps streaming is then used as it is, and the response says"loading": true. - Paths (
upload,screenshot) given to a session client are resolved in the caller’s working directory, not the session server’s. - New tabs: pages opened with
target=_blankorwindow.openare adopted into the tab list and become active. Tabs the page closes are dropped. - Dialogs are answered the moment they open: accept by default, or
dismiss after
dialog --dismiss. Abeforeunloaddialog is always accepted. Each dialog is reported in the response. - No
Runtime.enable: it is the best-known automation fingerprint, and nothing here needs it. - Logs: launch details go to stderr only with
NAVIGERA_VERBOSE=1.
Made from docs/navigera.md.