AI测试 把前端代码库变成「可测试模型」:给 AI 生成测试补一份可信输入

Hugo · 2026年08月05日 · 154 次阅读

现在 AI 已经很会 “生成” 了。

你可以让它生成测试用例、生成自动化脚本、补预期结果、改老用例,甚至把一批历史用例整理成新的测试资产。问题不再是 “能不能生成”,而是:它凭什么生成得准?

很多团队手里都有一堆 “古早用例”:当年靠人理解业务写出来,步骤省略、预期含糊、页面文案过期、前置条件写在测试人员脑子里。人执行时还能靠经验补齐,交给 AI 就会暴露问题。AI 看见 “点击发布”,并不知道要先进哪个页面、按钮在哪里、什么情况下按钮能点、点完之后弹窗该怎么处理。

如果没有一份完整、真实、可追溯的文档做输入,AI 生成出来的东西很容易变成 “看起来对”:

  • 定位器是编的,页面上根本没有那个文案;
  • 步骤少了前置条件,跑起来不是按钮置灰,就是被校验拦下;
  • 只写到「点击后弹出弹窗」,弹窗里的确认、取消、二级菜单全漏了。
  • 老用例被 “润色” 过,但其实没有跟当前产品行为对齐。

所以我越来越觉得,AI 测试生成的核心,不只是 prompt,也不只是模型能力,而是输入工程:我们能不能先把系统本身整理成一份机器能消费、也能被人审查的知识底座。

这篇文章分享的是其中一个具体案例:怎么从前端代码库里提取出一份「可交互元素清单」,让 AI 在生成新用例、完善旧用例、生成自动化脚本时,都有足够可靠的上下文。

前端只是切入口。背后的方法其实更通用:把代码里的真实行为,抽成结构化、可追溯、可持续更新的测试输入。换成后台接口,也可以用类似思路提取接口清单、参数约束、状态流转、错误码和调用前置条件。


我们真正缺的不是 “生成能力”

一开始很容易把目标想窄:既然要做 UI 相关测试,那把页面上所有按钮、输入框、菜单项列出来,不就行了吗?

后来发现不够。

对 AI 来说,一个按钮本身没有太大价值。真正有价值的是围绕这个按钮的一整段上下文:

  • 它在哪个页面;
  • 页面之间怎么跳过去;
  • 页面上真实显示的文案是什么;
  • 这个按钮藏在哪个弹窗、抽屉、子页或 SDK 区域里;
  • 点击之前要满足什么条件;
  • 点击之后会发生什么;
  • 如果点击后又弹出面板,面板里还有哪些操作。

换句话说,我们要的不是一张「控件花名册」,而是一份可以指导 AI 理解产品行为的页面模型。

我现在会把它拆成 10 类信息:

# 信息 对 AI 生成有什么用
1 有哪些页面 划定被测范围
2 页面之间怎么跳 补全导航前置步骤
3 哪些区域由 SDK 或引擎渲染 标记需要继续深追的区域
4 每页藏着哪些弹窗、抽屉、子页 不漏隐性的 UI 单元
5 每个单元里有哪些可交互功能 找到真正的测试对象
6 元素真实显示什么文案 生成可靠定位器,也能更新旧用例里的过期文案
7 功能源码在哪里 追行为、写断言、做溯源
8 从入口怎么一步步到达控件 补全测试 setup 和老用例缺失步骤
9 点击前需要满足什么前提 避免误判失败
10 点击后的完整交互序列 生成用例主体、断言和自动化脚本

这里最容易被低估的是第 9 和第 10 项。

举个真实例子:信息发布页的「发布」按钮。它看起来只是一个按钮,但真正能点下去之前,需要满足好几个条件:名称不能错、发布策略要校验、内容要添加、设备要选择、网页型内容要解析成功;如果遇到播放冲突,还要先在冲突弹窗里点「继续发布」。

如果这些前置条件没提取出来,AI 不管是生成新用例、改老用例,还是写自动化脚本,都会变得很悬。它失败了,但你不知道是产品坏了、用例坏了,还是前置状态根本没搭好。


为什么可以从代码里还原 UI 交互

这件事能做,是因为前端代码并不是任意形状的。

不管是 Vue、React,还是其他现代前端框架,UI 交互最终都会落在一些相对稳定的表达方式上:

  • 事件绑定:点击、输入、切换;
  • 状态变化:refreactiveuseState、store;
  • 条件渲染:显示、隐藏、展开、收起;
  • 路由跳转:从一个页面到另一个页面;
  • 命令式挂载:弹窗、抽屉、toast、portal;
  • 跨组件通信:emit、context、provide/inject、事件总线;
  • 配置驱动:菜单、表单、动态组件、远程配置。

这些机制在框架里长得不一样,但本质很像。

Vue 里你会搜 @clickv-modeluseDialogrouter.push。React 里你会搜 onClickuseStatecreatePortalnavigate。锚点不同,追踪思路是同一套。

所以我现在更愿意把这件事理解成:

前端框架把 UI 交互压缩进了有限几种代码模式里。我们要做的,是把这些模式重新展开成测试能理解的路径。

当然,光知道模式还不够。动手之前还要先摸清项目本身:

  • 用的是什么框架;
  • 路由配置在哪里;
  • 入口组件在哪里;
  • 组件文件是什么后缀;
  • locale 文件在哪里;
  • 有没有 SDK、页面引擎或跨包渲染。

这些项目级事实最好由脚本先探测出来。否则 LLM 拿着一堆搜索锚,也不知道该往哪些目录里搜。


后来沉淀出的流水线

我试过让一个 agent 一次性读完整个项目,然后直接吐出完整清单。效果很不稳定。

原因也很朴素:上下文不够、容易漏、还容易编。它可能读到了主页面,却没追到弹窗;追到了弹窗,又忘了菜单里的操作;为了把结果写完整,还会用一些看起来很合理但源码里没有的描述补上。

最后比较稳的方式,是把任务拆成几段,每段只解决一个问题,产物落盘,下一段继续消费。

每一步的职责尽量单一:

阶段 主要问题
detect 项目里有哪些页面?页面之间怎么跳?哪些区域不是普通页面源码直接渲染的?
deep_research 每个页面里还藏着哪些弹窗、抽屉、面板、子页?
restoration 每个 UI 单元里有哪些能点、能填、能选的功能?页面真实文案是什么?源码在哪?
link 从入口怎么走到这个功能?点击前要满足哪些门槛?
interaction 点击之后发生什么?弹出的二级操作有没有继续展开?

这套拆法不是为了显得流程很完整,而是为了克服 LLM 的几个实际弱点:

  • 一次性上下文装不下整库;
  • 单遍扫描很容易只看见主页面,漏掉隐性 UI;
  • 并发 agent 如果一起写一个大 JSON,互相覆盖的概率很高;
  • 错误如果拖到最后才发现,返工成本太大。

拆开之后,每一步都可以用更小的上下文、更明确的验收脚本和更清晰的文件边界来约束它。


我最在意的三件事:完整、真实、可追溯

这三个词听起来有点像工程口号,但它们其实来自很具体的失败场景。

完整,是为了防漏。

漏掉一个弹窗里的「确定」按钮,后面的用例和脚本就很难覆盖它。漏掉一个下拉选项,生成出来的用例就只测了表面路径。

真实,是为了防编。

元素文案必须来自真实渲染文本,不能把 deviceNamepublishStrategyform.submit 这种字段名或翻译 key 当成页面文案。源码位置也必须真的存在,最好精确到 path:line

可追溯,是为了方便人接管。

当测试生成器写出一条断言时,我们要能反查:这个按钮从哪个组件来的,前置条件从哪几行代码来的,点击后的弹窗是在哪里挂载的。如果只有一段自然语言总结,出问题时就很难修。

为此,每个阶段结束后都要过机器检查,再加人工抽审。

机器适合查这些:

  • JSON 格式是否合法;
  • 字段有没有缺失;
  • 声称的源码行号是否存在;
  • 圈定的源码文件有没有被静默跳过;
  • 文案里有没有明显的字段名、翻译 key、含糊词;
  • 弹窗、菜单、面板是否展开到了内部操作。

人工更适合看这些:

  • 描述和源码语义是否真的对得上;
  • 被跳过的项是不是合理;
  • 前置条件有没有多写或漏写;
  • 点击后的交互有没有停在「弹出 XX」这种半截描述;
  • 复杂控件的嵌套操作有没有展开到最小动作。

我自己的经验是:不要指望 LLM 自检解决所有问题。LLM 自检能降错,但终判最好交给确定性脚本和人工抽样。


以 Vue 为例,怎么搜

落到 Vue 项目里,搜索会围绕这些锚点展开。

要找的信息 Vue 里常见的锚
页面和路由 routes: [component: () => import<router-view>
跳转关系 router.pushrouter.replace<router-link>、菜单配置
可交互元素 @click@changev-model@update:defineEmits
真实文案 $t(t('labelplaceholder、slot、locale 文件
弹窗和抽屉 useDialog<Dialog>Modal.confirmh()createAppteleport
状态副作用 refreactive、Pinia/Vuex store、provideinject
动态组件 <component :is>、动态 import()、组件 registry
网络和配置 requestaxios、接口定义、远程 JSON、window.*

实际追的时候,我会遵守几个顺序。

先从配置和入口开始,不要一上来钻进某个组件。路由表、菜单配置、app.use()、SDK 注册顺序,能先帮我们建立一张项目地图。

然后按语法锚搜索,不按感觉猜。看到 @click="handleSure",就追 handleSure;看到 useDialog(SomePanel),就知道这个面板不在当前 template 里,要顺着命令式挂载继续追。

遇到 i18n,必须追到 locale。$t('content.publish') 不是页面文案,「发布」才是。

遇到副作用跨组件,不能只看 import。store、provide/inject、事件总线、动态组件、配置注册、iframe、postMessage,都可能让一个点击影响到另一个地方。

最后,在 interaction 阶段一定要穷举到底。不能写「点击后打开设置面板」就结束,而要继续读设置面板源码,把里面每个可操作项展开出来。对自动化测试来说,「打开面板」只是开始,不是结束。


再看一次「发布」按钮

还是用信息发布页的「发布」按钮做例子。

第一步,靠 @click 找到入口。比如模板里有 @click="handleSure",那它就是一个功能点。

第二步,读 handleSure。这里不能只看函数名,要看它具体做了哪些事:校验名称、校验发布策略、检查内容、检查设备、处理网页解析状态、判断播放冲突。

第三步,顺着命令式挂载继续追。如果冲突时会 useDialog(...) 打开确认弹窗,那弹窗里的「继续发布」和「取消」也必须进入交互序列。

第四步,把前置条件写清楚。比如:

  • 名称已填写且通过校验;
  • deliver 场景下发布策略已通过;
  • 已添加发布内容;
  • 已选择设备;
  • 网页型内容已解析成功;
  • 如果存在播放冲突,需要在冲突弹窗中确认继续发布。

第五步,把点击后的结果展开。校验失败会出现什么提示,冲突弹窗里点「继续发布」会继续走哪条链路,成功后页面是跳转、刷新还是 toast,这些都应该进入最终的交互描述。

这时,「发布」按钮才从一个孤零零的 DOM 元素,变成了一段机器可以执行、也可以被人审查的测试路径。


这份清单后来怎么被用起来

目前这类可交互元素清单主要有几种消费方式。

第一种是生成新用例。

过去让 AI 生成用例,常见输入是需求文档、页面截图、测试人员补充的一段说明。这些当然有用,但经常不够细。元素清单补上的,是代码里真实存在的交互边界:有哪些入口、哪些控件、哪些弹窗、哪些前置门闸、哪些异常分支。AI 再生成用例时,就不只是 “按经验猜场景”,而是能围绕真实行为枚举路径。

第二种是清洗和更新老用例。

很多人工用例的问题不是业务错,而是写得太省略:缺前置步骤、缺预期结果、控件描述含糊。拿元素清单对照之后,可以把「点那个按钮」补成真实文案,把缺失的导航链补上,把点击前置条件和点击后的断言补完整。

这对历史用例尤其重要。老用例经常停留在旧页面文案、旧入口、旧交互上。只靠人肉维护,很容易越积越乱;让 AI 直接改,又容易把错的东西改得更顺口。元素清单提供了一把尺子:哪些步骤还对,哪些文案过期,哪些前置条件缺了,哪些预期结果已经和代码不一致。

第三种是生成 UI 自动化脚本。

如果走 Computer Use 模式,可以把语义化路径交给 LLM,让它在真实浏览器里操作。它看到的不是坐标脚本,而是类似「进入设置菜单,在通用分区点击主题」这样的步骤。布局变了,只要语义没变,就不至于全盘崩掉。

如果走 Playwright 生成,清单里的真实文案和交互序列可以直接变成定位器、步骤和断言。它不再只是一个空架子,而是有页面依据、有源码依据的 E2E 用例草稿。

第四种是做测试知识的持续更新。

代码在变,测试资产也应该跟着变。可交互元素清单如果能持续从代码里重新提取,就可以反过来发现测试资产的漂移:页面新增了功能但没有用例,老用例引用了已经不存在的文案,某个弹窗新增了确认步骤但自动化脚本还停在旧路径上。

这时 AI 的角色就不只是 “帮我写一条用例”,而是 “帮我维护一份持续对齐代码的测试知识库”。


写在最后

我现在越来越觉得,AI 生成测试最难的部分不一定是 “怎么让它写”,而是 “拿什么喂给它”。

前端代码里其实已经有答案:页面、路由、文案、状态、弹窗、校验、跳转、网络请求,都在代码里。只是它们散落在模板、组件、store、配置、locale 和 SDK 之间。

把这些信息重新组织起来,AI 生成用例、完善旧用例、生成自动化脚本,才有了比较稳的输入。

前端代码提取只是一个案例。换成后台接口,也可以做类似的事:从路由、controller、service、schema、数据库约束和错误码里,提取接口能力、参数规则、状态流转和异常分支。领域不同,抽取对象不同,但目标是一样的:把代码里真实存在的行为,变成 AI 可以消费的测试上下文。

这也是这套方法论最朴素的出发点:先别急着让机器生成,先让机器知道系统到底是什么。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册