@testid/antd-testid-runtime 1.0.7 → 1.0.9
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/dist/index.d.mts +112 -22
- package/dist/index.d.ts +112 -22
- package/dist/index.js +156 -69
- package/dist/index.mjs +154 -68
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI 库适配器接口 — adapters/types.ts
|
|
3
|
+
*
|
|
4
|
+
* 将 UI 库相关的 CSS class 命名、标签前缀、交互元素识别等逻辑
|
|
5
|
+
* 从核心 Observer 中解耦,实现可插拔的 UI 库支持。
|
|
6
|
+
*
|
|
7
|
+
* 每个适配器描述一个 UI 库的浮层组件识别规则,
|
|
8
|
+
* 多个适配器可组合使用 (如同时使用 Ant Design Vue + Element UI)。
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 支持的浮层类型
|
|
12
|
+
*/
|
|
13
|
+
type PopupType = 'modal' | 'drawer' | 'select' | 'datePicker' | 'popconfirm' | 'dropdown' | 'tooltip' | 'message';
|
|
14
|
+
/**
|
|
15
|
+
* UI 库适配器接口
|
|
16
|
+
*
|
|
17
|
+
* 实现此接口即可让 Observer 自动识别该 UI 库的浮层组件。
|
|
18
|
+
*/
|
|
19
|
+
interface UiAdapter {
|
|
20
|
+
/** 适配器名称 (调试用) */
|
|
21
|
+
readonly name: string;
|
|
22
|
+
/** CSS 类名前缀数组 (如 AntD: ['ant'], Element: ['el']) */
|
|
23
|
+
readonly cssPrefixes: string[];
|
|
24
|
+
/**
|
|
25
|
+
* 浮层 CSS class 后缀映射
|
|
26
|
+
*
|
|
27
|
+
* 结构: PopupType → string[][],两层数组含义:
|
|
28
|
+
* - 外层: OR 关系,任一内层命中即匹配
|
|
29
|
+
* - 内层: AND 关系,所有 class 必须同时出现
|
|
30
|
+
*
|
|
31
|
+
* 后缀不含前缀,由 buildPopupClassMap 动态拼接。
|
|
32
|
+
*/
|
|
33
|
+
readonly popupClassSuffixMap: Record<PopupType, string[][]>;
|
|
34
|
+
/**
|
|
35
|
+
* 交互标签名列表 (用于 onlyInteractive 模式)
|
|
36
|
+
*
|
|
37
|
+
* 包含 UI 库的组件标签名 (如 'a-button', 'el-input') 和原生标签 (如 'button', 'input')。
|
|
38
|
+
* Observer 会合并所有适配器的 interactiveTags 进行判断。
|
|
39
|
+
*/
|
|
40
|
+
readonly interactiveTags: string[];
|
|
41
|
+
/**
|
|
42
|
+
* 标签前缀正则 (用于 getSimpleTag 去除 UI 库前缀)
|
|
43
|
+
*
|
|
44
|
+
* 例: AntD → /^a-/, Element → /^el-/
|
|
45
|
+
* 多个适配器的正则用 | 合并。
|
|
46
|
+
*/
|
|
47
|
+
readonly tagPrefixPattern: RegExp;
|
|
48
|
+
}
|
|
49
|
+
|
|
1
50
|
/**
|
|
2
51
|
* 全局配置模块 — testMark.ts
|
|
3
52
|
*
|
|
@@ -6,11 +55,9 @@
|
|
|
6
55
|
* - 黑白名单
|
|
7
56
|
* - 开关
|
|
8
57
|
* - 浮层前缀映射
|
|
58
|
+
* - UI 库适配器
|
|
9
59
|
*/
|
|
10
|
-
|
|
11
|
-
* 支持的浮层类型
|
|
12
|
-
*/
|
|
13
|
-
type PopupType = 'modal' | 'drawer' | 'select' | 'datePicker' | 'popconfirm' | 'dropdown' | 'tooltip';
|
|
60
|
+
|
|
14
61
|
/**
|
|
15
62
|
* 全量配置接口
|
|
16
63
|
*/
|
|
@@ -28,17 +75,27 @@ interface TestIdMarkConfig {
|
|
|
28
75
|
compilePrefix: string;
|
|
29
76
|
/** 页面内动态节点统一前缀 (默认 "dynamic_") */
|
|
30
77
|
runtimePagePrefix: string;
|
|
31
|
-
/**
|
|
78
|
+
/** 浮层组件专属前缀映射 */
|
|
32
79
|
popupPrefixMap: Record<PopupType, string>;
|
|
33
80
|
/**
|
|
34
|
-
*
|
|
81
|
+
* UI 库适配器列表 (默认为 [antdAdapter])
|
|
82
|
+
*
|
|
83
|
+
* 每个适配器定义了一个 UI 库的浮层 CSS class 识别规则、
|
|
84
|
+
* 交互标签列表和标签前缀。Observer 自动合并所有适配器的规则。
|
|
85
|
+
*
|
|
86
|
+
* 仅使用 Ant Design Vue:
|
|
87
|
+
* adapters: [antdAdapter]
|
|
88
|
+
*
|
|
89
|
+
* 仅使用 Element UI:
|
|
90
|
+
* adapters: [elementAdapter]
|
|
35
91
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
92
|
+
* 同时使用两种 UI 库:
|
|
93
|
+
* adapters: [antdAdapter, elementAdapter]
|
|
94
|
+
*
|
|
95
|
+
* 自定义 Ant Design Vue CSS 前缀 (如 <a-config-provider prefixCls="my-ui">):
|
|
96
|
+
* adapters: [{ ...antdAdapter, cssPrefixes: ['my-ui'] }]
|
|
40
97
|
*/
|
|
41
|
-
|
|
98
|
+
adapters: UiAdapter[];
|
|
42
99
|
/** 忽略不打标的 HTML 标签名 */
|
|
43
100
|
ignoreTags: string[];
|
|
44
101
|
/** 包含此 class 的 DOM 跳过打标 */
|
|
@@ -50,10 +107,6 @@ interface TestIdMarkConfig {
|
|
|
50
107
|
/** 路由切换时是否重置全部浮层计数器 */
|
|
51
108
|
resetPopupCounterOnRouteChange: boolean;
|
|
52
109
|
}
|
|
53
|
-
/**
|
|
54
|
-
* 交互控件白名单 (onlyInteractive === true 时生效)
|
|
55
|
-
*/
|
|
56
|
-
declare const INTERACTIVE_TAGS: Set<string>;
|
|
57
110
|
/**
|
|
58
111
|
* 默认配置对象 (enable 默认为 true,由调用方传入 DEV 判断)
|
|
59
112
|
*/
|
|
@@ -74,6 +127,34 @@ declare function initConfig(custom?: Partial<TestIdMarkConfig>): void;
|
|
|
74
127
|
*/
|
|
75
128
|
declare function getConfig(): Readonly<TestIdMarkConfig>;
|
|
76
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Ant Design Vue 适配器 — adapters/antd.ts
|
|
132
|
+
*
|
|
133
|
+
* 识别 Ant Design Vue 的浮层组件:
|
|
134
|
+
* - ant-modal, ant-drawer, ant-select-dropdown, ant-picker-dropdown,
|
|
135
|
+
* ant-popover+ant-popconfirm, ant-dropdown, ant-tooltip, ant-message
|
|
136
|
+
*
|
|
137
|
+
* 支持多版本兼容:
|
|
138
|
+
* - Ant Design Vue 4.x: ant-picker-dropdown
|
|
139
|
+
* - Ant Design Vue 1.x: ant-calendar-picker-container
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
declare const antdAdapter: UiAdapter;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Element UI 适配器 — adapters/element.ts
|
|
146
|
+
*
|
|
147
|
+
* 识别 Element UI / Element Plus 的浮层组件:
|
|
148
|
+
* - el-dialog, el-drawer, el-select-dropdown, el-picker-panel,
|
|
149
|
+
* el-message-box, el-popconfirm, el-dropdown-menu,
|
|
150
|
+
* el-tooltip__popper, el-message
|
|
151
|
+
*
|
|
152
|
+
* 注意: el-popper 被多种组件共用 (select/dropdown/tooltip 等),
|
|
153
|
+
* 不单独作为识别依据,避免误匹配。
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
declare const elementAdapter: UiAdapter;
|
|
157
|
+
|
|
77
158
|
/**
|
|
78
159
|
* 锚点局部计数器 — testIdAnchorCounter.ts
|
|
79
160
|
*
|
|
@@ -205,8 +286,12 @@ declare function buildPopupTestId(type: PopupType, tag: string, counterId: numbe
|
|
|
205
286
|
*/
|
|
206
287
|
declare class TestIdObserver {
|
|
207
288
|
private state;
|
|
208
|
-
/** 浮层 class 匹配映射 (
|
|
289
|
+
/** 浮层 class 匹配映射 (从 adapters 动态构建) */
|
|
209
290
|
private popupClassMap;
|
|
291
|
+
/** 合并后的交互标签列表 (来自所有适配器) */
|
|
292
|
+
private interactiveTags;
|
|
293
|
+
/** 合并后的标签前缀正则 (用于 getSimpleTag) */
|
|
294
|
+
private tagPrefixPattern;
|
|
210
295
|
constructor();
|
|
211
296
|
/**
|
|
212
297
|
* 启动 MutationObserver
|
|
@@ -291,7 +376,7 @@ declare class TestIdObserver {
|
|
|
291
376
|
* 因为 Ant Design Vue 4 可能在浮层根节点外包一层 wrapper DIV)。
|
|
292
377
|
* 找到浮层根节点后,继续向上验证其祖先链能到达 body (确保不在 #app 内)。
|
|
293
378
|
*
|
|
294
|
-
* @returns
|
|
379
|
+
* @returns 浮层类型 + 祖先元素,或 null (不在任何浮层内)
|
|
295
380
|
*/
|
|
296
381
|
private detectPopupAncestor;
|
|
297
382
|
/**
|
|
@@ -314,6 +399,9 @@ declare class TestIdObserver {
|
|
|
314
399
|
/**
|
|
315
400
|
* 处理浮层内部子节点 (Modal/Drawer/Dropdown 内的按钮、输入框等)
|
|
316
401
|
*
|
|
402
|
+
* 每个浮层实例独立计数: 以浮层根节点 data-testid 作为隔离 key,
|
|
403
|
+
* 重复打开相同类型的浮层,子元素 ID 均从 0 重新开始。
|
|
404
|
+
*
|
|
317
405
|
* ID 格式: ${runtimePagePrefix}${popupPrefix}${tag}_${counter}
|
|
318
406
|
* 例: hall_dynamic_modal_button_0, hall_dynamic_select_div_2
|
|
319
407
|
*
|
|
@@ -327,17 +415,19 @@ declare class TestIdObserver {
|
|
|
327
415
|
/**
|
|
328
416
|
* 获取元素的简化标签名
|
|
329
417
|
*
|
|
330
|
-
*
|
|
418
|
+
* 去除所有适配器注册的 UI 库标签前缀:
|
|
419
|
+
* AntD: a-button → button, a-input → input
|
|
420
|
+
* Element: el-button → button, el-input → input
|
|
331
421
|
*/
|
|
332
422
|
private getSimpleTag;
|
|
333
423
|
/**
|
|
334
424
|
* 判断是否可交互元素
|
|
335
425
|
*
|
|
336
426
|
* 可交互特征:
|
|
337
|
-
* -
|
|
427
|
+
* - 匹配任意适配器的交互标签 (含原生 + UI 库前缀)
|
|
338
428
|
* - onclick 属性
|
|
339
|
-
* - role="button" / role="checkbox" / role="radio"
|
|
340
|
-
* -
|
|
429
|
+
* - role="button" / role="checkbox" / role="radio" / role="switch"
|
|
430
|
+
* - tabindex 属性
|
|
341
431
|
*/
|
|
342
432
|
private isInteractive;
|
|
343
433
|
/**
|
|
@@ -394,4 +484,4 @@ declare class TestIdChecker {
|
|
|
394
484
|
private static reportGroupDuplicates;
|
|
395
485
|
}
|
|
396
486
|
|
|
397
|
-
export {
|
|
487
|
+
export { type ParsedBaseKey, type PopupType, TestIdChecker, type TestIdMarkConfig, TestIdObserver, type UiAdapter, antdAdapter, buildAnchorTestId, buildPopupTestId, defaultConfig, elementAdapter, getAnchorCounterMap, getConfig, getNextAnchorLocalIndex, getNextPopupId, getPopupCounterSnapshot, initConfig, mergeConfig, parseBaseKey, resetAllAnchorCounters, resetAllPopupCounters, resetPopupCounter };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI 库适配器接口 — adapters/types.ts
|
|
3
|
+
*
|
|
4
|
+
* 将 UI 库相关的 CSS class 命名、标签前缀、交互元素识别等逻辑
|
|
5
|
+
* 从核心 Observer 中解耦,实现可插拔的 UI 库支持。
|
|
6
|
+
*
|
|
7
|
+
* 每个适配器描述一个 UI 库的浮层组件识别规则,
|
|
8
|
+
* 多个适配器可组合使用 (如同时使用 Ant Design Vue + Element UI)。
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 支持的浮层类型
|
|
12
|
+
*/
|
|
13
|
+
type PopupType = 'modal' | 'drawer' | 'select' | 'datePicker' | 'popconfirm' | 'dropdown' | 'tooltip' | 'message';
|
|
14
|
+
/**
|
|
15
|
+
* UI 库适配器接口
|
|
16
|
+
*
|
|
17
|
+
* 实现此接口即可让 Observer 自动识别该 UI 库的浮层组件。
|
|
18
|
+
*/
|
|
19
|
+
interface UiAdapter {
|
|
20
|
+
/** 适配器名称 (调试用) */
|
|
21
|
+
readonly name: string;
|
|
22
|
+
/** CSS 类名前缀数组 (如 AntD: ['ant'], Element: ['el']) */
|
|
23
|
+
readonly cssPrefixes: string[];
|
|
24
|
+
/**
|
|
25
|
+
* 浮层 CSS class 后缀映射
|
|
26
|
+
*
|
|
27
|
+
* 结构: PopupType → string[][],两层数组含义:
|
|
28
|
+
* - 外层: OR 关系,任一内层命中即匹配
|
|
29
|
+
* - 内层: AND 关系,所有 class 必须同时出现
|
|
30
|
+
*
|
|
31
|
+
* 后缀不含前缀,由 buildPopupClassMap 动态拼接。
|
|
32
|
+
*/
|
|
33
|
+
readonly popupClassSuffixMap: Record<PopupType, string[][]>;
|
|
34
|
+
/**
|
|
35
|
+
* 交互标签名列表 (用于 onlyInteractive 模式)
|
|
36
|
+
*
|
|
37
|
+
* 包含 UI 库的组件标签名 (如 'a-button', 'el-input') 和原生标签 (如 'button', 'input')。
|
|
38
|
+
* Observer 会合并所有适配器的 interactiveTags 进行判断。
|
|
39
|
+
*/
|
|
40
|
+
readonly interactiveTags: string[];
|
|
41
|
+
/**
|
|
42
|
+
* 标签前缀正则 (用于 getSimpleTag 去除 UI 库前缀)
|
|
43
|
+
*
|
|
44
|
+
* 例: AntD → /^a-/, Element → /^el-/
|
|
45
|
+
* 多个适配器的正则用 | 合并。
|
|
46
|
+
*/
|
|
47
|
+
readonly tagPrefixPattern: RegExp;
|
|
48
|
+
}
|
|
49
|
+
|
|
1
50
|
/**
|
|
2
51
|
* 全局配置模块 — testMark.ts
|
|
3
52
|
*
|
|
@@ -6,11 +55,9 @@
|
|
|
6
55
|
* - 黑白名单
|
|
7
56
|
* - 开关
|
|
8
57
|
* - 浮层前缀映射
|
|
58
|
+
* - UI 库适配器
|
|
9
59
|
*/
|
|
10
|
-
|
|
11
|
-
* 支持的浮层类型
|
|
12
|
-
*/
|
|
13
|
-
type PopupType = 'modal' | 'drawer' | 'select' | 'datePicker' | 'popconfirm' | 'dropdown' | 'tooltip';
|
|
60
|
+
|
|
14
61
|
/**
|
|
15
62
|
* 全量配置接口
|
|
16
63
|
*/
|
|
@@ -28,17 +75,27 @@ interface TestIdMarkConfig {
|
|
|
28
75
|
compilePrefix: string;
|
|
29
76
|
/** 页面内动态节点统一前缀 (默认 "dynamic_") */
|
|
30
77
|
runtimePagePrefix: string;
|
|
31
|
-
/**
|
|
78
|
+
/** 浮层组件专属前缀映射 */
|
|
32
79
|
popupPrefixMap: Record<PopupType, string>;
|
|
33
80
|
/**
|
|
34
|
-
*
|
|
81
|
+
* UI 库适配器列表 (默认为 [antdAdapter])
|
|
82
|
+
*
|
|
83
|
+
* 每个适配器定义了一个 UI 库的浮层 CSS class 识别规则、
|
|
84
|
+
* 交互标签列表和标签前缀。Observer 自动合并所有适配器的规则。
|
|
85
|
+
*
|
|
86
|
+
* 仅使用 Ant Design Vue:
|
|
87
|
+
* adapters: [antdAdapter]
|
|
88
|
+
*
|
|
89
|
+
* 仅使用 Element UI:
|
|
90
|
+
* adapters: [elementAdapter]
|
|
35
91
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
92
|
+
* 同时使用两种 UI 库:
|
|
93
|
+
* adapters: [antdAdapter, elementAdapter]
|
|
94
|
+
*
|
|
95
|
+
* 自定义 Ant Design Vue CSS 前缀 (如 <a-config-provider prefixCls="my-ui">):
|
|
96
|
+
* adapters: [{ ...antdAdapter, cssPrefixes: ['my-ui'] }]
|
|
40
97
|
*/
|
|
41
|
-
|
|
98
|
+
adapters: UiAdapter[];
|
|
42
99
|
/** 忽略不打标的 HTML 标签名 */
|
|
43
100
|
ignoreTags: string[];
|
|
44
101
|
/** 包含此 class 的 DOM 跳过打标 */
|
|
@@ -50,10 +107,6 @@ interface TestIdMarkConfig {
|
|
|
50
107
|
/** 路由切换时是否重置全部浮层计数器 */
|
|
51
108
|
resetPopupCounterOnRouteChange: boolean;
|
|
52
109
|
}
|
|
53
|
-
/**
|
|
54
|
-
* 交互控件白名单 (onlyInteractive === true 时生效)
|
|
55
|
-
*/
|
|
56
|
-
declare const INTERACTIVE_TAGS: Set<string>;
|
|
57
110
|
/**
|
|
58
111
|
* 默认配置对象 (enable 默认为 true,由调用方传入 DEV 判断)
|
|
59
112
|
*/
|
|
@@ -74,6 +127,34 @@ declare function initConfig(custom?: Partial<TestIdMarkConfig>): void;
|
|
|
74
127
|
*/
|
|
75
128
|
declare function getConfig(): Readonly<TestIdMarkConfig>;
|
|
76
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Ant Design Vue 适配器 — adapters/antd.ts
|
|
132
|
+
*
|
|
133
|
+
* 识别 Ant Design Vue 的浮层组件:
|
|
134
|
+
* - ant-modal, ant-drawer, ant-select-dropdown, ant-picker-dropdown,
|
|
135
|
+
* ant-popover+ant-popconfirm, ant-dropdown, ant-tooltip, ant-message
|
|
136
|
+
*
|
|
137
|
+
* 支持多版本兼容:
|
|
138
|
+
* - Ant Design Vue 4.x: ant-picker-dropdown
|
|
139
|
+
* - Ant Design Vue 1.x: ant-calendar-picker-container
|
|
140
|
+
*/
|
|
141
|
+
|
|
142
|
+
declare const antdAdapter: UiAdapter;
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Element UI 适配器 — adapters/element.ts
|
|
146
|
+
*
|
|
147
|
+
* 识别 Element UI / Element Plus 的浮层组件:
|
|
148
|
+
* - el-dialog, el-drawer, el-select-dropdown, el-picker-panel,
|
|
149
|
+
* el-message-box, el-popconfirm, el-dropdown-menu,
|
|
150
|
+
* el-tooltip__popper, el-message
|
|
151
|
+
*
|
|
152
|
+
* 注意: el-popper 被多种组件共用 (select/dropdown/tooltip 等),
|
|
153
|
+
* 不单独作为识别依据,避免误匹配。
|
|
154
|
+
*/
|
|
155
|
+
|
|
156
|
+
declare const elementAdapter: UiAdapter;
|
|
157
|
+
|
|
77
158
|
/**
|
|
78
159
|
* 锚点局部计数器 — testIdAnchorCounter.ts
|
|
79
160
|
*
|
|
@@ -205,8 +286,12 @@ declare function buildPopupTestId(type: PopupType, tag: string, counterId: numbe
|
|
|
205
286
|
*/
|
|
206
287
|
declare class TestIdObserver {
|
|
207
288
|
private state;
|
|
208
|
-
/** 浮层 class 匹配映射 (
|
|
289
|
+
/** 浮层 class 匹配映射 (从 adapters 动态构建) */
|
|
209
290
|
private popupClassMap;
|
|
291
|
+
/** 合并后的交互标签列表 (来自所有适配器) */
|
|
292
|
+
private interactiveTags;
|
|
293
|
+
/** 合并后的标签前缀正则 (用于 getSimpleTag) */
|
|
294
|
+
private tagPrefixPattern;
|
|
210
295
|
constructor();
|
|
211
296
|
/**
|
|
212
297
|
* 启动 MutationObserver
|
|
@@ -291,7 +376,7 @@ declare class TestIdObserver {
|
|
|
291
376
|
* 因为 Ant Design Vue 4 可能在浮层根节点外包一层 wrapper DIV)。
|
|
292
377
|
* 找到浮层根节点后,继续向上验证其祖先链能到达 body (确保不在 #app 内)。
|
|
293
378
|
*
|
|
294
|
-
* @returns
|
|
379
|
+
* @returns 浮层类型 + 祖先元素,或 null (不在任何浮层内)
|
|
295
380
|
*/
|
|
296
381
|
private detectPopupAncestor;
|
|
297
382
|
/**
|
|
@@ -314,6 +399,9 @@ declare class TestIdObserver {
|
|
|
314
399
|
/**
|
|
315
400
|
* 处理浮层内部子节点 (Modal/Drawer/Dropdown 内的按钮、输入框等)
|
|
316
401
|
*
|
|
402
|
+
* 每个浮层实例独立计数: 以浮层根节点 data-testid 作为隔离 key,
|
|
403
|
+
* 重复打开相同类型的浮层,子元素 ID 均从 0 重新开始。
|
|
404
|
+
*
|
|
317
405
|
* ID 格式: ${runtimePagePrefix}${popupPrefix}${tag}_${counter}
|
|
318
406
|
* 例: hall_dynamic_modal_button_0, hall_dynamic_select_div_2
|
|
319
407
|
*
|
|
@@ -327,17 +415,19 @@ declare class TestIdObserver {
|
|
|
327
415
|
/**
|
|
328
416
|
* 获取元素的简化标签名
|
|
329
417
|
*
|
|
330
|
-
*
|
|
418
|
+
* 去除所有适配器注册的 UI 库标签前缀:
|
|
419
|
+
* AntD: a-button → button, a-input → input
|
|
420
|
+
* Element: el-button → button, el-input → input
|
|
331
421
|
*/
|
|
332
422
|
private getSimpleTag;
|
|
333
423
|
/**
|
|
334
424
|
* 判断是否可交互元素
|
|
335
425
|
*
|
|
336
426
|
* 可交互特征:
|
|
337
|
-
* -
|
|
427
|
+
* - 匹配任意适配器的交互标签 (含原生 + UI 库前缀)
|
|
338
428
|
* - onclick 属性
|
|
339
|
-
* - role="button" / role="checkbox" / role="radio"
|
|
340
|
-
* -
|
|
429
|
+
* - role="button" / role="checkbox" / role="radio" / role="switch"
|
|
430
|
+
* - tabindex 属性
|
|
341
431
|
*/
|
|
342
432
|
private isInteractive;
|
|
343
433
|
/**
|
|
@@ -394,4 +484,4 @@ declare class TestIdChecker {
|
|
|
394
484
|
private static reportGroupDuplicates;
|
|
395
485
|
}
|
|
396
486
|
|
|
397
|
-
export {
|
|
487
|
+
export { type ParsedBaseKey, type PopupType, TestIdChecker, type TestIdMarkConfig, TestIdObserver, type UiAdapter, antdAdapter, buildAnchorTestId, buildPopupTestId, defaultConfig, elementAdapter, getAnchorCounterMap, getConfig, getNextAnchorLocalIndex, getNextPopupId, getPopupCounterSnapshot, initConfig, mergeConfig, parseBaseKey, resetAllAnchorCounters, resetAllPopupCounters, resetPopupCounter };
|
package/dist/index.js
CHANGED
|
@@ -20,12 +20,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
-
INTERACTIVE_TAGS: () => INTERACTIVE_TAGS,
|
|
24
23
|
TestIdChecker: () => TestIdChecker,
|
|
25
24
|
TestIdObserver: () => TestIdObserver,
|
|
25
|
+
antdAdapter: () => antdAdapter,
|
|
26
26
|
buildAnchorTestId: () => buildAnchorTestId,
|
|
27
27
|
buildPopupTestId: () => buildPopupTestId,
|
|
28
28
|
defaultConfig: () => defaultConfig,
|
|
29
|
+
elementAdapter: () => elementAdapter,
|
|
29
30
|
getAnchorCounterMap: () => getAnchorCounterMap,
|
|
30
31
|
getConfig: () => getConfig,
|
|
31
32
|
getNextAnchorLocalIndex: () => getNextAnchorLocalIndex,
|
|
@@ -40,27 +41,50 @@ __export(index_exports, {
|
|
|
40
41
|
});
|
|
41
42
|
module.exports = __toCommonJS(index_exports);
|
|
42
43
|
|
|
44
|
+
// src/adapters/antd.ts
|
|
45
|
+
var popupClassSuffixMap = {
|
|
46
|
+
modal: [["-modal"]],
|
|
47
|
+
drawer: [["-drawer"]],
|
|
48
|
+
select: [["-select-dropdown"]],
|
|
49
|
+
datePicker: [
|
|
50
|
+
["-picker-dropdown"],
|
|
51
|
+
// Ant Design Vue 4.x
|
|
52
|
+
["-calendar-picker-container"]
|
|
53
|
+
// Ant Design Vue 1.x
|
|
54
|
+
],
|
|
55
|
+
popconfirm: [["-popover", "-popconfirm"]],
|
|
56
|
+
dropdown: [["-dropdown"]],
|
|
57
|
+
tooltip: [["-tooltip"]],
|
|
58
|
+
message: [["-message"]]
|
|
59
|
+
};
|
|
60
|
+
var antdAdapter = {
|
|
61
|
+
name: "ant-design-vue",
|
|
62
|
+
cssPrefixes: ["ant"],
|
|
63
|
+
popupClassSuffixMap,
|
|
64
|
+
interactiveTags: [
|
|
65
|
+
"button",
|
|
66
|
+
"input",
|
|
67
|
+
"select",
|
|
68
|
+
"textarea",
|
|
69
|
+
"a-button",
|
|
70
|
+
"a-input",
|
|
71
|
+
"a-input-number",
|
|
72
|
+
"a-select",
|
|
73
|
+
"a-textarea",
|
|
74
|
+
"a-checkbox",
|
|
75
|
+
"a-radio",
|
|
76
|
+
"a-switch"
|
|
77
|
+
],
|
|
78
|
+
tagPrefixPattern: /^a-/
|
|
79
|
+
};
|
|
80
|
+
|
|
43
81
|
// src/config/testMark.ts
|
|
44
|
-
var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
|
|
45
|
-
"button",
|
|
46
|
-
"a-button",
|
|
47
|
-
"input",
|
|
48
|
-
"a-input",
|
|
49
|
-
"a-input-number",
|
|
50
|
-
"select",
|
|
51
|
-
"a-select",
|
|
52
|
-
"textarea",
|
|
53
|
-
"a-textarea",
|
|
54
|
-
"a-checkbox",
|
|
55
|
-
"a-radio",
|
|
56
|
-
"a-switch"
|
|
57
|
-
]);
|
|
58
82
|
var defaultConfig = {
|
|
59
83
|
enable: true,
|
|
60
84
|
globalPrefix: "",
|
|
61
85
|
compilePrefix: "static_",
|
|
62
86
|
runtimePagePrefix: "dynamic_",
|
|
63
|
-
|
|
87
|
+
adapters: [antdAdapter],
|
|
64
88
|
popupPrefixMap: {
|
|
65
89
|
modal: "modal_",
|
|
66
90
|
drawer: "drawer_",
|
|
@@ -68,7 +92,8 @@ var defaultConfig = {
|
|
|
68
92
|
datePicker: "datePicker_",
|
|
69
93
|
popconfirm: "popconfirm_",
|
|
70
94
|
dropdown: "dropdown_",
|
|
71
|
-
tooltip: "tooltip_"
|
|
95
|
+
tooltip: "tooltip_",
|
|
96
|
+
message: "message_"
|
|
72
97
|
},
|
|
73
98
|
ignoreTags: ["script", "style", "svg", "br", "iframe"],
|
|
74
99
|
ignoreClass: ["no-test-mark", "hidden"],
|
|
@@ -115,6 +140,48 @@ function getConfig() {
|
|
|
115
140
|
return globalConfig;
|
|
116
141
|
}
|
|
117
142
|
|
|
143
|
+
// src/adapters/element.ts
|
|
144
|
+
var popupClassSuffixMap2 = {
|
|
145
|
+
modal: [["-dialog"]],
|
|
146
|
+
drawer: [["-drawer"]],
|
|
147
|
+
select: [["-select-dropdown"]],
|
|
148
|
+
datePicker: [
|
|
149
|
+
["-picker-panel"],
|
|
150
|
+
// 日期/时间选择器面板
|
|
151
|
+
["-date-range-picker__content"]
|
|
152
|
+
// 日期范围选择器内容区
|
|
153
|
+
],
|
|
154
|
+
popconfirm: [
|
|
155
|
+
["-message-box"],
|
|
156
|
+
// Element MessageBox
|
|
157
|
+
["-popconfirm"]
|
|
158
|
+
// Element Popconfirm
|
|
159
|
+
],
|
|
160
|
+
dropdown: [["-dropdown-menu"]],
|
|
161
|
+
tooltip: [["-tooltip__popper"]],
|
|
162
|
+
message: [["-message"]]
|
|
163
|
+
};
|
|
164
|
+
var elementAdapter = {
|
|
165
|
+
name: "element-ui",
|
|
166
|
+
cssPrefixes: ["el"],
|
|
167
|
+
popupClassSuffixMap: popupClassSuffixMap2,
|
|
168
|
+
interactiveTags: [
|
|
169
|
+
"button",
|
|
170
|
+
"input",
|
|
171
|
+
"select",
|
|
172
|
+
"textarea",
|
|
173
|
+
"el-button",
|
|
174
|
+
"el-input",
|
|
175
|
+
"el-input-number",
|
|
176
|
+
"el-select",
|
|
177
|
+
"el-textarea",
|
|
178
|
+
"el-checkbox",
|
|
179
|
+
"el-radio",
|
|
180
|
+
"el-switch"
|
|
181
|
+
],
|
|
182
|
+
tagPrefixPattern: /^el-/
|
|
183
|
+
};
|
|
184
|
+
|
|
118
185
|
// src/utils/testIdAnchorCounter.ts
|
|
119
186
|
var anchorCounterMap = /* @__PURE__ */ new Map();
|
|
120
187
|
function buildAnchorKey(anchorTestId, componentName, tagName) {
|
|
@@ -155,7 +222,8 @@ var popupCounters = {
|
|
|
155
222
|
datePicker: 0,
|
|
156
223
|
popconfirm: 0,
|
|
157
224
|
dropdown: 0,
|
|
158
|
-
tooltip: 0
|
|
225
|
+
tooltip: 0,
|
|
226
|
+
message: 0
|
|
159
227
|
};
|
|
160
228
|
function getNextPopupId(type) {
|
|
161
229
|
const key = type in popupCounters ? type : "modal";
|
|
@@ -182,32 +250,46 @@ function buildPopupTestId(type, tag, counterId) {
|
|
|
182
250
|
return `${config.runtimePagePrefix}${popupPrefix}${tag}_${counterId}`;
|
|
183
251
|
}
|
|
184
252
|
|
|
253
|
+
// src/adapters/types.ts
|
|
254
|
+
function mergeInteractiveTags(adapters) {
|
|
255
|
+
const set = /* @__PURE__ */ new Set();
|
|
256
|
+
for (const a of adapters) {
|
|
257
|
+
for (const tag of a.interactiveTags) {
|
|
258
|
+
set.add(tag);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return [...set];
|
|
262
|
+
}
|
|
263
|
+
function mergeTagPrefixPattern(adapters) {
|
|
264
|
+
const patterns = adapters.map((a) => a.tagPrefixPattern.source).filter(Boolean);
|
|
265
|
+
if (patterns.length === 0) return null;
|
|
266
|
+
return new RegExp(patterns.join("|"), "gi");
|
|
267
|
+
}
|
|
268
|
+
|
|
185
269
|
// src/utils/testIdObserver.ts
|
|
186
|
-
|
|
187
|
-
modal: [["-modal"]],
|
|
188
|
-
drawer: [["-drawer"]],
|
|
189
|
-
select: [["-select-dropdown"]],
|
|
190
|
-
datePicker: [
|
|
191
|
-
["-picker-dropdown"],
|
|
192
|
-
// Ant Design Vue 4.x (新)
|
|
193
|
-
["-calendar-picker-container"]
|
|
194
|
-
// Ant Design Vue 1.x (旧)
|
|
195
|
-
],
|
|
196
|
-
popconfirm: [["-popover", "-popconfirm"]],
|
|
197
|
-
dropdown: [["-dropdown"]],
|
|
198
|
-
tooltip: [["-tooltip"]]
|
|
199
|
-
};
|
|
200
|
-
function buildPopupClassMap(prefixes) {
|
|
270
|
+
function buildPopupClassMap(adapters) {
|
|
201
271
|
const result = {};
|
|
202
|
-
const
|
|
203
|
-
for (const
|
|
204
|
-
const
|
|
205
|
-
for (const
|
|
206
|
-
|
|
207
|
-
|
|
272
|
+
const seen = /* @__PURE__ */ new Map();
|
|
273
|
+
for (const adapter of adapters) {
|
|
274
|
+
const entries = Object.entries(adapter.popupClassSuffixMap);
|
|
275
|
+
for (const [type, suffixGroups] of entries) {
|
|
276
|
+
if (!seen.has(type)) {
|
|
277
|
+
seen.set(type, /* @__PURE__ */ new Set());
|
|
278
|
+
result[type] = [];
|
|
279
|
+
}
|
|
280
|
+
const typeSeen = seen.get(type);
|
|
281
|
+
const selectorSets = result[type];
|
|
282
|
+
for (const suffixGroup of suffixGroups) {
|
|
283
|
+
for (const prefix of adapter.cssPrefixes) {
|
|
284
|
+
const classCombo = suffixGroup.map((suffix) => `${prefix}${suffix}`);
|
|
285
|
+
const key = classCombo.join("|");
|
|
286
|
+
if (!typeSeen.has(key)) {
|
|
287
|
+
typeSeen.add(key);
|
|
288
|
+
selectorSets.push(classCombo);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
208
291
|
}
|
|
209
292
|
}
|
|
210
|
-
result[type] = selectorSets;
|
|
211
293
|
}
|
|
212
294
|
return result;
|
|
213
295
|
}
|
|
@@ -230,7 +312,9 @@ var TestIdObserver = class {
|
|
|
230
312
|
}
|
|
231
313
|
};
|
|
232
314
|
const config = getConfig();
|
|
233
|
-
this.popupClassMap = buildPopupClassMap(config.
|
|
315
|
+
this.popupClassMap = buildPopupClassMap(config.adapters);
|
|
316
|
+
this.interactiveTags = mergeInteractiveTags(config.adapters);
|
|
317
|
+
this.tagPrefixPattern = mergeTagPrefixPattern(config.adapters);
|
|
234
318
|
this.state = {
|
|
235
319
|
observer: null,
|
|
236
320
|
isRunning: false,
|
|
@@ -334,9 +418,9 @@ var TestIdObserver = class {
|
|
|
334
418
|
this.handlePopupNode(node, popupType);
|
|
335
419
|
return;
|
|
336
420
|
}
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
339
|
-
this.handlePopupChildNode(node,
|
|
421
|
+
const popupAncestor = this.detectPopupAncestor(node);
|
|
422
|
+
if (popupAncestor) {
|
|
423
|
+
this.handlePopupChildNode(node, popupAncestor.type, popupAncestor.element, config);
|
|
340
424
|
return;
|
|
341
425
|
}
|
|
342
426
|
if (this.isInsideApp(node)) {
|
|
@@ -431,7 +515,7 @@ var TestIdObserver = class {
|
|
|
431
515
|
* 因为 Ant Design Vue 4 可能在浮层根节点外包一层 wrapper DIV)。
|
|
432
516
|
* 找到浮层根节点后,继续向上验证其祖先链能到达 body (确保不在 #app 内)。
|
|
433
517
|
*
|
|
434
|
-
* @returns
|
|
518
|
+
* @returns 浮层类型 + 祖先元素,或 null (不在任何浮层内)
|
|
435
519
|
*/
|
|
436
520
|
detectPopupAncestor(node) {
|
|
437
521
|
let current = node.parentElement;
|
|
@@ -439,7 +523,7 @@ var TestIdObserver = class {
|
|
|
439
523
|
const type = this.matchPopupClass(current);
|
|
440
524
|
if (type) {
|
|
441
525
|
const reachesBody = this.reachesBody(current);
|
|
442
|
-
if (reachesBody) return type;
|
|
526
|
+
if (reachesBody) return { type, element: current };
|
|
443
527
|
}
|
|
444
528
|
if (current === document.body || current.id === "app") break;
|
|
445
529
|
current = current.parentElement;
|
|
@@ -498,15 +582,19 @@ var TestIdObserver = class {
|
|
|
498
582
|
/**
|
|
499
583
|
* 处理浮层内部子节点 (Modal/Drawer/Dropdown 内的按钮、输入框等)
|
|
500
584
|
*
|
|
585
|
+
* 每个浮层实例独立计数: 以浮层根节点 data-testid 作为隔离 key,
|
|
586
|
+
* 重复打开相同类型的浮层,子元素 ID 均从 0 重新开始。
|
|
587
|
+
*
|
|
501
588
|
* ID 格式: ${runtimePagePrefix}${popupPrefix}${tag}_${counter}
|
|
502
589
|
* 例: hall_dynamic_modal_button_0, hall_dynamic_select_div_2
|
|
503
590
|
*
|
|
504
591
|
* 浮层子元素均为运行时注入,统一使用 runtimePagePrefix 前缀。
|
|
505
592
|
*/
|
|
506
|
-
handlePopupChildNode(node, popupType, config) {
|
|
593
|
+
handlePopupChildNode(node, popupType, popupElement, config) {
|
|
507
594
|
if (config.onlyInteractive && !this.isInteractive(node)) return;
|
|
508
595
|
const tag = this.getSimpleTag(node);
|
|
509
|
-
const
|
|
596
|
+
const popupRootTestId = popupElement.getAttribute("data-testid") || `${popupType}_unknown`;
|
|
597
|
+
const key = `${popupRootTestId}_${tag}`;
|
|
510
598
|
const current = this.state.popupChildCounter.get(key) ?? 0;
|
|
511
599
|
this.state.popupChildCounter.set(key, current + 1);
|
|
512
600
|
const popupPrefix = config.popupPrefixMap[popupType] || `${popupType}_`;
|
|
@@ -527,34 +615,29 @@ var TestIdObserver = class {
|
|
|
527
615
|
/**
|
|
528
616
|
* 获取元素的简化标签名
|
|
529
617
|
*
|
|
530
|
-
*
|
|
618
|
+
* 去除所有适配器注册的 UI 库标签前缀:
|
|
619
|
+
* AntD: a-button → button, a-input → input
|
|
620
|
+
* Element: el-button → button, el-input → input
|
|
531
621
|
*/
|
|
532
622
|
getSimpleTag(node) {
|
|
533
|
-
|
|
623
|
+
let tag = node.tagName.toLowerCase();
|
|
624
|
+
if (this.tagPrefixPattern) {
|
|
625
|
+
tag = tag.replace(this.tagPrefixPattern, "");
|
|
626
|
+
}
|
|
627
|
+
return tag;
|
|
534
628
|
}
|
|
535
629
|
/**
|
|
536
630
|
* 判断是否可交互元素
|
|
537
631
|
*
|
|
538
632
|
* 可交互特征:
|
|
539
|
-
* -
|
|
633
|
+
* - 匹配任意适配器的交互标签 (含原生 + UI 库前缀)
|
|
540
634
|
* - onclick 属性
|
|
541
|
-
* - role="button" / role="checkbox" / role="radio"
|
|
542
|
-
* -
|
|
635
|
+
* - role="button" / role="checkbox" / role="radio" / role="switch"
|
|
636
|
+
* - tabindex 属性
|
|
543
637
|
*/
|
|
544
638
|
isInteractive(node) {
|
|
545
639
|
const tag = node.tagName.toLowerCase();
|
|
546
|
-
|
|
547
|
-
"button",
|
|
548
|
-
"a-button",
|
|
549
|
-
"input",
|
|
550
|
-
"a-input",
|
|
551
|
-
"a-input-number",
|
|
552
|
-
"select",
|
|
553
|
-
"a-select",
|
|
554
|
-
"textarea",
|
|
555
|
-
"a-textarea"
|
|
556
|
-
];
|
|
557
|
-
if (interactiveTags.includes(tag)) return true;
|
|
640
|
+
if (this.interactiveTags.includes(tag)) return true;
|
|
558
641
|
if (node.hasAttribute("onclick")) return true;
|
|
559
642
|
const role = node.getAttribute("role");
|
|
560
643
|
if (role === "button" || role === "checkbox" || role === "radio" || role === "switch") {
|
|
@@ -590,7 +673,8 @@ var GROUP_LABELS = {
|
|
|
590
673
|
datePicker: "[DatePicker \u6D6E\u5C42]",
|
|
591
674
|
popconfirm: "[Popconfirm \u6D6E\u5C42]",
|
|
592
675
|
dropdown: "[Dropdown \u6D6E\u5C42]",
|
|
593
|
-
tooltip: "[Tooltip \u6D6E\u5C42]"
|
|
676
|
+
tooltip: "[Tooltip \u6D6E\u5C42]",
|
|
677
|
+
message: "[Message \u6D6E\u5C42]"
|
|
594
678
|
};
|
|
595
679
|
var GROUP_SUGGESTIONS = {
|
|
596
680
|
custom: "\u4E1A\u52A1\u4EE3\u7801\u4E2D\u5B58\u5728\u624B\u5199\u56FA\u5B9A\u91CD\u590D data-testid\uFF0C\u8BF7\u68C0\u67E5\u76F8\u5173\u6A21\u677F\u4EE3\u7801",
|
|
@@ -602,7 +686,8 @@ var GROUP_SUGGESTIONS = {
|
|
|
602
686
|
datePicker: "DatePicker \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548",
|
|
603
687
|
popconfirm: "Popconfirm \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548",
|
|
604
688
|
dropdown: "Dropdown \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548",
|
|
605
|
-
tooltip: "Tooltip \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548"
|
|
689
|
+
tooltip: "Tooltip \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548",
|
|
690
|
+
message: "Message \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548"
|
|
606
691
|
};
|
|
607
692
|
var TestIdChecker = class {
|
|
608
693
|
/**
|
|
@@ -699,7 +784,8 @@ var TestIdChecker = class {
|
|
|
699
784
|
"datePicker",
|
|
700
785
|
"popconfirm",
|
|
701
786
|
"dropdown",
|
|
702
|
-
"tooltip"
|
|
787
|
+
"tooltip",
|
|
788
|
+
"message"
|
|
703
789
|
];
|
|
704
790
|
for (const type of popupTypes) {
|
|
705
791
|
if (prefix === `${type}_`) return type;
|
|
@@ -737,12 +823,13 @@ var TestIdChecker = class {
|
|
|
737
823
|
};
|
|
738
824
|
// Annotate the CommonJS export names for ESM import in node:
|
|
739
825
|
0 && (module.exports = {
|
|
740
|
-
INTERACTIVE_TAGS,
|
|
741
826
|
TestIdChecker,
|
|
742
827
|
TestIdObserver,
|
|
828
|
+
antdAdapter,
|
|
743
829
|
buildAnchorTestId,
|
|
744
830
|
buildPopupTestId,
|
|
745
831
|
defaultConfig,
|
|
832
|
+
elementAdapter,
|
|
746
833
|
getAnchorCounterMap,
|
|
747
834
|
getConfig,
|
|
748
835
|
getNextAnchorLocalIndex,
|
package/dist/index.mjs
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
|
+
// src/adapters/antd.ts
|
|
2
|
+
var popupClassSuffixMap = {
|
|
3
|
+
modal: [["-modal"]],
|
|
4
|
+
drawer: [["-drawer"]],
|
|
5
|
+
select: [["-select-dropdown"]],
|
|
6
|
+
datePicker: [
|
|
7
|
+
["-picker-dropdown"],
|
|
8
|
+
// Ant Design Vue 4.x
|
|
9
|
+
["-calendar-picker-container"]
|
|
10
|
+
// Ant Design Vue 1.x
|
|
11
|
+
],
|
|
12
|
+
popconfirm: [["-popover", "-popconfirm"]],
|
|
13
|
+
dropdown: [["-dropdown"]],
|
|
14
|
+
tooltip: [["-tooltip"]],
|
|
15
|
+
message: [["-message"]]
|
|
16
|
+
};
|
|
17
|
+
var antdAdapter = {
|
|
18
|
+
name: "ant-design-vue",
|
|
19
|
+
cssPrefixes: ["ant"],
|
|
20
|
+
popupClassSuffixMap,
|
|
21
|
+
interactiveTags: [
|
|
22
|
+
"button",
|
|
23
|
+
"input",
|
|
24
|
+
"select",
|
|
25
|
+
"textarea",
|
|
26
|
+
"a-button",
|
|
27
|
+
"a-input",
|
|
28
|
+
"a-input-number",
|
|
29
|
+
"a-select",
|
|
30
|
+
"a-textarea",
|
|
31
|
+
"a-checkbox",
|
|
32
|
+
"a-radio",
|
|
33
|
+
"a-switch"
|
|
34
|
+
],
|
|
35
|
+
tagPrefixPattern: /^a-/
|
|
36
|
+
};
|
|
37
|
+
|
|
1
38
|
// src/config/testMark.ts
|
|
2
|
-
var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
|
|
3
|
-
"button",
|
|
4
|
-
"a-button",
|
|
5
|
-
"input",
|
|
6
|
-
"a-input",
|
|
7
|
-
"a-input-number",
|
|
8
|
-
"select",
|
|
9
|
-
"a-select",
|
|
10
|
-
"textarea",
|
|
11
|
-
"a-textarea",
|
|
12
|
-
"a-checkbox",
|
|
13
|
-
"a-radio",
|
|
14
|
-
"a-switch"
|
|
15
|
-
]);
|
|
16
39
|
var defaultConfig = {
|
|
17
40
|
enable: true,
|
|
18
41
|
globalPrefix: "",
|
|
19
42
|
compilePrefix: "static_",
|
|
20
43
|
runtimePagePrefix: "dynamic_",
|
|
21
|
-
|
|
44
|
+
adapters: [antdAdapter],
|
|
22
45
|
popupPrefixMap: {
|
|
23
46
|
modal: "modal_",
|
|
24
47
|
drawer: "drawer_",
|
|
@@ -26,7 +49,8 @@ var defaultConfig = {
|
|
|
26
49
|
datePicker: "datePicker_",
|
|
27
50
|
popconfirm: "popconfirm_",
|
|
28
51
|
dropdown: "dropdown_",
|
|
29
|
-
tooltip: "tooltip_"
|
|
52
|
+
tooltip: "tooltip_",
|
|
53
|
+
message: "message_"
|
|
30
54
|
},
|
|
31
55
|
ignoreTags: ["script", "style", "svg", "br", "iframe"],
|
|
32
56
|
ignoreClass: ["no-test-mark", "hidden"],
|
|
@@ -73,6 +97,48 @@ function getConfig() {
|
|
|
73
97
|
return globalConfig;
|
|
74
98
|
}
|
|
75
99
|
|
|
100
|
+
// src/adapters/element.ts
|
|
101
|
+
var popupClassSuffixMap2 = {
|
|
102
|
+
modal: [["-dialog"]],
|
|
103
|
+
drawer: [["-drawer"]],
|
|
104
|
+
select: [["-select-dropdown"]],
|
|
105
|
+
datePicker: [
|
|
106
|
+
["-picker-panel"],
|
|
107
|
+
// 日期/时间选择器面板
|
|
108
|
+
["-date-range-picker__content"]
|
|
109
|
+
// 日期范围选择器内容区
|
|
110
|
+
],
|
|
111
|
+
popconfirm: [
|
|
112
|
+
["-message-box"],
|
|
113
|
+
// Element MessageBox
|
|
114
|
+
["-popconfirm"]
|
|
115
|
+
// Element Popconfirm
|
|
116
|
+
],
|
|
117
|
+
dropdown: [["-dropdown-menu"]],
|
|
118
|
+
tooltip: [["-tooltip__popper"]],
|
|
119
|
+
message: [["-message"]]
|
|
120
|
+
};
|
|
121
|
+
var elementAdapter = {
|
|
122
|
+
name: "element-ui",
|
|
123
|
+
cssPrefixes: ["el"],
|
|
124
|
+
popupClassSuffixMap: popupClassSuffixMap2,
|
|
125
|
+
interactiveTags: [
|
|
126
|
+
"button",
|
|
127
|
+
"input",
|
|
128
|
+
"select",
|
|
129
|
+
"textarea",
|
|
130
|
+
"el-button",
|
|
131
|
+
"el-input",
|
|
132
|
+
"el-input-number",
|
|
133
|
+
"el-select",
|
|
134
|
+
"el-textarea",
|
|
135
|
+
"el-checkbox",
|
|
136
|
+
"el-radio",
|
|
137
|
+
"el-switch"
|
|
138
|
+
],
|
|
139
|
+
tagPrefixPattern: /^el-/
|
|
140
|
+
};
|
|
141
|
+
|
|
76
142
|
// src/utils/testIdAnchorCounter.ts
|
|
77
143
|
var anchorCounterMap = /* @__PURE__ */ new Map();
|
|
78
144
|
function buildAnchorKey(anchorTestId, componentName, tagName) {
|
|
@@ -113,7 +179,8 @@ var popupCounters = {
|
|
|
113
179
|
datePicker: 0,
|
|
114
180
|
popconfirm: 0,
|
|
115
181
|
dropdown: 0,
|
|
116
|
-
tooltip: 0
|
|
182
|
+
tooltip: 0,
|
|
183
|
+
message: 0
|
|
117
184
|
};
|
|
118
185
|
function getNextPopupId(type) {
|
|
119
186
|
const key = type in popupCounters ? type : "modal";
|
|
@@ -140,32 +207,46 @@ function buildPopupTestId(type, tag, counterId) {
|
|
|
140
207
|
return `${config.runtimePagePrefix}${popupPrefix}${tag}_${counterId}`;
|
|
141
208
|
}
|
|
142
209
|
|
|
210
|
+
// src/adapters/types.ts
|
|
211
|
+
function mergeInteractiveTags(adapters) {
|
|
212
|
+
const set = /* @__PURE__ */ new Set();
|
|
213
|
+
for (const a of adapters) {
|
|
214
|
+
for (const tag of a.interactiveTags) {
|
|
215
|
+
set.add(tag);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return [...set];
|
|
219
|
+
}
|
|
220
|
+
function mergeTagPrefixPattern(adapters) {
|
|
221
|
+
const patterns = adapters.map((a) => a.tagPrefixPattern.source).filter(Boolean);
|
|
222
|
+
if (patterns.length === 0) return null;
|
|
223
|
+
return new RegExp(patterns.join("|"), "gi");
|
|
224
|
+
}
|
|
225
|
+
|
|
143
226
|
// src/utils/testIdObserver.ts
|
|
144
|
-
|
|
145
|
-
modal: [["-modal"]],
|
|
146
|
-
drawer: [["-drawer"]],
|
|
147
|
-
select: [["-select-dropdown"]],
|
|
148
|
-
datePicker: [
|
|
149
|
-
["-picker-dropdown"],
|
|
150
|
-
// Ant Design Vue 4.x (新)
|
|
151
|
-
["-calendar-picker-container"]
|
|
152
|
-
// Ant Design Vue 1.x (旧)
|
|
153
|
-
],
|
|
154
|
-
popconfirm: [["-popover", "-popconfirm"]],
|
|
155
|
-
dropdown: [["-dropdown"]],
|
|
156
|
-
tooltip: [["-tooltip"]]
|
|
157
|
-
};
|
|
158
|
-
function buildPopupClassMap(prefixes) {
|
|
227
|
+
function buildPopupClassMap(adapters) {
|
|
159
228
|
const result = {};
|
|
160
|
-
const
|
|
161
|
-
for (const
|
|
162
|
-
const
|
|
163
|
-
for (const
|
|
164
|
-
|
|
165
|
-
|
|
229
|
+
const seen = /* @__PURE__ */ new Map();
|
|
230
|
+
for (const adapter of adapters) {
|
|
231
|
+
const entries = Object.entries(adapter.popupClassSuffixMap);
|
|
232
|
+
for (const [type, suffixGroups] of entries) {
|
|
233
|
+
if (!seen.has(type)) {
|
|
234
|
+
seen.set(type, /* @__PURE__ */ new Set());
|
|
235
|
+
result[type] = [];
|
|
236
|
+
}
|
|
237
|
+
const typeSeen = seen.get(type);
|
|
238
|
+
const selectorSets = result[type];
|
|
239
|
+
for (const suffixGroup of suffixGroups) {
|
|
240
|
+
for (const prefix of adapter.cssPrefixes) {
|
|
241
|
+
const classCombo = suffixGroup.map((suffix) => `${prefix}${suffix}`);
|
|
242
|
+
const key = classCombo.join("|");
|
|
243
|
+
if (!typeSeen.has(key)) {
|
|
244
|
+
typeSeen.add(key);
|
|
245
|
+
selectorSets.push(classCombo);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
166
248
|
}
|
|
167
249
|
}
|
|
168
|
-
result[type] = selectorSets;
|
|
169
250
|
}
|
|
170
251
|
return result;
|
|
171
252
|
}
|
|
@@ -188,7 +269,9 @@ var TestIdObserver = class {
|
|
|
188
269
|
}
|
|
189
270
|
};
|
|
190
271
|
const config = getConfig();
|
|
191
|
-
this.popupClassMap = buildPopupClassMap(config.
|
|
272
|
+
this.popupClassMap = buildPopupClassMap(config.adapters);
|
|
273
|
+
this.interactiveTags = mergeInteractiveTags(config.adapters);
|
|
274
|
+
this.tagPrefixPattern = mergeTagPrefixPattern(config.adapters);
|
|
192
275
|
this.state = {
|
|
193
276
|
observer: null,
|
|
194
277
|
isRunning: false,
|
|
@@ -292,9 +375,9 @@ var TestIdObserver = class {
|
|
|
292
375
|
this.handlePopupNode(node, popupType);
|
|
293
376
|
return;
|
|
294
377
|
}
|
|
295
|
-
const
|
|
296
|
-
if (
|
|
297
|
-
this.handlePopupChildNode(node,
|
|
378
|
+
const popupAncestor = this.detectPopupAncestor(node);
|
|
379
|
+
if (popupAncestor) {
|
|
380
|
+
this.handlePopupChildNode(node, popupAncestor.type, popupAncestor.element, config);
|
|
298
381
|
return;
|
|
299
382
|
}
|
|
300
383
|
if (this.isInsideApp(node)) {
|
|
@@ -389,7 +472,7 @@ var TestIdObserver = class {
|
|
|
389
472
|
* 因为 Ant Design Vue 4 可能在浮层根节点外包一层 wrapper DIV)。
|
|
390
473
|
* 找到浮层根节点后,继续向上验证其祖先链能到达 body (确保不在 #app 内)。
|
|
391
474
|
*
|
|
392
|
-
* @returns
|
|
475
|
+
* @returns 浮层类型 + 祖先元素,或 null (不在任何浮层内)
|
|
393
476
|
*/
|
|
394
477
|
detectPopupAncestor(node) {
|
|
395
478
|
let current = node.parentElement;
|
|
@@ -397,7 +480,7 @@ var TestIdObserver = class {
|
|
|
397
480
|
const type = this.matchPopupClass(current);
|
|
398
481
|
if (type) {
|
|
399
482
|
const reachesBody = this.reachesBody(current);
|
|
400
|
-
if (reachesBody) return type;
|
|
483
|
+
if (reachesBody) return { type, element: current };
|
|
401
484
|
}
|
|
402
485
|
if (current === document.body || current.id === "app") break;
|
|
403
486
|
current = current.parentElement;
|
|
@@ -456,15 +539,19 @@ var TestIdObserver = class {
|
|
|
456
539
|
/**
|
|
457
540
|
* 处理浮层内部子节点 (Modal/Drawer/Dropdown 内的按钮、输入框等)
|
|
458
541
|
*
|
|
542
|
+
* 每个浮层实例独立计数: 以浮层根节点 data-testid 作为隔离 key,
|
|
543
|
+
* 重复打开相同类型的浮层,子元素 ID 均从 0 重新开始。
|
|
544
|
+
*
|
|
459
545
|
* ID 格式: ${runtimePagePrefix}${popupPrefix}${tag}_${counter}
|
|
460
546
|
* 例: hall_dynamic_modal_button_0, hall_dynamic_select_div_2
|
|
461
547
|
*
|
|
462
548
|
* 浮层子元素均为运行时注入,统一使用 runtimePagePrefix 前缀。
|
|
463
549
|
*/
|
|
464
|
-
handlePopupChildNode(node, popupType, config) {
|
|
550
|
+
handlePopupChildNode(node, popupType, popupElement, config) {
|
|
465
551
|
if (config.onlyInteractive && !this.isInteractive(node)) return;
|
|
466
552
|
const tag = this.getSimpleTag(node);
|
|
467
|
-
const
|
|
553
|
+
const popupRootTestId = popupElement.getAttribute("data-testid") || `${popupType}_unknown`;
|
|
554
|
+
const key = `${popupRootTestId}_${tag}`;
|
|
468
555
|
const current = this.state.popupChildCounter.get(key) ?? 0;
|
|
469
556
|
this.state.popupChildCounter.set(key, current + 1);
|
|
470
557
|
const popupPrefix = config.popupPrefixMap[popupType] || `${popupType}_`;
|
|
@@ -485,34 +572,29 @@ var TestIdObserver = class {
|
|
|
485
572
|
/**
|
|
486
573
|
* 获取元素的简化标签名
|
|
487
574
|
*
|
|
488
|
-
*
|
|
575
|
+
* 去除所有适配器注册的 UI 库标签前缀:
|
|
576
|
+
* AntD: a-button → button, a-input → input
|
|
577
|
+
* Element: el-button → button, el-input → input
|
|
489
578
|
*/
|
|
490
579
|
getSimpleTag(node) {
|
|
491
|
-
|
|
580
|
+
let tag = node.tagName.toLowerCase();
|
|
581
|
+
if (this.tagPrefixPattern) {
|
|
582
|
+
tag = tag.replace(this.tagPrefixPattern, "");
|
|
583
|
+
}
|
|
584
|
+
return tag;
|
|
492
585
|
}
|
|
493
586
|
/**
|
|
494
587
|
* 判断是否可交互元素
|
|
495
588
|
*
|
|
496
589
|
* 可交互特征:
|
|
497
|
-
* -
|
|
590
|
+
* - 匹配任意适配器的交互标签 (含原生 + UI 库前缀)
|
|
498
591
|
* - onclick 属性
|
|
499
|
-
* - role="button" / role="checkbox" / role="radio"
|
|
500
|
-
* -
|
|
592
|
+
* - role="button" / role="checkbox" / role="radio" / role="switch"
|
|
593
|
+
* - tabindex 属性
|
|
501
594
|
*/
|
|
502
595
|
isInteractive(node) {
|
|
503
596
|
const tag = node.tagName.toLowerCase();
|
|
504
|
-
|
|
505
|
-
"button",
|
|
506
|
-
"a-button",
|
|
507
|
-
"input",
|
|
508
|
-
"a-input",
|
|
509
|
-
"a-input-number",
|
|
510
|
-
"select",
|
|
511
|
-
"a-select",
|
|
512
|
-
"textarea",
|
|
513
|
-
"a-textarea"
|
|
514
|
-
];
|
|
515
|
-
if (interactiveTags.includes(tag)) return true;
|
|
597
|
+
if (this.interactiveTags.includes(tag)) return true;
|
|
516
598
|
if (node.hasAttribute("onclick")) return true;
|
|
517
599
|
const role = node.getAttribute("role");
|
|
518
600
|
if (role === "button" || role === "checkbox" || role === "radio" || role === "switch") {
|
|
@@ -548,7 +630,8 @@ var GROUP_LABELS = {
|
|
|
548
630
|
datePicker: "[DatePicker \u6D6E\u5C42]",
|
|
549
631
|
popconfirm: "[Popconfirm \u6D6E\u5C42]",
|
|
550
632
|
dropdown: "[Dropdown \u6D6E\u5C42]",
|
|
551
|
-
tooltip: "[Tooltip \u6D6E\u5C42]"
|
|
633
|
+
tooltip: "[Tooltip \u6D6E\u5C42]",
|
|
634
|
+
message: "[Message \u6D6E\u5C42]"
|
|
552
635
|
};
|
|
553
636
|
var GROUP_SUGGESTIONS = {
|
|
554
637
|
custom: "\u4E1A\u52A1\u4EE3\u7801\u4E2D\u5B58\u5728\u624B\u5199\u56FA\u5B9A\u91CD\u590D data-testid\uFF0C\u8BF7\u68C0\u67E5\u76F8\u5173\u6A21\u677F\u4EE3\u7801",
|
|
@@ -560,7 +643,8 @@ var GROUP_SUGGESTIONS = {
|
|
|
560
643
|
datePicker: "DatePicker \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548",
|
|
561
644
|
popconfirm: "Popconfirm \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548",
|
|
562
645
|
dropdown: "Dropdown \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548",
|
|
563
|
-
tooltip: "Tooltip \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548"
|
|
646
|
+
tooltip: "Tooltip \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548",
|
|
647
|
+
message: "Message \u72EC\u7ACB\u8BA1\u6570\u5668\u81EA\u589E\u903B\u8F91\u5931\u6548"
|
|
564
648
|
};
|
|
565
649
|
var TestIdChecker = class {
|
|
566
650
|
/**
|
|
@@ -657,7 +741,8 @@ var TestIdChecker = class {
|
|
|
657
741
|
"datePicker",
|
|
658
742
|
"popconfirm",
|
|
659
743
|
"dropdown",
|
|
660
|
-
"tooltip"
|
|
744
|
+
"tooltip",
|
|
745
|
+
"message"
|
|
661
746
|
];
|
|
662
747
|
for (const type of popupTypes) {
|
|
663
748
|
if (prefix === `${type}_`) return type;
|
|
@@ -694,12 +779,13 @@ var TestIdChecker = class {
|
|
|
694
779
|
}
|
|
695
780
|
};
|
|
696
781
|
export {
|
|
697
|
-
INTERACTIVE_TAGS,
|
|
698
782
|
TestIdChecker,
|
|
699
783
|
TestIdObserver,
|
|
784
|
+
antdAdapter,
|
|
700
785
|
buildAnchorTestId,
|
|
701
786
|
buildPopupTestId,
|
|
702
787
|
defaultConfig,
|
|
788
|
+
elementAdapter,
|
|
703
789
|
getAnchorCounterMap,
|
|
704
790
|
getConfig,
|
|
705
791
|
getNextAnchorLocalIndex,
|