vite-plugin-vue3-testid
v2.0.0
Published
Vite plugin to auto-inject data-testid into ant-design-vue (Vue 3) component DOM for E2E testing
Maintainers
Readme
vite-plugin-vue3-testid
为 Vue 3 所有 DOM 元素自动注入 data-testid 属性 — UI 库无关。适用于任何 Vue 3 项目,无论使用 Ant Design Vue、Element Plus、Vant、Naive UI 还是纯 HTML。
特性
- UI 库无关 — 对所有可见 DOM 元素注入 testid,无需组件前缀匹配
- 编译期三层计数器架构 — 优先级计数器确保 testid 稳定唯一:
- v-for 动态注入(最高优先级)— 利用循环 index 变量生成每轮迭代唯一 testid,无需运行时去重
- 条件块子计数器(第二优先级)— v-if/v-else/v-show 拥有独立子计数器,子元素以父 testid 为前缀,增删不影响外部稳定性
- 全局计数器(第三优先级)— 跨模板共享计数器 + usedId Set,保证全局唯一
- 三层注入系统 — 编译期 transform + Vue 插件桥接 + 运行时 MutationObserver 兜底
- 丰富的元素分类 —
v-for、事件监听、条件块、普通元素各有专用格式 - 全局唯一 — 编译期全局计数器 + 已用 ID Set + 运行时去重,确保每个 DOM 元素都有唯一的 testid
- 可配置前缀 —
testIdPrefix选项为所有编译期 testid 拼接公共前缀 - 运行时去重 — 检测
v-for、UI 库 slot 重复渲染等导致的重复 testid,自动追加:N后缀 - 幂等注入 — 所有注入层在注入前检查元素是否已有 testid,防止重复
- Teleport 支持 — 运行时层覆盖通过
<Teleport>或 UI 库弹窗/下拉框渲染到<body>的元素 - 零运行时依赖 — 运行时无额外依赖;
@vue/compiler-core仅作为 devDependency 用于类型定义
注入格式
编译期(三层注入)
| 优先级 | 层级 | 格式 | 示例 |
|--------|------|------|------|
| 1st(最高) | v-for 含 index | {tag}_in_for-{i0}[_{i1}] | li_in_for-0_1 |
| 2nd | 条件块子元素 | {parentTestId}__{tag}-{n} | div-1__span-0 |
| 3rd | 全局计数器 | {tag}-{n} | span-2, h1-3 |
v-for 动态注入:
<!-- 源码 -->
<li v-for="(item, i) in list" :key="i">...</li>
<!-- 编译后(动态表达式,每轮迭代 index 自动解析) -->
<li v-for="(item, i) in list" :key="i" :data-testid="`li_in_for-${i}`">...</li>
<!-- 无 index 变量 → 静态后缀 -->
<li v-for="item in list" :key="item.id" data-testid="li-in_for-0">...</li>条件块子计数器:
<!-- 各条件分支独立子计数器 -->
<div v-if="visible" data-testid="div-0">
<span data-testid="div-0__span-0" />
<button data-testid="div-0__button-event-click-0">提交</button>
</div>
<div v-else data-testid="div-1">
<span data-testid="div-1__span-0" />
</div>v-if/v-else/v-show 内部子元素使用 {parentTestId}__{tag}-{n} 格式,确保:
- testid 稳定 — 父元素数量变化不影响子元素编号
- 可预测 — 测试人员可通过 parent→child 链式定位嵌套元素
事件监听标记:
{tag}-event-{names}-{n}
例如:button-event-click-0, input-event-input_focus-1运行时去重: 编译期每个模板 AST 节点只能生成一个 testid。当同一节点在运行时被多次实例化(v-for 无 index、UI 库 slot 如
a-table的bodyCell等),运行时 MutationObserver 检测到重复 testid 后自动追加:2、:3、... 后缀。
安装
pnpm add -D vite-plugin-vue3-testid
# 或
npm install --save-dev vite-plugin-vue3-testid使用方式
方式 B:编译器 + Vue 插件 + 运行时(推荐)
全量覆盖:编译期用三层架构注入稳定 testid,运行时处理动态/teleport 元素。
// vite.config.ts
import vue from '@vitejs/plugin-vue'
import { vueTestIdPlugin, testIdTransforms } from 'vite-plugin-vue3-testid'
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
nodeTransforms: testIdTransforms({
testIdPrefix: '', // 可选:公共前缀
debug: false, // 可选:调试日志
})
}
}
}),
vueTestIdPlugin({ debug: false }),
]
})// main.ts
import { createTestIdBridge } from 'vite-plugin-vue3-testid/plugin'
import { setupTestIds } from 'vite-plugin-vue3-testid/runtime'
const app = createApp(App)
app.use(createTestIdBridge()) // 将编译期 testid 桥接到 DOM
app.mount('#app')
setupTestIds() // 运行时兜底:动态元素 / teleport / 去重方式 C:仅编译期(轻量)
无运行时开销。动态元素(teleport 弹窗、下拉框等)不会注入 testid。
// vite.config.ts
import vue from '@vitejs/plugin-vue'
import { testIdTransforms } from 'vite-plugin-vue3-testid'
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
nodeTransforms: testIdTransforms()
}
}
})
]
})// main.ts
import { createTestIdBridge } from 'vite-plugin-vue3-testid/plugin'
const app = createApp(App)
app.use(createTestIdBridge())
app.mount('#app')方式 A:纯运行时(无需修改 vite 配置)
无需修改 vite.config.ts,所有 testid 通过 MutationObserver 在运行时生成。
// main.ts
import { setupTestIds } from 'vite-plugin-vue3-testid/runtime'
createApp(App).mount('#app')
setupTestIds()架构:三层注入
┌──────────────────────────────────────────────────────┐
│ 1. 编译期 Transform(最高优先级) │
│ Vue 模板 AST → 三层计数器注入 testid │
│ - v-for+index:动态 JS 表达式 │
│ - 条件块内部:{父testid}__{tag}-{n} │
│ - 全局元素:{tag}-{n} │
│ 跨模板全局计数器 + 已用 ID Set │
├──────────────────────────────────────────────────────┤
│ 2. Vue 插件桥接 │
│ app.mixin({ mounted() }) │
│ 读取 vnode.props['data-testid'] → 写入 $el │
│ 绕过 inheritAttrs: false │
├──────────────────────────────────────────────────────┤
│ 3. 运行时 MutationObserver(兜底) │
│ 监听 document.body 的 DOM 变化 │
│ 处理动态/teleport/第三方 DOM │
│ 检测重复 testid → 追加 :N 后缀 │
└──────────────────────────────────────────────────────┘三层均为幂等注入 — 在注入前检查元素是否已有 data-testid 属性。
API 参考
testIdTransforms(options?)
返回编译期注入所需的 Vue 3 NodeTransform 函数数组。
import { testIdTransforms } from 'vite-plugin-vue3-testid'
// 默认配置
testIdTransforms()
testIdTransforms({
attributeName: 'data-testid',
testIdPrefix: '',
injectForLoops: true,
injectEventElements: true,
debug: false,
})参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| attributeName | string | 'data-testid' | 自定义 testid 属性名 |
| testIdPrefix | string | '' | 所有编译期 testid 的公共前缀,如 'static-' → static-div-0 |
| injectForLoops | boolean | true | 启用 v-for 动态注入(需要 index 变量);无 index 时回退为 -in_for- 后缀 |
| injectEventElements | boolean | true | 带 @click/v-on 元素使用特殊格式 {tag}-event-{names}-{n} |
| debug | boolean | false | 开启编译期调试日志 |
createTestIdBridge(options?)
创建 Vue 3 插件,将编译期 testid 从 vnode.props 桥接到 DOM 元素。
import { createTestIdBridge } from 'vite-plugin-vue3-testid/plugin'
const app = createApp(App)
app.use(createTestIdBridge({ attributeName: 'data-testid' }))参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| attributeName | string | 'data-testid' | 必须与编译期属性名一致 |
setupTestIds(options?)
启动基于 MutationObserver 的运行时 testid 注入与去重。返回清理函数。
注入:对编译期未覆盖的元素(动态内容、teleport、第三方 DOM)注入 {tag}-{n} 格式的 testid。
去重:扫描 DOM 中所有已有的 testid。当同一 testid 出现在多个元素上时(由 v-for 无 index、UI 库 slot 克隆等导致),自动追加 :2、:3、... 后缀使每个实例唯一。第一个出现的实例保持原值不变。
import { setupTestIds } from 'vite-plugin-vue3-testid/runtime'
const cleanup = setupTestIds()
// ... 需要停止监听时:
cleanup()参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| attributeName | string | 'data-testid' | 自定义 testid 属性名 |
| debug | boolean | false | 开启控制台调试日志 |
| dedupOnly | boolean | false | 仅去重模式:不注入新 testid,仅处理已有 testid 的重复(跨环境确定性) |
vueTestIdPlugin(options?)
Vite 插件。作为配置占位符存在;实际注入通过 testIdTransforms()(编译期)和 setupTestIds()(运行时)完成。
import { vueTestIdPlugin } from 'vite-plugin-vue3-testid'
vueTestIdPlugin({
attributeName: 'data-testid',
testIdPrefix: '',
debug: false,
})为什么需要 Vue 插件桥接?
Vue 的 inheritAttrs: false(UI 库组件常见)会阻止非 props 属性传递到 DOM。UI 组件也可能通过 useAttrs() 消费 testid,将其从 $attrs 中移除。桥接插件在 mounted 生命周期直接从 vnode.props 读取 testid,调用 el.setAttribute() 写入 DOM,确保 testid 始终到达目标元素。
调试模式
开启调试日志,查看 testid 注入过程:
// 运行时调试
setupTestIds({ debug: true })
// 编译期调试
testIdTransforms({ debug: true })
// 桥接层调试 — 在 mount 前全局设置
;(window as any).__TESTID_DEBUG = true跳过的元素
template和slot元素(纯容器,不渲染为独立 DOM)- 已有
data-testid属性的元素(用户手动设置或已被注入) - 运行时模式下的非可视化元素:
style、script、meta、link、title、head、html、body、noscript、br、hr
与 Monorepo 中其他包的对比
| 包 | 范围 | Vue 版本 |
|---|---|---|
| vite-plugin-vue3-testid | 所有元素,UI 库无关 | Vue 3 |
| vite-plugin-vue-testid | 仅 Ant Design Vue | Vue 3 |
| vite-plugin-vant-testid | 仅 Vant 4 | Vue 3 |
| vite-plugin-element-testid | 仅 Element Plus | Vue 3 |
| vue-testid-core | Vant 2(通用引擎) | Vue 2 |
| vant-testid-webpack-plugin | Vant 2(Webpack 4) | Vue 2 |
License
MIT
