dsh-browser-verify
Browser verification for DeepSeek Harness — open, mock, assert, screenshot in a few tool calls, screenshots auto-project into the model context
- Stars
- 0
- Language
- TypeScript
- Created
- Sep 2, 2026
- Updated
- Sep 29, 2026
Introduction
dsh-browser-verify
English | 中文
Give your DeepSeek Harness a pair of eyes on any web page — read-only browser verification in ≤4 tool calls: open, mock, assert, screenshot.
Pages that change every day (H5 carousels, payment flows, admin consoles) are hard to verify by eye. This plugin lets the model drive a real headless browser through five tools — open a page, reload it in place, intercept its APIs, assert on the DOM, screenshot it — and the screenshot lands straight back into the model's context as an image. No terminal scripts, no browser bookkeeping: a verification is just tool calls.
Quick start
dsh plugin --profile web add dsh-browser-verify@0.1.7
- Install with the command above (or see Install).
- Restart the GUI once — plugins load at boot; the tools become visible only after the restart.
- Open a new session and tell the model — or call directly:
browser_open url="http://localhost:5173/hweb/#/pages/lyp/livingPayment" waitSelector=".header"
Opens the page in a fresh headless session and returns
title / status / visible-text / console-errors.
browser_assert selector=".empty-wrap" text="暂无可用缴费服务"
Waits for the selector (5s default) and returns {pass, count,
actualText, elapsedMs}. A miss is a normal pass:false, never an error.
That is the whole loop — two calls to answer "does the page show the empty
state?"; add browser_screenshot when you need to see the layout, or
browser_mock first when the page needs mocked APIs (see below).
What it does
| Tool | Purpose |
|---|---|
browser_open | Open a URL in a fresh scenario (headless Chromium, default viewport 390×844 @2x) and report title / HTTP status / visible-text summary / console errors. Without waitSelector it waits for the page to render-settle (two identical consecutive visible-text samples, capped at ~3s) before snapshotting, so it never returns the boot/skeleton frame; loading-state noise (加载中... etc.) is filtered out of the summary. Optional waitSelector waits for a key element before returning, and optional inline mocks intercept APIs before the first navigation — for pages that boot against mocked data. |
browser_reload | Reload the current page in place and report the same page state as browser_open, keeping every registered mock. This is the re-verify call after an edit: it skips the context rebuild, the mock re-registration and the URL round-trip. |
browser_mock | Register a playwright-glob route (**/api/*.do*) returning your JSON, then auto-reload the page to show the mocked state — the quickest way to verify empty / error / abnormal states without touching the backend. Re-registering the same pattern updates that route's response instead of stacking a second one. The returned hits list shows which requests were actually intercepted, so an empty list means your glob matched nothing — fix the pattern instead of probing variants. |
browser_assert | The cheapest and most precise check: wait for a CSS selector, verify its count and contained text, return {pass, count, actualText, elapsedMs}. A mismatch is pass:false (with the diff), never a throw — so failure is a first-class result, not an error you debug. |
browser_screenshot | Capture the current page (viewport or full page) and auto-project the image block into the model context — the model sees the layout without any file handling. Reports dimensions, sha256, and identicalToPrevious:true when the shot is byte-identical to the previous one (page probably not refreshed). |
Use browser_assert before browser_screenshot: an assertion is cheaper, and
a screenshot is for when the rendering itself must be judged.
Real model experience
"Pleasant to use, cleanly layered." — a real model's verdict, after putting these tools to work on a real business page.
- A three-state verification in 4–5 tool calls, zero environment setup — the same job used to take ~20 manual script steps.
- Mock, then it reloads itself — no more "same URL, screenshot unchanged" traps.
- Screenshots land right in the model's context — no file paths to chase.
- Structured asserts (count / text / elapsed) that even warn you when the page didn't actually refresh.
- Every open resets the world — states never bleed into each other.
Verification should be a few tool calls, not script choreography.
Worked example — two states, six calls
The typical verification (empty state + normal state) is 6 calls:
browser_open url="…/livingPayment" mocks=[{urlPattern:"**/api/*.do*", json:{status:0,result:{list:[],data:{}}}}] waitSelector=".header"
browser_assert selector=".empty-wrap" text="暂无可用缴费服务"
browser_screenshot
browser_mock urlPattern="**/api/*.do*" json={status:0,result:{list:[{wegType:"WATER",name:"水费",info:"128.00"}],data:{}}}
Same pattern → updates that route and reloads: no second open,
no URL round-trip, no context rebuild.
browser_assert selector=".grid-item" text="水费"
browser_screenshot
Worked example — re-verify after an edit, two calls
The page is open and mocked, only your code changed. browser_open would
rebuild the context and drop every mock; browser_reload keeps both:
browser_reload
browser_assert selector=".saq-summary__title" text="该街道已有代理"
Install
dsh plugin --profile web add dsh-browser-verify@0.1.7
The version is pinned on purpose: pnpm 11 holds back packages published in the
last 24 hours, so a bare add dsh-browser-verify (latest) would silently
install the previous release on launch day. --profile web is the GUI profile
of this deployment — use your own profile name if it differs.
Requires dsh ≥ 0.1.2-alpha.1, on either host line: the declared DSH peer
range spans 0.1.x and 0.2.x. A dsh runtime outside it refuses to load the
plugin at profile startup until you grant an exact-version exemption.
Browser prerequisite (usually none)
The plugin does not download Chromium — it finds a browser on your machine:
the playwright cache (any installed version), then system Chrome / Chromium /
Edge on common install paths and $PATH. Most machines — anything that ever
ran a playwright project, or has Chrome installed — work with zero setup.
Only when neither exists, install the browser once:
npx playwright install chromium
or point the plugin at an existing binary via DSH_BROWSER_VERIFY_CHROMIUM
(see Environment variables). A system browser is
used as-is with an "unverified version" hint; without any browser, the first
browser_open fails with an actionable install hint.
Environment variables
| Variable | Default | Meaning |
|---|---|---|
DSH_BROWSER_VERIFY_CHROMIUM | (unset) | Full path to a Chromium binary; wins over cache probing. If the path is wrong, startup fails with a hint. |
DSH_BROWSER_VERIFY_TIMEOUT | 10000 | Wall-clock budget (ms) for the page-load path of browser_open (including wait-selector and mock reload). |
DSH_BROWSER_VERIFY_IDLE_MS | 600000 | Idle window (ms) before the browser instance auto-closes; the next browser_open relaunches it lazily. Plugin disposal force-cleans in any case. |
Reliability & housekeeping
-
One browser, one scenario — a lazy singleton per process, FIFO-serialized tool access, an idle reclaim after 10 min, and a full teardown on dispose.
-
Screenshot dedup — identical bytes report
identicalToPrevious:trueinstead of re-sending the model the same image. -
Garbage discipline — the plugin writes only to the system temp dir (
dsh-browser-verify-*), the harness attachment store, and explicit--persistpaths. On host crashes, clean leftovers with:rm -rf "$(node -p 'require("os").tmpdir()')/dsh-browser-verify-*" -
Error policy — every error carries a
browser-verify:prefix and ends with actionable advice; verification "failures" are results (pass:false), never exceptions.
Testing status
49 unit tests (fully offline — no browser needed), strict typecheck, and a
per-file ≥90% statement coverage gate. Verified end-to-end in the real DSH
web GUI on both dsh 0.1.2-alpha.4 and dsh 0.2.0-rc.1: a two-state loop
(empty + normal) against a live uni-app H5 (hhhweb) in 6 tool calls, with
screenshots auto-projected and zero leftover temp dirs or zombie processes. The
0.2.0-rc.1 run used the published release (dsh plugin add from the registry,
no version exemption) and was backed by typecheck, the full offline suite, and
the packed build on that host line.
Known limitations
- Read-only: no clicks, inputs, or scrolling — verification only. One
scenario at a time; each
browser_openresets mocks and page state. - Platform: verified on macOS arm64. On other platforms set
DSH_BROWSER_VERIFY_CHROMIUMto a browser binary. - Single-machine assumption: the startup sweep only touches
dsh-browser-verify-*pid dirs older than 1 h, so concurrent harnesses on one machine are safe. - No GUI config card — configuration is env-var only (see above).
Development
pnpm install
pnpm build # tsc -b && tsdown → lib/ (clean build; lib/ is gitignored)
pnpm test # vitest run (offline)
pnpm typecheck
pnpm vitest run --coverage # per-file ≥90% gate on discover / cleanup / attachments
scripts/smoke.sh # two-state end-to-end; requires the reference app dev server on :5173
CLI (harness-free debug path): node lib/cli.js --url <u> [--mock <file.json>] [--wait-selector <sel>] [--assert <sel>] [--screenshot] [--persist <dir>] [--viewport <WxH>].
Contributors can install the local build with
dsh plugin --profile web add ./dsh-browser-verify-<version>.tgz after
pnpm build; the tarball ships a prebuilt lib/, so no build step runs on
the installing machine.
License & attribution
Apache-2.0. Architecture and implementation notes for agents and contributors live in AGENTS.md.