# 示例：浏览器驱动的 Web 应用

你有一个开发服务器，将 HTML 提供给浏览器。在无头容器中的代理无法打开浏览器窗口——因此"运行应用"意味着启动开发服务器，用无头 Chromium 驱动它，并生成证明页面已渲染的截图。

不要编写浏览器驱动程序。使用 `chromium-cli`。

## 开发服务器

找到开发命令（`package.json` `scripts.dev`、`Makefile`、README），在后台启动它，等待它实际提供服务：

```bash
npm run dev &   # 或 yarn dev、pnpm dev、make serve、./dev.sh
echo $! > /tmp/dev.pid
timeout 30 bash -c 'until curl -sf http://localhost:3000 >/dev/null; do sleep 1; done'
```

不要 `sleep 5`——轮询端口。在重新启动之前用 `kill $(cat /tmp/dev.pid)`（或 `pkill -f 'npm run dev'`）停止，否则下次运行会报 `EADDRINUSE`。

## 驱动

`chromium-cli` 是一个无头 Chromium REPL。将脚本管道到 stdin：

```bash
chromium-cli --session app <<'EOF'
nav http://localhost:3000
wait-for text=Dashboard
screenshot
click button:has-text("New item")
fill input[name="title"] Smoke test
press Enter
wait-for text=Smoke test
screenshot
console --errors
EOF
```

截图落在 `chromium_cli/sessions/app/screenshots/`（最新的符号链接为 `screenshot.png`）。这就是完整的循环：`nav` → `wait-for` 你需要的元素 → 执行操作（`click` / `fill` / `type` / `press`）→ `screenshot` → `console --errors` 检查是否有任何抛出。完整命令参考：`chromium-cli` 技能，或在提示符下输入 `help`。

对于迭代调试，在 tmux 下运行它，并通过 `send-keys` 逐条发送命令——相同的命令，相同的会话。

**如果 `chromium-cli` 不可用：** 适配 [electron.md](electron.md) 的 REPL 驱动程序——结构和命令可迁移，但它是 `_electron` 特定的：改用 `import { chromium }`，使用 `chromium.launch({ args: ['--no-sandbox'] })` 启动，通过 `(await app.newContext()).newPage()` 获取页面然后 `goto()` 你的开发 URL，并省略仅 Electron 的窗口内省（`.windows()`/`.firstWindow()`/`windows` 命令）。

## 放在技能中的内容

仅特定于项目的部分。`chromium-cli` 处理了机制。

- **开发命令 + 端口 + 停止。** 确切的启动行、它需要的任何环境变量，以及停止它的 `kill`/`pkill`。
- **认证。** 获取已登录会话的任何方式——一行 `set-cookie` 行、一个 `fill`/`click` 登录序列，或一个执行 API 交互并输出 cookie 的辅助脚本。
- **一个代表性交互。** 不是整个应用——一个证明它正在运行的路径，以截图结束。
- **应用特定的陷阱。** 只有你实际遇到的。

## 反复出现的陷阱

- **React 受控输入。** `eval el.value = '…'` 不会触发 React 的 onChange。使用 `fill` / `type`——它们经过 Playwright 的输入管道。
- **Websockets / 长轮询。** `wait-idle` 永远不会停。`wait-for` 你实际需要的元素。
- **缓慢的首屏绘制。** Vite/Next 按需编译路由；第一个 `nav` 可能需要 10 秒+。`wait-for` 可以处理；原始的 `sleep` 不行。
- **`screenshot-element <sel>`** 裁剪到一个元素——当差异在特定组件中而非整个页面时使用。
- **在声明成功之前检查 `console --errors`。** 页面可以在每次数据获取都返回 500 的情况下渲染其外壳。
