@epoint-testtech/ep-stage-skill 0.0.3-alpha.1

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 (151) hide show
  1. package/SKILL.md +27 -0
  2. package/codex-skill/ep-stage/create-project/SKILL.md +59 -0
  3. package/codex-skill/ep-stage/glue-test/SKILL.md +258 -0
  4. package/codex-skill/ep-stage/glue-test/references/crud-pipeline.md +139 -0
  5. package/codex-skill/ep-stage/glue-test/references/gap-review-protocol.md +43 -0
  6. package/codex-skill/ep-stage/glue-test/references/harness-principles.md +46 -0
  7. package/codex-skill/ep-stage/glue-test/scripts/generate-crud-spec.mjs +149 -0
  8. package/codex-skill/ep-stage/glue-testcase/SKILL.md +31 -0
  9. package/codex-skill/ep-stage/glue-testcase/examples/observable-testcase.json +40 -0
  10. package/codex-skill/ep-stage/glue-testcase/references/testcase-schema.md +67 -0
  11. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +27 -0
  12. package/codex-skill/ep-stage/scripts/validate-skill.mjs +73 -0
  13. package/dist/src/capability/coverage-diff.d.ts +34 -0
  14. package/dist/src/capability/coverage-diff.d.ts.map +1 -0
  15. package/dist/src/capability/coverage-diff.js +91 -0
  16. package/dist/src/capability/page-structure.d.ts +31 -0
  17. package/dist/src/capability/page-structure.d.ts.map +1 -0
  18. package/dist/src/capability/page-structure.js +50 -0
  19. package/dist/src/capability/scenario-inference.d.ts +36 -0
  20. package/dist/src/capability/scenario-inference.d.ts.map +1 -0
  21. package/dist/src/capability/scenario-inference.js +114 -0
  22. package/dist/src/cli/generate-crud-contract.d.ts +2 -0
  23. package/dist/src/cli/generate-crud-contract.d.ts.map +1 -0
  24. package/dist/src/cli/generate-crud-contract.js +77 -0
  25. package/dist/src/cli/generate-playwright-tests.d.ts +30 -0
  26. package/dist/src/cli/generate-playwright-tests.d.ts.map +1 -0
  27. package/dist/src/cli/generate-playwright-tests.js +81 -0
  28. package/dist/src/cli/run-gap-pipeline.d.ts +256 -0
  29. package/dist/src/cli/run-gap-pipeline.d.ts.map +1 -0
  30. package/dist/src/cli/run-gap-pipeline.js +1468 -0
  31. package/dist/src/context/stage-context.d.ts +63 -0
  32. package/dist/src/context/stage-context.d.ts.map +1 -0
  33. package/dist/src/context/stage-context.js +297 -0
  34. package/dist/src/contracts/crud-business-module.d.ts +645 -0
  35. package/dist/src/contracts/crud-business-module.d.ts.map +1 -0
  36. package/dist/src/contracts/crud-business-module.js +1 -0
  37. package/dist/src/contracts/gap-inference.d.ts +213 -0
  38. package/dist/src/contracts/gap-inference.d.ts.map +1 -0
  39. package/dist/src/contracts/gap-inference.js +11 -0
  40. package/dist/src/contracts/observable-chain.d.ts +250 -0
  41. package/dist/src/contracts/observable-chain.d.ts.map +1 -0
  42. package/dist/src/contracts/observable-chain.js +1 -0
  43. package/dist/src/extractors/code-list.d.ts +40 -0
  44. package/dist/src/extractors/code-list.d.ts.map +1 -0
  45. package/dist/src/extractors/code-list.js +225 -0
  46. package/dist/src/extractors/html-page.d.ts +67 -0
  47. package/dist/src/extractors/html-page.d.ts.map +1 -0
  48. package/dist/src/extractors/html-page.js +195 -0
  49. package/dist/src/extractors/java-action.d.ts +8 -0
  50. package/dist/src/extractors/java-action.d.ts.map +1 -0
  51. package/dist/src/extractors/java-action.js +53 -0
  52. package/dist/src/extractors/spec-yaml.d.ts +28 -0
  53. package/dist/src/extractors/spec-yaml.d.ts.map +1 -0
  54. package/dist/src/extractors/spec-yaml.js +29 -0
  55. package/dist/src/gap-planner/action-candidates.d.ts +9 -0
  56. package/dist/src/gap-planner/action-candidates.d.ts.map +1 -0
  57. package/dist/src/gap-planner/action-candidates.js +66 -0
  58. package/dist/src/gap-planner/list-gap-workflows.d.ts +17 -0
  59. package/dist/src/gap-planner/list-gap-workflows.d.ts.map +1 -0
  60. package/dist/src/gap-planner/list-gap-workflows.js +47 -0
  61. package/dist/src/gap-planner/plan-agent-workflows.d.ts +26 -0
  62. package/dist/src/gap-planner/plan-agent-workflows.d.ts.map +1 -0
  63. package/dist/src/gap-planner/plan-agent-workflows.js +116 -0
  64. package/dist/src/gap-planner/skeleton-coverage.d.ts +9 -0
  65. package/dist/src/gap-planner/skeleton-coverage.d.ts.map +1 -0
  66. package/dist/src/gap-planner/skeleton-coverage.js +41 -0
  67. package/dist/src/gap-planner/stable-id.d.ts +16 -0
  68. package/dist/src/gap-planner/stable-id.d.ts.map +1 -0
  69. package/dist/src/gap-planner/stable-id.js +19 -0
  70. package/dist/src/generalization/generalization-eval.d.ts +71 -0
  71. package/dist/src/generalization/generalization-eval.d.ts.map +1 -0
  72. package/dist/src/generalization/generalization-eval.js +53 -0
  73. package/dist/src/generators/agent-inferred-workflow-script.d.ts +22 -0
  74. package/dist/src/generators/agent-inferred-workflow-script.d.ts.map +1 -0
  75. package/dist/src/generators/agent-inferred-workflow-script.js +230 -0
  76. package/dist/src/generators/stage-skeleton-script.d.ts +107 -0
  77. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -0
  78. package/dist/src/generators/stage-skeleton-script.js +607 -0
  79. package/dist/src/index.d.ts +52 -0
  80. package/dist/src/index.d.ts.map +1 -0
  81. package/dist/src/index.js +26 -0
  82. package/dist/src/material/material-inventory.d.ts +92 -0
  83. package/dist/src/material/material-inventory.d.ts.map +1 -0
  84. package/dist/src/material/material-inventory.js +191 -0
  85. package/dist/src/normalizers/crud-contract.d.ts +107 -0
  86. package/dist/src/normalizers/crud-contract.d.ts.map +1 -0
  87. package/dist/src/normalizers/crud-contract.js +1068 -0
  88. package/dist/src/testcase/testcase-generator.d.ts +43 -0
  89. package/dist/src/testcase/testcase-generator.d.ts.map +1 -0
  90. package/dist/src/testcase/testcase-generator.js +152 -0
  91. package/dist/src/testcase/testcase-skeleton.d.ts +91 -0
  92. package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -0
  93. package/dist/src/testcase/testcase-skeleton.js +121 -0
  94. package/dist/src/testcase/testcase-spec-assembly.d.ts +11 -0
  95. package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -0
  96. package/dist/src/testcase/testcase-spec-assembly.js +24 -0
  97. package/dist/src/trace/review-summary.d.ts +17 -0
  98. package/dist/src/trace/review-summary.d.ts.map +1 -0
  99. package/dist/src/trace/review-summary.js +34 -0
  100. package/dist/src/trace/trace-writer.d.ts +17 -0
  101. package/dist/src/trace/trace-writer.d.ts.map +1 -0
  102. package/dist/src/trace/trace-writer.js +81 -0
  103. package/dist/test/crud-contract.test.d.ts +2 -0
  104. package/dist/test/crud-contract.test.d.ts.map +1 -0
  105. package/dist/test/crud-contract.test.js +819 -0
  106. package/dist/test/gap-inference.test.d.ts +2 -0
  107. package/dist/test/gap-inference.test.d.ts.map +1 -0
  108. package/dist/test/gap-inference.test.js +597 -0
  109. package/dist/test/generalization.test.d.ts +2 -0
  110. package/dist/test/generalization.test.d.ts.map +1 -0
  111. package/dist/test/generalization.test.js +73 -0
  112. package/dist/test/material-inventory.test.d.ts +2 -0
  113. package/dist/test/material-inventory.test.d.ts.map +1 -0
  114. package/dist/test/material-inventory.test.js +141 -0
  115. package/dist/test/observable-chain.test.d.ts +2 -0
  116. package/dist/test/observable-chain.test.d.ts.map +1 -0
  117. package/dist/test/observable-chain.test.js +123 -0
  118. package/dist/test/observable-pipeline.test.d.ts +2 -0
  119. package/dist/test/observable-pipeline.test.d.ts.map +1 -0
  120. package/dist/test/observable-pipeline.test.js +461 -0
  121. package/dist/test/page-structure.test.d.ts +2 -0
  122. package/dist/test/page-structure.test.d.ts.map +1 -0
  123. package/dist/test/page-structure.test.js +45 -0
  124. package/dist/test/scenario-inference.test.d.ts +2 -0
  125. package/dist/test/scenario-inference.test.d.ts.map +1 -0
  126. package/dist/test/scenario-inference.test.js +73 -0
  127. package/dist/test/stage-context.test.d.ts +2 -0
  128. package/dist/test/stage-context.test.d.ts.map +1 -0
  129. package/dist/test/stage-context.test.js +263 -0
  130. package/dist/test/testcase-generator.test.d.ts +2 -0
  131. package/dist/test/testcase-generator.test.d.ts.map +1 -0
  132. package/dist/test/testcase-generator.test.js +276 -0
  133. package/dist/test/testcase-skeleton.test.d.ts +2 -0
  134. package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
  135. package/dist/test/testcase-skeleton.test.js +185 -0
  136. package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
  137. package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
  138. package/dist/test/testcase-spec-assembly.test.js +105 -0
  139. package/dist/vitest.config.d.ts +3 -0
  140. package/dist/vitest.config.d.ts.map +1 -0
  141. package/dist/vitest.config.js +7 -0
  142. package/docs/README.md +134 -0
  143. package/docs/mvp-usage-guide.md +298 -0
  144. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -0
  145. package/examples/schemeresource.module-hints.json +38 -0
  146. package/examples/schemeresource.observable.code_list.md +37 -0
  147. package/examples/zwplace-observable-docs/zwplace.context.md +16 -0
  148. package/examples/zwplace-placecategory-validation.json +29 -0
  149. package/examples/zwplace.module-hints.json +69 -0
  150. package/examples/zwplace.observable.code_list.md +37 -0
  151. package/package.json +38 -0
@@ -0,0 +1,607 @@
1
+ /**
2
+ * stage-skeleton CRUD 骨架脚本生成器。
3
+ *
4
+ * 核心职责:
5
+ * - 将 crud-business-module/v1 契约映射为胶水模板骨架使用的 CrudFlowSlots
6
+ * - 生成调用 LoginPage、MenuPage、CrudPage 的 Playwright 测试文件
7
+ * - 避免直接拼接 page.fill/page.click,保证测试流程复用骨架层
8
+ */
9
+ /**
10
+ * 将字符串渲染为 TypeScript 单引号字面量。
11
+ *
12
+ * @param value 原始字符串
13
+ * @returns 可直接写入 TypeScript 源码的单引号字符串
14
+ */
15
+ function singleQuoted(value) {
16
+ return `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'")}'`;
17
+ }
18
+ /**
19
+ * 将 locator 渲染为 TypeScript 字符串字面量。
20
+ *
21
+ * @param value locator 字符串
22
+ * @returns 可直接写入 TypeScript 源码的字符串
23
+ */
24
+ function locatorLiteral(value) {
25
+ return JSON.stringify(value);
26
+ }
27
+ /**
28
+ * 为 XPath locator 补齐 Playwright locator 引擎前缀。
29
+ *
30
+ * @param locator 契约中的 locator
31
+ * @returns 可传给 Playwright locator 的表达式
32
+ */
33
+ function toPlaywrightLocator(locator) {
34
+ if (locator.startsWith('xpath=') || locator.startsWith('css=')) {
35
+ return locator;
36
+ }
37
+ if (locator.startsWith('//') || locator.startsWith('(')) {
38
+ return `xpath=${locator}`;
39
+ }
40
+ return locator;
41
+ }
42
+ /**
43
+ * 优先使用契约中的 locator,缺失时再退回骨架默认 locator。
44
+ *
45
+ * @param control 流程控件契约
46
+ * @param fallbackLocator 骨架回退 locator
47
+ * @returns 最终用于骨架脚本的 locator
48
+ */
49
+ function resolveControlLocator(control, fallbackLocator) {
50
+ if (control?.locator) {
51
+ return toPlaywrightLocator(control.locator);
52
+ }
53
+ return fallbackLocator;
54
+ }
55
+ /**
56
+ * 优先使用字段契约中已有的 locator,缺失时再按字段类型推导。
57
+ *
58
+ * @param field 字段契约
59
+ * @returns 最终用于骨架脚本的字段 locator
60
+ */
61
+ function resolveFieldLocator(field) {
62
+ if (field.locator) {
63
+ return toPlaywrightLocator(field.locator);
64
+ }
65
+ return toMiniInputLocator(field);
66
+ }
67
+ /**
68
+ * 将页面字段转换为 zwplace mini 输入框定位。
69
+ *
70
+ * @param field 字段契约
71
+ * @returns 适配 stage-skeleton 骨架的输入框 locator
72
+ */
73
+ function toMiniInputLocator(field) {
74
+ if (field.controlType === 'mini-textbox') {
75
+ return `xpath=//div[@label=${singleQuoted(field.label)}]//input[@class='mini-textbox-input']`;
76
+ }
77
+ if (field.locator) {
78
+ return toPlaywrightLocator(field.locator);
79
+ }
80
+ return `xpath=//div[@label=${singleQuoted(field.label)}]//input`;
81
+ }
82
+ /**
83
+ * 将按钮标签转换为 zwplace mini 按钮定位。
84
+ *
85
+ * @param label 按钮中文文本
86
+ * @returns 按钮 locator
87
+ */
88
+ function buttonLocatorByLabel(label) {
89
+ return `xpath=//span[contains(normalize-space(),${singleQuoted(label)})]`;
90
+ }
91
+ /**
92
+ * 将保存按钮标签转换为精确文本定位。
93
+ *
94
+ * @param label 按钮中文文本
95
+ * @returns 保存按钮 locator
96
+ */
97
+ function exactButtonLocatorByLabel(label) {
98
+ return `xpath=//span[normalize-space()=${singleQuoted(label)}]`;
99
+ }
100
+ /**
101
+ * 生成 iframe src 关键字。
102
+ *
103
+ * @param pageId 页面 ID
104
+ * @param shouldAppendHtml 是否补齐 .html 后缀
105
+ * @returns iframe src 关键字
106
+ */
107
+ function iframeKeyword(pageId, shouldAppendHtml = false) {
108
+ if (!pageId)
109
+ return '';
110
+ if (shouldAppendHtml && !pageId.endsWith('.html')) {
111
+ return `${pageId}.html`;
112
+ }
113
+ return pageId;
114
+ }
115
+ /**
116
+ * 将结果表格 ID 转为 XPath 根节点。
117
+ *
118
+ * @param gridId 表格 ID
119
+ * @returns XPath 根路径
120
+ */
121
+ function gridRootXPath(gridId) {
122
+ return `//*[@id='${gridId}']`;
123
+ }
124
+ /**
125
+ * 将 contract 中的行操作 locator 嵌入到目标行模板中。
126
+ *
127
+ * @param locator 行操作 locator
128
+ * @returns 可拼接到行模板后的相对 XPath
129
+ */
130
+ function toRowActionRelativeXPath(locator) {
131
+ if (!locator) {
132
+ return "//i[@data-tooltip='修改']";
133
+ }
134
+ const rawLocator = locator.replace(/^xpath=/, '');
135
+ if (rawLocator.startsWith('.//')) {
136
+ return rawLocator.slice(1);
137
+ }
138
+ if (rawLocator.startsWith('//')) {
139
+ return rawLocator;
140
+ }
141
+ if (rawLocator.startsWith('/')) {
142
+ return `/${rawLocator.replace(/^\/+/, '')}`;
143
+ }
144
+ return `//${rawLocator}`;
145
+ }
146
+ /**
147
+ * 获取契约中的字段定义。
148
+ *
149
+ * @param fields 字段列表
150
+ * @param fieldName 字段名
151
+ * @param roleName 页面角色说明,用于错误消息
152
+ * @returns 匹配的字段契约
153
+ * @throws 当字段不存在时抛出异常,避免生成不可执行的骨架代码
154
+ */
155
+ function requireField(fields, fieldName, roleName) {
156
+ const field = fields?.find((item) => item.name === fieldName);
157
+ if (!field) {
158
+ throw new Error(`无法在${roleName}中找到字段: ${fieldName}`);
159
+ }
160
+ return field;
161
+ }
162
+ /**
163
+ * 将契约中的删除结果映射为骨架支持的删除策略。
164
+ *
165
+ * @param outcome 契约删除预期
166
+ * @returns 骨架删除策略
167
+ * @throws 当删除策略仍需要人工确认时抛出异常
168
+ */
169
+ function mapDeletePolicy(outcome) {
170
+ if (outcome === 'success_delete' || outcome === 'blocked_by_business_rule') {
171
+ return outcome;
172
+ }
173
+ throw new Error(`当前删除策略无法自动映射到 CrudFlowSlots: ${outcome ?? 'undefined'}`);
174
+ }
175
+ /**
176
+ * 查找删除被阻塞时的提示消息。
177
+ *
178
+ * @param contract CRUD 业务模块契约
179
+ * @returns 删除阻塞提示消息
180
+ */
181
+ function findBlockedDeleteMessage(contract) {
182
+ return contract.assertions.find((assertion) => assertion.type === 'toast_message' && assertion.expectedMessage.includes('不可删除'))?.expectedMessage;
183
+ }
184
+ /**
185
+ * 将契约中的数据生成策略映射为骨架支持的策略。
186
+ *
187
+ * @param strategy 契约数据生成策略
188
+ * @returns 骨架数据生成策略
189
+ */
190
+ function mapDataStrategy(strategy) {
191
+ if (strategy === 'fixed_value')
192
+ return 'fixed_value';
193
+ if (strategy === 'timestamp_prefix')
194
+ return 'timestamp_prefix';
195
+ throw new Error(`当前数据生成策略无法自动映射到 CrudFlowSlots: ${strategy}`);
196
+ }
197
+ /**
198
+ * 从 create.overrideFields 中读取数据主键种子值。
199
+ *
200
+ * @param contract CRUD 业务模块契约
201
+ * @returns 数据主键种子值
202
+ */
203
+ function resolveSeedValue(contract) {
204
+ const dataKeyOverride = contract.flows.create?.overrideFields.find((field) => field.field === contract.dataKey.field);
205
+ return dataKeyOverride?.value ?? `自动化测试${contract.dataKey.label}`;
206
+ }
207
+ function resolveConditionSeedValue(contract, condition) {
208
+ const override = contract.flows.create?.overrideFields.find((field) => field.field === condition.field);
209
+ return condition.seedValue ?? override?.value ?? `自动化测试${condition.label}`;
210
+ }
211
+ function requireSearchConditions(contract) {
212
+ if (contract.searchConditions && contract.searchConditions.length > 0) {
213
+ return contract.searchConditions;
214
+ }
215
+ return [{
216
+ field: contract.dataKey.field,
217
+ label: contract.dataKey.label,
218
+ component: 'input',
219
+ seedValue: resolveSeedValue(contract),
220
+ generationStrategy: contract.dataKey.generationStrategy,
221
+ updatePrefix: '修改',
222
+ sources: contract.dataKey.sources
223
+ }];
224
+ }
225
+ function mapSearchConditions(contract) {
226
+ return requireSearchConditions(contract).map((condition) => {
227
+ if (condition.component === 'listbox') {
228
+ if (!condition.value) {
229
+ throw new Error(`listbox 搜索条件 ${condition.field} 缺少测试值,请在 ModuleHints.searchConditions 中补充 value`);
230
+ }
231
+ return {
232
+ component: 'listbox',
233
+ field: condition.field,
234
+ label: condition.label,
235
+ value: condition.value,
236
+ createSelections: condition.createSelections,
237
+ };
238
+ }
239
+ const addField = requireField(contract.pages.add?.fields, condition.field, '新增页');
240
+ const editField = requireField(contract.pages.edit?.fields, condition.field, '修改页');
241
+ const listField = requireField(contract.pages.list.fields, condition.field, '列表页');
242
+ return {
243
+ component: 'input',
244
+ field: condition.field,
245
+ label: condition.label,
246
+ seedValue: resolveConditionSeedValue(contract, condition),
247
+ strategy: mapDataStrategy(condition.generationStrategy ?? contract.dataKey.generationStrategy),
248
+ updatePrefix: condition.updatePrefix ?? '修改',
249
+ addLocator: resolveFieldLocator(addField),
250
+ editLocator: resolveFieldLocator(editField),
251
+ listLocator: resolveFieldLocator(listField),
252
+ };
253
+ });
254
+ }
255
+ /**
256
+ * 将 CRUD 业务模块契约映射为 stage-skeleton 的 CrudFlowSlots。
257
+ *
258
+ * @param contract CRUD 业务模块契约
259
+ * @returns 可传给 CrudPage 的槽位对象
260
+ * @throws 当关键页面、字段或策略缺失时抛出异常
261
+ */
262
+ export function mapContractToStageSkeletonCrudSlots(contract) {
263
+ const createFlow = contract.flows.create;
264
+ const searchFlow = contract.flows.search;
265
+ const updateFlow = contract.flows.update;
266
+ const deleteFlow = contract.flows.delete;
267
+ if (!createFlow || !searchFlow || !updateFlow || !deleteFlow) {
268
+ throw new Error('生成 stage-skeleton 骨架前,contract 必须包含完整的 create/search/update/delete flows');
269
+ }
270
+ const deletePolicy = mapDeletePolicy(deleteFlow.expectedOutcome);
271
+ const blockedDeleteMessage = deletePolicy === 'blocked_by_business_rule' ? findBlockedDeleteMessage(contract) : undefined;
272
+ const resultGridId = searchFlow.resultGrid;
273
+ if (deletePolicy === 'blocked_by_business_rule' && !blockedDeleteMessage) {
274
+ throw new Error('删除策略为 blocked_by_business_rule 时,必须能从契约断言中解析 blockedDeleteMessage');
275
+ }
276
+ return {
277
+ moduleId: contract.module.id,
278
+ searchConditions: mapSearchConditions(contract),
279
+ frames: {
280
+ listIframeSrcKeyword: iframeKeyword(contract.pages.list.iframeSrcKeyword, true),
281
+ addIframeSrcKeyword: iframeKeyword(contract.pages.add?.iframeSrcKeyword),
282
+ editIframeSrcKeyword: iframeKeyword(contract.pages.edit?.iframeSrcKeyword),
283
+ },
284
+ locators: {
285
+ createButton: resolveControlLocator(createFlow.entryButton, buttonLocatorByLabel(createFlow.entryButton.label ?? '新增')),
286
+ addAutofillTrigger: "xpath=//*[@title='默认填充']",
287
+ addSaveButton: resolveControlLocator(createFlow.saveButton, exactButtonLocatorByLabel(createFlow.saveButton.label ?? '保存并关闭')),
288
+ listSearchSubmit: resolveControlLocator(searchFlow.submitControl, "xpath=//span[text()='搜索']"),
289
+ listResultValueLocatorTemplate: `xpath=${gridRootXPath(resultGridId)}//div[text()='{{value}}']`,
290
+ listEditActionLocatorTemplate: `xpath=${gridRootXPath(resultGridId)}//tr[.//div[text()='{{value}}']]${toRowActionRelativeXPath(updateFlow.entryAction.locator)}`,
291
+ editSaveButton: resolveControlLocator(updateFlow.saveButton, exactButtonLocatorByLabel(updateFlow.saveButton.label ?? '保存并关闭')),
292
+ listRowSelectLocatorTemplate: `xpath=${gridRootXPath(resultGridId)}//div[text()='{{value}}']`,
293
+ listDeleteButton: resolveControlLocator(deleteFlow.entryButton, buttonLocatorByLabel(deleteFlow.entryButton.label ?? '删除选定')),
294
+ deleteConfirmButton: resolveControlLocator(deleteFlow.confirmControl, 'text=确定'),
295
+ },
296
+ assertions: {
297
+ deletePolicy,
298
+ blockedDeleteMessage,
299
+ },
300
+ };
301
+ }
302
+ /**
303
+ * 渲染对象中的可选字符串属性。
304
+ *
305
+ * @param key 属性名
306
+ * @param value 属性值
307
+ * @param level 缩进层级
308
+ * @returns 属性源码行,值不存在时返回空字符串
309
+ */
310
+ function renderOptionalStringProperty(key, value, level) {
311
+ if (value === undefined)
312
+ return '';
313
+ return `${' '.repeat(level)}${key}: ${singleQuoted(value)},\n`;
314
+ }
315
+ function renderListboxSelections(selections, level) {
316
+ if (!selections || selections.length === 0) {
317
+ return '';
318
+ }
319
+ const indent = ' '.repeat(level);
320
+ const itemIndent = ' '.repeat(level + 4);
321
+ const items = selections
322
+ .map((selection) => `${itemIndent}{ field: ${singleQuoted(selection.field)}, label: ${singleQuoted(selection.label)}, value: ${singleQuoted(selection.value)} }`)
323
+ .join(',\n');
324
+ return `${indent}createSelections: [\n${items},\n${indent}],\n`;
325
+ }
326
+ function renderSearchCondition(condition, level) {
327
+ const indent = ' '.repeat(level);
328
+ const innerIndent = ' '.repeat(level + 4);
329
+ if (condition.component === 'listbox') {
330
+ return `${indent}{
331
+ ${innerIndent}component: 'listbox',
332
+ ${innerIndent}field: ${singleQuoted(condition.field)},
333
+ ${innerIndent}label: ${singleQuoted(condition.label)},
334
+ ${innerIndent}value: ${singleQuoted(condition.value)},
335
+ ${renderListboxSelections(condition.createSelections, level + 4)}${indent}}`;
336
+ }
337
+ return `${indent}{
338
+ ${innerIndent}component: 'input',
339
+ ${innerIndent}field: ${singleQuoted(condition.field)},
340
+ ${innerIndent}label: ${singleQuoted(condition.label)},
341
+ ${innerIndent}seedValue: ${singleQuoted(condition.seedValue)},
342
+ ${renderOptionalStringProperty('strategy', condition.strategy, level + 4)}${renderOptionalStringProperty('fixedValue', condition.fixedValue, level + 4)}${renderOptionalStringProperty('updatePrefix', condition.updatePrefix, level + 4)}${renderOptionalStringProperty('searchSeedValue', condition.searchSeedValue, level + 4)}${innerIndent}addLocator: ${locatorLiteral(condition.addLocator)},
343
+ ${innerIndent}editLocator: ${locatorLiteral(condition.editLocator)},
344
+ ${innerIndent}listLocator: ${locatorLiteral(condition.listLocator)},
345
+ ${indent}}`;
346
+ }
347
+ function renderSearchConditions(conditions, level) {
348
+ const indent = ' '.repeat(level);
349
+ const items = conditions.map((condition) => renderSearchCondition(condition, level + 4)).join(',\n');
350
+ return `${indent}searchConditions: [\n${items},\n${indent}],`;
351
+ }
352
+ /**
353
+ * 将 CrudFlowSlots 渲染为 TypeScript 对象字面量。
354
+ *
355
+ * @param slots CRUD 骨架槽位
356
+ * @returns TypeScript 对象源码
357
+ */
358
+ export function renderStageSkeletonCrudSlots(slots) {
359
+ return `{
360
+ moduleId: ${singleQuoted(slots.moduleId)},
361
+ ${renderSearchConditions(slots.searchConditions, 8)}
362
+ frames: {
363
+ listIframeSrcKeyword: ${singleQuoted(slots.frames.listIframeSrcKeyword)},
364
+ addIframeSrcKeyword: ${singleQuoted(slots.frames.addIframeSrcKeyword)},
365
+ editIframeSrcKeyword: ${singleQuoted(slots.frames.editIframeSrcKeyword)},
366
+ },
367
+ locators: {
368
+ createButton: ${locatorLiteral(slots.locators.createButton)},
369
+ ${renderOptionalStringProperty('addAutofillTrigger', slots.locators.addAutofillTrigger, 12)}
370
+ addSaveButton: ${locatorLiteral(slots.locators.addSaveButton)},
371
+ listSearchSubmit: ${locatorLiteral(slots.locators.listSearchSubmit)},
372
+ listResultValueLocatorTemplate: ${locatorLiteral(slots.locators.listResultValueLocatorTemplate)},
373
+ listEditActionLocatorTemplate: ${locatorLiteral(slots.locators.listEditActionLocatorTemplate)},
374
+ editSaveButton: ${locatorLiteral(slots.locators.editSaveButton)},
375
+ listRowSelectLocatorTemplate: ${locatorLiteral(slots.locators.listRowSelectLocatorTemplate)},
376
+ listDeleteButton: ${locatorLiteral(slots.locators.listDeleteButton)},
377
+ ${renderOptionalStringProperty('deleteConfirmButton', slots.locators.deleteConfirmButton, 12)} },
378
+ assertions: {
379
+ deletePolicy: ${singleQuoted(slots.assertions.deletePolicy)},
380
+ ${renderOptionalStringProperty('blockedDeleteMessage', slots.assertions.blockedDeleteMessage, 12)} },
381
+ }`;
382
+ }
383
+ /**
384
+ * 渲染导出骨架槽位对象。
385
+ *
386
+ * @param contract CRUD 业务模块契约
387
+ * @returns TypeScript 对象字面量源码
388
+ */
389
+ function renderExportSlots(contract) {
390
+ return `{
391
+ moduleId: ${singleQuoted(contract.module.id)},
392
+ frames: {
393
+ listIframeSrcKeyword: ${singleQuoted(iframeKeyword(contract.pages.list.iframeSrcKeyword, true))},
394
+ },
395
+ locators: {
396
+ exportButton: '.mini-button.mini-dataexport',
397
+ },
398
+ export: {
399
+ label: '导出',
400
+ mode: 'all' as const,
401
+ },
402
+ }`;
403
+ }
404
+ /**
405
+ * 生成动作级 CRUD + Export Playwright 测试脚本。
406
+ *
407
+ * 每个标准工作流(Create / Read / Update / Delete / Export)独立输出为
408
+ * 一个 test case,使用 GlueWorkflowRecorder 记录执行报告。
409
+ * 末尾保留 `// @stage-gap-append` 标记供 gap Custom test 追加。
410
+ *
411
+ * @param options 生成参数
412
+ * @returns 完整的 `.spec.ts` 源码
413
+ */
414
+ export function generateStageSkeletonCrudSpec(options) {
415
+ const { contract, menuNavigation } = options;
416
+ const slots = mapContractToStageSkeletonCrudSlots(contract);
417
+ const moduleId = contract.module.id;
418
+ const moduleLabel = contract.module.label;
419
+ const suiteTitle = `${moduleLabel} CRUD + Export`;
420
+ const deletePolicy = mapDeletePolicy(contract.flows.delete?.expectedOutcome);
421
+ const blockedDeleteMessage = deletePolicy === 'blocked_by_business_rule' ? findBlockedDeleteMessage(contract) : undefined;
422
+ // 删除工作流的业务意图和断言取决于 deletePolicy
423
+ const deleteIntent = deletePolicy === 'blocked_by_business_rule'
424
+ ? `在列表页选择${moduleLabel}记录后点击删除,验证业务规则拦截删除操作${blockedDeleteMessage ? `(${blockedDeleteMessage})` : ''}。`
425
+ : `在列表页选择${moduleLabel}记录后点击删除并确认,验证列表中不再存在该记录。`;
426
+ const deleteAssertion = deletePolicy === 'blocked_by_business_rule'
427
+ ? `删除操作应被业务规则拦截${blockedDeleteMessage ? `,提示「${blockedDeleteMessage}」` : ''},列表保留该记录。`
428
+ : '删除成功后,列表中不再存在该记录,搜索结果为空。';
429
+ return `import { test, expect, type Page } from '@playwright/test';
430
+ import { StagePath } from '@epoint-testtech/stage-core';
431
+ import { CrudPage, ExportPage, LoginPage, MenuPage, type CrudFlowSlots, type ExportFlowSlots, type CrudWorkflowData } from '../skeletons';
432
+ import { GlueWorkflowRecorder, runRecordedWorkflow, type GlueWorkflowRecordInput } from '../report/glue-report';
433
+
434
+ test.describe(${singleQuoted(suiteTitle)}, () => {
435
+ const menuRoute = ${singleQuoted(menuNavigation)};
436
+ const crudSlots: CrudFlowSlots = ${renderStageSkeletonCrudSlots(slots)};
437
+ const exportSlots = ${renderExportSlots(contract)};
438
+
439
+ test.describe.configure({ mode: 'serial' });
440
+ let workflowData: CrudWorkflowData | undefined;
441
+
442
+ const recorder = new GlueWorkflowRecorder({
443
+ outputRoot: StagePath.projectRootDirPath,
444
+ moduleId: ${singleQuoted(moduleId)},
445
+ moduleName: ${singleQuoted(moduleLabel)},
446
+ suiteTitle: ${singleQuoted(suiteTitle)},
447
+ generatedSpecFile: import.meta.url,
448
+ });
449
+
450
+ test.beforeAll(async ({ browser }) => {
451
+ recorder.updateEnvironment({
452
+ browserName: browser.browserType().name(),
453
+ browserVersion: browser.version(),
454
+ });
455
+ });
456
+
457
+ test.afterAll(() => {
458
+ recorder.writeReport();
459
+ });
460
+
461
+ async function openModule(page: Page): Promise<void> {
462
+ const loginPage = new LoginPage(page);
463
+ const menuPage = new MenuPage(page);
464
+ await loginPage.login();
465
+ await menuPage.navigateToMenu(menuRoute);
466
+ }
467
+
468
+ function workflowMeta(
469
+ input: Omit<
470
+ GlueWorkflowRecordInput,
471
+ 'moduleId' | 'executionSource' | 'evidenceSources' | 'evidenceRefs' | 'playwrightSuiteTitle'
472
+ >,
473
+ ): GlueWorkflowRecordInput {
474
+ return {
475
+ moduleId: ${singleQuoted(moduleId)},
476
+ executionSource: 'glue_code' as const,
477
+ evidenceSources: ['crud_flow_slots'],
478
+ evidenceRefs: [],
479
+ playwrightSuiteTitle: ${singleQuoted(suiteTitle)},
480
+ ...input,
481
+ };
482
+ }
483
+
484
+ test('Create:新增${moduleLabel}并校验列表存在对应记录', async ({ page }) => {
485
+ test.slow();
486
+
487
+ await runRecordedWorkflow(
488
+ recorder,
489
+ workflowMeta({
490
+ workflowId: ${singleQuoted(`${moduleId}.create`)},
491
+ workflowKind: 'create',
492
+ workflowName: '新增${moduleLabel}',
493
+ businessIntent: '在列表页点击新增按钮,填写表单并保存,验证列表中存在新增的记录。',
494
+ elementActions: ['点击列表页新增按钮打开新增弹框', '在新增弹框中填写表单字段', '点击保存并关闭提交表单', '在列表页搜索并验证记录存在'],
495
+ assertionExpectation: '新增成功后,列表页应能通过搜索找到刚新增的记录。',
496
+ playwrightTestTitle: 'Create:新增${moduleLabel}并校验列表存在对应记录',
497
+ }),
498
+ async () => {
499
+ await openModule(page);
500
+ const crudPage = new CrudPage(page, crudSlots);
501
+ workflowData = crudPage.prepareWorkflowData();
502
+ await crudPage.runCreateWorkflow(workflowData);
503
+ },
504
+ );
505
+ });
506
+
507
+ test('Read:搜索${moduleLabel}并校验搜索结果', async ({ page }) => {
508
+ test.slow();
509
+
510
+ await runRecordedWorkflow(
511
+ recorder,
512
+ workflowMeta({
513
+ workflowId: ${singleQuoted(`${moduleId}.read`)},
514
+ workflowKind: 'read',
515
+ workflowName: '搜索${moduleLabel}',
516
+ businessIntent: '使用搜索条件查询记录,验证搜索结果与搜索条件匹配。',
517
+ elementActions: ['在搜索区域填写搜索条件', '点击搜索按钮提交查询', '在结果列表中校验搜索结果'],
518
+ assertionExpectation: '搜索结果列表中应包含符合搜索条件的记录。',
519
+ playwrightTestTitle: 'Read:搜索${moduleLabel}并校验搜索结果',
520
+ }),
521
+ async () => {
522
+ await openModule(page);
523
+ if (!workflowData) {
524
+ throw new Error('Read 工作流依赖 Create 工作流先行生成 workflowData');
525
+ }
526
+ const crudPage = new CrudPage(page, crudSlots);
527
+ await crudPage.runReadWorkflow(workflowData);
528
+ },
529
+ );
530
+ });
531
+
532
+ test('Update:修改${moduleLabel}并校验修改结果', async ({ page }) => {
533
+ test.slow();
534
+
535
+ await runRecordedWorkflow(
536
+ recorder,
537
+ workflowMeta({
538
+ workflowId: ${singleQuoted(`${moduleId}.update`)},
539
+ workflowKind: 'update',
540
+ workflowName: '修改${moduleLabel}',
541
+ businessIntent: '定位目标记录行,点击修改按钮进入编辑弹框,修改字段后保存,验证修改生效。',
542
+ elementActions: ['在列表中定位目标记录行', '点击行操作中的修改按钮打开编辑弹框', '修改表单字段', '点击保存并关闭提交修改'],
543
+ assertionExpectation: '修改成功后,列表页应显示修改后的字段值。',
544
+ playwrightTestTitle: 'Update:修改${moduleLabel}并校验修改结果',
545
+ }),
546
+ async () => {
547
+ await openModule(page);
548
+ if (!workflowData) {
549
+ throw new Error('Update 工作流依赖 Create 工作流先行生成 workflowData');
550
+ }
551
+ const crudPage = new CrudPage(page, crudSlots);
552
+ await crudPage.runUpdateWorkflow(workflowData);
553
+ },
554
+ );
555
+ });
556
+
557
+ test('Delete:删除${moduleLabel}并校验${deletePolicy === 'blocked_by_business_rule' ? '业务规则拦截' : '删除结果'}', async ({ page }) => {
558
+ test.slow();
559
+
560
+ await runRecordedWorkflow(
561
+ recorder,
562
+ workflowMeta({
563
+ workflowId: ${singleQuoted(`${moduleId}.delete`)},
564
+ workflowKind: 'delete',
565
+ workflowName: '删除${moduleLabel}',
566
+ businessIntent: ${singleQuoted(deleteIntent)},
567
+ elementActions: ['在列表中选择目标记录', '点击删除按钮触发删除确认', ${deletePolicy === 'blocked_by_business_rule' ? "'确认删除操作并验证拦截提示'" : "'确认删除操作'"}],
568
+ assertionExpectation: ${singleQuoted(deleteAssertion)},
569
+ playwrightTestTitle: 'Delete:删除${moduleLabel}并校验${deletePolicy === 'blocked_by_business_rule' ? '业务规则拦截' : '删除结果'}',
570
+ }),
571
+ async () => {
572
+ await openModule(page);
573
+ if (!workflowData) {
574
+ throw new Error('Delete 工作流依赖 Create 工作流先行生成 workflowData');
575
+ }
576
+ const crudPage = new CrudPage(page, crudSlots);
577
+ await crudPage.runDeleteWorkflow(workflowData);
578
+ },
579
+ );
580
+ });
581
+
582
+ test('Export:导出${moduleLabel}列表并校验下载文件', async ({ page }, testInfo) => {
583
+ test.slow();
584
+
585
+ await runRecordedWorkflow(
586
+ recorder,
587
+ workflowMeta({
588
+ workflowId: ${singleQuoted(`${moduleId}.export`)},
589
+ workflowKind: 'export',
590
+ workflowName: '导出${moduleLabel}列表',
591
+ businessIntent: '在列表页触发全部导出,验证导出弹框可驱动并成功下载数据文件。',
592
+ elementActions: ['点击列表页数据导出按钮打开导出弹框', '在导出弹框选择「全部」并确认导出', '等待 download 事件并断言下载文件存在且非空'],
593
+ assertionExpectation: '导出操作应触发下载,落盘文件存在且大小大于 0。',
594
+ playwrightTestTitle: 'Export:导出${moduleLabel}列表并校验下载文件',
595
+ }),
596
+ async () => {
597
+ await openModule(page);
598
+ const exportPage = new ExportPage(page, exportSlots);
599
+ await exportPage.runExportWorkflow(testInfo);
600
+ },
601
+ );
602
+ });
603
+
604
+ // @stage-gap-append
605
+ });
606
+ `;
607
+ }
@@ -0,0 +1,52 @@
1
+ export type { AssertionContract, BusinessRuleContract, ButtonContract, Confidence, CreateFlowContract, CrudBusinessModuleContract, DataKeyContract, DeleteExpectedOutcome, DeleteFlowContract, DialogContract, EvidenceKind, EvidenceRef, FieldContract, FlowControlRef, GridActionContract, GridColumnContract, GridContract, ModuleHints, ModuleIdentity, OverrideFieldContract, PageContract, PageRole, RecordExistenceAssertionContract, SearchFlowContract, SearchConditionContract, SlotAvailability, SlotResolution, SourceKind, SourceRef, ToastMessageAssertionContract, UnresolvedSlot, UpdateFlowContract } from './contracts/crud-business-module.js';
2
+ export { extractHtmlPage } from './extractors/html-page.js';
3
+ export type { ExtractedHtmlButton, ExtractedHtmlDialog, ExtractedHtmlField, ExtractedHtmlGrid, ExtractedHtmlGridColumn, ExtractedHtmlRowAction, ExtractedHtmlPage } from './extractors/html-page.js';
4
+ export { extractSpecYaml } from './extractors/spec-yaml.js';
5
+ export type { ExtractedSpecBusinessRule, ExtractedSpecField, ExtractedSpecYaml } from './extractors/spec-yaml.js';
6
+ export { extractCodeListSummary } from './extractors/code-list.js';
7
+ export type { ExtractedCodeListSummary } from './extractors/code-list.js';
8
+ export { extractJavaAction } from './extractors/java-action.js';
9
+ export type { ExtractedJavaAction } from './extractors/java-action.js';
10
+ export { buildCrudBusinessModuleContract } from './normalizers/crud-contract.js';
11
+ export type { BuildCrudContractInput, ButtonLabelConfig } from './normalizers/crud-contract.js';
12
+ export { generateStageSkeletonCrudSpec, mapContractToStageSkeletonCrudSlots, renderStageSkeletonCrudSlots } from './generators/stage-skeleton-script.js';
13
+ export type { StageSkeletonCrudDataValueStrategy, StageSkeletonCrudDeletePolicy, StageSkeletonCrudFlowSlots, StageSkeletonScriptOptions } from './generators/stage-skeleton-script.js';
14
+ export type { ActionCandidate, AgentInferredWorkflow, GapActionType, GapExplorationResult, GapInferDraft, GapListItem, GapSignalType, GapWorkflowStatus, SkeletonCoverage } from './contracts/gap-inference.js';
15
+ export type { ObservableArtifactRef, ObservableGate, ObservableStageName, ObservableStagePayload, ObservableStageSummary, ReasoningEvidence, ReasoningSummary, TraceWriterOptions } from './contracts/observable-chain.js';
16
+ export { createTraceWriter, } from './trace/trace-writer.js';
17
+ export type { TraceWriter, } from './trace/trace-writer.js';
18
+ export { readStageContext, resolveStageContext, writeProjectIndex, } from './context/stage-context.js';
19
+ export type { ProjectIndex, ProjectIndexEntry, ResolveStageContextInput, ResolveStageContextResult, StageContext, StageContextMode, } from './context/stage-context.js';
20
+ export { renderReviewSummary, } from './trace/review-summary.js';
21
+ export type { ReviewSummaryInput, } from './trace/review-summary.js';
22
+ export { collectActionCandidates, } from './gap-planner/action-candidates.js';
23
+ export { createSkeletonCoverage, } from './gap-planner/skeleton-coverage.js';
24
+ export { stableActionHash, stableActionId, stableWorkflowId, } from './gap-planner/stable-id.js';
25
+ export { planAgentInferredWorkflows, } from './gap-planner/plan-agent-workflows.js';
26
+ export { listGapWorkflows, } from './gap-planner/list-gap-workflows.js';
27
+ export { appendAgentWorkflowTest, renderAgentWorkflowTest, } from './generators/agent-inferred-workflow-script.js';
28
+ export { resolveMaterialInventory, } from './material/material-inventory.js';
29
+ export type { MaterialResolution, ResolvedMaterialInventory, ResolveMaterialInventoryInput, ResolveMaterialInventoryResult, ValueResolution, } from './material/material-inventory.js';
30
+ export { summarizePageStructure, } from './capability/page-structure.js';
31
+ export type { PageStructureSummary, } from './capability/page-structure.js';
32
+ export type { ExtractedPageStructureSignal, ExtractedPageStructureSignals, } from './extractors/html-page.js';
33
+ export { applyScenarioHumanCorrection, inferPageScenarios, } from './capability/scenario-inference.js';
34
+ export type { InferPageScenariosInput, } from './capability/scenario-inference.js';
35
+ export type { PageScenario, ScenarioCandidate, ScenarioInferenceTrace, } from './contracts/observable-chain.js';
36
+ export { createCoverageDiff, } from './capability/coverage-diff.js';
37
+ export type { CreateCoverageDiffInput, } from './capability/coverage-diff.js';
38
+ export type { CoverageDiffAction, CoverageDiffTrace, NestedCrudCandidateTrace, } from './contracts/observable-chain.js';
39
+ export { generateGlueTestcases, } from './testcase/testcase-generator.js';
40
+ export type { GenerateGlueTestcasesInput, GenerateGlueTestcasesOutput, } from './testcase/testcase-generator.js';
41
+ export { mapContractToCrudTestcaseSlots, renderGlueTestcaseSkeleton, selectTestcaseSkeletonByScenario, } from './testcase/testcase-skeleton.js';
42
+ export { validateGlueTestcaseDocumentForSpecAssembly, } from './testcase/testcase-spec-assembly.js';
43
+ export type { CrudTestcaseSlots, MapContractToCrudTestcaseSlotsInput, TestcaseSkeletonSelection, } from './testcase/testcase-skeleton.js';
44
+ export type { GlueTestcase, GlueTestcaseDocument, } from './contracts/observable-chain.js';
45
+ export { applyCodeListWriteBack, collectCodeListMaterialRefsFromPath, isProtectedKnowledgeProjectPath, parseObservablePipelineArgs, resolveMenuRouteForPipeline, } from './cli/run-gap-pipeline.js';
46
+ export type { CodeListWriteBackInput, InteractiveCodeListSupplements, InteractiveSupplementResult, ObservablePipelineArgs, } from './cli/run-gap-pipeline.js';
47
+ export { createRuntimeFailureSummary, extractLoginSystemUrlFromEnvFile, parseRuntimeSummaryOutput, runtimeSummaryToTrace, } from './cli/run-gap-pipeline.js';
48
+ export { glueReportToTrace, glueSpecAssemblyToTrace, playwrightExecutionToTrace, } from './cli/run-gap-pipeline.js';
49
+ export type { GlueReportSummary, GlueSpecAssemblySummary, PlaywrightExecutionSummary, } from './cli/run-gap-pipeline.js';
50
+ export { compareObservableRuns, summarizeGeneralizationRun, } from './generalization/generalization-eval.js';
51
+ export type { GeneralizationComparison, GeneralizationRunMetrics, MaterialResolutionStatus, SummarizeGeneralizationRunInput, } from './generalization/generalization-eval.js';
52
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}