- browser-harness/SKILL.md:§1 起法整段重写为「只允许这一种 —— 本会话的后台任务」(字面命令/
为什么只许这一种/守卫语义/已知代价/起不来对照表);§3 补一条排障 —— 认进程命令行与它自己的日志,
⛔ 别信 pid 父链;**「删掉脚本」≠「停掉进程」**(依据 2026-10-10 实测:孤儿守卫在脚本已删后仍把浏览器拉回)
- browser-harness/agent-workspace/bh_chrome_keepalive.py:守卫语义分两档 —— Chrome 正常关窗(rc=0) ⇒ 停手退出;
异常退出(rc≠0) ⇒ 重拉(依据:用户手关被无条件拉回,连关三次弹三次)
- session-mechanism/scripts/selftest.py:删掉重复定义的 `t_check_cooldown_no_pileup`(两段逐字相同);自测 PASS 109 / FAIL 0
- session-mechanism/references/humanizer-en/SKILL.md:与顶层 `humanizer` 技能同名冲突 ⇒ 去掉本包这份的注册
(`user-invocable: false` + `disable-model-invocation: true`),6 个文件正文一字未动
- session-mechanism/references/01-文档索引.md:补登 `04-决策方法论.md`/`dsh-decision-method/`/`作业规矩/`/`humanizer-en/`
369 lines
23 KiB
Markdown
369 lines
23 KiB
Markdown
---
|
||
name: browser-harness
|
||
description: "Always use browser-harness for any web interaction: automation, scraping, testing, or site/app work."
|
||
---
|
||
|
||
# browser-harness
|
||
|
||
Direct browser control via CDP. For task-specific edits, use `agent-workspace/agent_helpers.py`. For setup, install, or connection problems, read https://github.com/browser-use/browser-harness/blob/main/install.md.
|
||
|
||
## 🔴 本机(用户明令):本技能是**唯一允许**的浏览器工具
|
||
|
||
这条**优先于本文档其它任何说法**(用户 2026-09-13 / 09-25 / 09-26 三次明令):
|
||
|
||
- ⛔ **不许**改用 playwright(含 `playwright-core`)/ puppeteer / selenium / 自己起 headless chrome /
|
||
`agent-browser` 技能 —— **包括"自己写个脚本调它们"**。
|
||
有钩子 `E:\ProgramData\.workbuddy\bin\browser-guard.py` 在执行前拦违规命令;
|
||
`agent-browser` / `playwright-cli` 插件已置 `false`。
|
||
- ⛔ **不附着用户日常 Chrome**。下面「Local Chrome」一节说的"默认附着正在运行的 Chrome"
|
||
在本机**不适用** —— 那样会弹「允许远程调试?」去动用户自己的浏览器(2026-09-26 实际踩到)。
|
||
⇒ 必须起**独立实例**:`BU_CDP_URL=127.0.0.1:9223`(不要用用户 Chrome 默认的 9222)。
|
||
- 🔴🔴 **起实例 = 丢进常驻后台任务,⛔ 别在同步调用里起**(2026-10-09 用户点破「每会开浏览器都要错一次」)。
|
||
本机沙箱**父进程一退就连带杀子进程** ⇒ 同步调用(含 PowerShell `Start-Process`)里起的 Chrome **会立刻死**,
|
||
症状是 `netstat` 查不到 9223、`curl` 报 `WinError 10061`(2026-10-09 实测踩到一次)。
|
||
做法:**先用常驻守端口把浏览器开着,之后每次操作只连不起**。
|
||
✅ **起法只有一条:用本会话的后台任务跑那条命令**(启动器在哪、怎么起、起不来怎么查,都在下面「调 AI 工具」§1)—— 那里给的是能整段粘的命令,不是叮嘱。
|
||
- 🔴🔴 **一类任务只开一个标签页并复用,⛔ 禁止反复开**(2026-10-09 用户令)。
|
||
反复开页会把共享实例堆到**整机卡死**(端口不应答、进程不死,保活脚本也救不回 —— 见下面那条的实测读数)。
|
||
细则**只写在下面那颗同名的 🔴🔴 子弹里**,⛔ 本处不复述 —— 但它**优先于任何"顺手再开一个"的念头**。
|
||
- 🔴 **先探测、能复用就复用**(2026-10-08 用户当场点破:「为什么要重新开浏览器,先检查之前是否开过浏览能否复用」)。
|
||
动手第一件事是查 `9223` 上**有没有已经在跑的实例**;有 ⇒ 直接连,⛔ 不要再起一个。
|
||
⛔ 「量个尺寸 / 截个图 / 取个数」这类小活最容易犯这个错 —— 为了量一次就另起一个 headless 实例,
|
||
既违了上面的禁令,又会和常驻实例抢同一个 profile。
|
||
探法(第一条最轻,够用):
|
||
`netstat -ano | grep -E "127\.0\.0\.1:9223 .*LISTENING"` ⇒ 有 pid = 在跑;
|
||
`curl --noproxy '*' -s http://127.0.0.1:9223/json/version` ⇒ 回 JSON 说明端口后面确实是 CDP。
|
||
确认在跑之后按下面第 2 条的 env 写法连上去,先 `list_tabs()` 摸清现有页,
|
||
自己需要的页用 `new_tab()` 开 —— ⛔ 不碰别人的页,也⛔ 不为自己方便关掉现有页。
|
||
- 🔴🔴 **一类任务只开一个标签页并复用,⛔ 禁止反复开**(2026-10-08 用户令「后续调试网页 一个页面开一个标签就行了不要反复开」;2026-10-09 加严到**按任务算**:「一类任务只允许开一个标签页并复用,禁止反复开标签页」)。
|
||
这条接在上一颗子弹后面:**先复用浏览器**是做对了,但**别在同一个浏览器里反复开页**。
|
||
⚠️ **这不是"标签堆着难看"的问题 —— 它能把整机拖到卡死**(2026-10-09 实测):反复开页把共享实例堆爆后,
|
||
Chrome 会进入**端口不应答、进程却还活着**的僵死态,而保活脚本只看端口 ⇒ 查不出、也救不回。
|
||
当天现场读数:保活日志 `06:56 端口 9223 不应答(pid alive=True)`,直到 `07:06` 进程才退出;用户手动关掉全部会话。
|
||
典型错法:调试一个原型,量一次 `new_tab(原型)` 一次、量完忘了关 ⇒ 一次会话堆出一串同一个页面的标签。
|
||
|
||
**什么算「一类任务」**:同一件事的一整串动作 —— 同一个原型 / 同一轮站点调研 / 同一批数据抓取。
|
||
这类任务**从头到尾只用一个标签**:
|
||
- **同一个页面**(同一 URL / 同一份原型)⇒ 就那一个标签。
|
||
- **换路由** ⇒ 改 hash(`js("location.hash='#/x'")`)或 `goto_url()`,⛔ 不要 `new_tab()`。
|
||
- **换到同任务的另一个页面** ⇒ 也优先在**同一个标签**里 `goto_url()`,⛔ 不要为新页面开新标签。
|
||
- **换视口** ⇒ `cdp("Emulation.setDeviceMetricsOverride", width=…, height=…)` 在**同一个标签**里改,
|
||
⛔ 不要为了量另一个宽度再开一个标签;量完 `cdp("Emulation.clearDeviceMetricsOverride")` 还原。
|
||
- **只有必须并排看两个页面时**(参照物 vs 我方)才开第二个,**用完立刻 `close_tab()`**。
|
||
- **完全不需要页面时**(只取一个数、截一张图)⇒ 复用**当前那个**标签,别再开。
|
||
- 🔴 **收工前必须 `list_tabs()` 复核一遍,把自己开的一个不留**。共享实例是别人的工作现场,
|
||
⛔ 别留尾巴;反向也一样 —— ⛔ 别顺手关掉不是自己开的页。
|
||
- ✅ **动手前先 `list_tabs()`** 看清有哪些标签页,别盲点。(这条同时是上面那条的**收工复核**用的)
|
||
- ⛔ **别自建取数通道**:需要 DOM 数值就用 `js(...)`、需要截图就用 harness 的截图helper,
|
||
⛔ 不要为了「量一个高度」另写一个脚本去起 Chrome。
|
||
- ✅ 静态页 / 公开 API 取数**优先 `curl` 或 WebFetch** —— 用不着浏览器就别起。
|
||
|
||
## 用 browser-harness 调 AI 工具(ChatGPT 等)的提问方法
|
||
|
||
> 2026-10-07 用户定案。适用于「打开 ChatGPT / Claude 网页版求建议、核对方案、做结构评审」这类活。
|
||
|
||
### 1. 🔴 起法:只允许这一种 —— **本会话的后台任务**(字面命令,照抄)
|
||
|
||
**目标**:本会话要用浏览器时有一个 9223 上的独立 Chrome;会话结束,浏览器随之下线。
|
||
|
||
**第 0 步 · 先探测** —— 先跑这两条:
|
||
|
||
```bash
|
||
netstat -ano | grep -E "127\.0\.0\.1:9223 .*LISTENING"
|
||
curl --noproxy '*' -s http://127.0.0.1:9223/json/version
|
||
```
|
||
|
||
有 pid + 回 JSON ⇒ **已经在跑,直接用**(怎么连见第 2 节),本节到此为止。
|
||
(避坑依据 2026-10-08 用户当场点破:「为什么要重新开浏览器,先检查之前是否开过浏览能否复用」。)
|
||
|
||
**第 1 步 · 没在跑 ⇒ 用本会话的后台任务起**
|
||
|
||
两份绝对路径固定,启动器就用**技能包内那一份**(参数化,换工作区不用改代码):
|
||
|
||
- 启动器:`E:\ProgramData\.workbuddy\skills\browser-harness\agent-workspace\bh_chrome_keepalive.py`
|
||
- 解释器:`E:\ProgramData\.workbuddy\binaries\python\versions\3.13.12\pythonw.exe`
|
||
|
||
命令(整段抄,然后**交给你这个会话的后台任务去跑**):
|
||
|
||
```bash
|
||
E:/ProgramData/.workbuddy/binaries/python/versions/3.13.12/pythonw.exe \
|
||
"E:/ProgramData/.workbuddy/skills/browser-harness/agent-workspace/bh_chrome_keepalive.py" \
|
||
--log "E:/ProgramData/.workbuddy/tmp/_bh_chrome.log" \
|
||
>> "E:/ProgramData/.workbuddy/tmp/_bh_bg.out" 2>&1
|
||
```
|
||
|
||
🔴 **"交给你这个会话的后台任务跑"是这条起法的本体**:后台任务只活到本会话结束
|
||
⇒ 会话在、浏览器在;会话收工、浏览器随之下线。这正是要的语义。
|
||
|
||
两处不能省:
|
||
|
||
- **`>> …_bh_bg.out 2>&1`(stdout 全重定向到文件)** —— 依据 2026-10-01 实测:常驻任务往会话里吐输出会把会话日志推过 ~10 MiB ⇒ 宿主 `diagnostic-log:dropped` ⇒ 界面不刷新、看着像卡死。
|
||
- **走后端任务,⛔ 不在同步调用里跑** —— 本机沙箱父进程一退就连带杀子进程;同步调用里起的 Chrome 端口 1 秒内就绪、调用一返回立刻死,随后 `curl` 报 `WinError 10061`(2026-10-09 实测)。
|
||
|
||
**为什么只许这一种**(用户 2026-10-10 令:「只允许这一种方式,使用 browser harness 打开浏览器的时候也必须用这种方式打开浏览器」)
|
||
|
||
- **同步调用里起** ⇒ 父进程一退 Chrome 就死(见上一段的实测)。
|
||
- **Windows 计划任务** ⇒ 它跨会话常驻,用户手关的浏览器会被守卫拉回来(2026-10-10 实测:连关三次、每次都在 1 秒内弹回),语义与"会话结束即回收"相反。⇒ 本机 2026-10-10 已把 `bh-chrome-keepalive` 计划任务**整个撤掉**,起法只留本会话后台任务这一条。
|
||
|
||
**守卫语义**(2026-10-10 改)
|
||
|
||
- Chrome **正常关窗**(`rc=0`,=用户自己点的关闭)⇒ 守卫**停手退出**,关掉就是关掉。
|
||
- Chrome **异常退出**(`rc≠0`,崩溃/被杀)⇒ 立刻重拉,干活不中断。
|
||
|
||
**已知代价(必须知道)**
|
||
|
||
- 本会话只要有 `pending`/`running` 的后台任务,宿主 idle 钩子就被**静默压制**(2026-10-05 实测:被僵尸任务压 6h20m、零日志)⇒ 这段期间本会话的「钩子驱动的唤醒/监管」不生效。所以**只在确实要用浏览器时起它,用完把后台任务停掉**。
|
||
|
||
**第 2 步 · 校验** —— 两条一起看:
|
||
|
||
```bash
|
||
netstat -ano | grep -E "127\.0\.0\.1:9223 .*LISTENING" # 有 pid
|
||
curl --noproxy '*' -s http://127.0.0.1:9223/json/version # 回 JSON
|
||
```
|
||
|
||
两条都过才叫可用。之后每次操作**只连不起**(用第 2 节那条 env 连上去)。
|
||
|
||
**第 3 步 · 起不来时按这张对照表查**
|
||
|
||
- **后台任务立刻结束、端口也没有** ⇒ 读 `E:/ProgramData/.workbuddy/tmp/_bh_bg.out` 与 `E:/ProgramData/.workbuddy/tmp/_bh_chrome.log` 的最后几行,理由都写在那里。
|
||
- **日志写着 `chrome 可执行体不存在`** ⇒ Chrome 不在默认路径。给启动器加 `--chrome "<你的 chrome.exe>"`,或设环境变量 `BH_CHROME`。
|
||
- **日志写着 `port 9223 already listening ⇒ exit`** ⇒ 已经有实例在跑,直接连。
|
||
- **日志写着 `chrome closed cleanly (rc=0) ⇒ stop guarding, exit`** ⇒ 浏览器被人手关过、守卫已按新语义停手。要再用就跑第 1 步那条命令。
|
||
- **端口被拉起来、却没人承认是自己起的** ⇒ 走**进程级证据**:先看 cmdline 找出是哪个脚本在守
|
||
(`Get-CimInstance Win32_Process -Filter "Name='pythonw.exe'"` 打 `CommandLine`),再看**它自己写的那份日志** ——
|
||
时间戳能直接和"关窗"动作对上(2026-10-10 实测:`[23:01:36] chrome exited rc=0 ⇒ relaunch` 连出两条,
|
||
正是用户两次关窗被弹回)。⚠️ **删掉脚本文件 ≠ 停掉进程** —— Python 启动时已把脚本读进内存,
|
||
文件删了它照样守;要 `Stop-Process` 按 pid 结束。⚠️ 也⛔ 别拿 pid 父链当结论(pid 会复用)。
|
||
- **后台任务还在跑、端口却没了** ⇒ 走下面「僵死态」那两条判据。
|
||
|
||
**僵死态:端口不应答、进程却还活着**(避坑依据 2026-10-09 实测)
|
||
|
||
共享实例被堆爆后会进入这个状态 —— 连接探不通,但 `tasklist` 里那个 chrome 进程仍在。
|
||
**判据(两条一起看)**:① `netstat -ano | grep 127.0.0.1:9223` **无 LISTENING**;② `tasklist | grep -i chrome` **仍有那个 pid**。
|
||
⇒ 处置:**重启实例**(关掉残留 chrome,再按第 1 步重起);在原地反复重试连接只会白等。
|
||
两条要记牢:**「端口在」=还要看进程**;**「进程在」=还要看端口** —— 两条都过才叫可用。
|
||
|
||
⚠️ **守端口脚本对僵死态只记日志、不自动重启**(正向目标:**保住现场**,交给会话按上面的判据处置)——
|
||
依据:自动重启会把正在被人排查的现场清掉。所以它只会连续写
|
||
`port 9223 not answering (pid alive=True)`(2026-10-09 `06:56:11` → `07:06:49` 进程才退出)。
|
||
|
||
### 2. 起独立实例(端口 9223)—— 下面这条是**启动器脚本内部**那一步
|
||
|
||
> 🔴 **要它常驻,用第 1 节那条命令(本会话的后台任务)**。
|
||
> 本节这条命令的用途是**看懂它在干什么**与**排障**:照抄它**同步**跑 ⇒ 父进程一退 Chrome 立刻死
|
||
> (避坑依据 2026-10-09:端口 1 秒内就绪、调用一返回就死,随后 `curl` 报 `WinError 10061`)。
|
||
|
||
```bash
|
||
chrome.exe --remote-debugging-port=9223 \
|
||
--user-data-dir=E:/ProgramData/.workbuddy/tmp/bu-chatgpt-profile \
|
||
--proxy-server="socks5://127.0.0.1:10800" about:blank
|
||
```
|
||
|
||
- 端口用 **9223**(用户日常 Chrome 是 9222);`--user-data-dir` 固定复用 ⇒ **登录态保留**。
|
||
- 目标站点要走代理时给启动器加 `--proxy`(例 `--proxy socks5://127.0.0.1:10800`);默认不带。
|
||
- `BU_CDP_URL` **必须带 scheme**:`http://127.0.0.1:9223`。
|
||
- 必须清掉沙箱代理变量(`HTTP_PROXY` / `HTTPS_PROXY` 会劫持本地 CDP ⇒ `502 Bad Gateway`):
|
||
`env -u HTTP_PROXY -u HTTPS_PROXY -u http_proxy -u https_proxy NO_PROXY=127.0.0.1,localhost BU_CDP_URL=http://127.0.0.1:9223 browser-harness`
|
||
- 可执行体写全名:`~/.local/bin/browser-harness.exe`。
|
||
|
||
### 3. 提问怎么写
|
||
|
||
- **说清「意图和目标」+ 给「完整上下文」**,末尾一句明确提问 —— 用户口径:不用写太多控制指令。
|
||
- ⛔ **它看不到你的本机路径**。要么**把全文逐字内联**,要么**上传文件**;⛔ 不要用「见 xxx.md」「文件在 E:/…」代替内容。
|
||
- 一轮一个主题。
|
||
|
||
### 4. ✅ 消息发出去的硬证据 = **输入框被清空**
|
||
|
||
⚠️ **页尾出现你写的提问 ≠ 已发出** —— 输入框本身就在 `main` 里,它的内容会落在 `innerText` **尾部**,看起来就像已发送。发送动作之后**回读输入框长度归零**才算数。
|
||
|
||
### 5. ChatGPT 当前页面(实测,UI 会换代)
|
||
|
||
| 项 | 写法 |
|
||
|---|---|
|
||
| 输入框 | `div.ProseMirror[role="textbox"]`(`#prompt-textarea` 已不存在) |
|
||
| 发送按钮 | `#composer-submit-button` → `button[data-testid="send-button"]` → `button[aria-label="发送"]` |
|
||
| 取正文 | `(document.querySelector('main')||document.body).innerText` |
|
||
| 上传文件 | `input[type=file]` + `cdp("DOM.setFileInputFiles", files=[路径], nodeId=...)` |
|
||
| 切分锚点 | `你说:` / `ChatGPT 说:` / `ChatGPT 可能会出错` |
|
||
|
||
三个易误判点:
|
||
|
||
1. 一页可能有**多个** `ChatGPT 说:`(历史轮次)⇒ 取正文用 **`rfind` 取最后一个**,`find` 会切到上一轮。
|
||
2. **「ChatGPT 可能会出错」不是完稿信号**(它一直在页面上)⇒ 完稿判据 = **正文长度连续两次采样不变**。
|
||
3. **「思考」是输入框工具栏的按钮文案**,不是「正在生成」状态。
|
||
|
||
### 6. 脚本怎么写
|
||
|
||
- ⛔ 不要在 Python 里拼 heredoc + 转义引号喂给 harness(嵌套转义 ⇒ `SyntaxError`)。
|
||
- ✅ 每一步脚本**写成独立 `.py` 文件**,驱动脚本读文件后经 stdin 喂给 harness。
|
||
- ✅ 全程**日志落盘**(每步 rc + 关键读数),失败能直接定位到哪一步。
|
||
- 多标签页时:先 `cdp("Target.getTargets", flatten=True)` 看清有哪些页,必要时关掉重复页再 `new_tab(目标URL)`;各步落在不同标签页上会导致读数错乱。
|
||
|
||
## When Not to Use
|
||
|
||
A basic fetch of public information needs no browser. If a plain HTTP request can read it — a public page, an API, docs — use `curl` or your fetch tool, and leave the browser alone. Use browser-harness when the task needs interaction (click, type, navigate), the user's logged-in session, JS rendering, or a bot-protected page. If a direct fetch fails or returns a shell page, then escalate to the browser.
|
||
|
||
Domain skills are off by default. Set `BH_DOMAIN_SKILLS=1` to enable them; see the bottom section.
|
||
|
||
**If `BH_DOMAIN_SKILLS=1` and the task is site-specific, read every file in the matching `$BH_AGENT_WORKSPACE/domain-skills/<site>/` directory before inventing an approach.**
|
||
|
||
## Usage
|
||
|
||
```bash
|
||
browser-harness <<'PY'
|
||
print(page_info())
|
||
PY
|
||
```
|
||
|
||
- Invoke as `browser-harness`. Use heredocs for multi-line commands.
|
||
- Helpers are pre-imported. `run.py` calls `ensure_daemon()` before `exec`.
|
||
- First navigation is `new_tab(url)`, not `goto_url(url)`.
|
||
- ⚠️ 上游默认流程是「附着到正在运行的 Chrome/Chromium CDP endpoint」—— **本机禁止这样做**,见文件头的🔴段落:必须用 `BU_CDP_URL=127.0.0.1:9223` 起独立实例。
|
||
|
||
## Local Chrome
|
||
|
||
> ⛔ **本机警告**:本节描述的是"附着用户现有 Chrome"的流程,**在本机不要用**(会动用户自己的浏览器)。
|
||
> 需要浏览器时按文件头的规定起独立实例;daemon 连不上先 `--doctor` 排查,⛔ 不要让 harness 自动去拉起用户日常 Chrome。
|
||
|
||
If the daemon cannot connect, run diagnostics:
|
||
|
||
```bash
|
||
browser-harness --doctor
|
||
```
|
||
|
||
If Chrome is not running at all, the harness launches it automatically and retries — no user action needed beyond clicking Allow if a permission popup appears.
|
||
|
||
If Chrome is running but remote debugging is not enabled, the harness opens:
|
||
|
||
```text
|
||
chrome://inspect/#remote-debugging
|
||
```
|
||
|
||
Ask the user to tick "Allow remote debugging for this browser instance" and click Allow if Chrome shows a permission popup. Then retry the same `browser-harness` command.
|
||
|
||
## Remote Browsers
|
||
|
||
Use Browser Use cloud for headless servers, parallel sub-agents, or isolated work.
|
||
|
||
Cloud browsers are managed Chrome instances hosted by Browser Use. Each one is a fresh, isolated browser. Proactively suggest one (briefly explain why) when:
|
||
|
||
- **The user wants multiple concurrent tasks.** Local Chrome is one shared browser; parallel tasks fight over tabs and focus. One cloud browser per task keeps them fully isolated.
|
||
- **Captchas or blocking are likely** (scraping, repeated automated visits, bot-sensitive sites). Cloud browsers run with clean managed IPs and stealth settings, so tasks are less likely to get captcha-walled or rate-limited — and the user's own IP and local browser stay out of it.
|
||
|
||
You can also direct the user to try the same agent behind Browser Harness, fully hosted, in Browser Use Cloud (it's called the v4 agent): https://cloud.browser-use.com?utm_source=skill&utm_medium=browser-use&utm_campaign=v4.
|
||
|
||
Authenticate once:
|
||
|
||
```bash
|
||
browser-harness auth login
|
||
```
|
||
|
||
Or import a key safely:
|
||
|
||
```bash
|
||
printf '%s' "$BROWSER_USE_API_KEY" | browser-harness auth login --api-key-stdin
|
||
```
|
||
|
||
Pick a short made-up name; `r7k2` below is just a placeholder:
|
||
|
||
```bash
|
||
browser-harness <<'PY'
|
||
start_remote_daemon("r7k2")
|
||
PY
|
||
|
||
BU_NAME=r7k2 browser-harness <<'PY'
|
||
new_tab("https://example.com")
|
||
print(page_info())
|
||
PY
|
||
```
|
||
|
||
When the task is done and a cloud browser is still running, ask directly: "Should I close this browser now?" If yes, run `stop_remote_daemon(name)`. Remote daemons bill until they stop or time out.
|
||
|
||
Do not start a remote daemon and then keep using the default daemon. Use the same name for `BU_NAME`.
|
||
|
||
Cloud profile cookie sync reference: https://github.com/browser-use/browser-harness/blob/main/interaction-skills/profile-sync.md.
|
||
|
||
## Page Workflow
|
||
|
||
- Prefer to find elements with the accessibility tree, not screenshots: `cdp("Accessibility.getFullAXTree")["nodes"]` has every element's role, name, and `backendDOMNodeId` — filter in Python before printing (it is thousands of nodes). Coordinates: `q = cdp("DOM.getBoxModel", backendNodeId=n)["model"]["content"]; x, y = sum(q[0::2])/4, sum(q[1::2])/4` (viewport px, ready for `click_at_xy`; negative/oversized means scroll first).
|
||
- Clicking: AX node -> box center -> `click_at_xy(x, y)` -> verify with a targeted `js(...)`/`page_info()` check.
|
||
- Fall back to raw HTML via `js(...)` only when the AX tree lacks the element (canvas, exotic widgets); screenshot when layout or imagery matters.
|
||
- After navigation, call `wait_for_load()`.
|
||
- If the current tab is stale or internal, call `ensure_real_tab()`.
|
||
- Use `js(...)` for DOM inspection or extraction when coordinates are the wrong tool.
|
||
- Login walls: stop and ask. Exception: use available SSO automatically when Chrome is already signed in; still stop for passwords, MFA, consent, or ambiguous account choice.
|
||
- Raw CDP is available with `cdp("Domain.method", ...)`.
|
||
|
||
## Recordings and Videos
|
||
|
||
Fresh installs do not record. Users can enable local background traces:
|
||
|
||
```bash
|
||
browser-harness recordings enable
|
||
browser-harness recordings disable
|
||
browser-harness recordings
|
||
```
|
||
|
||
`BH_RECORD=1` or `BH_RECORD=0` overrides the preference for one process. Any
|
||
natural nudge to “record,” “show,” “demo,” or “make a video” opts in that task;
|
||
significant work alone does not.
|
||
|
||
Before browser work, call `start_recording(name, title=...)`, retain its exact
|
||
returned directory, and call `stop_recording()` after verifying the result.
|
||
Never replace that path with `recordings --latest`. For a request made after
|
||
the task, use:
|
||
|
||
```bash
|
||
browser-harness recordings --latest
|
||
```
|
||
|
||
Use it only if timestamps and pages match; otherwise say the work was not
|
||
captured. Never reenact a completed task. For a video, follow
|
||
[make-video.md](https://github.com/browser-use/browser-harness/blob/main/interaction-skills/make-video.md).
|
||
If sub-agents are available, they may handle post-production from the exact
|
||
recording path while the main agent returns the task result.
|
||
|
||
## Interaction Skills
|
||
|
||
If you get stuck on a browser mechanic, check https://github.com/browser-use/browser-harness/tree/main/interaction-skills.
|
||
|
||
- connection.md
|
||
- cookies.md
|
||
- cross-origin-iframes.md
|
||
- dialogs.md
|
||
- downloads.md
|
||
- drag-and-drop.md
|
||
- dropdowns.md
|
||
- iframes.md
|
||
- make-video.md
|
||
- network-requests.md
|
||
- print-as-pdf.md
|
||
- profile-sync.md
|
||
- screenshots.md
|
||
- scrolling.md
|
||
- shadow-dom.md
|
||
- tabs.md
|
||
- uploads.md
|
||
- viewport.md
|
||
|
||
## Design Constraints
|
||
|
||
- Coordinate clicks default. CDP mouse events pass through iframes/shadow/cross-origin at the compositor level.
|
||
- Keep the connection model simple: use the default daemon, `BU_NAME`, `BU_CDP_URL`, `BU_CDP_WS`, or `start_remote_daemon(...)`.
|
||
- Core helpers stay short. Put task-specific helper additions in `$BH_AGENT_WORKSPACE/agent_helpers.py`.
|
||
|
||
## Gotchas
|
||
|
||
- `chrome://inspect/#remote-debugging` must be enabled for local Chrome control.
|
||
- Chrome may show an "Allow remote debugging?" popup; wait for the user to click Allow. Do not retry in a loop — Chrome pops a fresh dialog for every new connection, and the daemon's single held connection is what makes this a one-time click.
|
||
- Omnibox popups are not real work tabs.
|
||
- CDP target order is not Chrome's visible tab-strip order.
|
||
- `BU_CDP_URL` is an HTTP DevTools endpoint; the daemon resolves it to WebSocket.
|
||
- Ask before leaving cloud browsers running; stop them with `stop_remote_daemon(name)` or `PATCH /browsers/{id} {"action":"stop"}`.
|
||
|
||
## Domain Skills
|
||
|
||
Only applies when `BH_DOMAIN_SKILLS=1`. Otherwise ignore domain skills.
|
||
|
||
When enabled, search `$BH_AGENT_WORKSPACE/domain-skills/<host>/` before inventing an approach. `goto_url(...)` returns up to 10 skill filenames for the navigated host.
|