Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Inspect and test:
- `routes [file]` - Show routes of your Hono app
- `request <path> [file]` - Send request to Hono app using `app.request()`
- `batch <source> [file]` - Run multiple requests from JSONL using `app.request()`
- `browse <path> [file]` - Load a page in happy-dom, run its JavaScript, fill and click
- `snapshot [file]` - Print the current behavior as batch JSONL lines
- `diff` - Show how your uncommitted changes change what the app answers
- `benchmark [file]` - Measure the performance of your Hono app
Expand Down Expand Up @@ -272,6 +273,59 @@ With `--runtime workerd`, one workerd starts and every step runs in it, so a flo

One JSON object per line: `method`, `path`, `body`, `headers`, `expect`, `save`. `save` stores a value from the response body by dot path, and later steps use it as `{{id}}` (a whole-variable string keeps the saved type). A redirect shows its `location` in the step, and `save` reads a header with `header:`: `"save":{"next":"header:location"}`, then `"path":"{{next}}"` follows the URL a create redirects to. A string `body` without a `content-type` header goes as a form, or as JSON when it is a JSON object or array. Cookies carry over like in a browser, so a login step signs in the steps after it; a `cookie` header in a step wins. `expect` declares the acceptance criteria: `status` matches exactly, `body` is a deep partial match (declared fields must match, extra response fields are ignored). The output carries the actual `status` and `body`, `pass` per step, and a `summary` — rerun until `failed` is 0. A step without `expect` passes on any 2xx or 3xx and fails on a 4xx or 5xx; to accept a 4xx on purpose, declare it with `expect.status`.

### `browse`

Load a page in [happy-dom](https://github.com/capricorn86/happy-dom), run its JavaScript, and fill and click like a user. Use it for what an HTTP request cannot show: React hydration, a form that JavaScript handles, client-side navigation. Every request the page makes goes to the app the same way as `request` and `batch` (`app.request()`, `--runtime vite`, or `--runtime workerd`), so no server and no Chrome are needed.

```bash
hono browse <path> [file] [options]
```

**Arguments:**

- `path` - Page path (e.g. `/todos`)
- `file` - Path to the Hono app file (optional)

**Options:**

- `--fill <name=value>` - type a value into a field (can be used multiple times)
- `--click <selector>` - click the element that matches the CSS selector (can be used multiple times)
- `-H, --header <header>` - header for every request the page makes
- `--runtime <runtime>` - runtime to execute the app: `node` (default), `workerd`, or `vite`
- `--no-bindings` - Skip loading the local Cloudflare bindings
- `-e, --external <package>` - Mark package as external (can be used multiple times)

```bash
npm install -D happy-dom
hono browse / --fill Title=milk --click 'button[type=submit]'
```

`--fill` and `--click` run in the order given, and each waits until the page is quiet. `--fill` finds the field by its `name`, label text, placeholder, `aria-label`, or `id` (the keys in `forms`), or by a CSS selector. `confirm()` is accepted. Redirects are followed like a browser, and a request to another origin gets 403: only the app runs here.

happy-dom is not a dependency of Hono CLI. Install it in the project; without it, `browse` fails with `HAPPY_DOM_NOT_FOUND`. happy-dom has no layout or CSS, so check visual details in a real browser.

**Output:**

```json
{
"ok": true,
"data": {
"url": "/?added=milk",
"title": "Todos",
"text": "Todos milk Add",
"forms": [{ "action": "/todos", "method": "POST", "fields": { "Title": "" } }],
"console": [],
"actions": [
{ "action": "fill Title=milk", "ok": true },
{ "action": "click button[type=submit]", "ok": true }
],
"note": "happy-dom runs the page JS but has no layout or CSS. Confirm visual details in a real browser."
}
}
```

`console` has the page's console messages and its uncaught errors. An action that finds nothing stops the run with `"ok": false` and an `error`.

### `snapshot`

Print the current behavior of the app as batch JSONL lines, to stdout — no file is written.
Expand Down
30 changes: 30 additions & 0 deletions docs/agent-dx-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,36 @@
How measurements from [honojs/agent-dx](https://github.com/honojs/agent-dx)
changed Hono CLI. Newest first.

## 2026-10-16: HTTP checks miss client-side bugs — add `hono browse`

**Experiment**: bench3 on a Void app (a ramen log with create, edit,
delete), `claude -p`, 5 runs each: no CLI, `agent-init` with the Void
facts inline, and the released `agent-init` + the `hono-void` skill.
A black-box script graded each app.

**Findings**:

- `agent-init` cut the median cost from $2.23 / 59 turns to $1.64 / 42
(p≈0.02). The released version with the skill: $1.80 / 58 (p≈0.06).
- Quality was the same in every condition (4/5 pass), and 1/5 in each
still shipped a delete that lands on `/?delete`.
- Client-side behavior could not be checked over HTTP: no run could see
whether the form clears after a submit. Many runs tried Chrome
DevTools MCP (denied; 5/5 without the CLI), and one installed
Playwright itself.
- A happy-dom spike loaded the Vite dev page, hydrated React, typed,
clicked, and found both bugs in about 5 seconds.

**Changes**:

- `hono browse <path> --fill name=value --click <selector>`: the page
runs in happy-dom, and every request it makes goes to the app like
`request` and `batch`. happy-dom resolves from the project, so the
CLI does not grow for those who do not use it.
- happy-dom's module loader misreads a regex literal after `!`, `||`,
`?`, `-` or `return`. `browse` rewrites regex literals to
`new RegExp()` until it is fixed upstream.

## 2026-10-11: Code mode (`hono exec`) loses to batch; the CLI wins on side effects (#166, #167)

**Experiment 1: code mode.** Opus without the CLI already does a kind of
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@babel/parser": "^7.28.5",
"@babel/types": "^7.28.5",
"@types/node": "^24.7.0",
"happy-dom": "^21.0.1",
"magic-string": "^0.30.21",
"np": "^10.2.0",
"pkg-pr-new": "^0.0.62",
Expand Down
81 changes: 67 additions & 14 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Command, CommanderError } from 'commander'
import { agentInitCommand } from './commands/agent-init/index.js'
import { batchCommand } from './commands/batch/index.js'
import { benchmarkCommand } from './commands/benchmark/index.js'
import { browseCommand } from './commands/browse/index.js'
import { diffCommand } from './commands/diff/index.js'
import { initCommand } from './commands/init/index.js'
import { requestCommand } from './commands/request/index.js'
Expand Down Expand Up @@ -37,6 +38,7 @@ program
routesCommand(program)
requestCommand(program)
batchCommand(program)
browseCommand(program)
snapshotCommand(program)
diffCommand(program)
benchmarkCommand(program)
Expand Down
Loading
Loading