让 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 对照,通常会得到三种结果:

  1. 一致且仍在有效期内:什么都不改;
  2. 一致,但首次验证或已过期:刷新验证时间,草稿转为已验证;
  3. 发生漂移:插入新步骤、记录失败、累加坑、提升版本号。

机械部分可以自动写回:新步骤、lastIssue、坑的次数、统计和 changelog。判断部分必须人工处理:

  • 旧 selector 失效,就要换掉,而不是再叠一个备用 selector;
  • 同一步反复出问题,就要拆步骤、补等待或改锚点;
  • 一个步骤总被跳过,要么标成可选,要么删掉;
  • 重复出现的坑,不只是提醒,更要把应对写进正常步骤。

如果只做机械累加,Playbook 会越来越长,却不会越来越稳。

八、什么时候才值得入库

不是每次运行都要提一个版本。高频操作可以当天多次运行共用一个分支,把统计和坑累计起来,结束时统一整理。真正值得更新的是这些情况:

  • 首次从草稿跑通;
  • 超过 30 天重新验证;
  • 出现新步骤、顺序变化、失败或重试;
  • 同一个问题重复出现;
  • 页面改版导致锚点变化。

普通成功、没有偏差、仍在有效期内的运行,不需要为了“有记录”制造一个空提交。

最后

Agent 的浏览器记忆不应该是一个神秘的黑盒。它应该是几份能被打开、比较、拒绝和回滚的操作规范,再加一套能证明这次执行确实按它跑过的日志。

第一次靠探索,第二次靠记忆,第三次靠迭代。

如果第二次还要重新猜按钮在哪里,那不是 Agent 在成长,只是同一个任务又做了一遍。