news 2026/11/3 8:57:43

LLM自动化测试的三大底座:视觉定位、DOM语义化与Computer Use实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LLM自动化测试的三大底座:视觉定位、DOM语义化与Computer Use实战

做LLM自动化测试也有一段时间了,这个系列写下来,发现大多数团队卡在同一个地方:LLM到底怎么“看懂”被测界面?传统的Selector、XPath模型用不惯,直接丢一张截图过去又经常定位不准,试了几次就放弃了。系列第01篇聊过整体架构选型,这篇把最核心的“统一技术底座”掰开讲清楚——视觉定位、DOM语义化、Computer Use,这三板斧才是LLM自动化测试能不能在团队里落地的关键。先把结论说透:模型聪明不聪明不是瓶颈,这三块底座稳不稳才是。下文全部是我实际跑过的路径和踩过的坑,按可以直接复现的标准写。

1. 传统定位方式的痛点:LLM为什么需要一个新底座

1.1 老一套玩法为什么让人心力交瘁

做过UI自动化的人都知道,传统定位方式的核心是“找特征”。找一个稳定ID,找不到就拼XPath,XPath太脆就上CSS Selector,再不行就加一堆># 以视觉定位服务为例,传入截图返回候选区域 def vision_locate(screenshot_path: str, instruction: str): with open(screenshot_path, "rb") as f: response = vision_service.detect( image=f.read(), text=instruction, top_k=3, # 返回最可能的3个候选区域 need_labels=True, # 让模型顺带输出每个候选区域的标签 ) # 返回示例: # [ # {"box": [210, 340, 90, 40], "label": "登录按钮", "confidence": 0.97}, # {"box": [210, 340, 90, 40], "label": "登录按钮(悬停态)", "confidence": 0.86}, # ] return response # 拿到坐标后,交给执行层做真实点击 def click_at_point(page, box): x, y, w, h = box center_x = x + w / 2 center_y = y + h / 2 page.mouse.click(center_x, center_y)

关键参数就三个:top_k候选数量、confidence置信度阈值、box_format坐标格式。建议top_k设成3而不是1,因为模型偶尔会有多个相似目标(比如两个“登录”入口),多候选能配合后续决策逻辑做兜底。置信度阈值我一般设0.85,低于这个值宁可放弃本次识别,也不要硬点,点错一次的代价比等一轮重试大得多。

2.3 视觉定位的三个实战雷区

第一个雷区是分辨率适配。截图如果直接用视口尺寸(比如1920×1080),而视觉模型内部会把图缩放到固定尺寸,返回的坐标可能和原始截图对不上。解决办法很简单:请求视觉服务时带上你的视口宽高,让服务端返回的坐标统一缩放回原尺寸,或者你在客户端自己按比例做一次坐标映射。我踩过这个坑,坐标偏移一整块都点不中,排查了半天发现是缩放没还原。

第二个雷区是动态遮挡。页面有悬浮层、弹窗、动态动画时,截图里的目标元素可能被遮挡一半。视觉模型有时候会幻觉出完整目标,给一个错误的框。应对办法:截图前先做一次“页面静止”处理,关闭所有动画和过渡效果,必要时强制等requestAnimationFrame轮询结束再截图。

第三个雷区是成本控制。多模态视觉识别单次调用比纯文本贵一个量级,而且延迟通常在2到5秒。别傻傻地每个元素都用视觉定位,那样一次用例跑下来光视觉调用就几十次,时间和钱都受不了。我的做法是先用下一章的DOM语义化去定位,只有语义化结果置信度低时才走视觉兜底,这样视觉调用占比能控制在10%以内。

3. DOM语义化实操:把页面翻译成LLM的语言

3.1 为什么原始HTML不能直接喂给模型

很多人一开始图省事,直接把page.content()拿到的HTML丢给LLM分析。我强烈不建议这么干。一个中型页面的原始HTML轻松几万甚至几十万个字符,直接撞爆上下文窗口先不说,即使塞得下,模型也会被里面海量的布局噪声带偏。

原始HTML里包含大量与用户意图无关的东西:布局用的div嵌套、样式容器、脚本标签、注释、隐藏节点。这些噪声对测试来说毫无价值,却会稀释模型对真正交互元素的注意力。打个比方,你要吩咐一个人去按电梯按钮,结果给了一整栋楼的建筑图纸,上面标了每个房间的下水管道位置。这不是帮忙,是添乱。

DOM语义化要做的事就四个字:降维提炼。从“布局树”里提取“意图树”,只保留和测试动作相关的信息:可点击的按钮、可输入的框、可选的选项、可跳转的链接、弹窗、表单控件。这个步骤做完,一个几万字符的页面能被压缩成几百到一两千字符的轻量描述,并且全是有效信息。

3.2 用Accessibility Tree构建“交互元素清单”

具体怎么做语义化?优先用浏览器自带的可访问性树(Accessibility Tree)。这个方案的出发点很简单:浏览器已经帮我们把DOM解析成了语义角色,按钮就是按钮,输入框就是输入框,弹窗就是弹窗。我们要做的只是把这些角色按LLM友好的格式重新组织一下。

以Playwright为例,获取语义快照非常直接:

from playwright.sync_api import sync_playwright with sync_playwright() as p: browser = p.chromium.launch() page = browser.new_page() page.goto("https://your-target-page.com") # 方案A:页面自带的accessibility tree快照 snapshot = page.accessibility.snapshot() print(json.dumps(snapshot, ensure_ascii=False, indent=2))

拿到Accessibility快照之后,还要做一轮“交互元素过滤”:只保留满足以下任一条件的节点——role为按钮、链接、输入框、下拉框、单选框、复选框、选项卡、对话框、菜单项;或者节点自带onclick、tabindex、href等可交互属性。过滤完之后再按层级关系压缩成缩进结构。

我习惯的最终输出格式类似这样:

[页面] 登录页 [输入框] 用户名 (placeholder: 请输入手机号) [输入框] 密码 (placeholder: 请输入密码) [复选框] 记住我 [链接] 忘记密码? [按钮] 登录 (primary) [文字] 注册即同意《用户协议》

这种格式就是给LLM的“菜单”。模型拿到菜单以后,只需要在给定选项里选,而不是凭空创造。后者正是幻觉的主要来源。

实测下来,同一页面原始HTML大约45KB,语义化压缩后只有800多字符,Token消耗直接降了一个量级,模型准确率反而高了。因为它不用再顶着噪声找重点。

3.3 DOM语义化与传统选择器定位的直观对比

为了让你看得更清楚,我把四种定位方式的差异放在一张表里:

定位方式输入对前端改版的抗性Token成本维护成本典型场景
ID定位唯一标识极差无高老系统、结构稳定
XPath/CSS路径表达式极差无高传统自动化脚本
DOM语义化可访问性树/角色描述强低低LLM测试的主力定位
视觉定位截图+指令极强高极低复杂渲染、兜底方案

用同一个登录页面做实验,前端改版把按钮从btn-login改成submit-btn-primary,ID和XPath全部失效,DOM语义化输出的还是“按钮:登录”,完全不受影响。这就是LLM测试和传统测试的本质区别——模型理解的是语义,不是实现细节。

顺带说一句,很多团队问要不要给DOM加>def run_test_agent(task: str, page, max_steps=10): history = [] for step in range(max_steps): # 1. 观察:获取语义快照,必要时补充截图 snapshot = get_semantic_snapshot(page) screenshot = page.screenshot() # 2. 理解 + 决策:让LLM输出下一步动作 action = llm.plan( task=task, snapshot=snapshot, # DOM语义化底座 screenshot=screenshot, # 视觉定位底座(兜底用) history=history, ) # action示例:{"type": "click", "target": "登录按钮", "reason": "点击登录提交表单"} # 3. 执行:动作翻译成浏览器操作 result = execute_action(page, action, snapshot) # 4. 反馈:观察新状态并记录 history.append({"action": action, "result": result}) # 5. 判断:任务是否完成 if is_task_done(snapshot, task): return "PASS", history return "TIMEOUT", history

这里最关键的一步是execute_action。它不能直接拿LLM的文本去执行,得先做一层“动作映射”:把LLM输出的目标描述(比如“登录按钮”)在语义快照里匹配到具体元素引用,再调用page.click(element)或者page.locator(...).click()。这样即使同页面有两个“登录”字样(一个在导航栏,一个在表单底部),也能靠上下文语义正确区分。

我在实验项目里跑通过一个最常见的登录流程:输入用户名、输入密码、点击登录、断言URL跳转。用这套Agent循环,中间不需要写任何固定选择器。把页面语义化做扎实之后,换两套不同的前端UI都能跑过,这就是底座的威力。

4.3 安全与稳定:Computer Use落地必须加的“保险丝”

Computer Use最让人担心的是失控。模型下一个错误指令,脚本可能点了删除按钮、退出登录,甚至批量操作。所以落地时一定要加四道保险:

一是动作白名单。按项目风险等级配置允许执行的动作类型,高风险操作(如删除、清空、提交订单)默认禁止,除非测试任务里明确包含该动作且经过人工确认。

二是确认回滚机制。执行任何破坏性动作之前,Agent必须先把“准备做某事”写入日志,并生成操作前状态快照。一旦后续步骤发现状态异常,立即回滚到上一个稳定状态。

三是步骤上限。所有Agent任务必须设置最大步数,我一般给10到15步。超过上限强制失败并输出完整历史,方便排查是任务描述不清还是Agent绕圈。

四是人工审核模式。系统上线初期,每跑的N条用例随机抽1条完整回放操作序列,让人工确认Agent的决策链路是否合理。等你积累了足够的回放数据,再逐步降低抽检比例。

这些“保险丝”看着繁琐,但它们是Computer Use能走进稳定测试环境的前提。没有约束的Agent,拿来做Demo可以,进CI门禁迟早出事。

5. 三套方案怎么选:能力对比与落地路径

5.1 一张表看懂三种能力的适用边界

很多朋友问我三者到底怎么选。先给一张我常用的对比表,你对着场景判断就行:

能力输入形态抗UI变化定位精度成本延迟适用场景
DOM语义化可访问性树文本强高(明确元素)低低常规表单、列表、CRUD页面
视觉定位截图极强中(坐标框)高高Canvas、WebGL、自定义渲染、无DOM场景
Computer Use决策指令强由前两者决定中中端到端流程、多步骤任务、探索性测试

注意看“定位精度”这一行。DOM语义化虽然抗变化强,但遇到一个元素动态生成的不同实例(比如一排相同结构的卡片),它只能给出“第3个卡片”这样的位置描述;视觉定位能给精确坐标,但坐标本身不等于稳定,UI一改就得重新识别。所以精度维度上二者各有取舍。

成本维度上,DOM语义化基本可以忽略不计,一个快照加上过滤就是毫秒级;视觉定位则要想清楚调用频率。我见过一些团队把所有元素识别都走视觉,一个月账单跑下来够买三年内部测试机,这种做法不建议盲目复制。

5.2 场景化选型:什么时候该优先用哪套

选型不用搞复杂,记住三条经验法则:

第一,能语义化,别上视觉。90%的Web页面,Accessibility Tree都能提供足够的交互元素信息。先走语义化,命中不了再降级。

第二,无DOM场景,直接上视觉。Electron里的Canvas画布、WebGL报表、视频播放器内部控件、图像编辑器的画布区域,这些组件在DOM树里就是一块空白,不截图连“目标长什么样”都不知道。这种场景视觉定位几乎是唯一选择。

第三,复杂流程任务,用Computer Use统一调度。单个元素的定位选语义化或视觉,但一旦用例有多个步骤、有分支判断、有异常兜底,就需要Computer Use这一层来做“动作编排”。它不关心底层是哪个定位器给的坐标。

我还给团队画过一条极简决策链:页面是否有可交互DOM节点?有,用DOM语义化;没有,用视觉定位;需要在多个步骤间动态决策?挂Computer Use。三个问题走完,方案就定了。

5.3 团队落地三步走

第一步,搭底座做验证。先只实现DOM语义化快照,跑通一个最简单的登录用例,让团队熟悉LLM测试的基本工作流。这一步不用上视觉和Computer Use,把地基打牢。

第二步,补视觉兜底。挑一条语义化搞不定的用例,比如canvas绘制的流程图上点某个节点,接入视觉定位服务,让用例跑通。这一步的产出是建立降级策略:语义化失败时自动切换视觉。

第三步,上Computer Use做编排。把单条用例改写成任务描述式,验证Agent能自主完成多步骤操作,再逐步把用例池往里迁。同时把日志、回放、审计这些配套功能完善起来。

三步走完,LLM测试底座就算真正落地了。整个过程不建议跳步,尤其不要一上来就全量上Computer Use——没有语义化快照托底,Agent的容错空间很小。

6. 高频踩坑记录:定位不准背后的真实原因

6.1 坐标点到了,却总是点不中

这是视觉定位最常见的坑,九成原因是坐标系不一致。截图用的视口尺寸是1920×1080,视觉模型服务内部把图缩放到1024,返回的坐标基于缩放后的坐标系,直接拿去做mouse.click自然点偏。

解决方式是在调用视觉服务前约定好坐标基准。我个人习惯是让视觉服务返回“归一化坐标”(0到1区间),客户端再乘回实际视口尺寸。归一化坐标的好处是和分辨率完全解耦,无论浏览器窗口怎么缩放,坐标都不会偏。

# 归一化坐标转换示例 norm_x, norm_y = 0.32, 0.55 viewport_width = page.viewport_size["width"] viewport_height = page.viewport_size["height"] real_x = int(norm_x * viewport_width) real_y = int(norm_y * viewport_height)

另外注意浏览器缩放比。如果页面整体缩放不是100%,截图和实际点击的物理坐标也有偏差。跑用例前先统一page.set_viewport_size和DPI设置,把变量控制住。

6.2 模型选错元素:幻觉怎么破

这是LLM测试特有的问题。模型明明知道要点击“登录”,结果在候选列表里选中了“注册”。传统框架不会出现这种事,因为选择器写死了。

我的排查经验,核心是缩小模型的“决策面”。做法包括:给语义快照里的每个元素加编号,让模型输出编号而不是自由文本;每个候选元素附带简短描述,减少歧义;给任务描述加上“必须从候选列表中选择”的硬性约束。

还有一个容易被忽略的因素:历史上下文污染。Agent跑过多步之后,历史记录里堆积了大量界面对话,模型容易被早先的旧状态误导,误以为当前元素还存在于页面上。解决办法是只保留最近2到3轮的状态快照,以及在上一步动作完成后的新快照上重新决策。

6.3 Token成本和响应延迟怎么控

LLM测试跑一次用例,Token消耗比普通API调用大很多,响应延迟也高。控制的关键是“少喂、快断”。

少喂:语义快照只保留交互元素,背景文字、装饰性段落、非交互区域一律过滤。截图只在视觉兜底时使用,不为每个动作都截图。历史记录做滚动窗口,超过3轮的旧记录及时截断。

快断:设置动作决策超时(我通常给8到10秒),超过直接降级。降级链是:语义化快照 → 候选元素列表 → 视觉定位 → 人工确认失败。每降一级,响应时间换个数量级。

另外强烈建议做Token预算巡检。一条用例跑完,统计每个环节的Token消耗,你会惊讶地发现50%的Token浪费在重复获取未变化的页面上。优化思路是加“页面状态指纹”:如果语义快照的哈希值没变化,就直接复用上一次的决策结果,不再调模型。

6.4 动态内容下的等待与重试策略

LLM测试和普通自动化一样,逃不开动态加载问题。页面异步渲染、接口返回慢、加载动画遮挡,都会导致Agent在错误的状态下做决策。

我的等待策略用了“三静”原则:网络静、动画静、元素静。网络静是指监听networkidle事件,确保没有在途请求;动画静是指关闭过渡样式或等待动画完成;元素静是指目标元素在连续两次轮询中位置不变。三个条件都满足,才把当前状态喂给模型。

重试策略上,我不建议对单动作无限重试。标准做法是:每个动作最多重试2次,每次重试前先刷新语义快照,确认页面状态是否真的变了。如果快照没变,说明问题不在等待,而在模型决策本身,这时候继续重试就是白烧Token,应该立即把用例状态置为“待人工排查”。

这套等待和重试策略在稳定性上帮了大忙。加了“三静”约束之后,Agent误判率大概下降了六成,主要原因是模型拿到的不再是“半成品”页面,而是一个稳定可理解的界面状态。

我个人在实际操作里的体会是,LLM自动化测试的难点从来不在模型选型,而在怎么把界面信息稳定地抽象出来喂给模型。视觉定位、DOM语义化、Computer Use这三个底座,本质上就是在做这件抽象工作。先把它们打磨稳,再谈聪明的Agent,否则一切上层能力都是空中楼阁。最后分享一个我常用的组合拳:DOM语义化先快速框定候选集,视觉定位对候选集做精确认证,Computer Use统一执行动作,三套配合起来,准确性、成本、速度才能同时兼顾。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/3 8:57:10

点云法向量估计:MLS算法原理与PCL工程实践

1. 点云处理中的法向量估计基础点云法向量估计是三维数据处理的关键预处理步骤,在逆向工程、三维重建、机器人导航等领域具有广泛应用。传统估计方法如PCA(主成分分析)在噪声环境下表现欠佳,而移动最小二乘法(MLS&…

作者头像 李华
网站建设 2026/11/3 8:56:49

本地优先知识管理:mybase单文件库的长期归档实践

1. 为什么我至今还在用一款“上古”知识管理工具聊知识管理这个话题,很多人第一反应是各种云端笔记、双链笔记、块编辑器,界面漂亮、同步方便、生态热闹。但我用了十几年下来,硬盘里始终留着一个位置给 mybase——一款界面朴素到有点“上个时…

作者头像 李华
网站建设 2026/11/3 8:56:45

外卖小程序CPS分销系统落地实战:从源码到生产可用

简介:这是一套2024年最新上线的外卖CPS分销微信小程序完整源码,面向开发者、创业团队及副业实践者,解决外卖平台佣金分发与用户裂变增长问题。资源覆盖前端(三套可选模板)、后台(PHPMySQL)、数据…

作者头像 李华
网站建设 2026/11/3 8:56:39

免费音视频转字幕工具:提升3倍效率的解决方案

1. 项目概述 今天要分享的是一个让我工作效率提升至少3倍的神器——一款完全免费且无广告的音视频转字幕工具。作为经常需要处理会议录音和视频素材的自媒体从业者,这个工具彻底解决了我的字幕制作痛点。 传统的字幕制作流程需要反复听写、校对,一个10分…

作者头像 李华
网站建设 2026/11/3 8:56:38

DeepSeek Harness 与 Pi:编排层与执行层的正确选型

最近圈子里关于 DeepSeek Harness 和 Pi 的讨论挺多,但很多人把这两个东西放在同一个维度里比,甚至有人问“该选哪个”,这其实是把概念搞混了。我翻了不少项目的源码、配置文档和社区帖子,可以负责任地说:同样叫 Harne…

作者头像 李华
网站建设 2026/11/3 8:56:30

PostgreSQL窗口函数:数据分析与SQL优化实战

1. 窗口函数:数据分析师的秘密武器 第一次接触PostgreSQL的窗口函数时,我正面临一个棘手的数据分析需求——需要在保留原始数据行的同时,计算每类产品的销售额排名和累计占比。传统的GROUP BY聚合让我丢失了明细数据,而繁琐的子查…

作者头像 李华