一个反直觉的观察
让 Agent 操作浏览器完成一组社区日常任务(浏览、点赞、收藏、评论、发布),最容易归因错误的就是失败:任务没完成,第一反应往往是模型不行。真把每一处失败摊开看,模型负责的部分几乎没出问题,出问题的是环境、框架契约和状态确认。
第一类卡点:环境不配合
浏览器实例的视口尺寸是 0x0,所有 getBoundingClientRect() 返回的坐标落在负半轴。点击工具把 rect 中心点当成真实鼠标坐标,直接判定超出可视区域。模型给出的指令是对的,元素也确实存在,失败发生在坐标层。
绕过方式是放弃坐标点击,改为在目标元素上派发完整事件序列:pointerover、pointerdown、mousedown、pointerup、mouseup、click,其中 pointer 前缀用 PointerEvent,其余用 MouseEvent。只发 click 在很多组件库上静默无效,因为内部状态切换挂在 pointerdown 上。
function fire(el, x, y) {
['pointerover', 'pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'].forEach(function (tp) {
var E = tp.indexOf('pointer') === 0 ? PointerEvent : MouseEvent;
el.dispatchEvent(new E(tp, { bubbles: true, cancelable: true, view: window, clientX: x, clientY: y }));
});
}
工程含义很直接:Agent 的 action 空间不该只有「点击」这一个原子动作,至少要能降级到事件派发,并且能读取环境约束(视口、滚动位置、可见性)。
第二类卡点:框架契约不透明
社区富文本评论框是 contenteditable 的 div。写入要 focus、设置内容、再派发 InputEvent(事件名 input),框架才会把内容同步进自己的响应式模型,字数统计跟着更新。看起来一切正常,但点发送不发请求——提交逻辑挂在键盘事件上,只认 Ctrl+Enter。
var el = document.querySelector('.rich-input');
el.focus();
el.textContent = 内容;
el.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true }));
// 提交需要真实键盘事件:Ctrl + Enter
收藏是另一种形式的不透明。点收藏只打开收藏集弹窗,真正的写入发生在第二步:勾选条目内的 checkbox-icon,再点确定,此时才发出 add_article 接口。窄视口下悬浮栏被媒体查询隐藏,还要先强制显形才能取到按钮。
这些行为文档里查不到,只能从 DOM 与网络请求反推。Agent 缺的不是智能,是可观测性:能读网络请求、能读 class 变化,才能把「做了」和「成功了」区分开。
第三类卡点:把隐藏模板读成风控
页面 innerText 里能读到「当前操作失败」,很容易判定为风控而中止。实测那个 risk-modal 常驻在 DOM 里,getBoundingClientRect() 的宽高都是 0,从未弹出——innerText 与 textContent 都会包含隐藏节点。
var m = document.querySelector('.risk-modal');
var r = m.getBoundingClientRect();
// width 为 0 且 height 为 0:模板常驻,并非真实拦截
var real = r.width > 0 || r.height > 0;
判断条件必须是可见性断言:宽度大于 0 才算真弹窗。缺了这一层,Agent 会在完全正常的页面上反复误停;反过来只凭文案就继续,又会把真实拦截吞掉。
第四类卡点:延迟计数与不可控项
社区学习类的浏览任务,单日连开多篇、每篇停留十几秒并滚动,任务页仍可能停在原计数,说明阅读事件上报存在额外限制。反复重试只会制造无效点击,正确做法是记录未计入并在结算里标注。
移动端登录这类项则完全在浏览器 Agent 的能力边界之外,写进「必须手动」清单比绕路实现更省时间。
结论
模型能力决定 Agent 能不能想到下一步;能不能把日常任务跑完,取决于另外三件事:
- action 空间的降级能力:坐标点击之外,有没有事件派发、键盘输入、DOM 直接写入。
- 状态确认机制:每个动作后跟一次断言(字数、class、URL、网络请求),让失败可以稳定复现。
- 可见性与真实性的区分:读 DOM 必须带 rect 判断,否则隐藏模板会持续制造假信号。
按这套流程重跑,成功率提升来自工程改动,而不是换模型。