让 Agent 操作浏览器,第一次跑通其实不难。难的是第二周还能不能跑:窗口大小变了,按钮改名了,成功提示换了位置,上次踩过的坑没有留下任何痕迹,于是下一次又从零开始。
我把这类经验从聊天记录里拿出来,改成一种可校验的 Playbook。它不是“教模型记住页面”,而是把一次操作拆成输入、步骤、期望、回执和坑,每次执行后再拿实际过程与它对照。
目标很朴素:同一个任务,第二次必须比第一次更稳。
一、三件东西要分开
浏览器操作里最容易混在一起的是三类信息:
| 东西 | 作用 | 是否入库 |
|---|---|---|
| Playbook | 某个站点、某个任务的标准步骤和环境假设 | 是,受版本控制 |
| 运行记录 | 这一次实际怎么走的、哪里重试了 | 否,临时保存 |
| 工具 | 校验、展示、对照和机械写回 | 是 |
Playbook 不是“万能记忆”。它只回答一个范围很窄的问题:在某个站点、某个入口、某组前置条件下,这个任务通常怎么做,什么信号算完成。
运行记录也不是 Playbook。它允许失败、重试、跳过和异常,是一次真实发生的轨迹。
把两者混成一个文档,很快就会得到一个既不像规范、也不像日志的怪物:没人知道哪一行是必须遵守的规则,哪一行只是上次碰巧遇到的情况。
二、开工前先读,而不是先让模型猜
一个可用的 Playbook 至少应该写清楚:
- 任务 ID 和平台;
- 窗口尺寸、登录态、前置页面;
- 输入占位符,而不是真实账号或 Token;
- 每一步的意图、目标、动作、期望和失败兜底;
- 哪些步骤会产生外部副作用;
- 哪些步骤是人工点、哪些可由 Agent 点;
- 已知坑、上次出问题的步骤和累计失败次数;
- 最近一次真实验证时间。
开工时先列出已有 Playbook,再打开本次任务对应的那一份。如果当前是草稿、从未实测,或者超过 30 天没有复跑,系统不会让 Agent 按记忆连跑,而是提示逐步核对:每做一步,对照一次期望;对不上就停下记录。
这一步非常重要。浏览器自动化最大的危险不是“不会点”,而是“按照一份过期记忆自信地点”。
三、控件要用语义锚点,不要只写坐标
坐标按钮很好写:屏幕左上角向右多少像素、向下多少像素。也很好失效:窗口换屏、系统缩放、浏览器侧栏、页面浮动提示,任何一个变化都可能让点击落到别处。
Playbook 优先记录:
- 可见名称
label; - 控件角色
role; - 稳定的
selector,例如data-testid; - 必要时再补文本和坐标。
坐标只能和语义锚点一起出现。单独存在的坐标应该被校验工具直接警告。
这不是教条。对于 B 站投稿这种页面,按钮文案、角色和控件状态都比像素位置更可靠;对于动态弹窗,等待一个明确的 expect 也比固定 sleep(3) 更可靠。
四、副作用必须单独守门
Playbook 里出现“发布”“提交”“发送”“删除”这类步骤,并不等于授权。
每个外部副作用步骤都要显式标成 sideEffect: true,并在前面放一个 confirm。Agent 执行时必须取得用户当次逐项确认,运行记录里写清 approved: true。有些平台甚至应该规定 actor: "human",由人亲自点最后一步。
这条边界不能藏在自然语言里。不能因为 Playbook 写了“点击发布”,就把它当成长期授权。
Playbook 还不能保存 Cookie、Token、带签名的 URL 或真实密码。所有变量都用占位符,由运行时注入。校验工具会扫描类似凭据的键值形态;发现后不是“警告一下”,而是让这份 Playbook 不通过。
五、操作中写运行记录:允许偏差,但不允许含糊
执行过程中,每一步都必须有一个明确结果:
ok:符合期望;retry:重试过,写清尝试次数;failed:确实失败;skipped:主动跳过,写原因。
Playbook 里没有的新步骤、新弹窗、新滚动位置,要按实际位置插进运行记录。新踩的坑要同时写症状和绕过方式。最后记录整轮结果与耗时。
这一层看起来像日志洁癖,实际决定了后面能不能学到东西。只写“操作成功”没有价值;写“第 4 步按钮点击后标题被回滚,改为逐字输入后成功”,下一次才能直接避开。
六、一个真实例子:标题填进去了,组件状态却没变
B 站投稿遇到过一个很典型的自动化假成功。
第一次上传多分 P 视频时,平台把稿件标题回填成了 P1 的文件名。Agent 进入编辑页后,通过填充或粘贴把正确标题写进输入框,页面上看起来已经变了;但失焦后标题又回到旧值。
根因不是“标题输入框不可编辑”,而是操作只改了 DOM 的显示值,没有触发组件内部状态。最后可行的办法是:在标题框中逐字键入,让输入事件真正发生,再用提交按钮保存;投稿后还必须回到公开页读回标题,确认不是只在编辑框里看起来正确。
这个案例改变了我写 Playbook 的方式。以前会写:
步骤 4:把标题填进标题框
期望:标题框显示新标题
现在会写:
步骤 4:逐字输入标题,触发组件状态
期望:失焦后标题仍为新值
步骤 5:提交后打开公开页
期望:公开页标题与目标标题完全一致
“输入框里出现了文字”不是完成条件。“公开页回读到正确标题”才是。
七、做完以后,机械写回和人工判断分开
运行结束后,用本次记录和 Playbook 对照,通常会得到三种结果:
- 一致且仍在有效期内:什么都不改;
- 一致,但首次验证或已过期:刷新验证时间,草稿转为已验证;
- 发生漂移:插入新步骤、记录失败、累加坑、提升版本号。
机械部分可以自动写回:新步骤、lastIssue、坑的次数、统计和 changelog。判断部分必须人工处理:
- 旧 selector 失效,就要换掉,而不是再叠一个备用 selector;
- 同一步反复出问题,就要拆步骤、补等待或改锚点;
- 一个步骤总被跳过,要么标成可选,要么删掉;
- 重复出现的坑,不只是提醒,更要把应对写进正常步骤。
如果只做机械累加,Playbook 会越来越长,却不会越来越稳。
八、什么时候才值得入库
不是每次运行都要提一个版本。高频操作可以当天多次运行共用一个分支,把统计和坑累计起来,结束时统一整理。真正值得更新的是这些情况:
- 首次从草稿跑通;
- 超过 30 天重新验证;
- 出现新步骤、顺序变化、失败或重试;
- 同一个问题重复出现;
- 页面改版导致锚点变化。
普通成功、没有偏差、仍在有效期内的运行,不需要为了“有记录”制造一个空提交。
最后
Agent 的浏览器记忆不应该是一个神秘的黑盒。它应该是几份能被打开、比较、拒绝和回滚的操作规范,再加一套能证明这次执行确实按它跑过的日志。
第一次靠探索,第二次靠记忆,第三次靠迭代。
如果第二次还要重新猜按钮在哪里,那不是 Agent 在成长,只是同一个任务又做了一遍。
COMMENTS
读者评论