@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 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
- /** Antd 浮层组件专属前缀映射 */
78
+ /** 浮层组件专属前缀映射 */
32
79
  popupPrefixMap: Record<PopupType, string>;
33
80
  /**
34
- * Ant Design Vue CSS 类名前缀 (对应 ConfigProvider 的 prefixCls)
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
- * 默认: ['ant'] → 匹配 .ant-modal, .ant-picker-dropdown 等
38
- * 若项目使用 <a-config-provider prefixCls="my-ui"> 则设为 ['my-ui']
39
- * 混合使用: ['ant', 'my-ui'] → 同时匹配两个体系的所有浮层组件
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
- antdClassPrefix: string[];
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 匹配映射 (根据 antdClassPrefix 动态构建) */
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 浮层类型或 null (不在任何浮层内)
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
- * 处理 Antd 组件前缀: a-button → button, a-input → input
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
- * - 交互类标签: button, input, select, textarea
427
+ * - 匹配任意适配器的交互标签 (含原生 + UI 库前缀)
338
428
  * - onclick 属性
339
- * - role="button" / role="checkbox" / role="radio"
340
- * - cursor:pointer (不检测,因为可能从 CSS 继承,误判率高)
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 { INTERACTIVE_TAGS, type ParsedBaseKey, type PopupType, TestIdChecker, type TestIdMarkConfig, TestIdObserver, buildAnchorTestId, buildPopupTestId, defaultConfig, getAnchorCounterMap, getConfig, getNextAnchorLocalIndex, getNextPopupId, getPopupCounterSnapshot, initConfig, mergeConfig, parseBaseKey, resetAllAnchorCounters, resetAllPopupCounters, resetPopupCounter };
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
- /** Antd 浮层组件专属前缀映射 */
78
+ /** 浮层组件专属前缀映射 */
32
79
  popupPrefixMap: Record<PopupType, string>;
33
80
  /**
34
- * Ant Design Vue CSS 类名前缀 (对应 ConfigProvider 的 prefixCls)
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
- * 默认: ['ant'] → 匹配 .ant-modal, .ant-picker-dropdown 等
38
- * 若项目使用 <a-config-provider prefixCls="my-ui"> 则设为 ['my-ui']
39
- * 混合使用: ['ant', 'my-ui'] → 同时匹配两个体系的所有浮层组件
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
- antdClassPrefix: string[];
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 匹配映射 (根据 antdClassPrefix 动态构建) */
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 浮层类型或 null (不在任何浮层内)
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
- * 处理 Antd 组件前缀: a-button → button, a-input → input
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
- * - 交互类标签: button, input, select, textarea
427
+ * - 匹配任意适配器的交互标签 (含原生 + UI 库前缀)
338
428
  * - onclick 属性
339
- * - role="button" / role="checkbox" / role="radio"
340
- * - cursor:pointer (不检测,因为可能从 CSS 继承,误判率高)
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 { INTERACTIVE_TAGS, type ParsedBaseKey, type PopupType, TestIdChecker, type TestIdMarkConfig, TestIdObserver, buildAnchorTestId, buildPopupTestId, defaultConfig, getAnchorCounterMap, getConfig, getNextAnchorLocalIndex, getNextPopupId, getPopupCounterSnapshot, initConfig, mergeConfig, parseBaseKey, resetAllAnchorCounters, resetAllPopupCounters, resetPopupCounter };
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
- antdClassPrefix: ["ant"],
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
- var POPUP_CLASS_SUFFIX_MAP = {
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 entries = Object.entries(POPUP_CLASS_SUFFIX_MAP);
203
- for (const [type, suffixGroups] of entries) {
204
- const selectorSets = [];
205
- for (const suffixGroup of suffixGroups) {
206
- for (const prefix of prefixes) {
207
- selectorSets.push(suffixGroup.map((suffix) => `${prefix}${suffix}`));
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.antdClassPrefix);
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 popupAncestorType = this.detectPopupAncestor(node);
338
- if (popupAncestorType) {
339
- this.handlePopupChildNode(node, popupAncestorType, config);
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 浮层类型或 null (不在任何浮层内)
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 key = `${popupType}_${tag}`;
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
- * 处理 Antd 组件前缀: a-button → button, a-input → input
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
- return node.tagName.toLowerCase().replace(/^a-/, "");
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
- * - 交互类标签: button, input, select, textarea
633
+ * - 匹配任意适配器的交互标签 (含原生 + UI 库前缀)
540
634
  * - onclick 属性
541
- * - role="button" / role="checkbox" / role="radio"
542
- * - cursor:pointer (不检测,因为可能从 CSS 继承,误判率高)
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
- const interactiveTags = [
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
- antdClassPrefix: ["ant"],
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
- var POPUP_CLASS_SUFFIX_MAP = {
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 entries = Object.entries(POPUP_CLASS_SUFFIX_MAP);
161
- for (const [type, suffixGroups] of entries) {
162
- const selectorSets = [];
163
- for (const suffixGroup of suffixGroups) {
164
- for (const prefix of prefixes) {
165
- selectorSets.push(suffixGroup.map((suffix) => `${prefix}${suffix}`));
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.antdClassPrefix);
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 popupAncestorType = this.detectPopupAncestor(node);
296
- if (popupAncestorType) {
297
- this.handlePopupChildNode(node, popupAncestorType, config);
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 浮层类型或 null (不在任何浮层内)
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 key = `${popupType}_${tag}`;
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
- * 处理 Antd 组件前缀: a-button → button, a-input → input
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
- return node.tagName.toLowerCase().replace(/^a-/, "");
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
- * - 交互类标签: button, input, select, textarea
590
+ * - 匹配任意适配器的交互标签 (含原生 + UI 库前缀)
498
591
  * - onclick 属性
499
- * - role="button" / role="checkbox" / role="radio"
500
- * - cursor:pointer (不检测,因为可能从 CSS 继承,误判率高)
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
- const interactiveTags = [
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testid/antd-testid-runtime",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "运行时兜底打标模块 — MutationObserver + 锚点计数器 + 浮层计数器 + ID 重复检测",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",