@testid/antd-testid-runtime 1.0.0
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 +343 -0
- package/dist/index.d.ts +343 -0
- package/dist/index.js +656 -0
- package/dist/index.mjs +613 -0
- package/package.json +27 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局配置模块 — testMark.ts
|
|
3
|
+
*
|
|
4
|
+
* 管理所有打标相关的配置项:
|
|
5
|
+
* - 前缀 (compile / runtime / popup)
|
|
6
|
+
* - 黑白名单
|
|
7
|
+
* - 开关
|
|
8
|
+
* - 浮层前缀映射
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 支持的浮层类型
|
|
12
|
+
*/
|
|
13
|
+
type PopupType = 'modal' | 'drawer' | 'select' | 'datePicker' | 'popconfirm' | 'dropdown' | 'tooltip';
|
|
14
|
+
/**
|
|
15
|
+
* 全量配置接口
|
|
16
|
+
*/
|
|
17
|
+
interface TestIdMarkConfig {
|
|
18
|
+
/** 总开关,生产环境强制为 false */
|
|
19
|
+
enable: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 全局前缀,拼接到所有 testid 前缀之前
|
|
22
|
+
* 例: globalPrefix = "hall" → compilePrefix 变为 hall_static_,runtimePagePrefix 变为 hall_dynamic_
|
|
23
|
+
* 设值后会自动应用到 compilePrefix、runtimePagePrefix 和 popupPrefixMap
|
|
24
|
+
* 默认: 空 (不添加)
|
|
25
|
+
*/
|
|
26
|
+
globalPrefix: string;
|
|
27
|
+
/** 编译期静态节点统一前缀 (默认 "static_") */
|
|
28
|
+
compilePrefix: string;
|
|
29
|
+
/** 页面内动态节点统一前缀 (默认 "dynamic_") */
|
|
30
|
+
runtimePagePrefix: string;
|
|
31
|
+
/** Antd 浮层组件专属前缀映射 */
|
|
32
|
+
popupPrefixMap: Record<PopupType, string>;
|
|
33
|
+
/** 忽略不打标的 HTML 标签名 */
|
|
34
|
+
ignoreTags: string[];
|
|
35
|
+
/** 包含此 class 的 DOM 跳过打标 */
|
|
36
|
+
ignoreClass: string[];
|
|
37
|
+
/** 是否仅给可交互控件打标 */
|
|
38
|
+
onlyInteractive: boolean;
|
|
39
|
+
/** 路由切换时是否清空锚点局部计数器 */
|
|
40
|
+
resetInstanceOnRouteChange: boolean;
|
|
41
|
+
/** 路由切换时是否重置全部浮层计数器 */
|
|
42
|
+
resetPopupCounterOnRouteChange: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 交互控件白名单 (onlyInteractive === true 时生效)
|
|
46
|
+
*/
|
|
47
|
+
declare const INTERACTIVE_TAGS: Set<string>;
|
|
48
|
+
/**
|
|
49
|
+
* 默认配置对象 (enable 默认为 true,由调用方传入 DEV 判断)
|
|
50
|
+
*/
|
|
51
|
+
declare const defaultConfig: TestIdMarkConfig;
|
|
52
|
+
/**
|
|
53
|
+
* 合并用户自定义配置与默认配置
|
|
54
|
+
* 采用浅合并策略,popupPrefixMap 支持部分覆盖
|
|
55
|
+
*
|
|
56
|
+
* 关键: 若设置了 globalPrefix,自动拼接到所有前缀字段
|
|
57
|
+
*/
|
|
58
|
+
declare function mergeConfig(userConfig?: Partial<TestIdMarkConfig>): TestIdMarkConfig;
|
|
59
|
+
/**
|
|
60
|
+
* 初始化全局配置 (应用启动时调用)
|
|
61
|
+
*/
|
|
62
|
+
declare function initConfig(custom?: Partial<TestIdMarkConfig>): void;
|
|
63
|
+
/**
|
|
64
|
+
* 获取当前全局配置 (只读)
|
|
65
|
+
*/
|
|
66
|
+
declare function getConfig(): Readonly<TestIdMarkConfig>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 锚点局部计数器 — testIdAnchorCounter.ts
|
|
70
|
+
*
|
|
71
|
+
* 解决公共组件多实例 ID 重复问题。
|
|
72
|
+
*
|
|
73
|
+
* 核心思路:
|
|
74
|
+
* 不依赖全局挂载顺序(会因异步渲染而变化),
|
|
75
|
+
* 而是依赖"父模板结构"来定位 ——
|
|
76
|
+
* 向上查找最近带 data-testid 的祖先元素作为"锚点",
|
|
77
|
+
* 在锚点下按 (组件名, 标签名) 维度维护局部计数器。
|
|
78
|
+
*
|
|
79
|
+
* 数据结构:
|
|
80
|
+
* Map<`${anchorTestId}__${componentName}__${tagName}`, counter>
|
|
81
|
+
*
|
|
82
|
+
* 示例:
|
|
83
|
+
* "static_page_dashboard_tag_div_3__BaseSearch__button" → 0
|
|
84
|
+
* "static_page_dashboard_tag_div_3__BaseSearch__button" → 1 (第 2 次出现)
|
|
85
|
+
* "static_page_dashboard_tag_div_7__BaseSearch__button" → 0 (不同锚点, 独立计数)
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* 获取锚点下某组件某标签的下一个局部索引 (自增)
|
|
89
|
+
*
|
|
90
|
+
* @param anchorTestId - 锚点 testid (findAnchor 返回)
|
|
91
|
+
* @param componentName - 组件名称 (从 base-key 解析)
|
|
92
|
+
* @param tagName - 标签名 (从 base-key 解析)
|
|
93
|
+
* @returns 局部索引 (从 0 开始)
|
|
94
|
+
*/
|
|
95
|
+
declare function getNextAnchorLocalIndex(anchorTestId: string, componentName: string, tagName: string): number;
|
|
96
|
+
/**
|
|
97
|
+
* 重置所有锚点计数器 (路由切换时调用)
|
|
98
|
+
*/
|
|
99
|
+
declare function resetAllAnchorCounters(): void;
|
|
100
|
+
/**
|
|
101
|
+
* 获取当前锚点计数器映射(仅用于调试)
|
|
102
|
+
*/
|
|
103
|
+
declare function getAnchorCounterMap(): ReadonlyMap<string, number>;
|
|
104
|
+
/**
|
|
105
|
+
* base-key 解析结果
|
|
106
|
+
*/
|
|
107
|
+
interface ParsedBaseKey {
|
|
108
|
+
componentName: string;
|
|
109
|
+
tagName: string;
|
|
110
|
+
templateIndex: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 从 base-key 中解析组件名和标签名
|
|
114
|
+
*
|
|
115
|
+
* base-key 格式: common_comp_{componentName}_tag_{tagName}_{templateIndex}
|
|
116
|
+
*
|
|
117
|
+
* @param baseKey - base-key 字符串
|
|
118
|
+
* @returns 解析结果或 null (格式不匹配)
|
|
119
|
+
*/
|
|
120
|
+
declare function parseBaseKey(baseKey: string): ParsedBaseKey | null;
|
|
121
|
+
/**
|
|
122
|
+
* 拼接最终 testid
|
|
123
|
+
*
|
|
124
|
+
* 格式: ${anchorTestId}__${componentName}_${tagName}_${localIndex}
|
|
125
|
+
*
|
|
126
|
+
* 示例:
|
|
127
|
+
* anchorTestId = "static_page_dashboard_tag_div_3"
|
|
128
|
+
* componentName = "BaseSearch"
|
|
129
|
+
* tagName = "button"
|
|
130
|
+
* localIndex = 0
|
|
131
|
+
* 返回: "static_page_dashboard_tag_div_3__BaseSearch_button_0"
|
|
132
|
+
*
|
|
133
|
+
* @param anchorTestId - 锚点 testid
|
|
134
|
+
* @param componentName - 组件名
|
|
135
|
+
* @param tagName - 标签名
|
|
136
|
+
* @param localIndex - 局部索引 (从 getNextAnchorLocalIndex 获取)
|
|
137
|
+
*/
|
|
138
|
+
declare function buildAnchorTestId(anchorTestId: string, componentName: string, tagName: string, localIndex: number): string;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 浮层独立计数器 — testIdPopupCounter.ts
|
|
142
|
+
*
|
|
143
|
+
* 每种浮层类型维护独立的自增计数器,类型之间互不干扰。
|
|
144
|
+
*
|
|
145
|
+
* 这意味着:
|
|
146
|
+
* - Modal 第 1 次出现: modal_button_0
|
|
147
|
+
* - Select 第 1 次出现: select_div_0
|
|
148
|
+
* - Modal 第 2 次出现: modal_button_1
|
|
149
|
+
* - Select 第 2 次出现: select_div_1
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 获取某类型浮层的下一个 ID (自增)
|
|
154
|
+
*
|
|
155
|
+
* @param type - 浮层类型
|
|
156
|
+
* @returns 计数器值 (使用前,从 0 开始)
|
|
157
|
+
*/
|
|
158
|
+
declare function getNextPopupId(type: PopupType): number;
|
|
159
|
+
/**
|
|
160
|
+
* 重置所有浮层计数器
|
|
161
|
+
*/
|
|
162
|
+
declare function resetAllPopupCounters(): void;
|
|
163
|
+
/**
|
|
164
|
+
* 重置指定类型浮层计数器
|
|
165
|
+
*/
|
|
166
|
+
declare function resetPopupCounter(type: PopupType): void;
|
|
167
|
+
/**
|
|
168
|
+
* 获取所有计数器快照 (调试用)
|
|
169
|
+
*/
|
|
170
|
+
declare function getPopupCounterSnapshot(): Record<PopupType, number>;
|
|
171
|
+
/**
|
|
172
|
+
* 生成浮层节点 testid
|
|
173
|
+
*
|
|
174
|
+
* 格式: ${prefix}${tag}_${counterId}
|
|
175
|
+
*
|
|
176
|
+
* 例: modal_button_0, select_div_2
|
|
177
|
+
*
|
|
178
|
+
* @param type - 浮层类型
|
|
179
|
+
* @param tag - HTML 标签名 (简化后)
|
|
180
|
+
* @param counterId - 计数器 ID (从 getNextPopupId 获取)
|
|
181
|
+
*/
|
|
182
|
+
declare function buildPopupTestId(type: PopupType, tag: string, counterId: number): string;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* MutationObserver DOM 监听与自动打标器 — testIdObserver.ts
|
|
186
|
+
*
|
|
187
|
+
* 运行时核心模块。通过 MutationObserver 监听 document.body 的 DOM 变化,
|
|
188
|
+
* 对编译期无法捕获的动态节点和浮层节点进行兜底打标。
|
|
189
|
+
*
|
|
190
|
+
* 处理流程:
|
|
191
|
+
* 1. 带 data-test-base-key 的节点 → 公共组件实例 → 锚点定位 + 局部计数
|
|
192
|
+
* 2. body 直系浮层节点 → 匹配浮层类型 → 独立前缀 + 计数器
|
|
193
|
+
* 3. #app 内普通新增节点 → 页面动态节点 → dynamic_ 前缀
|
|
194
|
+
*/
|
|
195
|
+
declare class TestIdObserver {
|
|
196
|
+
private state;
|
|
197
|
+
constructor();
|
|
198
|
+
/**
|
|
199
|
+
* 启动 MutationObserver
|
|
200
|
+
*/
|
|
201
|
+
start(): void;
|
|
202
|
+
/**
|
|
203
|
+
* 停止 Observer
|
|
204
|
+
*/
|
|
205
|
+
stop(): void;
|
|
206
|
+
/**
|
|
207
|
+
* 更新当前路由 (路由切换时调用)
|
|
208
|
+
*/
|
|
209
|
+
setRoute(routeName: string): void;
|
|
210
|
+
/**
|
|
211
|
+
* 全量扫描当前 DOM (用于启动时兜底, 处理 Observer 启动前已渲染的节点)
|
|
212
|
+
*/
|
|
213
|
+
fullScan(): void;
|
|
214
|
+
/**
|
|
215
|
+
* MutationObserver 回调 (箭头函数绑定 this)
|
|
216
|
+
*/
|
|
217
|
+
private handleMutations;
|
|
218
|
+
/**
|
|
219
|
+
* 递归处理元素节点及其子节点
|
|
220
|
+
*/
|
|
221
|
+
private processNodeRecursive;
|
|
222
|
+
/**
|
|
223
|
+
* 处理单个节点
|
|
224
|
+
*
|
|
225
|
+
* 决策优先级:
|
|
226
|
+
* 0. 已有 data-testid → 跳过
|
|
227
|
+
* 1. 带 data-test-base-key → 公共组件实例 (锚点定位)
|
|
228
|
+
* 2. body 直系浮层 → 匹配浮层类型 → 独立前缀
|
|
229
|
+
* 3. #app 内普通节点 → dynamic
|
|
230
|
+
*/
|
|
231
|
+
private processSingleNode;
|
|
232
|
+
/**
|
|
233
|
+
* 处理公共组件 base-key 节点 — 锚点定位方案
|
|
234
|
+
*
|
|
235
|
+
* 流程:
|
|
236
|
+
* 1. 解析 baseKey → { componentName, tagName, templateIndex }
|
|
237
|
+
* 2. 查找最近锚点 → anchorTestId (祖先元素上的 data-testid)
|
|
238
|
+
* 3. 锚点下局部计数 → localIndex
|
|
239
|
+
* 4. 拼接: {anchorTestId}__{componentName}_{tagName}_{localIndex}
|
|
240
|
+
* 5. 移除 data-test-base-key
|
|
241
|
+
*/
|
|
242
|
+
private handleBaseKeyNode;
|
|
243
|
+
/**
|
|
244
|
+
* 向上遍历 DOM 树,找到第一个带 data-testid 属性的祖先元素作为锚点
|
|
245
|
+
*
|
|
246
|
+
* 如果一直找到 #app / body 都没有,返回 "__root" 作为兜底
|
|
247
|
+
*/
|
|
248
|
+
private findAnchor;
|
|
249
|
+
/**
|
|
250
|
+
* 处理 Antd 浮层根节点
|
|
251
|
+
*
|
|
252
|
+
* @param node - 浮层根节点元素
|
|
253
|
+
* @param type - 浮层类型
|
|
254
|
+
*/
|
|
255
|
+
private handlePopupNode;
|
|
256
|
+
/**
|
|
257
|
+
* 通过 CSS class 匹配浮层类型
|
|
258
|
+
*
|
|
259
|
+
* @param node - 待检测节点
|
|
260
|
+
* @returns 浮层类型或 null (非 Antd 浮层)
|
|
261
|
+
*/
|
|
262
|
+
private detectPopupType;
|
|
263
|
+
/**
|
|
264
|
+
* 处理页面内动态新增节点 (v-for / 异步 v-if 等)
|
|
265
|
+
*
|
|
266
|
+
* ID 格式: ${runtimePagePrefix}${route}_${tag}_${counter}
|
|
267
|
+
*/
|
|
268
|
+
private handleDynamicNode;
|
|
269
|
+
/**
|
|
270
|
+
* 检测元素是否在 #app 容器内
|
|
271
|
+
*/
|
|
272
|
+
private isInsideApp;
|
|
273
|
+
/**
|
|
274
|
+
* 获取元素的简化标签名
|
|
275
|
+
*
|
|
276
|
+
* 处理 Antd 组件前缀: a-button → button, a-input → input
|
|
277
|
+
*/
|
|
278
|
+
private getSimpleTag;
|
|
279
|
+
/**
|
|
280
|
+
* 判断是否可交互元素
|
|
281
|
+
*
|
|
282
|
+
* 可交互特征:
|
|
283
|
+
* - 交互类标签: button, input, select, textarea
|
|
284
|
+
* - onclick 属性
|
|
285
|
+
* - role="button" / role="checkbox" / role="radio"
|
|
286
|
+
* - cursor:pointer (不检测,因为可能从 CSS 继承,误判率高)
|
|
287
|
+
*/
|
|
288
|
+
private isInteractive;
|
|
289
|
+
/**
|
|
290
|
+
* 检查是否应跳过 (ignoreTags / ignoreClass)
|
|
291
|
+
*/
|
|
292
|
+
private shouldIgnore;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* ID 重复检测调试工具 — testIdChecker.ts
|
|
297
|
+
*
|
|
298
|
+
* 按前缀分组检测 data-testid 重复,控制台告警定位问题来源。
|
|
299
|
+
*
|
|
300
|
+
* 分组维度:
|
|
301
|
+
* - custom: 业务手动自定义 data-testid
|
|
302
|
+
* - static: 编译静态 + 公共组件实例
|
|
303
|
+
* - dynamic: 页面内动态节点
|
|
304
|
+
* - modal / drawer / select / datePicker / popconfirm / dropdown / tooltip: 各类浮层
|
|
305
|
+
*/
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 检测分组维度
|
|
309
|
+
*/
|
|
310
|
+
type CheckGroup = 'custom' | 'static' | 'dynamic' | PopupType;
|
|
311
|
+
declare class TestIdChecker {
|
|
312
|
+
/**
|
|
313
|
+
* 执行全量重复检测
|
|
314
|
+
*
|
|
315
|
+
* 步骤:
|
|
316
|
+
* 1. 查询所有带 data-testid 属性的 DOM 节点
|
|
317
|
+
* 2. 按 testid 前缀分组 (custom / static / dynamic / modal / ...)
|
|
318
|
+
* 3. 组内统计出现次数 > 1 的 testid
|
|
319
|
+
* 4. 控制台按分组输出告警信息
|
|
320
|
+
*
|
|
321
|
+
* @returns 是否有重复 ID
|
|
322
|
+
*/
|
|
323
|
+
static check(): boolean;
|
|
324
|
+
/**
|
|
325
|
+
* 执行检测并返回统计摘要 (不输出控制台)
|
|
326
|
+
*/
|
|
327
|
+
static getStats(): {
|
|
328
|
+
group: CheckGroup;
|
|
329
|
+
total: number;
|
|
330
|
+
unique: number;
|
|
331
|
+
duplicates: number;
|
|
332
|
+
}[];
|
|
333
|
+
/**
|
|
334
|
+
* 按 testid 前缀分类
|
|
335
|
+
*/
|
|
336
|
+
private static classifyTestId;
|
|
337
|
+
/**
|
|
338
|
+
* 控制台告警输出 (按分组格式化)
|
|
339
|
+
*/
|
|
340
|
+
private static reportGroupDuplicates;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
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 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局配置模块 — testMark.ts
|
|
3
|
+
*
|
|
4
|
+
* 管理所有打标相关的配置项:
|
|
5
|
+
* - 前缀 (compile / runtime / popup)
|
|
6
|
+
* - 黑白名单
|
|
7
|
+
* - 开关
|
|
8
|
+
* - 浮层前缀映射
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* 支持的浮层类型
|
|
12
|
+
*/
|
|
13
|
+
type PopupType = 'modal' | 'drawer' | 'select' | 'datePicker' | 'popconfirm' | 'dropdown' | 'tooltip';
|
|
14
|
+
/**
|
|
15
|
+
* 全量配置接口
|
|
16
|
+
*/
|
|
17
|
+
interface TestIdMarkConfig {
|
|
18
|
+
/** 总开关,生产环境强制为 false */
|
|
19
|
+
enable: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* 全局前缀,拼接到所有 testid 前缀之前
|
|
22
|
+
* 例: globalPrefix = "hall" → compilePrefix 变为 hall_static_,runtimePagePrefix 变为 hall_dynamic_
|
|
23
|
+
* 设值后会自动应用到 compilePrefix、runtimePagePrefix 和 popupPrefixMap
|
|
24
|
+
* 默认: 空 (不添加)
|
|
25
|
+
*/
|
|
26
|
+
globalPrefix: string;
|
|
27
|
+
/** 编译期静态节点统一前缀 (默认 "static_") */
|
|
28
|
+
compilePrefix: string;
|
|
29
|
+
/** 页面内动态节点统一前缀 (默认 "dynamic_") */
|
|
30
|
+
runtimePagePrefix: string;
|
|
31
|
+
/** Antd 浮层组件专属前缀映射 */
|
|
32
|
+
popupPrefixMap: Record<PopupType, string>;
|
|
33
|
+
/** 忽略不打标的 HTML 标签名 */
|
|
34
|
+
ignoreTags: string[];
|
|
35
|
+
/** 包含此 class 的 DOM 跳过打标 */
|
|
36
|
+
ignoreClass: string[];
|
|
37
|
+
/** 是否仅给可交互控件打标 */
|
|
38
|
+
onlyInteractive: boolean;
|
|
39
|
+
/** 路由切换时是否清空锚点局部计数器 */
|
|
40
|
+
resetInstanceOnRouteChange: boolean;
|
|
41
|
+
/** 路由切换时是否重置全部浮层计数器 */
|
|
42
|
+
resetPopupCounterOnRouteChange: boolean;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 交互控件白名单 (onlyInteractive === true 时生效)
|
|
46
|
+
*/
|
|
47
|
+
declare const INTERACTIVE_TAGS: Set<string>;
|
|
48
|
+
/**
|
|
49
|
+
* 默认配置对象 (enable 默认为 true,由调用方传入 DEV 判断)
|
|
50
|
+
*/
|
|
51
|
+
declare const defaultConfig: TestIdMarkConfig;
|
|
52
|
+
/**
|
|
53
|
+
* 合并用户自定义配置与默认配置
|
|
54
|
+
* 采用浅合并策略,popupPrefixMap 支持部分覆盖
|
|
55
|
+
*
|
|
56
|
+
* 关键: 若设置了 globalPrefix,自动拼接到所有前缀字段
|
|
57
|
+
*/
|
|
58
|
+
declare function mergeConfig(userConfig?: Partial<TestIdMarkConfig>): TestIdMarkConfig;
|
|
59
|
+
/**
|
|
60
|
+
* 初始化全局配置 (应用启动时调用)
|
|
61
|
+
*/
|
|
62
|
+
declare function initConfig(custom?: Partial<TestIdMarkConfig>): void;
|
|
63
|
+
/**
|
|
64
|
+
* 获取当前全局配置 (只读)
|
|
65
|
+
*/
|
|
66
|
+
declare function getConfig(): Readonly<TestIdMarkConfig>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 锚点局部计数器 — testIdAnchorCounter.ts
|
|
70
|
+
*
|
|
71
|
+
* 解决公共组件多实例 ID 重复问题。
|
|
72
|
+
*
|
|
73
|
+
* 核心思路:
|
|
74
|
+
* 不依赖全局挂载顺序(会因异步渲染而变化),
|
|
75
|
+
* 而是依赖"父模板结构"来定位 ——
|
|
76
|
+
* 向上查找最近带 data-testid 的祖先元素作为"锚点",
|
|
77
|
+
* 在锚点下按 (组件名, 标签名) 维度维护局部计数器。
|
|
78
|
+
*
|
|
79
|
+
* 数据结构:
|
|
80
|
+
* Map<`${anchorTestId}__${componentName}__${tagName}`, counter>
|
|
81
|
+
*
|
|
82
|
+
* 示例:
|
|
83
|
+
* "static_page_dashboard_tag_div_3__BaseSearch__button" → 0
|
|
84
|
+
* "static_page_dashboard_tag_div_3__BaseSearch__button" → 1 (第 2 次出现)
|
|
85
|
+
* "static_page_dashboard_tag_div_7__BaseSearch__button" → 0 (不同锚点, 独立计数)
|
|
86
|
+
*/
|
|
87
|
+
/**
|
|
88
|
+
* 获取锚点下某组件某标签的下一个局部索引 (自增)
|
|
89
|
+
*
|
|
90
|
+
* @param anchorTestId - 锚点 testid (findAnchor 返回)
|
|
91
|
+
* @param componentName - 组件名称 (从 base-key 解析)
|
|
92
|
+
* @param tagName - 标签名 (从 base-key 解析)
|
|
93
|
+
* @returns 局部索引 (从 0 开始)
|
|
94
|
+
*/
|
|
95
|
+
declare function getNextAnchorLocalIndex(anchorTestId: string, componentName: string, tagName: string): number;
|
|
96
|
+
/**
|
|
97
|
+
* 重置所有锚点计数器 (路由切换时调用)
|
|
98
|
+
*/
|
|
99
|
+
declare function resetAllAnchorCounters(): void;
|
|
100
|
+
/**
|
|
101
|
+
* 获取当前锚点计数器映射(仅用于调试)
|
|
102
|
+
*/
|
|
103
|
+
declare function getAnchorCounterMap(): ReadonlyMap<string, number>;
|
|
104
|
+
/**
|
|
105
|
+
* base-key 解析结果
|
|
106
|
+
*/
|
|
107
|
+
interface ParsedBaseKey {
|
|
108
|
+
componentName: string;
|
|
109
|
+
tagName: string;
|
|
110
|
+
templateIndex: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 从 base-key 中解析组件名和标签名
|
|
114
|
+
*
|
|
115
|
+
* base-key 格式: common_comp_{componentName}_tag_{tagName}_{templateIndex}
|
|
116
|
+
*
|
|
117
|
+
* @param baseKey - base-key 字符串
|
|
118
|
+
* @returns 解析结果或 null (格式不匹配)
|
|
119
|
+
*/
|
|
120
|
+
declare function parseBaseKey(baseKey: string): ParsedBaseKey | null;
|
|
121
|
+
/**
|
|
122
|
+
* 拼接最终 testid
|
|
123
|
+
*
|
|
124
|
+
* 格式: ${anchorTestId}__${componentName}_${tagName}_${localIndex}
|
|
125
|
+
*
|
|
126
|
+
* 示例:
|
|
127
|
+
* anchorTestId = "static_page_dashboard_tag_div_3"
|
|
128
|
+
* componentName = "BaseSearch"
|
|
129
|
+
* tagName = "button"
|
|
130
|
+
* localIndex = 0
|
|
131
|
+
* 返回: "static_page_dashboard_tag_div_3__BaseSearch_button_0"
|
|
132
|
+
*
|
|
133
|
+
* @param anchorTestId - 锚点 testid
|
|
134
|
+
* @param componentName - 组件名
|
|
135
|
+
* @param tagName - 标签名
|
|
136
|
+
* @param localIndex - 局部索引 (从 getNextAnchorLocalIndex 获取)
|
|
137
|
+
*/
|
|
138
|
+
declare function buildAnchorTestId(anchorTestId: string, componentName: string, tagName: string, localIndex: number): string;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 浮层独立计数器 — testIdPopupCounter.ts
|
|
142
|
+
*
|
|
143
|
+
* 每种浮层类型维护独立的自增计数器,类型之间互不干扰。
|
|
144
|
+
*
|
|
145
|
+
* 这意味着:
|
|
146
|
+
* - Modal 第 1 次出现: modal_button_0
|
|
147
|
+
* - Select 第 1 次出现: select_div_0
|
|
148
|
+
* - Modal 第 2 次出现: modal_button_1
|
|
149
|
+
* - Select 第 2 次出现: select_div_1
|
|
150
|
+
*/
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* 获取某类型浮层的下一个 ID (自增)
|
|
154
|
+
*
|
|
155
|
+
* @param type - 浮层类型
|
|
156
|
+
* @returns 计数器值 (使用前,从 0 开始)
|
|
157
|
+
*/
|
|
158
|
+
declare function getNextPopupId(type: PopupType): number;
|
|
159
|
+
/**
|
|
160
|
+
* 重置所有浮层计数器
|
|
161
|
+
*/
|
|
162
|
+
declare function resetAllPopupCounters(): void;
|
|
163
|
+
/**
|
|
164
|
+
* 重置指定类型浮层计数器
|
|
165
|
+
*/
|
|
166
|
+
declare function resetPopupCounter(type: PopupType): void;
|
|
167
|
+
/**
|
|
168
|
+
* 获取所有计数器快照 (调试用)
|
|
169
|
+
*/
|
|
170
|
+
declare function getPopupCounterSnapshot(): Record<PopupType, number>;
|
|
171
|
+
/**
|
|
172
|
+
* 生成浮层节点 testid
|
|
173
|
+
*
|
|
174
|
+
* 格式: ${prefix}${tag}_${counterId}
|
|
175
|
+
*
|
|
176
|
+
* 例: modal_button_0, select_div_2
|
|
177
|
+
*
|
|
178
|
+
* @param type - 浮层类型
|
|
179
|
+
* @param tag - HTML 标签名 (简化后)
|
|
180
|
+
* @param counterId - 计数器 ID (从 getNextPopupId 获取)
|
|
181
|
+
*/
|
|
182
|
+
declare function buildPopupTestId(type: PopupType, tag: string, counterId: number): string;
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* MutationObserver DOM 监听与自动打标器 — testIdObserver.ts
|
|
186
|
+
*
|
|
187
|
+
* 运行时核心模块。通过 MutationObserver 监听 document.body 的 DOM 变化,
|
|
188
|
+
* 对编译期无法捕获的动态节点和浮层节点进行兜底打标。
|
|
189
|
+
*
|
|
190
|
+
* 处理流程:
|
|
191
|
+
* 1. 带 data-test-base-key 的节点 → 公共组件实例 → 锚点定位 + 局部计数
|
|
192
|
+
* 2. body 直系浮层节点 → 匹配浮层类型 → 独立前缀 + 计数器
|
|
193
|
+
* 3. #app 内普通新增节点 → 页面动态节点 → dynamic_ 前缀
|
|
194
|
+
*/
|
|
195
|
+
declare class TestIdObserver {
|
|
196
|
+
private state;
|
|
197
|
+
constructor();
|
|
198
|
+
/**
|
|
199
|
+
* 启动 MutationObserver
|
|
200
|
+
*/
|
|
201
|
+
start(): void;
|
|
202
|
+
/**
|
|
203
|
+
* 停止 Observer
|
|
204
|
+
*/
|
|
205
|
+
stop(): void;
|
|
206
|
+
/**
|
|
207
|
+
* 更新当前路由 (路由切换时调用)
|
|
208
|
+
*/
|
|
209
|
+
setRoute(routeName: string): void;
|
|
210
|
+
/**
|
|
211
|
+
* 全量扫描当前 DOM (用于启动时兜底, 处理 Observer 启动前已渲染的节点)
|
|
212
|
+
*/
|
|
213
|
+
fullScan(): void;
|
|
214
|
+
/**
|
|
215
|
+
* MutationObserver 回调 (箭头函数绑定 this)
|
|
216
|
+
*/
|
|
217
|
+
private handleMutations;
|
|
218
|
+
/**
|
|
219
|
+
* 递归处理元素节点及其子节点
|
|
220
|
+
*/
|
|
221
|
+
private processNodeRecursive;
|
|
222
|
+
/**
|
|
223
|
+
* 处理单个节点
|
|
224
|
+
*
|
|
225
|
+
* 决策优先级:
|
|
226
|
+
* 0. 已有 data-testid → 跳过
|
|
227
|
+
* 1. 带 data-test-base-key → 公共组件实例 (锚点定位)
|
|
228
|
+
* 2. body 直系浮层 → 匹配浮层类型 → 独立前缀
|
|
229
|
+
* 3. #app 内普通节点 → dynamic
|
|
230
|
+
*/
|
|
231
|
+
private processSingleNode;
|
|
232
|
+
/**
|
|
233
|
+
* 处理公共组件 base-key 节点 — 锚点定位方案
|
|
234
|
+
*
|
|
235
|
+
* 流程:
|
|
236
|
+
* 1. 解析 baseKey → { componentName, tagName, templateIndex }
|
|
237
|
+
* 2. 查找最近锚点 → anchorTestId (祖先元素上的 data-testid)
|
|
238
|
+
* 3. 锚点下局部计数 → localIndex
|
|
239
|
+
* 4. 拼接: {anchorTestId}__{componentName}_{tagName}_{localIndex}
|
|
240
|
+
* 5. 移除 data-test-base-key
|
|
241
|
+
*/
|
|
242
|
+
private handleBaseKeyNode;
|
|
243
|
+
/**
|
|
244
|
+
* 向上遍历 DOM 树,找到第一个带 data-testid 属性的祖先元素作为锚点
|
|
245
|
+
*
|
|
246
|
+
* 如果一直找到 #app / body 都没有,返回 "__root" 作为兜底
|
|
247
|
+
*/
|
|
248
|
+
private findAnchor;
|
|
249
|
+
/**
|
|
250
|
+
* 处理 Antd 浮层根节点
|
|
251
|
+
*
|
|
252
|
+
* @param node - 浮层根节点元素
|
|
253
|
+
* @param type - 浮层类型
|
|
254
|
+
*/
|
|
255
|
+
private handlePopupNode;
|
|
256
|
+
/**
|
|
257
|
+
* 通过 CSS class 匹配浮层类型
|
|
258
|
+
*
|
|
259
|
+
* @param node - 待检测节点
|
|
260
|
+
* @returns 浮层类型或 null (非 Antd 浮层)
|
|
261
|
+
*/
|
|
262
|
+
private detectPopupType;
|
|
263
|
+
/**
|
|
264
|
+
* 处理页面内动态新增节点 (v-for / 异步 v-if 等)
|
|
265
|
+
*
|
|
266
|
+
* ID 格式: ${runtimePagePrefix}${route}_${tag}_${counter}
|
|
267
|
+
*/
|
|
268
|
+
private handleDynamicNode;
|
|
269
|
+
/**
|
|
270
|
+
* 检测元素是否在 #app 容器内
|
|
271
|
+
*/
|
|
272
|
+
private isInsideApp;
|
|
273
|
+
/**
|
|
274
|
+
* 获取元素的简化标签名
|
|
275
|
+
*
|
|
276
|
+
* 处理 Antd 组件前缀: a-button → button, a-input → input
|
|
277
|
+
*/
|
|
278
|
+
private getSimpleTag;
|
|
279
|
+
/**
|
|
280
|
+
* 判断是否可交互元素
|
|
281
|
+
*
|
|
282
|
+
* 可交互特征:
|
|
283
|
+
* - 交互类标签: button, input, select, textarea
|
|
284
|
+
* - onclick 属性
|
|
285
|
+
* - role="button" / role="checkbox" / role="radio"
|
|
286
|
+
* - cursor:pointer (不检测,因为可能从 CSS 继承,误判率高)
|
|
287
|
+
*/
|
|
288
|
+
private isInteractive;
|
|
289
|
+
/**
|
|
290
|
+
* 检查是否应跳过 (ignoreTags / ignoreClass)
|
|
291
|
+
*/
|
|
292
|
+
private shouldIgnore;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* ID 重复检测调试工具 — testIdChecker.ts
|
|
297
|
+
*
|
|
298
|
+
* 按前缀分组检测 data-testid 重复,控制台告警定位问题来源。
|
|
299
|
+
*
|
|
300
|
+
* 分组维度:
|
|
301
|
+
* - custom: 业务手动自定义 data-testid
|
|
302
|
+
* - static: 编译静态 + 公共组件实例
|
|
303
|
+
* - dynamic: 页面内动态节点
|
|
304
|
+
* - modal / drawer / select / datePicker / popconfirm / dropdown / tooltip: 各类浮层
|
|
305
|
+
*/
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 检测分组维度
|
|
309
|
+
*/
|
|
310
|
+
type CheckGroup = 'custom' | 'static' | 'dynamic' | PopupType;
|
|
311
|
+
declare class TestIdChecker {
|
|
312
|
+
/**
|
|
313
|
+
* 执行全量重复检测
|
|
314
|
+
*
|
|
315
|
+
* 步骤:
|
|
316
|
+
* 1. 查询所有带 data-testid 属性的 DOM 节点
|
|
317
|
+
* 2. 按 testid 前缀分组 (custom / static / dynamic / modal / ...)
|
|
318
|
+
* 3. 组内统计出现次数 > 1 的 testid
|
|
319
|
+
* 4. 控制台按分组输出告警信息
|
|
320
|
+
*
|
|
321
|
+
* @returns 是否有重复 ID
|
|
322
|
+
*/
|
|
323
|
+
static check(): boolean;
|
|
324
|
+
/**
|
|
325
|
+
* 执行检测并返回统计摘要 (不输出控制台)
|
|
326
|
+
*/
|
|
327
|
+
static getStats(): {
|
|
328
|
+
group: CheckGroup;
|
|
329
|
+
total: number;
|
|
330
|
+
unique: number;
|
|
331
|
+
duplicates: number;
|
|
332
|
+
}[];
|
|
333
|
+
/**
|
|
334
|
+
* 按 testid 前缀分类
|
|
335
|
+
*/
|
|
336
|
+
private static classifyTestId;
|
|
337
|
+
/**
|
|
338
|
+
* 控制台告警输出 (按分组格式化)
|
|
339
|
+
*/
|
|
340
|
+
private static reportGroupDuplicates;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
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 };
|