vite-plugin-vue-testid 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +302 -198
- package/README.zh.md +481 -0
- package/package.json +3 -2
- package/README.en.md +0 -377
package/README.md
CHANGED
|
@@ -1,47 +1,78 @@
|
|
|
1
1
|
# vite-plugin-vue-testid
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[中文文档](./README.zh.md)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
A Vite plugin that auto-injects `data-testid` into Vue component templates via compile-time + runtime transforms, tailored for ant-design-vue 4.x. Dramatically reduces the effort of locating elements in E2E tests.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
- **
|
|
13
|
-
-
|
|
14
|
-
- **
|
|
15
|
-
-
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Full runtime coverage** — MutationObserver-based injection for all ant-design-vue component roots, sub-elements, and teleported panels, no compile-time config required
|
|
10
|
+
- **Optional compile-time enhancement** — Inject stable, unique testids into custom business components (e.g., `<MyInput />`) at compile time, automatically passed through to inner antd components via `inheritAttrs`
|
|
11
|
+
- **Multi-root component fix** — Auto-adds `v-bind="$attrs"` to the first antd root element in multi-root child components, ensuring testid pass-through
|
|
12
|
+
- **Custom prefixCls** — Full support for ConfigProvider's custom CSS class prefix
|
|
13
|
+
- **Custom component mapping** — Extendable matcher rules for third-party UI libraries or custom components
|
|
14
|
+
- **Full panel coverage** — DatePicker (date/month/year/decade), RangePicker, TimePicker, Cascader, Select, TreeSelect
|
|
15
|
+
- **Sub-element injection** — InputNumber internal input & up/down buttons, Slider handles, Rate stars, Upload file input, Tabs tabs/panes
|
|
16
|
+
- **Menu sub-elements** — Text-based testids for menu-item, sub-menu, and menu-item-group
|
|
17
|
+
- **Preserves existing testIds** — Elements with manually assigned `data-testid` are skipped
|
|
18
|
+
- **MutationObserver dynamic watch** — Panel switching, tree node expand/collapse, and async component loading all auto-trigger re-injection
|
|
19
|
+
- **One-shot injection mode** — SSR/hydration friendly, call directly without MutationObserver
|
|
16
20
|
|
|
17
21
|
---
|
|
18
22
|
|
|
19
|
-
##
|
|
23
|
+
## Installation
|
|
20
24
|
|
|
21
25
|
```bash
|
|
22
26
|
pnpm add -D vite-plugin-vue-testid
|
|
23
27
|
```
|
|
24
28
|
|
|
25
|
-
>
|
|
29
|
+
> Requirements: `vite >= 5.0.0`, `vue >= 3.3.0`
|
|
26
30
|
|
|
27
31
|
---
|
|
28
32
|
|
|
29
|
-
##
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
**Mode A** (runtime-only) is recommended — simplest with full coverage. Choose **Mode B** if you need stable, unchanging testids for custom business components.
|
|
36
|
+
|
|
37
|
+
### Mode A: Runtime-only (recommended, zero compile-time config)
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
```ts
|
|
40
|
+
// vite.config.ts — no plugin config needed
|
|
41
|
+
import { defineConfig } from 'vite'
|
|
42
|
+
import vue from '@vitejs/plugin-vue'
|
|
43
|
+
|
|
44
|
+
export default defineConfig({
|
|
45
|
+
plugins: [vue()]
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
// main.ts
|
|
51
|
+
import { createApp } from 'vue'
|
|
52
|
+
import App from './App.vue'
|
|
53
|
+
import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
54
|
+
|
|
55
|
+
const app = createApp(App)
|
|
56
|
+
app.mount('#app')
|
|
57
|
+
|
|
58
|
+
// Call once after mount
|
|
59
|
+
setupAntTestIds()
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Mode B: Compile-time + Runtime (for custom components needing stable testids)
|
|
32
63
|
|
|
33
64
|
```ts
|
|
34
65
|
// vite.config.ts
|
|
35
66
|
import { defineConfig } from 'vite'
|
|
36
67
|
import vue from '@vitejs/plugin-vue'
|
|
37
|
-
import {
|
|
68
|
+
import { testIdTransforms } from 'vite-plugin-vue-testid'
|
|
38
69
|
|
|
39
70
|
export default defineConfig({
|
|
40
71
|
plugins: [
|
|
41
72
|
vue({
|
|
42
73
|
template: {
|
|
43
74
|
compilerOptions: {
|
|
44
|
-
nodeTransforms:
|
|
75
|
+
nodeTransforms: testIdTransforms()
|
|
45
76
|
}
|
|
46
77
|
}
|
|
47
78
|
})
|
|
@@ -49,271 +80,321 @@ export default defineConfig({
|
|
|
49
80
|
})
|
|
50
81
|
```
|
|
51
82
|
|
|
52
|
-
### 2. 运行时注入(main.ts)
|
|
53
|
-
|
|
54
83
|
```ts
|
|
55
|
-
// main.ts
|
|
56
|
-
import {
|
|
57
|
-
|
|
58
|
-
|
|
84
|
+
// main.ts — same as Mode A
|
|
85
|
+
import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
86
|
+
setupAntTestIds()
|
|
87
|
+
```
|
|
59
88
|
|
|
60
|
-
|
|
61
|
-
|
|
89
|
+
### Mode C: Compile-time only (panels not injected, not recommended)
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// vite.config.ts
|
|
93
|
+
import vue from '@vitejs/plugin-vue'
|
|
94
|
+
import { testIdTransforms } from 'vite-plugin-vue-testid'
|
|
62
95
|
|
|
63
|
-
|
|
64
|
-
|
|
96
|
+
export default defineConfig({
|
|
97
|
+
plugins: [
|
|
98
|
+
vue({
|
|
99
|
+
template: {
|
|
100
|
+
compilerOptions: {
|
|
101
|
+
nodeTransforms: testIdTransforms()
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
]
|
|
106
|
+
})
|
|
107
|
+
// main.ts — do NOT call setupAntTestIds()
|
|
65
108
|
```
|
|
66
109
|
|
|
67
110
|
---
|
|
68
111
|
|
|
69
|
-
##
|
|
112
|
+
## Generated testId Examples
|
|
70
113
|
|
|
71
|
-
###
|
|
114
|
+
### Runtime: Component Roots
|
|
72
115
|
|
|
73
116
|
```html
|
|
74
|
-
<!--
|
|
75
|
-
<a-input
|
|
76
|
-
<a-select
|
|
77
|
-
<a-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
<
|
|
81
|
-
<a-select data-testid="a-select-1" />
|
|
82
|
-
<a-button data-testid="a-button-2" />
|
|
117
|
+
<!-- Runtime auto-matches ant-design-vue components, globally incrementing counter -->
|
|
118
|
+
<div class="ant-input-number" data-testid="a-input-number-0">
|
|
119
|
+
<div class="ant-select" data-testid="a-select-1" id="a-select-1">
|
|
120
|
+
<div class="ant-picker" data-testid="a-date-picker-2" id="a-date-picker-2">
|
|
121
|
+
<button class="ant-btn" data-testid="a-button-3">
|
|
122
|
+
<input class="ant-input" data-testid="a-input-4">
|
|
123
|
+
<span class="ant-slider" data-testid="a-slider-5">
|
|
83
124
|
```
|
|
84
125
|
|
|
85
|
-
###
|
|
86
|
-
|
|
87
|
-
```html
|
|
88
|
-
<!-- 源码 -->
|
|
89
|
-
<a-date-picker />
|
|
90
|
-
<a-modal v-model:visible="show" title="提示" />
|
|
126
|
+
### Runtime: Sub-elements
|
|
91
127
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
```
|
|
129
|
+
<!-- InputNumber: internal input + up/down handlers -->
|
|
130
|
+
a-input-number-0-input # internal <input>
|
|
131
|
+
a-input-number-0-up # increase button
|
|
132
|
+
a-input-number-0-down # decrease button
|
|
133
|
+
|
|
134
|
+
<!-- Slider: handles -->
|
|
135
|
+
a-slider-5-handle-0 # 1st handle
|
|
136
|
+
a-slider-5-handle-1 # 2nd handle (range mode)
|
|
137
|
+
|
|
138
|
+
<!-- Rate: stars -->
|
|
139
|
+
a-rate-0-star-0 # 1st star
|
|
140
|
+
a-rate-0-star-1 # 2nd star
|
|
141
|
+
|
|
142
|
+
<!-- Upload: internal file input -->
|
|
143
|
+
a-upload-0-input # <input type="file">
|
|
144
|
+
|
|
145
|
+
<!-- Tabs: tab items and panes -->
|
|
146
|
+
a-tabs-0-tab-0-Tab-One # 1st tab
|
|
147
|
+
a-tabs-0-tab-1-Tab-Two # 2nd tab
|
|
148
|
+
a-tabs-0-pane-0-Tab-One # 1st pane
|
|
149
|
+
a-tabs-0-add # add tab button (editable tabs)
|
|
150
|
+
a-tabs-0-more # more button
|
|
95
151
|
```
|
|
96
152
|
|
|
97
|
-
###
|
|
153
|
+
### Runtime: DatePicker Panel
|
|
98
154
|
|
|
99
155
|
```
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
156
|
+
a-date-picker-0-dropdown # panel container
|
|
157
|
+
a-date-picker-0-dropdown-super-prev # previous year
|
|
158
|
+
a-date-picker-0-dropdown-prev # previous month
|
|
159
|
+
a-date-picker-0-dropdown-next # next month
|
|
160
|
+
a-date-picker-0-dropdown-super-next # next year
|
|
161
|
+
a-date-picker-0-dropdown-header-date # header container
|
|
162
|
+
a-date-picker-0-dropdown-header-month-btn # header month button
|
|
163
|
+
a-date-picker-0-dropdown-header-year-btn # header year button
|
|
164
|
+
a-date-picker-0-dropdown-date-2026-06-15 # date cell
|
|
165
|
+
a-date-picker-0-dropdown-ok # OK button
|
|
105
166
|
```
|
|
106
167
|
|
|
107
|
-
|
|
168
|
+
After switching to month/year/decade panel (auto re-injected by MutationObserver):
|
|
108
169
|
|
|
109
170
|
```
|
|
110
|
-
a-date-picker-0-dropdown
|
|
111
|
-
a-date-picker-0-dropdown-
|
|
112
|
-
a-date-picker-0-dropdown-
|
|
113
|
-
a-date-picker-0-dropdown-
|
|
114
|
-
a-date-picker-0-dropdown-
|
|
115
|
-
a-date-picker-0-dropdown-header-year-btn # header 年份按钮
|
|
116
|
-
a-date-picker-0-dropdown-date-2026-06-15 # 日期单元格
|
|
117
|
-
a-date-picker-0-dropdown-ok # 确定按钮
|
|
171
|
+
a-date-picker-0-dropdown-header-month # month panel header
|
|
172
|
+
a-date-picker-0-dropdown-month-06 # June
|
|
173
|
+
a-date-picker-0-dropdown-header-year # year panel header
|
|
174
|
+
a-date-picker-0-dropdown-year-2026 # year 2026
|
|
175
|
+
a-date-picker-0-dropdown-decade-2020-2029 # decade 2020-2029
|
|
118
176
|
```
|
|
119
177
|
|
|
120
|
-
|
|
178
|
+
### Runtime: RangePicker Panel
|
|
121
179
|
|
|
122
180
|
```
|
|
123
|
-
a-
|
|
124
|
-
a-
|
|
125
|
-
a-
|
|
126
|
-
a-
|
|
127
|
-
a-
|
|
181
|
+
a-range-picker-0-dropdown-super-prev-left # left panel previous year
|
|
182
|
+
a-range-picker-0-dropdown-prev-left # left panel previous month
|
|
183
|
+
a-range-picker-0-dropdown-next-right # right panel next month
|
|
184
|
+
a-range-picker-0-dropdown-date-2026-05-15-left # left panel date
|
|
185
|
+
a-range-picker-0-dropdown-date-2026-06-20-right # right panel date
|
|
128
186
|
```
|
|
129
187
|
|
|
130
|
-
###
|
|
188
|
+
### Runtime: TimePicker
|
|
131
189
|
|
|
132
190
|
```
|
|
133
|
-
a-
|
|
134
|
-
a-
|
|
135
|
-
a-
|
|
191
|
+
a-time-picker-0-dropdown-time-column-0 # hour column
|
|
192
|
+
a-time-picker-0-dropdown-time-0-08 # 08 hour
|
|
193
|
+
a-time-picker-0-dropdown-time-column-1 # minute column
|
|
194
|
+
a-time-picker-0-dropdown-time-1-30 # 30 minutes
|
|
136
195
|
```
|
|
137
196
|
|
|
138
|
-
###
|
|
197
|
+
### Runtime: Preset Shortcuts
|
|
139
198
|
|
|
140
199
|
```
|
|
141
|
-
a-
|
|
142
|
-
a-
|
|
143
|
-
a-time-picker-0-dropdown-time-column-1 # 分列
|
|
144
|
-
a-time-picker-0-dropdown-time-30 # 30分
|
|
200
|
+
a-date-picker-0-dropdown-preset-This Week # preset tag
|
|
201
|
+
a-date-picker-0-dropdown-preset-This Month # preset tag
|
|
145
202
|
```
|
|
146
203
|
|
|
147
|
-
###
|
|
204
|
+
### Runtime: Cascader
|
|
148
205
|
|
|
149
206
|
```
|
|
150
|
-
a-cascader-0-dropdown
|
|
151
|
-
a-cascader-0-dropdown-menu-0
|
|
152
|
-
a-cascader-0-dropdown-item
|
|
153
|
-
a-cascader-0-dropdown-menu-1
|
|
154
|
-
a-cascader-0-dropdown-item
|
|
207
|
+
a-cascader-0-dropdown # panel container
|
|
208
|
+
a-cascader-0-dropdown-menu-0 # level 1 menu
|
|
209
|
+
a-cascader-0-dropdown-item-Zhejiang # Zhejiang option
|
|
210
|
+
a-cascader-0-dropdown-menu-1 # level 2 menu
|
|
211
|
+
a-cascader-0-dropdown-item-Hangzhou # Hangzhou option
|
|
155
212
|
```
|
|
156
213
|
|
|
157
|
-
###
|
|
214
|
+
### Runtime: Select / TreeSelect
|
|
158
215
|
|
|
159
216
|
```
|
|
160
|
-
a-select-0-dropdown
|
|
161
|
-
a-select-0-dropdown-option
|
|
162
|
-
a-select-0-dropdown-option
|
|
217
|
+
a-select-0-dropdown # dropdown container
|
|
218
|
+
a-select-0-dropdown-option-Option1 # option
|
|
219
|
+
a-select-0-dropdown-option-Option2 # option
|
|
220
|
+
|
|
221
|
+
a-tree-select-0-dropdown # dropdown container
|
|
222
|
+
a-tree-select-0-dropdown-tree-Root-Node # tree node
|
|
223
|
+
a-tree-select-0-dropdown-tree-switcher-expand-Root-Node # expand icon (collapsed)
|
|
224
|
+
a-tree-select-0-dropdown-tree-switcher-collapse-Root-Node # expand icon (expanded)
|
|
163
225
|
```
|
|
164
226
|
|
|
165
|
-
###
|
|
227
|
+
### Runtime: Menu Sub-elements
|
|
166
228
|
|
|
167
229
|
```
|
|
168
|
-
a-
|
|
169
|
-
a-
|
|
170
|
-
a-
|
|
171
|
-
a-
|
|
172
|
-
a-
|
|
173
|
-
a-tree-select-0-dropdown-tree-my-leaf # 叶子节点
|
|
230
|
+
a-menu-0 # menu container (by injectComponentRoots)
|
|
231
|
+
a-menu-item-Menu-Item-1 # menu item (runtime, text-based)
|
|
232
|
+
a-menu-item-Sub-Item-3 # submenu item
|
|
233
|
+
a-sub-menu-Submenu # submenu
|
|
234
|
+
a-menu-item-group-Group-Title # menu item group
|
|
174
235
|
```
|
|
175
236
|
|
|
176
|
-
###
|
|
237
|
+
### Compile-time: Custom Business Components
|
|
177
238
|
|
|
178
239
|
```
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
240
|
+
Format: {tagName}-{perTemplateIndex}
|
|
241
|
+
|
|
242
|
+
<!-- Source -->
|
|
243
|
+
<MyInput />
|
|
244
|
+
<MySelect />
|
|
245
|
+
<MyInput />
|
|
246
|
+
|
|
247
|
+
<!-- Compiled -->
|
|
248
|
+
<MyInput data-testid="MyInput-0" />
|
|
249
|
+
<MySelect data-testid="MySelect-0" />
|
|
250
|
+
<MyInput data-testid="MyInput-1" />
|
|
184
251
|
```
|
|
185
252
|
|
|
253
|
+
> **Design note**: The compile-time transform only injects testids on **custom business components** (passed through to inner antd components via `inheritAttrs`), NOT on antd components directly. All antd component testids are handled by the runtime layer (global counter ensures uniqueness).
|
|
254
|
+
|
|
186
255
|
---
|
|
187
256
|
|
|
188
|
-
##
|
|
257
|
+
## Configuration
|
|
189
258
|
|
|
190
|
-
###
|
|
259
|
+
### Compile-time Options
|
|
191
260
|
|
|
192
261
|
```ts
|
|
193
|
-
import {
|
|
262
|
+
import { testIdTransforms } from 'vite-plugin-vue-testid'
|
|
194
263
|
|
|
195
|
-
|
|
264
|
+
testIdTransforms({
|
|
196
265
|
/**
|
|
197
|
-
*
|
|
198
|
-
* @default 见下方「默认支持的组件」
|
|
199
|
-
*/
|
|
200
|
-
components: ['a-input', 'a-select', 'my-button'],
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* 自定义 testId 生成函数
|
|
204
|
-
* @param tagName 组件标签名
|
|
205
|
-
* @param count 全局递增序号
|
|
206
|
-
* @returns testId 字符串
|
|
207
|
-
*/
|
|
208
|
-
generateId: (tagName, count) => `myapp-${tagName}-${count}`,
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* testId 属性名(可改为 data-cy 适配 Cypress)
|
|
266
|
+
* testId attribute name
|
|
212
267
|
* @default 'data-testid'
|
|
213
268
|
*/
|
|
214
|
-
attributeName: 'data-
|
|
269
|
+
attributeName: 'data-testid',
|
|
215
270
|
|
|
216
271
|
/**
|
|
217
|
-
*
|
|
218
|
-
* @default
|
|
272
|
+
* ant-design-vue tag prefix
|
|
273
|
+
* @default 'a-'
|
|
219
274
|
*/
|
|
220
|
-
|
|
275
|
+
antPrefix: 'a-',
|
|
221
276
|
|
|
222
277
|
/**
|
|
223
|
-
*
|
|
224
|
-
* @default
|
|
278
|
+
* Additional custom component prefixes to inject testids for
|
|
279
|
+
* @default []
|
|
225
280
|
*/
|
|
226
|
-
|
|
281
|
+
customPrefixes: ['myapp-', 'el-'],
|
|
227
282
|
})
|
|
228
283
|
```
|
|
229
284
|
|
|
230
|
-
###
|
|
285
|
+
### Runtime Options
|
|
231
286
|
|
|
232
287
|
```ts
|
|
233
|
-
import {
|
|
288
|
+
import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
234
289
|
|
|
235
|
-
|
|
236
|
-
const cleanup = setupAntDropdownTestIds()
|
|
237
|
-
|
|
238
|
-
// 自定义配置
|
|
239
|
-
const cleanup = setupAntDropdownTestIds({
|
|
290
|
+
const cleanup = setupAntTestIds({
|
|
240
291
|
/**
|
|
241
|
-
* testId
|
|
292
|
+
* testId attribute name, must match compile-time config
|
|
242
293
|
* @default 'data-testid'
|
|
243
294
|
*/
|
|
244
295
|
attributeName: 'data-testid',
|
|
245
296
|
|
|
246
297
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
|
|
298
|
+
* ant-design-vue CSS class prefix, matching ConfigProvider's prefixCls
|
|
299
|
+
* @default 'ant'
|
|
300
|
+
*/
|
|
301
|
+
prefixCls: 'ant',
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Custom CSS selector → testid prefix mapping
|
|
305
|
+
* Merged with defaults (custom overrides default)
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* components: {
|
|
309
|
+
* '.el-input': 'el-input',
|
|
310
|
+
* '.el-button': 'el-button',
|
|
311
|
+
* }
|
|
312
|
+
*/
|
|
313
|
+
components: {
|
|
314
|
+
'.el-input': 'el-input',
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Custom panel selector → injection strategy mapping
|
|
319
|
+
* key: CSS selector
|
|
320
|
+
* value: injection function; set to undefined to disable a built-in panel
|
|
250
321
|
*/
|
|
251
322
|
panels: {
|
|
252
|
-
'.ant-picker-dropdown': undefined,
|
|
323
|
+
'.ant-picker-dropdown': undefined, // disable built-in Picker panel
|
|
253
324
|
'.my-custom-dropdown': (ctx) => {
|
|
254
|
-
// ctx.container — 面板容器 HTMLElement
|
|
255
|
-
// ctx.trigger — 触发器 HTMLElement | null
|
|
256
|
-
// ctx.triggerTestId — 触发器的 testId 值
|
|
257
|
-
// ctx.attrName — testId 属性名
|
|
258
325
|
const prefix = `${ctx.triggerTestId}-dropdown`
|
|
259
326
|
ctx.container.setAttribute(ctx.attrName, prefix)
|
|
260
|
-
// ...
|
|
327
|
+
// ... custom injection logic
|
|
261
328
|
},
|
|
262
329
|
},
|
|
263
330
|
})
|
|
264
331
|
|
|
265
|
-
//
|
|
266
|
-
onUnmounted(cleanup)
|
|
332
|
+
// Clean up observer on component unmount
|
|
333
|
+
// onUnmounted(cleanup)
|
|
267
334
|
```
|
|
268
335
|
|
|
269
|
-
###
|
|
336
|
+
### One-shot Manual Injection
|
|
270
337
|
|
|
271
338
|
```ts
|
|
272
339
|
import { injectCurrentDropdowns } from 'vite-plugin-vue-testid/runtime'
|
|
273
340
|
|
|
274
|
-
// SSR / hydration
|
|
275
|
-
injectCurrentDropdowns({
|
|
341
|
+
// For SSR / hydration scenarios where MutationObserver is not desired
|
|
342
|
+
injectCurrentDropdowns({ prefixCls: 'ant' })
|
|
276
343
|
```
|
|
277
344
|
|
|
278
345
|
---
|
|
279
346
|
|
|
280
|
-
##
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
|
285
|
-
|
|
286
|
-
| `a-input-
|
|
287
|
-
| `a-
|
|
288
|
-
| `a-
|
|
289
|
-
| `a-
|
|
290
|
-
| `a-
|
|
291
|
-
| `a-
|
|
292
|
-
| `a-
|
|
293
|
-
| `a-
|
|
294
|
-
| `a-
|
|
295
|
-
| `a-
|
|
296
|
-
| `a-
|
|
297
|
-
| `a-
|
|
298
|
-
| `a-
|
|
299
|
-
| `a-
|
|
300
|
-
| `a-
|
|
301
|
-
| `a-
|
|
302
|
-
| `a-
|
|
303
|
-
| `a-
|
|
304
|
-
| `a-
|
|
305
|
-
| `a-
|
|
347
|
+
## Default Supported Components
|
|
348
|
+
|
|
349
|
+
### Runtime (component roots + sub-elements + panels)
|
|
350
|
+
|
|
351
|
+
| Component | Root testid | Sub-elements | Panels |
|
|
352
|
+
|-----------|:---:|:---:|:---:|
|
|
353
|
+
| Input | `a-input-N` | — | — |
|
|
354
|
+
| Input.Search | `a-input-search-N` | — | — |
|
|
355
|
+
| Input.Password (affix-wrapper) | `a-input-N` | — | — |
|
|
356
|
+
| Textarea | `a-textarea-N` | — | — |
|
|
357
|
+
| InputNumber | `a-input-number-N` | input / up / down | — |
|
|
358
|
+
| Select | `a-select-N` | — | ✅ dropdown |
|
|
359
|
+
| Cascader | `a-cascader-N` | — | ✅ cascader panel |
|
|
360
|
+
| TreeSelect | `a-tree-select-N` | — | ✅ tree select panel |
|
|
361
|
+
| DatePicker | `a-date-picker-N` | — | ✅ date/month/year/decade |
|
|
362
|
+
| RangePicker | `a-range-picker-N` | — | ✅ dual panel + time columns |
|
|
363
|
+
| TimePicker | `a-time-picker-N` | — | ✅ time columns |
|
|
364
|
+
| Radio.Group | `a-radio-group-N` | — | — |
|
|
365
|
+
| Radio | `a-radio-N` | — | — |
|
|
366
|
+
| Checkbox.Group | `a-checkbox-group-N` | — | — |
|
|
367
|
+
| Checkbox | `a-checkbox-N` | — | — |
|
|
368
|
+
| Switch | `a-switch-N` | — | — |
|
|
369
|
+
| Slider | `a-slider-N` | handle-0 / handle-1 | — |
|
|
370
|
+
| Rate | `a-rate-N` | star-0 / star-1 / ... | — |
|
|
371
|
+
| Upload | `a-upload-N` | file input | — |
|
|
372
|
+
| Button | `a-button-N` | — | — |
|
|
373
|
+
| Form.Item | `a-form-item-N` | — | — |
|
|
374
|
+
| Modal | `a-modal-N` | — | — |
|
|
375
|
+
| Menu | `a-menu-N` | menu-item / sub-menu / group | — |
|
|
376
|
+
| Tabs | `a-tabs-N` | tab / pane / add / more | — |
|
|
377
|
+
|
|
378
|
+
> Components with `id` injection (Select, Cascader, DatePicker, RangePicker, TimePicker, Modal, Upload) also have the `id` attribute set, with the same value as testid.
|
|
379
|
+
|
|
380
|
+
### Compile-time (custom business components)
|
|
381
|
+
|
|
382
|
+
The compile-time transform injects testids on components matching:
|
|
383
|
+
- Custom components with `tagType === Component` (e.g., `<MyInput />`)
|
|
384
|
+
- Components matching any prefix in `customPrefixes`
|
|
385
|
+
- **Excluding** antd components (`a-*`), which are handled by the runtime
|
|
306
386
|
|
|
307
387
|
---
|
|
308
388
|
|
|
309
|
-
##
|
|
389
|
+
## Custom Panel Injection Strategies
|
|
310
390
|
|
|
311
|
-
|
|
391
|
+
Support overlays from other UI libraries by providing custom strategies:
|
|
312
392
|
|
|
313
393
|
```ts
|
|
314
394
|
import type { PanelInjectStrategy } from 'vite-plugin-vue-testid/runtime'
|
|
395
|
+
import { setupAntTestIds } from 'vite-plugin-vue-testid/runtime'
|
|
315
396
|
|
|
316
|
-
//
|
|
397
|
+
// Example: inject testIds into Element Plus overlays
|
|
317
398
|
const injectElDropdown: PanelInjectStrategy = (ctx) => {
|
|
318
399
|
const { container, triggerTestId, attrName } = ctx
|
|
319
400
|
const prefix = `${triggerTestId}-dropdown`
|
|
@@ -327,7 +408,7 @@ const injectElDropdown: PanelInjectStrategy = (ctx) => {
|
|
|
327
408
|
})
|
|
328
409
|
}
|
|
329
410
|
|
|
330
|
-
|
|
411
|
+
setupAntTestIds({
|
|
331
412
|
panels: {
|
|
332
413
|
'.el-select-dropdown': injectElDropdown,
|
|
333
414
|
'.el-cascader-dropdown': injectElDropdown,
|
|
@@ -337,38 +418,61 @@ setupAntDropdownTestIds({
|
|
|
337
418
|
|
|
338
419
|
---
|
|
339
420
|
|
|
340
|
-
## API
|
|
421
|
+
## API Reference
|
|
422
|
+
|
|
423
|
+
### Compile-time
|
|
424
|
+
|
|
425
|
+
| Export | Type | Description |
|
|
426
|
+
|--------|------|-------------|
|
|
427
|
+
| `testIdTransforms(opts?)` | `() => NodeTransform[]` | Returns `[multiRootFixTransform, testIdInjectTransform]`, register in `vue()`'s `compilerOptions.nodeTransforms` |
|
|
428
|
+
| `createTestIdTransforms(opts?)` | `() => NodeTransform[]` | Same as above, full export name |
|
|
429
|
+
| `createTestIdInjectTransform(opts?)` | `() => NodeTransform` | Create only the testid inject transform (without multi-root fix) |
|
|
430
|
+
| `createMultiRootFixTransform(opts?)` | `() => NodeTransform` | Create only the multi-root fix transform |
|
|
431
|
+
| `TransformOptions` | `interface` | `{ attributeName?, antPrefix?, customPrefixes? }` |
|
|
341
432
|
|
|
342
|
-
###
|
|
433
|
+
### Runtime (`/runtime` subpath)
|
|
343
434
|
|
|
344
|
-
|
|
|
345
|
-
|
|
346
|
-
| `
|
|
347
|
-
| `
|
|
435
|
+
| Export | Type | Description |
|
|
436
|
+
|--------|------|-------------|
|
|
437
|
+
| `setupAntTestIds(opts?)` | `() => () => void` | Starts MutationObserver, returns cleanup function |
|
|
438
|
+
| `injectCurrentDropdowns(opts?)` | `() => void` | One-shot manual injection of current DOM components & panels |
|
|
439
|
+
| `RuntimeOptions` | `interface` | `{ attributeName?, prefixCls?, components?, panels? }` |
|
|
440
|
+
| `PanelContext` | `interface` | `{ container, trigger, triggerTestId, attrName }` |
|
|
441
|
+
| `PanelInjectStrategy` | `(ctx: PanelContext) => void` | Panel injection strategy function type |
|
|
348
442
|
|
|
349
|
-
###
|
|
443
|
+
### Vite Plugin
|
|
350
444
|
|
|
351
|
-
|
|
|
352
|
-
|
|
353
|
-
| `
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
|
445
|
+
| Export | Type | Description |
|
|
446
|
+
|--------|------|-------------|
|
|
447
|
+
| `vueTestIdPlugin(opts?)` | `Plugin` | Vite plugin (placeholder, currently no functionality) |
|
|
448
|
+
|
|
449
|
+
### Deprecated
|
|
450
|
+
|
|
451
|
+
| Export | Replacement |
|
|
452
|
+
|--------|-------------|
|
|
453
|
+
| `setupAntDropdownTestIds` | Use `setupAntTestIds` |
|
|
454
|
+
| `createVueTestIdTransform` | Use `testIdTransforms` |
|
|
358
455
|
|
|
359
456
|
---
|
|
360
457
|
|
|
361
|
-
##
|
|
458
|
+
## How It Works
|
|
459
|
+
|
|
460
|
+
### Runtime (Core)
|
|
461
|
+
|
|
462
|
+
`setupAntTestIds()` uses `MutationObserver` to watch DOM changes on `document.body`, processing newly inserted nodes in sequence:
|
|
362
463
|
|
|
363
|
-
|
|
464
|
+
1. **Component root injection** — Iterates CSS selectors matching antd component root elements, injecting globally incrementing testids. Internal inputs/textareas inside wrappers are excluded via a skip list
|
|
465
|
+
2. **Panel injection** — Detects teleported panel containers (picker-dropdown / cascader-dropdown / select-dropdown, etc.), resolves the triggering component via `findActiveTrigger`, and injects associated testids
|
|
466
|
+
3. **Sub-element injection** — Injects suffixed testids into special child elements of InputNumber / Slider / Rate / Upload / Tabs
|
|
467
|
+
4. **Menu injection** — Since Menu components have `inheritAttrs: false`, additionally traverses menu-item / sub-menu / menu-item-group to inject text-based testids
|
|
364
468
|
|
|
365
|
-
|
|
469
|
+
Panel containers also have their own MutationObserver to handle child node changes (panel mode switches, tree node expand/collapse) with automatic re-injection.
|
|
366
470
|
|
|
367
|
-
###
|
|
471
|
+
### Compile-time (Optional)
|
|
368
472
|
|
|
369
|
-
|
|
473
|
+
Uses Vue's compiler `nodeTransform` hook at AST stage to inject `data-testid="{tagName}-{index}"` on custom business components. Since injection happens in the parent template, testids are automatically passed through to the child component's root element via Vue's `fallthroughAttrs` mechanism.
|
|
370
474
|
|
|
371
|
-
|
|
475
|
+
For multi-root components (Fragment), `inheritAttrs` is not effective. `createMultiRootFixTransform` automatically adds `v-bind="$attrs"` to the first antd component root element to ensure pass-through.
|
|
372
476
|
|
|
373
477
|
---
|
|
374
478
|
|