vite-plugin-vue-testid

Vite plugin to auto-inject data-testid into Vue component templates for E2E testing
3,214
Install
npm install vite-plugin-vue-testid
Latest Version:1.1.5
Vue:3
License:MIT
Last Updated:Jul 7, 2026
Links: npm
Maintainer: allen1314

vite-plugin-vue-testid

English

编译期 + 运行时自动为 Vue 组件模板注入 data-testid 的 Vite 插件,专为 ant-design-vue 4.x 适配,大幅降低 E2E 测试中的元素定位成本。

特性

  • 纯运行时全覆盖 — 通过 MutationObserver 为所有 ant-design-vue 组件根元素、子元素、teleport 面板自动注入 data-testid,无需编译期配置即可使用
  • 编译期可选增强 — 为业务自定义组件(如 <MyInput />)在模板编译期注入唯一稳定的 testid,通过 inheritAttrs 自动透传到内部 antd 组件
  • 多根组件修复 — 自动为多根子组件的 antd 根元素添加 v-bind="$attrs",确保 testid 正确透传
  • 自定义 prefixCls — 完美支持 ConfigProvider 的自定义 CSS 类名前缀
  • 自定义组件映射 — 支持为第三方 UI 库或自定义组件扩展匹配规则
  • 面板全覆盖 — DatePicker (date/month/year/decade)、RangePicker、TimePicker、Cascader、Select、TreeSelect
  • 子元素注入 — InputNumber 内部输入框及增减按钮、Slider handle、Rate 星星、Upload file input、Tabs tab/pane
  • Menu 子元素 — 基于文本内容自动注入 menu-item、sub-menu、menu-item-group
  • 不会覆盖已有 testId — 手动标注了 data-testid 的元素会被跳过
  • MutationObserver 动态监听 — 面板切换、树节点展开、异步组件加载后自动注入新 DOM
  • 一次性注入模式 — SSE/hydration 场景可直接调用,不使用 MutationObserver

安装

pnpm add -D vite-plugin-vue-testid

要求:vite >= 5.0.0,vue >= 3.3.0


快速开始

推荐方式 A(纯运行时),最简单且覆盖最全。如果需要对业务自定义组件生成稳定不变的 testid,选择方式 B。

方式 A:纯运行时(推荐,零编译期配置)

// vite.config.ts — 不需要任何插件配置
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()]
})
// main.ts
import { createApp } from 'vue'
import App from './App.vue'
import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'

const app = createApp(App)
app.mount('#app')

// 在 mount 之后调用一次即可
setupAntTestIds()

方式 B:编译期 + 运行时(业务自定义组件需要稳定 testid)

// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { testIdTransforms } from 'vite-plugin-vue-testid'

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          nodeTransforms: testIdTransforms()
        }
      }
    })
  ]
})
// main.ts — 与方式 A 相同
import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
setupAntTestIds()

方式 C:仅编译期(面板不注入 testid,不推荐)

// vite.config.ts
import vue from '@vitejs/plugin-vue'
import { testIdTransforms } from 'vite-plugin-vue-testid'

export default defineConfig({
  plugins: [
    vue({
      template: {
        compilerOptions: {
          nodeTransforms: testIdTransforms()
        }
      }
    })
  ]
})
// main.ts — 不调用 setupAntTestIds()

生成的 testId 示例

运行时注入:组件根元素

<!-- 运行时自动匹配 ant-design-vue 组件,全局递增编号 -->
<div class="ant-input-number" data-testid="a-input-number-0">
<div class="ant-select" data-testid="a-select-1" id="a-select-1">
<div class="ant-picker" data-testid="a-date-picker-2" id="a-date-picker-2">
<button class="ant-btn" data-testid="a-button-3">
<input class="ant-input" data-testid="a-input-4">
<span class="ant-slider" data-testid="a-slider-5">

运行时注入:组件子元素

<!-- InputNumber:内部 input + 增减按钮 -->
a-input-number-0-input          # 内部 <input>
a-input-number-0-up             # 增加按钮
a-input-number-0-down           # 减少按钮

<!-- Slider:各 handle -->
a-slider-5-handle-0             # 第1个 handle
a-slider-5-handle-1             # 第2个 handle(range 模式)

<!-- Rate:各星星 -->
a-rate-0-star-0                 # 第1颗星
a-rate-0-star-1                 # 第2颗星

<!-- Upload:内部 file input -->
a-upload-0-input                # <input type="file">

<!-- Tabs:标签项和内容面板 -->
a-tabs-0-tab-0-标签一            # 第1个 tab
a-tabs-0-tab-1-标签二            # 第2个 tab
a-tabs-0-pane-0-标签一           # 第1个 pane
a-tabs-0-add                    # 新增标签按钮(editable tabs)
a-tabs-0-more                   # 更多按钮

运行时注入:DatePicker 面板

a-date-picker-0-dropdown                        # 面板容器
a-date-picker-0-dropdown-super-prev             # 上一年
a-date-picker-0-dropdown-prev                   # 上一月
a-date-picker-0-dropdown-next                   # 下一月
a-date-picker-0-dropdown-super-next             # 下一年
a-date-picker-0-dropdown-header-date            # header 容器
a-date-picker-0-dropdown-header-month-btn       # header 月份按钮
a-date-picker-0-dropdown-header-year-btn        # header 年份按钮
a-date-picker-0-dropdown-date-2026-06-15        # 日期单元格
a-date-picker-0-dropdown-ok                     # 确定按钮

切换到月/年/年代面板后(MutationObserver 自动重注入):

a-date-picker-0-dropdown-header-month           # 月面板 header
a-date-picker-0-dropdown-month-06               # 6月
a-date-picker-0-dropdown-header-year            # 年面板 header
a-date-picker-0-dropdown-year-2026              # 2026年
a-date-picker-0-dropdown-decade-2020-2029       # 2020-2029 年代

运行时注入:RangePicker 面板

a-range-picker-0-dropdown-super-prev-left       # 左侧面板上一年
a-range-picker-0-dropdown-prev-left             # 左侧面板上一月
a-range-picker-0-dropdown-next-right            # 右侧面板下一月
a-range-picker-0-dropdown-date-2026-05-15-left  # 左侧面板日期
a-range-picker-0-dropdown-date-2026-06-20-right # 右侧面板日期

运行时注入:TimePicker

a-time-picker-0-dropdown-time-column-0          # 时列
a-time-picker-0-dropdown-time-0-08              # 08时
a-time-picker-0-dropdown-time-column-1          # 分列
a-time-picker-0-dropdown-time-1-30              # 30分

运行时注入:Preset 快捷范围

a-date-picker-0-dropdown-preset-本周            # preset 标签
a-date-picker-0-dropdown-preset-本月            # preset 标签

运行时注入:Cascader

a-cascader-0-dropdown                           # 面板容器
a-cascader-0-dropdown-menu-0                    # 第1级菜单
a-cascader-0-dropdown-item-浙江                 # 浙江选项
a-cascader-0-dropdown-menu-1                    # 第2级菜单
a-cascader-0-dropdown-item-杭州                 # 杭州选项

运行时注入:Select / TreeSelect

a-select-0-dropdown                             # 下拉容器
a-select-0-dropdown-option-选项1                # 选项
a-select-0-dropdown-option-选项2                # 选项

a-tree-select-0-dropdown                        # 下拉容器
a-tree-select-0-dropdown-tree-根节点            # 树节点
a-tree-select-0-dropdown-tree-switcher-expand-根节点  # 展开图标(可展开)
a-tree-select-0-dropdown-tree-switcher-collapse-根节点 # 展开图标(已展开)

运行时注入:Menu 子元素

a-menu-0                             # 菜单容器(由 injectComponentRoots 注入)
a-menu-item-菜单项1                  # 菜单项(运行时,基于文本内容)
a-menu-item-子项3                    # 子菜单内的菜单项
a-sub-menu-子菜单                    # 子菜单
a-menu-item-group-分组标题           # 菜单分组标题

编译期注入:业务自定义组件

格式:{tagName}-{perTemplateIndex}

<!-- 源码 -->
<MyInput />
<MySelect />
<MyInput />

<!-- 编译后 -->
<MyInput data-testid="MyInput-0" />
<MySelect data-testid="MySelect-0" />
<MyInput data-testid="MyInput-1" />

设计说明:编译期 transform 只对业务自定义组件注入 testid(通过 inheritAttrs 透传到内部 antd 组件),不对 antd 组件直接注入。antd 组件的 testid 全部由运行时负责(全局计数器保证唯一)。


配置

编译期配置

import { testIdTransforms } from 'vite-plugin-vue-testid'

testIdTransforms({
  /**
   * testId 属性名
   * @default 'data-testid'
   */
  attributeName: 'data-testid',

  /**
   * ant-design-vue 的 tag 前缀
   * @default 'a-'
   */
  antPrefix: 'a-',

  /**
   * 额外需要注入 testid 的自定义组件前缀
   * @default []
   */
  customPrefixes: ['myapp-', 'el-'],
})

运行时配置

import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'

const cleanup = setupAntTestIds({
  /**
   * testId 属性名,需与编译期配置一致
   * @default 'data-testid'
   */
  attributeName: 'data-testid',

  /**
   * ant-design-vue CSS 类名前缀,对应 ConfigProvider 的 prefixCls
   * @default 'ant'
   */
  prefixCls: 'ant',

  /**
   * 自定义组件 CSS 选择器 → testid 前缀映射
   * 会与默认映射合并(自定义覆盖默认)
   *
   * @example
   * components: {
   *   '.el-input': 'el-input',
   *   '.el-button': 'el-button',
   * }
   */
  components: {
    '.el-input': 'el-input',
  },

  /**
   * 自定义面板选择器 → 注入策略映射
   * key: CSS 选择器
   * value: 注入函数,设为 undefined 可禁用某个默认面板
   */
  panels: {
    '.ant-picker-dropdown': undefined,  // 禁用内置 Picker 面板注入
    '.my-custom-dropdown': (ctx) => {
      const prefix = `${ctx.triggerTestId}-dropdown`
      ctx.container.setAttribute(ctx.attrName, prefix)
      // ... 自定义注入逻辑
    },
  },
})

// 组件卸载时清理 Observer
// onUnmounted(cleanup)

一次性手动注入

import { injectCurrentDropdowns } from 'vite-plugin-vue-testid/runtime'

// SSR / hydration 场景下,不想用 MutationObserver 时直接调用
injectCurrentDropdowns({ prefixCls: 'ant' })

默认支持的组件

运行时(组件根元素 + 子元素 + 面板)

组件 根元素 testid 子元素 面板
Input a-input-N — —
Input.Search a-input-search-N — —
Input.Password (affix-wrapper) a-input-N — —
Textarea a-textarea-N — —
InputNumber a-input-number-N input / up / down —
Select a-select-N — ✅ 下拉面板
Cascader a-cascader-N — ✅ 级联面板
TreeSelect a-tree-select-N — ✅ 树选择面板
DatePicker a-date-picker-N — ✅ 日期/月/年/年代面板
RangePicker a-range-picker-N — ✅ 双面板 + 时间列
TimePicker a-time-picker-N — ✅ 时间列
Radio.Group a-radio-group-N — —
Radio a-radio-N — —
Checkbox.Group a-checkbox-group-N — —
Checkbox a-checkbox-N — —
Switch a-switch-N — —
Slider a-slider-N handle-0 / handle-1 —
Rate a-rate-N star-0 / star-1 / ... —
Upload a-upload-N file input —
Button a-button-N — —
Form.Item a-form-item-N — —
Modal a-modal-N — —
Menu a-menu-N menu-item / sub-menu / group —
Tabs a-tabs-N tab / pane / add / more —

所有带 id 注入的组件(Select、Cascader、DatePicker、RangePicker、TimePicker、Modal、Upload)同时设置 id 属性,值等于 testid。

编译期(业务自定义组件)

编译期 transform 对满足以下条件的组件注入 testid:

  • tagType === Component 的自定义组件(如 <MyInput />)
  • 匹配 customPrefixes 中任一前缀的组件
  • 不包括 antd 组件(a-*),这些由运行时负责

自定义面板注入策略

如果需要支持其他 UI 库的弹出面板,可以自定义策略:

import type { PanelInjectStrategy } from 'vite-plugin-vue-testid/runtime'
import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'

// 示例:为 Element Plus 的弹出层注入 testId
const injectElDropdown: PanelInjectStrategy = (ctx) => {
  const { container, triggerTestId, attrName } = ctx
  const prefix = `${triggerTestId}-dropdown`

  container.setAttribute(attrName, prefix)

  container.querySelectorAll('.el-select-dropdown__item').forEach((item) => {
    const el = item as HTMLElement
    const text = el.textContent?.trim() || ''
    el.setAttribute(attrName, `${prefix}-option-${text}`)
  })
}

setupAntTestIds({
  panels: {
    '.el-select-dropdown': injectElDropdown,
    '.el-cascader-dropdown': injectElDropdown,
  },
})

API 参考

编译期

导出 类型 说明
testIdTransforms(opts?) () => NodeTransform[] 返回 [multiRootFixTransform, testIdInjectTransform],注册到 vue() 的 compilerOptions.nodeTransforms
createTestIdTransforms(opts?) () => NodeTransform[] 同上,完整导出名
createTestIdInjectTransform(opts?) () => NodeTransform 仅创建 testid 注入 transform(不含 multi-root fix)
createMultiRootFixTransform(opts?) () => NodeTransform 仅创建多根组件修复 transform
TransformOptions interface { attributeName?, antPrefix?, customPrefixes? }

运行时(/runtime 子路径)

导出 类型 说明
setupAntTestIds(opts?) () => () => void 启动 MutationObserver,返回清理函数
injectCurrentDropdowns(opts?) () => void 一次性手动注入当前 DOM 中已有的组件和面板
RuntimeOptions interface { attributeName?, prefixCls?, components?, panels? }
PanelContext interface { container, trigger, triggerTestId, attrName }
PanelInjectStrategy (ctx: PanelContext) => void 面板注入策略函数类型

Vite 插件

导出 类型 说明
vueTestIdPlugin(opts?) Plugin Vite 插件(占位符,当前无实际功能)

已废弃

导出 替代
setupAntDropdownTestIds 使用 setupAntTestIds
createVueTestIdTransform 使用 testIdTransforms

原理

运行时(核心)

setupAntTestIds() 通过 MutationObserver 监听 document.body 的 DOM 变化,对新插入的节点依次执行:

  1. 组件根注入 — 遍历 CSS 选择器匹配 antd 组件根元素,注入全局递增编号的 testid。同时通过 skip 列表排除组件包装器内部的 input/textarea
  2. 面板注入 — 检测 teleport 面板容器(picker-dropdown / cascader-dropdown / select-dropdown 等),通过 findActiveTrigger 找到触发面板的组件,关联注入
  3. 子元素注入 — 对 InputNumber / Slider / Rate / Upload / Tabs 的特殊子元素注入带后缀的 testid
  4. Menu 注入 — 由于 Menu 组件的 inheritAttrs: false,额外遍历 menu-item / sub-menu / menu-item-group 注入基于文本的 testid

面板容器内也使用 MutationObserver 监听子节点变化(如面板模式切换、树节点展开),自动重新注入。

编译期(可选)

通过 Vue 编译器的 nodeTransform 钩子在 AST 阶段为业务自定义组件注入 data-testid="{tagName}-{index}"。由于注入在父模板中完成,testid 通过 Vue 的 fallthroughAttrs 机制自动透传到子组件的根元素。

对于多根组件(Fragment),inheritAttrs 不生效,createMultiRootFixTransform 自动为第一个 antd 组件根元素添加 v-bind="$attrs" 以确保透传。


License

MIT

Related Packages

vite-plugin-vue3-testid

Vite plugin to auto-inject data-testid into ant-design-vue (Vue 3) component DOM...

926
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
vite-plugin-use-modules

自动加载 modules

8,078 14

Version History

Version Released Vue License
1.1.5 >=3.3.0 MIT
1.1.4 >=3.3.0 MIT
1.1.3 >=3.3.0 MIT
1.1.2 >=3.3.0 MIT
1.1.1 >=3.3.0 MIT