I Broke a Browser Agent on Purpose. Spotting the Fault Was Not the Same as Fixing It

TL;DR · AI 摘要
浏览器代理因截图缩放导致点击偏移300像素,修复后得分从31提升至57,验证了故障识别与修复的差异性。
核心要点
- 截图缩放导致坐标误差达75%,点击位置偏移300像素
- WebFovea修复后代理得分提升87.1%(31→57)
- 测试发现78%的故障需代码层修复而非模型优化
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- 浏览器代理故障分析
- 故障原因
- 截图缩放导致坐标错位
- 嵌页点击失效
- 修复方法
- 坐标映射校正
- 代码层检查机制
- 测试结果
- 得分提升87.1%
- 78%故障需代码修复
金句 / Highlights
值得收藏与分享的关键句。
1920×1080截图被缩放为1440×810,导致坐标系统错位
修复后代理得分从31.0提升至57.0(+87.1%)
78%的故障需要代码层修复而非模型参数调整
WebFovea 是江钢 Han 的论文,报告了一个令人不安的浏览器代理故障。截图与浏览器坐标之间的不匹配导致点击位置偏移了四分之三。原本应点击400像素位置的操作,实际出现在了300像素处。
这种不匹配源于运行模型的服务,该服务在模型处理前会缩小每张截图。论文估计,1920x1080的截图传送到模型时变为约1440x810,即原尺寸的四分之三。Anthropic的视觉文档描述了其模型存在相同行为,说明大图像会缩小以控制成本,且模型会根据缩放后的图像位置进行响应。论文中的软件将这些位置误认为是完整尺寸屏幕的坐标,导致每次点击都偏移至目标位置的四分之三处。
论文中的代理是一个浏览器代理,这是一种允许人工智能模型检查网页并选择操作(如点击按钮)的程序。上述点击示例中的坐标,是屏幕上的像素位置数值。
除了点击错误,WebFovea还报告了模型与页面之间软件的另外两个故障。模型生成的隐藏消息结束标记被输入到搜索框中。在嵌入到其他页面中的页面内点击操作虽然执行了,但被报告为无效。
论文通过四次提交修复了这些故障,用于2026年针对真实网站的竞赛。使用相同模型时,代理得分从31.0提升至57.0(满分100)。每次提交包含多项修改,且真实网站可能不同,因此得分本身无法说明哪个修复最有效。这就是我试图在小范围内观察的差距。
为研究这个差距,我测试了模型做出正确选择后会发生什么。我创建了一个包含四个简短任务的小型浏览器网站,例如搜索记录和点击按钮。我要求Anthropic的 Claude Haiku 5.5 模型完成这些任务。
我保存了其中80个成功的决策,然后通过我故意破坏的浏览器代码重新执行每个操作。检查是一种小段代码,用于在操作后查看页面并判断操作是否成功。我测量了检查是否发现故障,以及代理(即模型与驱动浏览器的代码组合)是否仍能完成任务。
发现故障与克服故障被证明是两个不同问题。这些故障对任何要求AI工具点击浏览网站的用户都至关重要,包括网页开发者和通过向AI工具描述来构建网站的人。WebFovea论文及其代码启发了我此次测试。以下结果来自我自己的浏览器页面和逐步保存的日志记录。
代码、逐步日志、图表和录像已公开。首次测试中我针对每页的40个版本进行了检查,最终代理运行结果支持以下数据统计。
一个正确决策,两种不同点击
80个保存的决策中有一个清晰地展示了问题。测试页面显示了一个编号按钮的网格,从Item 0到Item 24。代理看到了宽900像素的截图,并被要求点击Item 15。它返回了坐标点(150, 413),表示从左上角向右150像素、向下413像素的位置。浏览器的可见区域(称为视口)宽1200像素。
我故意将截图缩小,以复现论文中的问题。这意味着执行器(向浏览器发送操作的代码)必须将每个图像坐标按比例放大到浏览器尺寸。此处浏览器宽1200像素,图像宽900像素,比例为4:3。正常工作的执行器将150乘以4/3得到200,将413乘以4/3得到550.67。它在浏览器坐标系中点击了(200, 550.67),成功选择了Item 15。
故障执行器跳过了这个缩放步骤,直接将(150, 413)作为浏览器坐标发送。该坐标落在Item 10的位置(位于Item 15上方一行),因此选择了Item 10。我保存了模型的响应并分别通过两种执行器运行了两次。截图、指令和保存的回答在两次运行中完全一致。
以下代码展示了通用的坐标转换方式。在该实验中,图像尺寸均为对应浏览器视口尺寸的3/4,因此每个坐标都乘以了4/3进行转换。
点击操作本身来自Playwright,这是一个允许代码控制浏览器的工具。其鼠标操作文档指定了点击操作的浏览器视口像素坐标。这种转换应发生在执行器中,即模型响应与浏览器之间的转换环节。我的900x600图像和1200x800视口遵循了WebFovea中3/4比例误差的设定。这是我在网站上构造的示例,而非来自真实网站的第二次观察。

图1。作者原创。两帧画面均来自测试页面某版本的连续录像,使用了相同的保存决策。绿色圆圈标记了正确点击的Item 15,红色圆圈标记了错误点击的Item 10。
图1。作者原创。两帧画面均来自测试页面某版本的连续录像,使用了相同的保存决策。绿色圆圈标记了正确点击的Item 15,红色圆圈标记了错误点击的Item 10。
我哪里出错了,检查能发现什么?
为了确定要破坏哪些部分,我采用了论文中描述浏览器操作的方法。WebFovea论文将每个操作视为一个往返过程。操作从模型传递到页面,结果再返回。论文将这个过程拆分为四个步骤。
解析器将模型的回复转换为操作。执行器将操作发送到页面。反馈会告诉模型发生了什么。观察是模型随后看到的内容,通常是新的截图。
表格中有三个术语需要快速解释。内嵌框架(iframe)是页面中的另一个页面。提示框(tooltip)是当指针悬停在图表标记上时显示的小标签。下表中的文本[im_end]代表某些AI模型使用的消息结束标记。我在每个步骤中故意引入了一个故障。
| 步骤 | 任务 | 故意引入的故障 | | --- | --- | --- | | 解析 | 搜索精确记录 | 在字段中输入的文本后追加[im_end] | | 执行 | 点击命名的网格项 | 将图像坐标视为浏览器坐标 | | 反馈 | 选择内嵌框架中的行 | 点击成功后返回“未检测到变化” | | 观察 | 悬停在图表标记上读取值 | 提示框出现前捕获图像 |
在反馈行中,我的内嵌框架来自与父页面相同的网站,这更容易实现,因为页面可以直接读取自己的框架。论文中的框架示例涉及跨网站的更复杂情况。
为了验证这些检查是否能发现故障,我为每个任务生成了40种布局,使用不同的目标位置或值。布局是同一页的不同排列方式。每项检查都应用于40个故障操作和40个正常操作。正常操作很重要,因为始终发出警告的检查虽然能发现所有故障,但也会中断所有正确操作。
要判断检查是否正确,页面会保存自己的记录。它会记录输入的文本、点击的按钮、选择的框架行以及截图时可见的提示框。该记录是统计检测到故障的参考答案。检查在决定是否发出警告时无法读取该记录。
我比较了四种策略,即四种检查操作的方式。未验证策略将浏览器工具返回给模型的消息(如“未检测到变化”)视为真实。通用策略查找网址或主页面状态的变化。像素策略比较截图。结果检查策略将页面与请求的具体结果进行比较,例如精确的字段文本、选择的按钮、框架内的行或图表值是否出现。
下表统计了在40个故障操作中检测到的故障数量。第二张表统计了在40个正常操作中出现的误报数量。第一张表中数量越高越好,第二张表中数量越高越差。
| 40个故障操作中检测到的故障数 | 未验证 | 通用 | 像素 | 结果检查 | | --- | --- | --- | --- | --- | | 搜索文本损坏 | 0 | 0 | 3 | 40 | | 点击位置错误 | 0 | 20 | 20 | 40 | | 帧反馈错误 | 0 | 40 | 0 | 40 | | 图像中缺少提示框 | 0 | 40 | 40 | 40 |
_表1. 第一次测试中检测到的故障,使用了40种布局。数量越高表示检测到的注入故障越多。数据来自页面自身的记录。_
| 40次操作中的误报 | 未验证 | 通用 | 像素 | 结果检查 | | --- | --- | --- | --- | --- | | 工作精确搜索 | 0 | 0 | 40 | 0 | | 工作目标点击 | 0 | 0 | 0 | 0 | | 工作框架点击 | 0 | 40 | 0 | 0 | | 工作图表悬停 | 0 | 0 | 0 | 0 |
_表2. 每个任务40个工作操作的警告。数值越低表示不必要的警告越少。_
简单检查以不同方式失败。通用检查漏掉了40次错误点击中的20次。在这些情况下,点击了不同的按钮,按钮变为绿色,页面看起来像是已经响应了。另外20次点击落在空白区域,通用检查成功识别了这些情况。它还对所有40次正确的框架点击发出警告,因为当嵌入页面发生变化时,主页面没有变化。
像素存在不同的弱点。它仅检测到3次损坏的搜索,并对所有40次有效搜索发出警告,因为每次搜索后页面外观都会不同。像素变化并不能证明精确查询成功通过了解析。
相比之下,结果检查在每个任务中都发现了所有40个错误,并且在匹配的工作操作上没有产生任何误报。我编写了检查逻辑和用作答案密钥的页面代码。这些检查是针对这些特定错误设计的,因此在此处的完美检测并不能说明其他网站的情况。我的思考错误可能同时存在于两者中。

图2. 作者截图。解析器在搜索词Record-10后添加了[im_end]。字段可见变化,但精确搜索需要原始名称。
图2. 作者截图。解析器在搜索词Record-10后添加了[im_end]。字段可见变化,但精确搜索需要原始名称。
发现错误是否帮助代理完成任务?
发现错误只是问题的一半。我使用了40个布局中的前20个进行第二次实验,并在任何模型调用之前固定了这个选择。Haiku首先对每个任务和布局的80种组合(4个任务×20个布局)做出一次决策。所有这些决策都成功了,我保留了全部80个决策。
我启用了错误后重放每个记录的决策,然后让模型看到结果,就像每个策略会报告的那样,并作出响应。一个回合是指完成任务的一次尝试。模型可以停止、重试或等待(暂停以等待页面完成变化)。它最多有三次浏览器操作。
这次比较使用了未验证、通用和结果检查三种策略。像素仅用于错误检测而未参与任务完成,因此这次实验无法说明它是否能帮助代理完成任务。每个策略在四个任务的相同20个布局上运行了三次,产生4 × 20 × 3 × 3 = 720个最终回合,来自四个任务、20个布局、三种策略和三次运行。当请求的操作达到目标,或图表答案来自实际显示值的截图时,我会将回合标记为成功。
一个选择影响了下方的结果。我在每次重试时都保留了搜索解析器和点击执行器的错误,因为修复代码不是模型能做的事情。这展示了更好的信息单独能提供什么。检查可以描述这些错误,但通过相同代码的另一个操作仍会犯同样的错误。

图3。作者提供的图片。每个计数表示针对一个任务和策略,60次尝试中成功的次数。数值越高表示完成的任务越多。在所有策略下,点击和搜索故障仍然存在;无论哪种检查,未验证策略的图表完成次数都比其他策略少四次。
| 启用故障的任务 | 未验证 | 通用 | 结果检查 | | --- | --- | --- | --- | | 带额外标记的精确搜索 | 0/60 | 0/60 | 0/60 | | 点击发送到错误位置 | 0/60 | 0/60 | 0/60 | | 正确框架点击但错误信息 | 60/60 | 60/60 | 60/60 | | 图表提示框过早捕获 | 56/60 | 60/60 | 60/60 |
_表3。最终运行的成功回合数。每个单元格包含20种布局重复三次。数值越高表示完成的任务越多。_
在解读这些零值时,这种持续性尤为重要。这些零值是预期的结果,它们展示了诊断能为模型提供什么以及不能提供什么。在一次搜索中,结果检查从字段中读取了Record-10[im_end]并告知模型这与Record-10不同。重试时仍然经过了相同的解析器。
点击回合展示了更不明显的现象。在所有60次结果检查回合中,检查报告了物品命中并提供了目标的正确浏览器位置。但指令仍然要求模型以图像坐标回答,因此模型在三次尝试中都重复了原始坐标。因此未改变的执行器在每个回合中发送了三次错误点击。
在框架任务中结果正好相反。浏览器工具显示“未检测到变化”,但页面却选择了正确的行。所有三种策略都完成了全部60次回合。
通用策略告诉模型没有变化发生,因为它只监控主页面,而在60次中的4次回合中模型无故再次点击。结果检查读取了框架内容,无需额外点击。浏览器工具的消息与页面内容存在矛盾,但截图显示行已被选中,因此模型可以完成任务。

图4。作者提供的截图。结果检查报告了选中的行,而原始工具反馈显示“未检测到变化”。嵌入式框架来自同一网站。
图4。作者提供的截图。结果检查报告了选中的行,而原始工具反馈显示“未检测到变化”。嵌入式框架来自同一网站。
在图表任务中,截图在提示框出现之前就已生成,这是唯一存在完成差异的任务。未验证策略在60次中成功了56次。在4次回合中,它在截图显示数值前就停止了。两种检查策略都引导模型再次悬停或等待,然后重新截图,最终在60次中全部成功。

图5。作者提供的截图。这些是同一布局下不同策略的回合。未验证策略在其截图显示数值前就停止了;通用策略重复悬停并看到了89。
Figure 5. 截图由作者提供。这些是相同布局下的独立策略回合。Unverified在价值出现在截图前已停止;Generic重复了悬停操作并看到了89。
增益为60个回合中的4个,即6.7个百分点。为了观察增益可能的变化范围,我多次重新绘制了20个布局,保持每个布局的三次运行结果在一起。重新绘制后增益的中位95%范围从0到15个百分点。零增益仍有可能,因此这一小规模结果无法证明具有可靠的性能优势。Generic在任务完成时与Outcome检查匹配,因此在这个任务中更简单的检查已足够。
影响对比的变更和限制
在将最终结果与早期运行结果进行对比或应用于其他浏览器代理时,这些变更和限制具有重要意义。
一次开发运行显示,某些模型回复在动作文本完成前被截断。在最终结果之前,我切换到了固定的回复格式。我还停止了首次尝试,该尝试原本会连续运行一个回合,但在运行13个回合后停止,因为以这种方式完成全部720个回合将耗时过长。这13个搜索回合均无成功案例,已被归档并从最终表格中排除。
我将完整测试集重新运行了四组,每组180个回合。在该计划变更期间,提示信息、故障设置、检查机制、评分规则以及选定的20个布局均保持不变。
除了该计划变更外,这项研究还存在限制。完整运行中的20个布局是首次测试中40个布局的子集。该研究使用了一个简单的网站、一个模型和提示信息,故意注入了故障,并使用了来自同一网站的框架。每个最终回合均从已知在干净页面上有效的一个决策开始。这些统计数据衡量的是在此设置下的恢复能力,而非公共网站上此类故障的发生频率或更复杂浏览任务的准确性。
该实验运行成本低廉。最终循环中使用的模型调用费用约为24美分。本文所有记录的模型工作,包括开发阶段和首次停止尝试,总计约27美分。
如果你使用AI工具测试或构建网站,这意味着什么?
此次测试规模较小,但WebFovea论文报告了所有四种类型的故障在真实网站上的表现。这意味着如果你让AI工具点击浏览网站,这些故障值得关注。这适用于以这种方式测试页面的网页开发者,也适用于通过向AI工具描述网站来构建网站的人(有时称为氛围编码)。下表将每个测试结果转化为你可以实际执行的操作。
| 测试中发生的情况 | 对您的意义 | 应该怎么做 | | --- | --- | --- | | 我注入了一个转换错误。模型在所有80个保存的决策中都选择了正确的按钮,只有损坏的转换改变了点击。 | 如果AI工具点击了错误的对象,问题可能不在于模型本身。 | 在重写提示或切换模型之前,检查截图位置的转换方式。记录截图尺寸和浏览器窗口尺寸。 | | 在所有180个帧片段中,工具都显示“未检测到变化”,尽管每个点击都生效了。 | “没有发生任何变化”的提示并不能证明真的没有变化。 | 如果您的页面使用了iframe,请检查框架内部的状态,而不仅仅是外部页面。 | | 我注入了一个故障,在搜索框输入的文本中添加了一个多余标记。论文在真实运行中发现了类似这样的标记。 | 工具可能输入了与您要求不同的内容。 | 在信任结果之前,先回读搜索框中实际输入的内容。 | | 在60次未检查的运行中有4次,截图在工具提示出现之前就已生成,导致值被遗漏。 | 悬停或延迟后才显示的值可能被遗漏。 | 等待后,在工具回答前重新截取新的截图。 |
_表4。对于使用或构建AI工具的测试者而言,每个结果的含义。每行均来自上方报告的结果。_
这些建议来自一个小规模测试,请根据您自己的网站和工具进行验证。其中一个想法超出了我的测试范围:如果您构建网站时,除了悬停显示外,还直接在页面文本中展示重要数值,可能会让基于截图的工具更容易读取。我未对此进行测试,因此请将其视为推测。
最后一个建议适用于所有使用或检查AI工具构建的网站人员:如果工具声称点击了按钮但页面外观未变化,请让它重新读取页面并说明发生了什么变化。这正是本实验中“结果检查”所做的操作。
我会在浏览器代理中检查哪些内容?
所有这些故障都发生在模型响应与页面之间的环节。因此我会保存模型的请求、截图和浏览器尺寸、以及实际发送到浏览器的操作。我还会记录接收该操作的元素或框架,以及展示给模型的观察结果。
上述点击转换明确展示了其中一个步骤——从截图位置到浏览器位置的转换。精确搜索还需要从搜索框读取返回的文本。框架点击需要检查该框架的状态,图表回答则需要包含其数值的截图。
检查可以揭示不匹配的情况,并在其他观察提供缺失证据时有所帮助。但除非代理或其周围代码改变后续操作的发送方式,否则无法修正持续存在的解析器或执行器故障。当代理似乎推理错误时,请在修改提示前检查操作在浏览器中的执行路径。
点击失败后,您的日志会显示模型请求的坐标、浏览器实际接收的点,还是仅显示最终截图?
- 实验代码和原始结果,包括浏览器页面、逐步日志、图表和录制内容。