harn browse
harn browse [options] <url>Playwright-backed headless Chromium with a persistent profile and the shared cookie jar. Reach for it when you need a real browser: post-JavaScript DOM, a screenshot, console/network diagnostics, an interaction flow, or an assertion that a page actually renders the way it should. It is the Playwright sister of harn browse-ai (which returns an accessibility tree instead of DOM/HTML).
By default it writes a trio of files (<prefix>.png, <prefix>.html, <prefix>.json) for the iterate-and-inspect loop. The .json carries diagnostics: title, url, status, viewport, console events, console errors, page errors, and failed requests. <prefix> defaults to ~/.cache/harnery/browse/last; override with --out. The print-mode flags (--snapshot, --html, --json) skip the files and write to stdout instead, which is what makes the scrape pipeline harn browse <url> --html | harn read - work.
The trio’s <prefix>.html is a standalone snapshot, not a raw DOM dump. Stylesheets are fetched and inlined, fonts and images are embedded as data: URIs, and every remaining reference is rewritten to an absolute URL on the captured origin, so the file renders the same wherever it is opened later. Root-relative paths like /_next/static/app.css would otherwise resolve against whatever host serves the saved file and 404, which is what makes an unprocessed snapshot render as unstyled text. Scripts and preload hints are dropped, since neither can work in a saved page. The .json reports what happened under htmlSnapshot. Print mode (--html) is unaffected and still emits the raw serialization; a --selector capture is a DOM fragment rather than a document, so it stays raw too.
Options
Section titled “Options”Output
Section titled “Output”| Flag | Description |
|---|---|
--out <prefix> |
Output prefix for the trio (<prefix>.png, .html, .json). Default ~/.cache/harnery/browse/last. |
--no-screenshot |
Skip the .png (DOM + JSON only). |
--no-full-page |
Capture only the viewport, not the full scrollable page. |
--snapshot |
Print body innerText to stdout (skips file writes). |
--html |
Print raw outer HTML to stdout (skips file writes; pair with harn read -). |
--json |
Print the full JSON envelope to stdout (skips file writes). |
--selector <css> |
Scope --html / --snapshot to one element. |
--viewport <preset|WxH> |
mobile (390×844), tablet (820×1180), desktop (1280×800), hd (1920×1080), or explicit 1920x1080. Default desktop. |
--color-scheme <scheme> |
Emulate prefers-color-scheme for the whole session: light | dark. Renders theme-aware pages in that scheme without page-specific toggles. Absent: the browser default (unchanged behavior). |
--network-har <path> |
Record network traffic to a HAR file (finalized on close). |
Interaction
Section titled “Interaction”| Flag | Description |
|---|---|
--click <selector> |
Click this selector after navigation. |
--fill <selector=>value> |
Fill an input, e.g. input[name=q]=>hello. The separator is => (not =) so attribute selectors don’t collide. |
--press <key> |
Press a key after navigation/fill (e.g. Enter). |
--wait-for <selector> |
Wait for this selector before capturing output. |
--evaluate <js> |
Run JS in the page context after navigation; the result is printed to stdout and recorded in the envelope under eval. |
--batch <steps> |
Run multiple steps in one session, semicolon-separated (see Batch flows). |
--wait-until <strategy> |
Navigation wait strategy: load | domcontentloaded | networkidle | commit. Default load. |
--timeout <ms> |
Navigation timeout in milliseconds. Default 30000. |
Session
Section titled “Session”| Flag | Description |
|---|---|
--login |
Headed mode for a one-time auth flow; cookies persist in the profile. |
--login-close-file <path> |
With --login, keep the process open until another process creates this signal file, then remove it and close cleanly. |
--control-file <path> |
With --login, publish an owner-only descriptor for repeated harn browse-session control. |
--headed |
Headed mode for a one-off (no auth-flow framing). |
--browser-arg <flag> |
Extra Chromium launch flag, passed straight to the browser (repeatable). Also settable machine-wide via HARNERY_BROWSER_ARGS (whitespace-separated). See WSLg note below. |
--proxy-from-env |
Pass HTTPS_PROXY/HTTP_PROXY to Playwright as an authenticated browser proxy. Credentials remain in the child environment instead of command arguments. |
--no-cookies |
Skip cookie-jar attach and persist. Also skips a host extraCookies hook. |
--store <path> |
Cookie store path. Default ~/.cache/harnery/cookies.json. |
--profile <dir> |
Persistent Chromium profile dir. Default ~/.cache/harnery/browser-profile. |
--export-cookies <path> |
Export the live profile to an owner-only Netscape cookies.txt file before closing. |
Authenticated proxy sessions
Section titled “Authenticated proxy sessions”--proxy-from-env reads HTTPS_PROXY, https_proxy, HTTP_PROXY, then
http_proxy. It separates userinfo from the proxy server URL and passes the
credentials through Playwright’s native proxy option, so Chromium can answer a
Basic-authentication 407 without showing a prompt. The secret is never part of
the harn browse argument vector.
When a host wrapper sets both HARNERY_BROWSER_PROXY_EXPECTED_IP and
HARNERY_BROWSER_PROXY_CHECK_URL, browse opens the HTTPS check URL first and
requires its ip, query, or origin value to match exactly. A missing field,
bad response, or mismatch stops the command before the requested URL opens.
Proxy-enabled launches also set Chromium’s WebRTC IP handling policy to
disable_non_proxied_udp. Playwright does not fall back to a direct connection
when its configured proxy becomes unavailable.
HTTPS_PROXY='http://user:password@proxy.example:7000' \HARNERY_BROWSER_PROXY_EXPECTED_IP='192.0.2.10' \HARNERY_BROWSER_PROXY_CHECK_URL='https://ip.example/json' \harn browse --proxy-from-env https://example.comKeep fixed-route checks in the host wrapper when it owns the proxy inventory.
For example, a wrapper can validate the route with curl, inject the same
expected IP into the child, and let Chromium repeat the gate before target
navigation.
For an agent-driven visible flow, pair --login with either
--control-file or --login-close-file. The owner-only control descriptor lets
repeated browse-session processes inspect and act on
the original Playwright context. This avoids relying on an operating-system
window handle while the operator can still use the visible tab directly.
The signal-file form remains useful when another trusted control surface drives
the window. A second process creates the signal only after the flow is complete;
browse removes it, exports cookies if requested, persists the profile, and
closes Chromium. The signal must not exist at launch, which prevents a stale
file from closing a new session immediately.
harn browse --login --login-close-file /tmp/auth-complete https://example.com# From a second process, after the visible flow is complete:touch /tmp/auth-completeFor live CLI control, create a private directory and launch with a descriptor:
install -d -m 700 ~/.cache/harnery/live-sessionharn browse --login \ --control-file ~/.cache/harnery/live-session/control.json \ https://example.comVisibility + layout checks
Section titled “Visibility + layout checks”These assert on the rendered layout and surface findings in the JSON envelope; each --*-fail variant makes the check exit non-zero, so they slot into deploy/CI scripts. By default each check also annotates the screenshot with colored boxes.
| Flag | Description |
|---|---|
--check-visible <selector> |
Occlusion check: samples an n×n grid inside the element’s rect and reports visibleRatio plus the dominant occluder. Repeatable. |
--check-visible-threshold <num> |
visibleRatio below which a target counts as occluded (0–1). Default 0.9. |
--check-visible-fail |
Exit non-zero if any target falls below threshold. |
--check-visible-sample-grid <n> |
Grid size for occlusion sampling (n×n points). Default 3 (9 samples). |
--no-check-visible-annotate |
Skip drawing target/occluder boxes (JSON still emitted). |
--check-width <selector> |
Assert the selector’s rect width is at least --check-width-threshold of the viewport. Reports viewportFill + parentFill. Repeatable. |
--check-width-threshold <ratio> |
viewportFill below which a target counts as too narrow (0–1). Default 0.9. |
--check-width-fail |
Exit non-zero if any width target falls below threshold. |
--no-check-width-annotate |
Skip drawing width-check boxes. |
--check-overflow |
Assert no horizontal overflow (document.scrollWidth <= window.innerWidth); surfaces protruding elements. |
--check-overflow-fail |
Run the overflow check when needed, then exit 2 if horizontal overflow is detected. |
--no-check-overflow-annotate |
Skip drawing overflow annotations. |
--check-runts [selector] |
Scan text blocks for runts (a single word alone on a block’s last visual line), counting words via per-word Range rects. Optional selector scopes the sweep. |
--check-runts-min-chars <n> |
Minimum block text length to scan. Default 40. |
--check-runts-fail |
Run a document-wide check when needed, then exit 2 for a runt, missing scope, or incomplete sweep. |
--no-check-runts-annotate |
Skip drawing runt boxes. |
--check-align <selector> |
Compare rendered child-content centers inside a container. Uses text fragments, then descendant SVGs, then border boxes. Repeatable. |
--check-align-axis <axis> |
auto, x, or y. Auto checks y-centers for rows and x-centers for flex columns. Default auto. |
--check-align-threshold <px> |
Maximum center drift in CSS pixels. Default 2. |
--check-align-fail |
Exit 2 when an alignment target fails, is unknown, or is missing. |
--no-check-align-annotate |
Skip alignment annotations. |
--check-gap <selector> |
Compare adjacent rendered gaps inside a container. Groups of 3+ infer their median gap. Repeatable. |
--check-gap-axis <axis> |
auto, x, or y. Default auto. |
--check-gap-expected <px> |
Supply the expected gap. Required to make a two-item group conclusive. |
--check-gap-threshold <px> |
Maximum gap deviation in CSS pixels. Default 2. |
--check-gap-fail |
Exit 2 when a gap target fails, is unknown, or is missing. A two-item group needs --check-gap-expected to produce a conclusive result. |
--no-check-gap-annotate |
Skip gap annotations. |
--check-clip <selector> |
Check every matching container for descendant boxes or text paint leaving its rectangular clipping chain or nearest block parent. In-flow boxes are checked horizontally against that parent even when CSS overflow is visible. Repeatable. |
--check-clip-threshold <px> |
Allowed rectangular overrun in CSS pixels. Default 0. |
--check-clip-fail |
Exit 2 when rectangular clipping fails, is unknown, or the selector is missing. |
--no-check-clip-annotate |
Skip clipping annotations. |
--check-overlap <selector> |
Detect area intersections among measurable in-flow siblings. Repeatable. |
--check-overlap-threshold <px> |
Allowed overlap depth on either axis. Default 0. |
--check-overlap-fail |
Exit 2 when an overlap target fails, is unknown, or is missing. |
--no-check-overlap-annotate |
Skip overlap annotations. |
--check-crowd <selector> |
Flag adjacent card panels (a full border, a modest corner radius, or a box-shadow) that touch or nearly touch. Wrappers that contain panels count as peers too; separation uses the nearest face panels inside them. Walks the whole subtree, so one selector catches nested cases. Repeatable. |
--check-crowd-min <px> |
Minimum acceptable edge gap between adjacent panel faces. Default 6. Negative separation (overlap) always flags. |
--check-crowd-fail |
Exit 2 when a crowd target fails, is unknown, or is missing. |
--no-check-crowd-annotate |
Skip crowd annotations. |
--check-hit [selector] |
Check interactive target size and spacing in the document or optional scope. Repeatable. |
--check-hit-profile <profile> |
wcag-aa uses 24px; comfortable uses 44px as a product-quality policy. Default wcag-aa. |
--check-hit-fail |
Exit 2 on target-size fail, unknown, or a missing scope. |
--no-check-hit-annotate |
Skip target-size annotations. |
Alignment and gap checks cluster wrapped rows or columns before comparing them.
They measure the rendered result rather than trusting align-items, gap, or
margin declarations. Do not aim center alignment at an intentional
align-items: baseline row.
Clipping is deliberately rectangular. The selector is a scope, not a single
element lookup: every match is checked. Within that scope, the check measures
element boxes against clipping ancestors and rendered text fragments against
their nearest non-inline parent. That catches an unbreakable label painting out
of a narrow card even when every element box still fits. An axis stops being
checked at the first ancestor that scrolls it — including the queried container —
because content the reader can scroll to is reachable by design, so a capped
table or a wide code block reports nothing. Anything an ancestor inside that
scroller clips is still reported, since scrolling the outer box cannot reveal
it. Content inside a collapsed <details> is treated as hidden rather than
clipped. The check ignores sub-half-pixel rasterization noise and normal vertical glyph
overhang; neither is visible clipping. Hanging indents remain valid when their
paint stays inside the block border. The check uses the rectangular padding box
for border radius. Arbitrary clip-path,
non-axis-aligned transforms, closed shadow roots, and cross-origin frame content
are reported as unknown, not certified. Overlap covers sibling geometry;
paint-order and sticky-layer obstruction remain --check-visible concerns.
Crowd sits between overlap and gap: overlap needs a real 2D intersection and gap
flags uneven spacing, so a stack of cards all sitting flush passes both. Crowd
targets exactly that: distinct panels with no breathing room. Peers are
card-like panels (a full border, a modest corner radius, or a box-shadow) or
wrappers that contain at least one panel. Separation is measured between the
nearest face panels inside each peer, not the wrapper boxes, so a tall section
with a card near the top and prose below does not false-fail against the next
sibling, while a card-grid flush against the next card still fails. Plain blocks
with no panel chrome stay out of the peer list. Table cells, list and definition
rows, and divided segments carry no card boundary of their own and are skipped,
as are pills, chips, and badges (corner radius at least half their shorter side).
Per-pair results carry the two face panels, beforeKind / afterKind
(panel or composite), the edge separationPx, the axis (x side-by-side,
y stacked, overlap intersecting), and the shared parentLabel.
All requested checks run before one screenshot is captured. Fail gates are
evaluated only after JSON and screenshot evidence is complete. Missing selectors
and layout unknown results fail every enabled gate. The JSON still records the
unknown reason so the recipe can replace an unsupported selector or supply a
missing expectation.
A fail flag never succeeds without running its check. Whole-document checks can
infer their scope: overflow, runts, placeholders, images, truncation, contrast,
target size, and critique all run against the page when their fail flag is used
alone. Selector-based fail flags cannot invent a target, so
--check-clip-fail without --check-clip <selector> (and the same pattern for
visibility, width, alignment, gap, overlap, and crowd) stops with a usage error
before Chromium opens. --assert-fail requires --assert, and --diff-fail
requires --diff.
Runt results carry scope, found, and outcome. A safety-cap truncation is
unknown, not a clean result; a requested selector that matches nothing is
fail.
Content checks
Section titled “Content checks”These cover failures a human catches at a glance even when the geometry is perfect: leaked template tokens, broken images, clipped text, unreadable contrast. Each takes an optional selector (default: the whole body).
| Flag | Description |
|---|---|
--check-placeholder [selector] |
Flag unrendered tells in the visible text: {{x}} and JS template tokens, [object Object], Invalid Date, NaN, or an element whose whole text is literally “undefined” / “null”. |
--check-placeholder-fail |
Run a document-wide check when needed; exit 2 unless it passes. |
--check-images [selector] |
Audit <img> for a failed load (naturalWidth 0), a still-loading image, or a stretched one (rendered aspect far from intrinsic, uncorrected by object-fit). |
--check-images-tolerance <ratio> |
Aspect-ratio deviation above which an image counts as stretched. Default 0.1. |
--check-images-fail |
Run a document-wide check when needed; exit 2 unless it passes. |
--check-truncation [selector] |
Flag text actively cut off by an ellipsis or -webkit-line-clamp. Stays quiet on plain overflow: hidden. |
--check-truncation-tolerance <px> |
Overflow past the clip box, in px, above which truncation counts. Default 2. |
--check-truncation-fail |
Run a document-wide check when needed; exit 2 unless it passes. |
--check-contrast [selector] |
Flag rendered text below the WCAG AA ratio (4.5:1 normal, 3:1 large) against its effective background. |
--check-contrast-fail |
Run a document-wide check when needed; exit 2 on failed, unknown, or missing results. |
--no-check-<name>-annotate |
Skip that check’s screenshot annotations (JSON still emitted). |
Placeholder is prose-safe: bare “undefined” / “null” only flag when they are an
element’s whole text, not the word inside a sentence. Do not point it at a page
that documents template syntax. Contrast resolves the effective background by
walking ancestors to the first opaque color; text over an image or gradient is
unknown, not failed, and the check reads whatever theme the page is in, so
toggle the theme with --batch to cover light and dark. Truncation is
deliberately narrow (an active ellipsis or line-clamp only), so a decorative
overflow: hidden does not read as a bug.
Vision critique
Section titled “Vision critique”Heuristic checks only catch what you can enumerate. --check-critique hands the
rendered page to a vision model for the long tail: the thing that looks off
without tripping a named rule.
| Flag | Description |
|---|---|
--check-critique [selector] |
Send the page to a vision model, tile by tile. Default tiling cuts vertical bands whose seams snap into content gaps; a selector tiles one screenshot per matching element, banding any element taller than the band budget internally. |
--check-critique-band <px> |
Band height budget for tiling. Seams may move up to 30% above the budget to land in a gap, never below it. Default 1400. |
--check-critique-overlap <px> |
Vertical overlap below a seam that had to cut through content, so a straddling finding stays visible. Clean seams (nothing straddles them) carry a 16px margin instead. Default 120. |
--check-critique-max-tiles <n> |
Cap on tiles sent to the model, to bound cost. Default 24. |
--check-critique-rubric <text> |
Override the default rubric. |
--check-critique-fail |
Run a whole-page critique when needed; exit 2 unless it conclusively passes. |
The page is tiled because a tall screenshot downscaled to a model’s input budget
loses the detail the critique depends on; each tile is captured at full
resolution and judged on its own, and findings carry their tile and scroll
offset for locality. Band seams are content-aware: the page’s text lines,
images, and small bordered boxes are extracted first, and each cut lands in
the cheapest nearby gap rather than at a fixed offset, so tiles rarely open or
close mid-element. Where no clean cut exists (an element taller than the band
budget), the cut falls back to the fixed offset with the full overlap, and the
rubric tells the model to treat slice-edge clipping there as an artifact. harnery ships no model client, so a host injects a
critiqueProvider into HarneryProgramContext (the extraHeaders pattern);
without one, the result is skipped, and --check-critique-fail exits 2 rather
than certifying a review that did not happen. A provider that throws becomes a
high-severity finding for that tile rather than aborting the run.
Value assertions
Section titled “Value assertions”The layout and content checks answer “does the page look right”; --assert
answers “does it say the right thing”. One repeatable flag,
<op> <selector> => <expected>:
| Op | Passes when |
|---|---|
text |
the first match’s trimmed text equals <expected> |
contains |
the first match’s text includes <expected> |
matches |
the first match’s text matches the <expected> regex |
count |
the match count equals <expected>, or satisfies a >= / <= / > / < comparator |
exists / absent |
at least one / zero matches (no => expected) |
For example --assert 'text h1 => Welcome', --assert 'count .card => >=3',
--assert 'absent .error'. Results land under asserts in the JSON envelope
(each carries the observed actual); --assert-fail exits 2 on any failure. A
malformed spec reports an error and fails rather than throwing. This is the
portable primitive a host layers domain assertions on (funnel totals, warehouse
reconciliation).
Visual regression
Section titled “Visual regression”| Flag | Description |
|---|---|
--baseline <name> |
Save the captured screenshot as a named baseline at ~/.cache/harnery/visual-baselines/<name>.png. |
--diff <name> |
Pixel-diff the capture against the named baseline; writes a diff PNG next to the screenshot and reports mismatchedPixels + similarity. |
--diff-threshold <ratio> |
mismatchRatio below which the diff is a match (0–1). Default 0.01 (1%). |
--diff-fail |
Exit non-zero if mismatchRatio exceeds the threshold. |
Diff-aware QA planning
Section titled “Diff-aware QA planning”Vision review costs the same whether you changed one word or the whole theme,
unless something classifies the change first. --qa-plan compares the rendered
page against a persisted baseline signature (per-element structure, attributes,
direct-text digests, applied-stylesheet digests) and emits a review manifest
under qaPlan in the JSON envelope before any model call is made: the change
class, the stable scope selectors that contain it, the render contexts worth
reviewing, and the provider-call ceiling.
| Flag | Description |
|---|---|
--qa-plan |
Classify this render against the persisted baseline and emit the review manifest. No baseline classifies as unknown and widens to full coverage. |
--qa-snapshot |
Persist this render (signature + DOM + full-page screenshot) as the QA baseline for the target + context, atomically. Run on a page state you trust. |
--qa-target <key> |
Override the snapshot-store key (default: the url argument), so a production render can seed the baseline for a local route. |
--qa-theme <theme> |
Context label for the store: light | dark (default light). Labels only; it does not switch the page theme. |
--qa-state <name> |
Context state label (default default). |
--qa-scope <selector> |
Explicit scope selector from the producing task (repeatable); overrides lifted anchors. |
--qa-states <names> |
Comma-separated interaction states to review; promotes the plan to interaction-state. |
--qa-reuse |
With --check-critique: reuse the baseline run’s verdicts for tiles whose pixels provably didn’t change (band-diff against the persisted snapshot screenshot). Only clean regions reuse; baseline findings or any pixel drift force a fresh review. Stats land under qaReuse. |
--qa-reuse-threshold <ratio> |
Mismatch ratio at or below which a clean tile region is reused (default 0.001; lower toward 0 for stricter reuse). |
The classes: text-data-only (only direct text changed, so zero vision
calls), local-visual (structural changes contained by stable anchors, so
review scopes to them in light contexts), interaction-state (explicit states
requested), large-structural (stylesheet changed, unprovable boundary, or
multi-region change: the full desktop/mobile and light/dark matrix), and
unknown (no or truncated baseline: widened, never treated as text-only).
Ambiguity always widens. A one-line edit to a linked stylesheet changes zero
DOM bytes and every pixel, so DOM equality alone never proves a change is
text-only; stylesheet digests (external sheets fetched and hashed) must match
too. Stable anchors are
unique #ids or data-qa-scope attributes; nested anchors dedupe to the
outermost root so a container is reviewed once, not per descendant.
Reuse closes the loop: --qa-snapshot on a critique run persists the verdicts
with the screenshot, and the next --check-critique --qa-reuse run pixel-diffs
each tile’s document rect against that baseline and skips the model call for
clean, finding-free regions that match at or below the strict threshold. Reuse
can produce false re-reviews, never false passes: a region with baseline
findings is always re-reviewed, a rubric or contract change is a whole-run
miss, and a partially-reused run is never persisted as the next baseline.
Page review pack capture
Section titled “Page review pack capture”--review-pack is the capture half of the capture-then-judge split behind
harn review-pack and harn qa-run.
The page is rendered once; its full-page screenshot, critique tiles (as PNG
files, cut with the --check-critique-* knobs), serialized DOM, and QA
signature are written into <dir>/contexts/<id>/; and the browser closes.
No vision call is made. A judge reads the tiles back later, with no browser
open, through the same host-injected provider --check-critique uses.
| Flag | Description |
|---|---|
--review-pack <dir> |
Write this render into the pack at <dir>. Pair with --no-screenshot: the pack holds the full-page image. |
--review-pack-context <id> |
Context id inside the pack (default <viewport>-<theme>-<state> from --viewport, --qa-theme, --qa-state). |
--review-pack-scope <selector> |
Also tile one screenshot per matching element (repeatable). Scope tiles follow the full-page bands in the tile order. |
--review-pack-plan |
Emit all native tile candidates and stable capture metadata without capturing the pack. Known gate failures still emit a plan. |
--review-pack-allocation <file> |
Capture exactly the selected IDs from a validated context allocation. Source or geometry changes require a new plan. Page edges remain required. |
--review-pack-reservation <file> |
Use the preliminary allocation only as a tile-count reservation. Repeat supplied gates and allocate on the current page; require unchanged full source and complete native coverage. One source-change retry is allowed. Cannot combine with allocation, expansion or inline critique. |
--review-pack-hit-rect <x,y,w,h> |
A document-space rectangle a deterministic gate flagged (repeatable, at most 50). A band that contains one is captured as a hit band even when it lies below the tile cap, so the reviewer gets the tile that shows the defect. qa-run passes each context’s gate rectangles automatically. |
--review-pack-expand <tile-id> |
Instead of capturing a context, re-render one existing tile of --review-pack-context at --device-scale-factor and write it beside the original as tiles/<tile>@<dpr>x.png. Existing tiles are untouched. This is the child harn review-pack expand runs. |
--device-scale-factor <n> |
Device scale factor for the browser context (1 to 4; default 1). Every screenshot renders at that multiple of its CSS pixels. |
The JSON envelope gains reviewPack (dir, context_id, tiles, scopes,
coverage, and the pack-relative files; plus expanded after
--review-pack-expand). The context’s coverage is the band coverage when
bands were kept, else the scopes folded together.
Before the browser closes, the capture checks its own fidelity. Two probe
bands (the top band and the middle band) are re-shot by scrolling to them
and compared with the same region of the full-page screenshot using the
band-diff mismatch threshold. When a probe disagrees, every band is
re-captured by scrolling and the tiles are cut from those captures instead;
the context record’s capture_fidelity says which source the tiles came
from and which probes mismatched. This is how a blank block or a clipped
callout that exists only in a tall full-page screenshot stays out of the
judge’s findings, without any browser opening after capture.
Diagnostics
Section titled “Diagnostics”| Flag | Description |
|---|---|
--no-dev-overlay |
Skip auto-capture of Next.js dev-overlay issues (see Dev-overlay capture). |
Batch flows
Section titled “Batch flows”--batch runs several steps in a single browser session, semicolon-separated. Each step is one verb: click <selector>, fill <selector=>value>, press <key>, wait <selector|ms>, eval <js>, reload, or clipboard [<label>]. reload preserves sessionStorage and cookies, which is how you reproduce sessionStorage-restored UI state. clipboard reads the system clipboard via navigator.clipboard.readText() and records each read in the envelope under batchClipboardReads.
harn browse https://example.com --batch "click button.load-more; wait 1500; reload; wait 3000"Dev-overlay capture
Section titled “Dev-overlay capture”On by default. When a <nextjs-portal> shadow root is present, browse reads the queued dev-overlay issues (kind/code/message/stack) and reports them under devOverlay in the envelope. This matters because Next.js 16 + React 19 route hydration errors and most React warnings through onCaughtError into next-devtools’ error queue, not through console.error, so they never appear in consoleErrors. Pass --no-dev-overlay to skip the capture.
Output
Section titled “Output”In print mode (--json) or the trio’s .json file, the envelope carries: url, title, status, viewport, consoleEvents, consoleErrors, pageErrors, failedRequests, and, when the corresponding flag ran, eval, har, visibility, width, overflow, runts, align, gap, clip, overlap, hit, devOverlay, batchClipboardReads, baselineSaved, and diff. failedRequests includes network and HTTP failures but omits canceled Next.js React Server Component prefetches. The trio’s .json additionally carries htmlSnapshot (stylesheetsInlined, stylesheetsLinked, resourcesInlined, resourcesLinked, inlinedBytes).
Examples
Section titled “Examples”# Default trio: screenshot + DOM + diagnostics JSON at the default prefixharn browse https://example.com
# Scrape-to-markdown pipelineharn browse https://example.com --html | harn read -
# Mobile viewport, custom output prefix, viewport-only screenshotharn browse https://example.com --viewport mobile --no-full-page --out /tmp/run
# Fill a search box, submit, and wait for results before capturingharn browse https://example.com --fill 'input[name=q]=>hello' --click 'button[type=submit]' --wait-for 'main .results'
# Assert layout on mobile: break the build on overflow or narrow tablesharn browse https://example.com --viewport 430x932 --check-width 'table' --check-overflow --check-overflow-fail
# Run the rendered-layout suite in one browser stateharn browse https://example.com --viewport mobile \ --check-align 'nav' --check-gap 'nav' --check-clip 'main' \ --check-overlap '.toolbar' --check-hit 'header' \ --check-align-fail --check-gap-fail --check-clip-fail \ --check-overlap-fail --check-hit-fail
# Save a visual baseline, then diff a later run against itharn browse https://example.com --baseline home-desktopharn browse https://example.com --diff home-desktop --diff-fail
# One-time headed login; cookies persist to the shared profile for later runsharn browse --login https://example.com
# Export one dedicated profile for a consumer that requires Netscape cookies.txtharn browse --profile ~/.cache/harnery/profiles/media --no-cookies \ --export-cookies ~/.cache/harnery/exports/media-cookies.txt https://example.com