@epoint-testtech/ep-stage-skill 0.0.9 → 0.5.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/SKILL.md +27 -27
  2. package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -223
  3. package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -275
  4. package/codex-skill/ep-stage/glue-generate-testcase/examples/observable-testcase.json +40 -40
  5. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +95 -95
  6. package/codex-skill/ep-stage/glue-run-test/SKILL.md +273 -296
  7. package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -30
  8. package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -132
  9. package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -44
  10. package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
  11. package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +127 -166
  12. package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
  13. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
  14. package/codex-skill/ep-stage/scripts/validate-skill.mjs +95 -98
  15. package/dist/src/cli/generate-testcase.d.ts +0 -1
  16. package/dist/src/cli/generate-testcase.d.ts.map +1 -1
  17. package/dist/src/cli/generate-testcase.js +1 -6
  18. package/dist/src/cli/index.d.ts.map +1 -1
  19. package/dist/src/cli/index.js +14 -38
  20. package/dist/src/cli/run.d.ts +0 -2
  21. package/dist/src/cli/run.d.ts.map +1 -1
  22. package/dist/src/cli/run.js +21 -70
  23. package/dist/src/cli/spec.d.ts.map +1 -1
  24. package/dist/src/cli/spec.js +0 -9
  25. package/dist/src/contracts/glue-contract-v1.d.ts +0 -35
  26. package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -1
  27. package/dist/src/extractors/code-list-json5.d.ts +1 -34
  28. package/dist/src/extractors/code-list-json5.d.ts.map +1 -1
  29. package/dist/src/extractors/code-list-json5.js +0 -1
  30. package/dist/src/extractors/html-page.d.ts +0 -9
  31. package/dist/src/extractors/html-page.d.ts.map +1 -1
  32. package/dist/src/extractors/html-page.js +2 -26
  33. package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
  34. package/dist/src/generators/stage-skeleton-script.d.ts +0 -50
  35. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
  36. package/dist/src/generators/stage-skeleton-script.js +116 -449
  37. package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -1
  38. package/dist/src/runtime/ai-runtime-trace.js +0 -8
  39. package/dist/src/testcase/contract-testcase-assembler.d.ts +0 -28
  40. package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
  41. package/dist/src/testcase/contract-testcase-assembler.js +75 -329
  42. package/dist/src/validation/contract.d.ts.map +1 -1
  43. package/dist/src/validation/contract.js +0 -29
  44. package/dist/test/cli/generate-testcase-json5.test.d.ts +2 -0
  45. package/dist/test/cli/generate-testcase-json5.test.d.ts.map +1 -0
  46. package/dist/test/cli/generate-testcase-json5.test.js +154 -0
  47. package/dist/test/cli/index.test.d.ts +2 -0
  48. package/dist/test/cli/index.test.d.ts.map +1 -0
  49. package/dist/test/cli/index.test.js +114 -0
  50. package/dist/test/cli/run.test.d.ts +2 -0
  51. package/dist/test/cli/run.test.d.ts.map +1 -0
  52. package/dist/test/cli/run.test.js +429 -0
  53. package/dist/test/cli/spec.test.d.ts +2 -0
  54. package/dist/test/cli/spec.test.d.ts.map +1 -0
  55. package/dist/test/cli/spec.test.js +112 -0
  56. package/dist/test/code-list-json5.test.d.ts +2 -0
  57. package/dist/test/code-list-json5.test.d.ts.map +1 -0
  58. package/dist/test/code-list-json5.test.js +84 -0
  59. package/dist/test/code-list.test.d.ts +2 -0
  60. package/dist/test/code-list.test.d.ts.map +1 -0
  61. package/dist/test/code-list.test.js +39 -0
  62. package/dist/test/gap-inference.test.d.ts +2 -0
  63. package/dist/test/gap-inference.test.d.ts.map +1 -0
  64. package/dist/test/gap-inference.test.js +597 -0
  65. package/dist/test/generalization.test.d.ts +2 -0
  66. package/dist/test/generalization.test.d.ts.map +1 -0
  67. package/dist/test/generalization.test.js +73 -0
  68. package/dist/test/material-inventory.test.d.ts +2 -0
  69. package/dist/test/material-inventory.test.d.ts.map +1 -0
  70. package/dist/test/material-inventory.test.js +155 -0
  71. package/dist/test/observable-chain.test.d.ts +2 -0
  72. package/dist/test/observable-chain.test.d.ts.map +1 -0
  73. package/dist/test/observable-chain.test.js +123 -0
  74. package/dist/test/page-structure.test.d.ts +2 -0
  75. package/dist/test/page-structure.test.d.ts.map +1 -0
  76. package/dist/test/page-structure.test.js +45 -0
  77. package/dist/test/runtime/ai-runtime-trace.test.d.ts +2 -0
  78. package/dist/test/runtime/ai-runtime-trace.test.d.ts.map +1 -0
  79. package/dist/test/runtime/ai-runtime-trace.test.js +78 -0
  80. package/dist/test/scenario-inference.test.d.ts +2 -0
  81. package/dist/test/scenario-inference.test.d.ts.map +1 -0
  82. package/dist/test/scenario-inference.test.js +73 -0
  83. package/dist/test/stage-context.test.d.ts +2 -0
  84. package/dist/test/stage-context.test.d.ts.map +1 -0
  85. package/dist/test/stage-context.test.js +395 -0
  86. package/dist/test/testcase/testcase-markdown.test.d.ts +2 -0
  87. package/dist/test/testcase/testcase-markdown.test.d.ts.map +1 -0
  88. package/dist/test/testcase/testcase-markdown.test.js +48 -0
  89. package/dist/test/testcase-generator.test.d.ts +2 -0
  90. package/dist/test/testcase-generator.test.d.ts.map +1 -0
  91. package/dist/test/testcase-generator.test.js +276 -0
  92. package/dist/test/testcase-skeleton.test.d.ts +2 -0
  93. package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
  94. package/dist/test/testcase-skeleton.test.js +185 -0
  95. package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
  96. package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
  97. package/dist/test/testcase-spec-assembly.test.js +105 -0
  98. package/dist/test/util/credentials.test.d.ts +2 -0
  99. package/dist/test/util/credentials.test.d.ts.map +1 -0
  100. package/dist/test/util/credentials.test.js +64 -0
  101. package/dist/test/util/i18n-testcase.test.d.ts +2 -0
  102. package/dist/test/util/i18n-testcase.test.d.ts.map +1 -0
  103. package/dist/test/util/i18n-testcase.test.js +119 -0
  104. package/dist/test/util/softlink.test.d.ts +2 -0
  105. package/dist/test/util/softlink.test.d.ts.map +1 -0
  106. package/dist/test/util/softlink.test.js +82 -0
  107. package/dist/test/validation/contract.test.d.ts +2 -0
  108. package/dist/test/validation/contract.test.d.ts.map +1 -0
  109. package/dist/test/validation/contract.test.js +116 -0
  110. package/dist/test/validation/credentials.test.d.ts +2 -0
  111. package/dist/test/validation/credentials.test.d.ts.map +1 -0
  112. package/dist/test/validation/credentials.test.js +74 -0
  113. package/dist/test/validation/projects-index.test.d.ts +2 -0
  114. package/dist/test/validation/projects-index.test.d.ts.map +1 -0
  115. package/dist/test/validation/projects-index.test.js +48 -0
  116. package/dist/test/validation/testcase-v1-draft.test.d.ts +2 -0
  117. package/dist/test/validation/testcase-v1-draft.test.d.ts.map +1 -0
  118. package/dist/test/validation/testcase-v1-draft.test.js +173 -0
  119. package/dist/vitest.config.d.ts +3 -0
  120. package/dist/vitest.config.d.ts.map +1 -0
  121. package/dist/vitest.config.js +7 -0
  122. package/docs/README.md +124 -124
  123. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
  124. package/examples/schemeresource.module-hints.json +38 -38
  125. package/examples/schemeresource.observable.code_list.md +37 -37
  126. package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
  127. package/examples/zwplace-placecategory-validation.json +29 -29
  128. package/examples/zwplace.module-hints.json +69 -69
  129. package/examples/zwplace.observable.code_list.md +37 -37
  130. package/package.json +19 -13
  131. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +0 -72
  132. package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +0 -233
  133. package/codex-skill/ep-stage/page-element-scan/SKILL.md +0 -91
  134. package/codex-skill/ep-stage/page-element-scan/evals/evals.json +0 -37
  135. package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +0 -85
  136. package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +0 -153
  137. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +0 -233
  138. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +0 -298
  139. package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +0 -749
  140. package/dist/src/cli/probe.d.ts +0 -46
  141. package/dist/src/cli/probe.d.ts.map +0 -1
  142. package/dist/src/cli/probe.js +0 -447
  143. package/dist/src/cli/run-report.d.ts +0 -46
  144. package/dist/src/cli/run-report.d.ts.map +0 -1
  145. package/dist/src/cli/run-report.js +0 -316
  146. package/dist/src/extractors/prd-extractor.d.ts +0 -70
  147. package/dist/src/extractors/prd-extractor.d.ts.map +0 -1
  148. package/dist/src/extractors/prd-extractor.js +0 -248
  149. package/dist/src/testcase/button-interaction-inference.d.ts +0 -37
  150. package/dist/src/testcase/button-interaction-inference.d.ts.map +0 -1
  151. package/dist/src/testcase/button-interaction-inference.js +0 -385
  152. package/dist/src/testcase/prd-button-mapper.d.ts +0 -72
  153. package/dist/src/testcase/prd-button-mapper.d.ts.map +0 -1
  154. package/dist/src/testcase/prd-button-mapper.js +0 -148
  155. package/dist/src/testcase/testcase-v2.d.ts +0 -50
  156. package/dist/src/testcase/testcase-v2.d.ts.map +0 -1
  157. package/dist/src/testcase/testcase-v2.js +0 -1
  158. package/docs/mvp-usage-guide.md +0 -298
@@ -1,385 +0,0 @@
1
- /**
2
- * 前端按钮交互行为推理器。
3
- *
4
- * 职责:
5
- * 1. 从 code_list.json5 的 sourceRoot + pages.list.path 定位列表页 HTML;
6
- * 2. 解析 HTML 中自定义按钮的 onclick 属性及同页内联脚本中的函数体;
7
- * 3. 判断交互模式(确认弹窗 / toast / 异步调用 / 打开弹窗 / 通用);
8
- * 4. 为每个 custom 按钮生成精确的中文步骤与断言;
9
- * 5. 当 HTML 不可读、解析失败或模式未知时,回退到通用描述并记录原因。
10
- */
11
- import { existsSync, readFileSync } from 'node:fs';
12
- import path from 'node:path';
13
- import * as cheerio from 'cheerio';
14
- /**
15
- * 归一化文本:去空白、去首尾空格。
16
- */
17
- function normalizeText(value) {
18
- return (value ?? '').replace(/\s+/g, ' ').trim();
19
- }
20
- /**
21
- * 从内联脚本内容中提取指定函数体。
22
- *
23
- * @param scriptContent - 页面内所有 inline script 拼接文本。
24
- * @param functionName - 函数名。
25
- * @returns 函数体或 undefined。
26
- */
27
- function extractFunctionBody(scriptContent, functionName) {
28
- const pattern = new RegExp(`function\\s+${functionName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*\\([^)]*\\)\\s*\\{`, 'g');
29
- const match = pattern.exec(scriptContent);
30
- if (!match)
31
- return undefined;
32
- const start = match.index + match[0].length;
33
- let depth = 1;
34
- let index = start;
35
- while (index < scriptContent.length && depth > 0) {
36
- const char = scriptContent[index];
37
- if (char === '{')
38
- depth += 1;
39
- else if (char === '}')
40
- depth -= 1;
41
- index += 1;
42
- }
43
- return scriptContent.slice(start, index - 1);
44
- }
45
- /**
46
- * 从合并后的 JS 文本中识别交互模式。
47
- *
48
- * 优先级:
49
- * 1. epoint.confirm -> confirm_then_execute
50
- * 2. epoint.openTopDialog / epoint.openDialog -> open_dialog
51
- * 3. epoint.execute / epoint.ajax / $.ajax / .ajax( / XMLHttpRequest / fetch( -> async_call
52
- * 4. epoint.message / epoint.showTips / epoint.alert -> toast
53
- * 5. 其他 -> generic
54
- */
55
- function detectPattern(combinedJs) {
56
- const normalized = combinedJs.replace(/\s+/g, ' ');
57
- if (/epoint\.confirm\s*\(/.test(normalized))
58
- return 'confirm_then_execute';
59
- if (/epoint\.(?:openTopDialog|openDialog)\s*\(/.test(normalized))
60
- return 'open_dialog';
61
- if (/epoint\.(?:execute|ajax)\s*\(/.test(normalized) ||
62
- /\$\.ajax\s*\(/.test(normalized) ||
63
- /[^a-zA-Z0-9_$]ajax\s*\(/.test(normalized) ||
64
- /XMLHttpRequest/.test(normalized) ||
65
- /[^a-zA-Z0-9_$]fetch\s*\(/.test(normalized)) {
66
- return 'async_call';
67
- }
68
- if (/epoint\.(?:message|showTips|alert)\s*\(/.test(normalized))
69
- return 'toast';
70
- return 'generic';
71
- }
72
- /**
73
- * 尝试提取 epoint.confirm 的第一个参数(提示文案)。
74
- *
75
- * @param combinedJs - 合并后的 JS 文本。
76
- * @returns 提取到的文案或 undefined。
77
- */
78
- function extractConfirmMessage(combinedJs) {
79
- const match = combinedJs.match(/epoint\.confirm\s*\(\s*["']([^"']+)["']/);
80
- return match?.[1];
81
- }
82
- /**
83
- * 根据交互模式渲染中文步骤和断言。
84
- */
85
- function renderStepsAndAssertion(label, pattern, confirmMessage) {
86
- if (pattern === 'confirm_then_execute') {
87
- const confirmStep = confirmMessage
88
- ? `2. 页面弹出确认弹窗,文案包含「${confirmMessage}」。`
89
- : '2. 页面弹出确认弹窗。';
90
- return {
91
- steps: [
92
- `1. 在列表页定位并点击「${label}」按钮。`,
93
- confirmStep,
94
- `3. 在确认弹窗中点击「确定」。`,
95
- `4. 等待后端请求完成。`,
96
- `5. 观察并记录最终反馈(提示信息、弹窗或列表刷新)。`,
97
- ].join(' '),
98
- assertion: `点击「${label}」并确认后,系统应调用对应接口并给出可见成功反馈;若接口返回错误,应展示错误提示。`,
99
- };
100
- }
101
- if (pattern === 'toast') {
102
- return {
103
- steps: [
104
- `1. 在列表页定位并点击「${label}」按钮。`,
105
- `2. 观察页面是否出现 toast / 提示信息。`,
106
- `3. 记录提示文案与页面状态变化。`,
107
- ].join(' '),
108
- assertion: `点击「${label}」按钮后,页面应展示可见的提示信息。`,
109
- };
110
- }
111
- if (pattern === 'async_call') {
112
- return {
113
- steps: [
114
- `1. 在列表页定位并点击「${label}」按钮。`,
115
- `2. 等待异步请求完成。`,
116
- `3. 观察并记录页面反馈(提示信息、列表刷新等)。`,
117
- ].join(' '),
118
- assertion: `点击「${label}」按钮后,系统应发起异步请求并给出成功或错误反馈。`,
119
- };
120
- }
121
- if (pattern === 'open_dialog') {
122
- return {
123
- steps: [
124
- `1. 在列表页定位并点击「${label}」按钮。`,
125
- `2. 等待弹窗或新页面打开。`,
126
- `3. 确认弹窗/页面标题与内容符合预期。`,
127
- ].join(' '),
128
- assertion: `点击「${label}」按钮后,应打开对应的弹窗或页面。`,
129
- };
130
- }
131
- return {
132
- steps: [
133
- `1. 在列表页定位并点击「${label}」按钮。`,
134
- `2. 观察并记录页面反馈(如弹窗、提示信息、列表刷新等)。`,
135
- `3. 确认页面状态与按钮功能预期一致。`,
136
- ].join(' '),
137
- assertion: `点击「${label}」按钮后,页面应出现与该功能对应的可见响应(如提示信息、弹窗确认或数据刷新)。`,
138
- };
139
- }
140
- /**
141
- * 从 code_list.pages[list].buttons 中提取 action='custom' 的按钮。
142
- */
143
- function collectCustomButtons(codeList) {
144
- const listPage = codeList.pages.find((p) => p.role === 'list');
145
- if (!listPage)
146
- return [];
147
- return (listPage.buttons ?? []).reduce((acc, raw) => {
148
- const anyB = raw;
149
- if (anyB.action === 'custom' && anyB.label) {
150
- acc.push({
151
- label: anyB.label,
152
- customAction: anyB.customAction ?? anyB.label,
153
- backendMethod: anyB.backendMethod,
154
- });
155
- }
156
- return acc;
157
- }, []);
158
- }
159
- /**
160
- * 解析 HTML 中 .mini-button 按钮,按 label 建立映射。
161
- */
162
- function buildHtmlButtonMap(html) {
163
- const $ = cheerio.load(html);
164
- const map = new Map();
165
- $('.mini-button').each((_, element) => {
166
- const button = $(element);
167
- const label = normalizeText(button.text());
168
- if (!label)
169
- return;
170
- map.set(label, {
171
- label,
172
- onclick: button.attr('onclick') ?? undefined,
173
- });
174
- });
175
- return map;
176
- }
177
- /**
178
- * 收集页面中所有 inline script 内容。
179
- */
180
- function collectInlineScripts(html) {
181
- const $ = cheerio.load(html);
182
- return $('script')
183
- .toArray()
184
- .map((element) => $(element).html() ?? '')
185
- .join('\n');
186
- }
187
- /**
188
- * 收集页面中外部 script src 的内容。
189
- *
190
- * @param html - HTML 文本。
191
- * @param htmlDir - HTML 文件所在目录,用于解析相对 src。
192
- * @returns 所有可读外部脚本内容拼接。
193
- */
194
- function collectExternalScripts(html, htmlDir) {
195
- const $ = cheerio.load(html);
196
- const contents = [];
197
- $('script[src]').each((_, element) => {
198
- const src = $(element).attr('src');
199
- if (!src)
200
- return;
201
- // 忽略远程脚本和 data URI。
202
- if (/^(https?:|data:|\/\/)/i.test(src))
203
- return;
204
- const scriptPath = path.resolve(htmlDir, src);
205
- if (!existsSync(scriptPath))
206
- return;
207
- try {
208
- contents.push(readFileSync(scriptPath, 'utf8'));
209
- }
210
- catch {
211
- // 忽略无法读取的外部脚本。
212
- }
213
- });
214
- return contents.join('\n');
215
- }
216
- /**
217
- * 从 onclick 值中提取可能调用的函数名。
218
- *
219
- * 兼容形式:
220
- * - `openAdd()`
221
- * - `syncWindowSystem`
222
- * - `epoint.confirm('...', cb)`(返回 epoint,会被过滤)
223
- *
224
- * @param onclick - onclick 属性值。
225
- * @returns 函数名或 undefined。
226
- */
227
- function extractFunctionNameFromOnclick(onclick) {
228
- const trimmed = onclick.trim();
229
- // 1. 优先匹配 functionName(...)
230
- const callMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/);
231
- if (callMatch)
232
- return callMatch[1];
233
- // 2. 兼容裸函数名赋值,如 onclick="syncWindowSystem"
234
- const bareMatch = trimmed.match(/^([A-Za-z_$][A-Za-z0-9_$]*)$/);
235
- if (bareMatch)
236
- return bareMatch[1];
237
- return undefined;
238
- }
239
- /**
240
- * 选择并返回可用的源码根目录。
241
- *
242
- * 优先级:options.sourceRootOverride -> process.env.EP_STAGE_SOURCE_ROOT -> codeList.sourceRoot.root
243
- */
244
- function resolveSourceRoot(codeList, options) {
245
- const candidates = [
246
- options?.sourceRootOverride,
247
- process.env.EP_STAGE_SOURCE_ROOT,
248
- codeList.sourceRoot?.root,
249
- ].filter((item) => Boolean(item));
250
- return candidates[0];
251
- }
252
- /**
253
- * 推理所有 custom 按钮的交互行为。
254
- *
255
- * @param codeList - 解析后的 code_list.json5。
256
- * @param options - 可选覆盖。
257
- * @returns label -> 推理结果 的映射。
258
- */
259
- export function inferButtonInteractions(codeList, options) {
260
- const customButtons = collectCustomButtons(codeList);
261
- const result = new Map();
262
- if (customButtons.length === 0)
263
- return result;
264
- const listPage = codeList.pages.find((p) => p.role === 'list');
265
- const listPath = listPage?.path;
266
- const sourceRoot = resolveSourceRoot(codeList, options);
267
- if (!listPath || !sourceRoot) {
268
- const reason = !listPath
269
- ? 'code_list.json5 缺少 pages[role=list].path'
270
- : '未提供可用的源码根目录(sourceRoot.root / EP_STAGE_SOURCE_ROOT / --source-root)';
271
- for (const btn of customButtons) {
272
- const generic = renderStepsAndAssertion(btn.label, 'generic');
273
- result.set(btn.label, {
274
- label: btn.label,
275
- pattern: 'generic',
276
- steps: generic.steps,
277
- assertion: generic.assertion,
278
- evidence: [],
279
- fallbackReason: reason,
280
- });
281
- }
282
- return result;
283
- }
284
- const htmlPath = path.resolve(sourceRoot, listPath);
285
- if (!existsSync(htmlPath)) {
286
- const reason = `列表页 HTML 不存在: ${htmlPath}`;
287
- for (const btn of customButtons) {
288
- const generic = renderStepsAndAssertion(btn.label, 'generic');
289
- result.set(btn.label, {
290
- label: btn.label,
291
- pattern: 'generic',
292
- steps: generic.steps,
293
- assertion: generic.assertion,
294
- evidence: [],
295
- fallbackReason: reason,
296
- });
297
- }
298
- return result;
299
- }
300
- let html;
301
- try {
302
- html = readFileSync(htmlPath, 'utf8');
303
- }
304
- catch (err) {
305
- const reason = `读取列表页 HTML 失败: ${err instanceof Error ? err.message : String(err)}`;
306
- for (const btn of customButtons) {
307
- const generic = renderStepsAndAssertion(btn.label, 'generic');
308
- result.set(btn.label, {
309
- label: btn.label,
310
- pattern: 'generic',
311
- steps: generic.steps,
312
- assertion: generic.assertion,
313
- evidence: [],
314
- fallbackReason: reason,
315
- });
316
- }
317
- return result;
318
- }
319
- let htmlButtonMap;
320
- let scriptContent;
321
- try {
322
- htmlButtonMap = buildHtmlButtonMap(html);
323
- const inlineScripts = collectInlineScripts(html);
324
- const externalScripts = collectExternalScripts(html, path.dirname(htmlPath));
325
- scriptContent = `${inlineScripts}\n${externalScripts}`;
326
- }
327
- catch (err) {
328
- const reason = `解析 HTML 失败: ${err instanceof Error ? err.message : String(err)}`;
329
- for (const btn of customButtons) {
330
- const generic = renderStepsAndAssertion(btn.label, 'generic');
331
- result.set(btn.label, {
332
- label: btn.label,
333
- pattern: 'generic',
334
- steps: generic.steps,
335
- assertion: generic.assertion,
336
- evidence: [],
337
- fallbackReason: reason,
338
- });
339
- }
340
- return result;
341
- }
342
- for (const btn of customButtons) {
343
- const htmlBtn = htmlButtonMap.get(btn.label);
344
- if (!htmlBtn || !htmlBtn.onclick) {
345
- const generic = renderStepsAndAssertion(btn.label, 'generic');
346
- result.set(btn.label, {
347
- label: btn.label,
348
- pattern: 'generic',
349
- steps: generic.steps,
350
- assertion: generic.assertion,
351
- evidence: [],
352
- fallbackReason: !htmlBtn
353
- ? `HTML 中未找到 label 为「${btn.label}」的按钮`
354
- : `HTML 中按钮「${btn.label}」缺少 onclick 属性`,
355
- });
356
- continue;
357
- }
358
- const onclick = htmlBtn.onclick;
359
- const functionName = extractFunctionNameFromOnclick(onclick);
360
- let functionBody = '';
361
- if (functionName && functionName !== 'epoint') {
362
- functionBody = extractFunctionBody(scriptContent, functionName) ?? '';
363
- }
364
- const combinedJs = `${onclick}\n${functionBody}`;
365
- const pattern = detectPattern(combinedJs);
366
- const confirmMessage = pattern === 'confirm_then_execute' ? extractConfirmMessage(combinedJs) : undefined;
367
- const rendered = renderStepsAndAssertion(btn.label, pattern, confirmMessage);
368
- const evidence = [onclick];
369
- if (functionName)
370
- evidence.push(`functionName=${functionName}`);
371
- if (functionBody)
372
- evidence.push(functionBody.slice(0, 500));
373
- if (confirmMessage)
374
- evidence.push(`confirmMessage=${confirmMessage}`);
375
- result.set(btn.label, {
376
- label: btn.label,
377
- pattern,
378
- functionName,
379
- steps: rendered.steps,
380
- assertion: rendered.assertion,
381
- evidence,
382
- });
383
- }
384
- return result;
385
- }
@@ -1,72 +0,0 @@
1
- /**
2
- * PRD 功能条目 → 自定义按钮 的确定性映射与模板渲染。
3
- *
4
- * 职责:
5
- * 1. 根据 code_list.implementedScope 把列表页 action='custom' 按钮映射到 PRD 功能条目。
6
- * 2. 没有 implementedScope 时,使用 label↔featureName 精确/包含匹配作为兜底。
7
- * 3. 将 PRD 字段渲染成中文用例 steps / assertion / businessIntent。
8
- *
9
- * 设计取舍:
10
- * - 全程纯代码逻辑,不调用 LLM,保证幂等。
11
- * - PRD 映射优先于 button-interaction-inference 的 assertion,因为 PRD 的 output/result
12
- * 是业务期望的事实源;inference 的 steps 仍优先用于 UI 机械步骤。
13
- * - 无法映射时返回 undefined,由调用方继续使用 inference 或通用兜底。
14
- */
15
- import type { PrdFunctionEntry } from '../extractors/prd-extractor.js';
16
- /** code_list 中 implementedScope 的单项。 */
17
- export type ImplementedScopeItem = {
18
- featureId: string;
19
- featureName: string;
20
- implementationStatus?: string;
21
- implementedBy: string[];
22
- note?: string;
23
- };
24
- /** 列表页自定义按钮的精简描述。 */
25
- export type CustomButtonRef = {
26
- label: string;
27
- customAction?: string;
28
- actionName?: string;
29
- backendMethod?: string | null;
30
- };
31
- /** 映射结果。 */
32
- export type PrdButtonMapping = {
33
- featureId: string;
34
- featureName: string;
35
- matchedBy: 'implementedScope' | 'label-exact' | 'label-contains';
36
- };
37
- /**
38
- * 把自定义按钮映射到 PRD 功能条目。
39
- *
40
- * 映射优先级:
41
- * 1. implementedScope.implementedBy 命中按钮的 customAction / actionName / backendMethod。
42
- * 2. 按钮 label 与 featureName 精确相等。
43
- * 3. featureName 包含 label 或 label 包含 featureName(按 PRD 顺序取第一个)。
44
- *
45
- * @param button - 按钮元数据。
46
- * @param prdFunctions - PRD 提取出的功能条目。
47
- * @param implementedScope - code_list 中的实现范围(可选)。
48
- * @returns 映射结果;未命中时返回 undefined。
49
- */
50
- export declare function mapButtonToPrdFunction(button: CustomButtonRef, prdFunctions: PrdFunctionEntry[], implementedScope?: ImplementedScopeItem[]): PrdButtonMapping | undefined;
51
- /**
52
- * 根据 PRD 功能条目渲染 businessIntent。
53
- */
54
- export declare function renderPrdBusinessIntent(entry: PrdFunctionEntry, label: string): string;
55
- /**
56
- * 根据 PRD 功能条目渲染执行步骤。
57
- *
58
- * 优先使用 entry.processingLogic;若为空则使用 entry.inputConditions。
59
- */
60
- export declare function renderPrdSteps(entry: PrdFunctionEntry, label: string): string;
61
- /**
62
- * 根据 PRD 功能条目渲染断言。
63
- *
64
- * 从 outputResult 中提取成功/失败结果,组合成选择型断言。
65
- * 若 outputResult 为空,则使用 businessRules 兜底。
66
- */
67
- export declare function renderPrdAssertion(entry: PrdFunctionEntry, label: string): string;
68
- /**
69
- * 获取 PRD 功能条目的输出结果文本(用于 trace / 测试断言)。
70
- */
71
- export declare function getPrdOutputResultText(entry: PrdFunctionEntry): string;
72
- //# sourceMappingURL=prd-button-mapper.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"prd-button-mapper.d.ts","sourceRoot":"","sources":["../../../src/testcase/prd-button-mapper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEvE,wCAAwC;AACxC,MAAM,MAAM,oBAAoB,GAAG;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,qBAAqB;AACrB,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,YAAY;AACZ,MAAM,MAAM,gBAAgB,GAAG;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,kBAAkB,GAAG,aAAa,GAAG,gBAAgB,CAAC;CAClE,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,eAAe,EACvB,YAAY,EAAE,gBAAgB,EAAE,EAChC,gBAAgB,CAAC,EAAE,oBAAoB,EAAE,GACxC,gBAAgB,GAAG,SAAS,CAsB9B;AA+CD;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAMtF;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAkB7E;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAiBjF;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,gBAAgB,GAAG,MAAM,CAEtE"}
@@ -1,148 +0,0 @@
1
- /**
2
- * PRD 功能条目 → 自定义按钮 的确定性映射与模板渲染。
3
- *
4
- * 职责:
5
- * 1. 根据 code_list.implementedScope 把列表页 action='custom' 按钮映射到 PRD 功能条目。
6
- * 2. 没有 implementedScope 时,使用 label↔featureName 精确/包含匹配作为兜底。
7
- * 3. 将 PRD 字段渲染成中文用例 steps / assertion / businessIntent。
8
- *
9
- * 设计取舍:
10
- * - 全程纯代码逻辑,不调用 LLM,保证幂等。
11
- * - PRD 映射优先于 button-interaction-inference 的 assertion,因为 PRD 的 output/result
12
- * 是业务期望的事实源;inference 的 steps 仍优先用于 UI 机械步骤。
13
- * - 无法映射时返回 undefined,由调用方继续使用 inference 或通用兜底。
14
- */
15
- /**
16
- * 把自定义按钮映射到 PRD 功能条目。
17
- *
18
- * 映射优先级:
19
- * 1. implementedScope.implementedBy 命中按钮的 customAction / actionName / backendMethod。
20
- * 2. 按钮 label 与 featureName 精确相等。
21
- * 3. featureName 包含 label 或 label 包含 featureName(按 PRD 顺序取第一个)。
22
- *
23
- * @param button - 按钮元数据。
24
- * @param prdFunctions - PRD 提取出的功能条目。
25
- * @param implementedScope - code_list 中的实现范围(可选)。
26
- * @returns 映射结果;未命中时返回 undefined。
27
- */
28
- export function mapButtonToPrdFunction(button, prdFunctions, implementedScope) {
29
- // 1. Primary:implementedScope
30
- if (implementedScope && implementedScope.length > 0) {
31
- const actionKeys = collectButtonActionKeys(button);
32
- for (const scope of implementedScope) {
33
- for (const implementedBy of scope.implementedBy) {
34
- const segments = implementedBy.split('.').map((s) => s.trim());
35
- for (const segment of segments) {
36
- if (segment && actionKeys.includes(segment)) {
37
- return {
38
- featureId: scope.featureId,
39
- featureName: scope.featureName,
40
- matchedBy: 'implementedScope',
41
- };
42
- }
43
- }
44
- }
45
- }
46
- }
47
- // 2. Fallback:label 匹配
48
- return mapByLabel(button.label, prdFunctions);
49
- }
50
- /**
51
- * 收集可用于匹配 implementedScope 的按钮动作键。
52
- */
53
- function collectButtonActionKeys(button) {
54
- const keys = new Set();
55
- if (button.customAction)
56
- keys.add(button.customAction);
57
- if (button.actionName)
58
- keys.add(button.actionName);
59
- if (button.backendMethod)
60
- keys.add(button.backendMethod);
61
- return Array.from(keys).filter(Boolean);
62
- }
63
- /**
64
- * 按 label↔featureName 匹配。
65
- */
66
- function mapByLabel(label, prdFunctions) {
67
- const normalizedLabel = label.trim();
68
- // 精确匹配
69
- const exact = prdFunctions.find((f) => f.featureName.trim() === normalizedLabel);
70
- if (exact) {
71
- return { featureId: exact.featureId, featureName: exact.featureName, matchedBy: 'label-exact' };
72
- }
73
- // 包含匹配:featureName 包含 label
74
- const contains = prdFunctions.find((f) => f.featureName.includes(normalizedLabel));
75
- if (contains) {
76
- return { featureId: contains.featureId, featureName: contains.featureName, matchedBy: 'label-contains' };
77
- }
78
- // 包含匹配:label 包含 featureName
79
- const containedBy = prdFunctions.find((f) => normalizedLabel.includes(f.featureName));
80
- if (containedBy) {
81
- return {
82
- featureId: containedBy.featureId,
83
- featureName: containedBy.featureName,
84
- matchedBy: 'label-contains',
85
- };
86
- }
87
- return undefined;
88
- }
89
- /**
90
- * 根据 PRD 功能条目渲染 businessIntent。
91
- */
92
- export function renderPrdBusinessIntent(entry, label) {
93
- const description = entry.description?.trim();
94
- if (description) {
95
- return `覆盖列表页「${label}」按钮的控件功能:${description}`;
96
- }
97
- return `覆盖列表页「${label}」按钮的控件功能(featureId=${entry.featureId})。`;
98
- }
99
- /**
100
- * 根据 PRD 功能条目渲染执行步骤。
101
- *
102
- * 优先使用 entry.processingLogic;若为空则使用 entry.inputConditions。
103
- */
104
- export function renderPrdSteps(entry, label) {
105
- const steps = [];
106
- steps.push(`1. 在目标页面定位并点击「${label}」按钮。`);
107
- const logic = entry.processingLogic.filter(Boolean);
108
- if (logic.length > 0) {
109
- logic.forEach((item, index) => {
110
- steps.push(`${index + 2}. ${item}。`);
111
- });
112
- }
113
- else if (entry.inputConditions) {
114
- const input = entry.inputConditions.trim();
115
- if (input && input !== '无') {
116
- steps.push(`2. 确保满足输入条件:${input}。`);
117
- }
118
- }
119
- steps.push(`${steps.length + 1}. 观察并记录页面反馈。`);
120
- return steps.join(' ');
121
- }
122
- /**
123
- * 根据 PRD 功能条目渲染断言。
124
- *
125
- * 从 outputResult 中提取成功/失败结果,组合成选择型断言。
126
- * 若 outputResult 为空,则使用 businessRules 兜底。
127
- */
128
- export function renderPrdAssertion(entry, label) {
129
- const outputs = entry.outputResult.filter(Boolean);
130
- if (outputs.length > 0) {
131
- // 若 outputResult 有 2 条以上,通常对应成功/失败分支,用「;或」连接。
132
- if (outputs.length >= 2) {
133
- return `页面出现${outputs[0]};或出现${outputs[1]}。`;
134
- }
135
- return `页面出现${outputs[0]}。`;
136
- }
137
- const rules = entry.businessRules.filter(Boolean);
138
- if (rules.length > 0) {
139
- return `点击「${label}」按钮后,${rules.join(';')}。`;
140
- }
141
- return `点击「${label}」按钮后,页面应出现与该功能对应的可见响应(如提示信息、弹窗确认或数据刷新)。`;
142
- }
143
- /**
144
- * 获取 PRD 功能条目的输出结果文本(用于 trace / 测试断言)。
145
- */
146
- export function getPrdOutputResultText(entry) {
147
- return renderPrdAssertion(entry, entry.featureName);
148
- }
@@ -1,50 +0,0 @@
1
- /** testcase.json5 v2 顶层结构(REQ-DATA-02)。字段名永远英文(机器格式)。 */
2
- export type TestcaseV2 = {
3
- schemaVersion: 'v2';
4
- scenario: string;
5
- moduleId: string;
6
- moduleName: string;
7
- menuPath: string;
8
- requiredSystems: Array<{
9
- url: string;
10
- systemName?: string;
11
- }>;
12
- requiredRoles: Array<{
13
- role: string;
14
- hint?: string;
15
- }>;
16
- coveredActions: Array<{
17
- actionId: string;
18
- kind: string;
19
- evidence: string[];
20
- }>;
21
- uncoveredCandidates: Array<{
22
- actionId: string;
23
- label: string;
24
- status: 'planned' | 'candidate' | 'unresolved' | 'resolved';
25
- requiredRole: string;
26
- businessIntent: string;
27
- assertionExpectation: string;
28
- evidence: string[];
29
- }>;
30
- cases: Array<{
31
- caseId: string;
32
- title: string;
33
- scenario: string;
34
- reviewStatus: 'confirmed' | 'needs_review' | 'blocked';
35
- requiredRole: string;
36
- evidence: string[];
37
- steps: string[];
38
- assertions: string[];
39
- unresolved: string[];
40
- }>;
41
- reasoningSummary: {
42
- conclusion: string;
43
- evidenceChain: string[];
44
- alternatives: string[];
45
- confidence: 'high' | 'medium' | 'low';
46
- risks: string[];
47
- needsHumanReview: boolean;
48
- };
49
- };
50
- //# sourceMappingURL=testcase-v2.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"testcase-v2.d.ts","sourceRoot":"","sources":["../../../src/testcase/testcase-v2.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,MAAM,MAAM,UAAU,GAAG;IACvB,aAAa,EAAE,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC7D,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtD,cAAc,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAC9E,mBAAmB,EAAE,KAAK,CAAC;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,SAAS,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,CAAC;QAC5D,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;KACpB,CAAC,CAAC;IACH,KAAK,EAAE,KAAK,CAAC;QACX,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,WAAW,GAAG,cAAc,GAAG,SAAS,CAAC;QACvD,YAAY,EAAE,MAAM,CAAC;QACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;QACnB,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,UAAU,EAAE,MAAM,EAAE,CAAC;QACrB,UAAU,EAAE,MAAM,EAAE,CAAC;KACtB,CAAC,CAAC;IACH,gBAAgB,EAAE;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,EAAE,CAAC;QACxB,YAAY,EAAE,MAAM,EAAE,CAAC;QACvB,UAAU,EAAE,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;QACtC,KAAK,EAAE,MAAM,EAAE,CAAC;QAChB,gBAAgB,EAAE,OAAO,CAAC;KAC3B,CAAC;CACH,CAAC"}
@@ -1 +0,0 @@
1
- export {};