前言
最近在做 Vue 项目的自动化测试,尝试了很多方案,走了不少弯路。从一开始用 Class 和 Tag 定位元素,到后来尝试 Playwright MCP,再到视觉大模型,最后发现最简单的 data-testid 才是最稳定、最好用的方案。这里记录一下这个探索过程。
一、基于 Class 和 ID 定位
很多新手(包括我)刚开始写测试的时候,习惯直接从 Chrome DevTools 里面 Copy Selector,然后就有了这样的代码:
// ❌ 典型的"一改即挂"代码
await page.locator('.main-content > div:nth-child(2) > button.btn-primary').click()
存在的问题:
1、Vue 的 Scope CSS 问题
Vue 的 Scope CSS 编译后的类名会带 Hash(比如 .btn-v-7a2b),每次重新打包都会变,测试代码立马就不能用了。
2、结构太脆弱
只要你给外层加个 <div> 或者改下 Flex 布局,nth-child(2) 可能就变成 nth-child(3) 了,测试立马报错。
结论:基于样式和层级的定位方式,维护成本太高,完全不可行。
Playwright MCP 原理
既然有这个问题,Playwright MCP 是如何做自动化测试的呢?
实现原理:Playwright MCP 不读原始 HTML,而是提取浏览器的 Accessibility Tree(无障碍树),并给每个可交互节点动态打上一个临时编号(Dynamic Ref)。
实现流程是这样的:
页面 -> 提取语义树 -> 按钮变为 [Ref: 12] -> AI 指令 Click Ref 12
存在的问题:
1、动态 Ref 不可复用
这个 Ref 12 是运行时生成的。你没法把”点击 Ref 12”写进代码里,因为下次运行它可能就是 Ref 15 了。
2、无法生成代码
MCP 本质是一个 Agent(代理),它适合”帮我点一下”这种一次性操作,但不适合”帮我写一个在 CI 流水线里跑三年的脚本”。
结论:MCP 适合做探索性测试,但没法产出稳定的、可维护的测试代码。
二、基于视觉大模型 (Vision LLM)
既然 DOM 和语义树都不好用,我想到了一个更”高级”的方案:像人眼一样直接看截图。
实现流程如下:
截图 -> 发送给 GPT-4o 等多模态模型 -> 问"购物车按钮坐标在哪?" -> 点击坐标
存在的问题:
1、速度太慢
每次操作都要经历”截图-上传-推理-返回”,一个点击就要几秒钟。如果有几百个 Case,跑完要等到天荒地老。
2、稳定性差
页面加载稍微慢一点,或者渲染有 1px 的像素差异,模型就可能出问题(AI 幻觉)。
结论:看起来很美,但效率太低,根本没法支撑高频 CI。
三、data-test-id 才是正解
排除掉上面这些”花里胡哨”的方案后,我发现最稳、最快、最好维护的方式,其实是最简单的 data-test-id。
为什么说它是最优解?
1、解耦样式
按钮从蓝色改成红色,Class 改得面目全非,测试依然能通过。
2、解耦结构
按钮从 header 移到 footer,Playwright 依然能瞬间抓到。
3、速度极快
原生 DOM 查询,毫秒级响应,比 AI 快一万倍。
Vue 项目中如何使用
在 Vue 组件中,养成随手写”测试钩子”的习惯就行了。
前端代码 (Vue)
<template>
<div class="complex-wrapper-v2">
<button class="btn-dynamic-hash" data-testid="submit-order-btn" @click="submit">提交订单</button>
</div>
</template>
测试代码 (Playwright)
// ✅ 极其稳定、可复用、速度快
// 只要"提交订单"功能还在,这行代码就永远有效
await page.getByTestId('submit-order-btn').click()
传统方案需要手动添加,但存在明显痛点:
- 开发体验差: 写完业务逻辑还要绞尽脑汁想一个唯一的字符串。
- 维护成本高: 经常忘记加,或者复制粘贴代码时忘记改,导致 ID 重复。
- 语义混乱: 张三写 btn-submit,李四写 submit-button,毫无规范。
解决办法:
- 新项目:方案 1:采用 AI 工具自动生成 data-testid(如通过 AST 分析,找出所有关键交互节点,自动注入)
- 方案 2:开发源码中完全不写 data-testid,由编译工具(Webpack/Vite)在构建时自动根据语义指纹注入。
- 存量项目:使用 AI 批量分析生成 testid,通过
codemod自动插入
方案 1 工作流:
- 解析: 使用 Babel/SWC 将代码解析为 AST (抽象语法树)。
- 定位: 找出所有关键交互节点(如
<button>,<input>, 或自定义组件)。 - AI 命名: 将该节点的代码片段(包含上下文)发给 LLM,要求生成符合团队规范的
data-testid(如 page-login-btn-submit)。 - 去重: 脚本维护一个全局 Set,确保生成的 ID 不重复。
- 回写: 将生成的 ID 插入 AST 并转回代码。
创新点: 可以配置 git hook,在 commit 前自动检查并修复。
命名规范:让 AI 基于元素功能(而非位置)生成语义化 ID,如:
<!-- AI生成 -->
<button data-testid="user-profile-save-btn">保存</button>
<!-- 而非手工命名 -->
<button data-testid="btn-12">保存</button>
(完)