vite-plugin-vue3-testid

Vite plugin to auto-inject data-testid into ant-design-vue (Vue 3) component DOM for E2E testing
926
Install
npm install vite-plugin-vue3-testid
Latest Version:2.0.0
Vue:3
License:MIT
Last Updated:Jul 7, 2026
Links: npm
Maintainer: allen1314

vite-plugin-vue3-testid

English

为 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

Related Packages

vite-plugin-vue-testid

Vite plugin to auto-inject data-testid into Vue component templates for E2E test...

3,214
testid-autoinject

Zero-touch build-time injection of stable testID / data-testid for React Native...

1,468 14
vite-plugin-vue-meta-layouts

vite 的 vue-router 的元信息布局系统

314,627 65
vite-plugin-vue-docgen

A simple vue-docgen plugin for Vite

91,834 8
vite-plugin-favicons-inject

A Vite plugin for creating and injecting favicons during the application build!

533,525 10

Version History

Version Released Vue License
2.0.0 >=3.3.0 MIT
1.1.2 >=3.3.0 MIT
1.1.1 >=3.3.0 MIT
1.0.1 >=3.3.0 MIT
1.0.0 >=3.3.0 MIT