@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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gap-inference.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gap-inference.test.d.ts","sourceRoot":"","sources":["../../test/gap-inference.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,597 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { collectActionCandidates, createSkeletonCoverage, planAgentInferredWorkflows, listGapWorkflows, appendAgentWorkflowTest, renderAgentWorkflowTest, createCoverageDiff, } from '../src/index.js';
3
+ describe('gap inference contracts', () => {
4
+ it('exports gap workflow contract types through package index', () => {
5
+ const hints = {
6
+ buttonAliases: {
7
+ create: ['新增'],
8
+ export: ['导出'],
9
+ },
10
+ customWorkflows: [
11
+ {
12
+ label: '同步窗口管理系统',
13
+ actionAlias: ['同步窗口管理系统'],
14
+ expectedIntent: '同步窗口管理系统数据',
15
+ expectedAssertion: '出现同步完成提示',
16
+ include: true,
17
+ },
18
+ ],
19
+ };
20
+ const candidate = {
21
+ actionId: 'zwplace.list.syncWindow',
22
+ moduleId: 'zwplace',
23
+ pageId: 'gxhzwplacelist',
24
+ pageRole: 'list',
25
+ actionType: 'page_button',
26
+ label: '同步窗口管理系统',
27
+ locator: "xpath=//a[contains(@class,'mini-button')][normalize-space()='同步窗口管理系统']",
28
+ evidence: [],
29
+ sourceRefs: [],
30
+ };
31
+ const coverage = {
32
+ coverageId: 'crud-plus-export/v1',
33
+ coveredWorkflows: ['crud.create', 'crud.read', 'crud.update', 'crud.delete', 'export.run'],
34
+ coveredActionLabels: {
35
+ create: ['新增'],
36
+ export: ['导出'],
37
+ internal: ['保存并关闭', '确定', '取消', '关闭'],
38
+ },
39
+ };
40
+ const workflow = {
41
+ workflowId: 'zwplace.syncWindow',
42
+ workflowKind: 'custom',
43
+ executionSource: 'agent_inferred',
44
+ status: 'planned',
45
+ businessIntent: { status: 'resolved', value: '同步窗口管理系统数据' },
46
+ elementActions: { status: 'resolved', value: ['点击「同步窗口管理系统」按钮'] },
47
+ assertionExpectation: { status: 'resolved', value: '出现同步完成提示' },
48
+ actionCandidates: [candidate],
49
+ triggerReason: '该动作不属于 CRUD + Export 骨架覆盖范围。',
50
+ evidence: [],
51
+ reviewReason: '由 gap-planner 基于上游物料识别为自定义业务动作。',
52
+ };
53
+ const contract = {
54
+ agentInferredWorkflows: [workflow],
55
+ };
56
+ expect(hints.customWorkflows?.[0]?.label).toBe('同步窗口管理系统');
57
+ expect(coverage.coveredWorkflows).toContain('export.run');
58
+ expect(contract.agentInferredWorkflows?.[0]?.executionSource).toBe('agent_inferred');
59
+ });
60
+ });
61
+ describe('gap action candidates and skeleton coverage', () => {
62
+ it('collects list page buttons and row actions as action candidates', () => {
63
+ const contract = {
64
+ module: { id: 'zwplace', label: '场所窗口信息管理' },
65
+ pages: {
66
+ list: {
67
+ role: 'list',
68
+ pageId: 'gxhzwplacelist',
69
+ title: '场所信息管理列表',
70
+ iframeSrcKeyword: 'gxhzwplacelist',
71
+ fields: [],
72
+ buttons: [
73
+ {
74
+ id: 'mini-button-1',
75
+ label: '新增场所信息管理',
76
+ locator: "xpath=//a[normalize-space()='新增场所信息管理']",
77
+ availability: 'auto_extractable',
78
+ sources: [{ kind: 'html', path: 'gxhzwplacelist.html', confidence: 'high' }],
79
+ },
80
+ {
81
+ id: 'mini-button-4',
82
+ label: '同步窗口管理系统',
83
+ onclick: 'syncWindowManagement()',
84
+ locator: "xpath=//a[normalize-space()='同步窗口管理系统']",
85
+ availability: 'auto_extractable',
86
+ sources: [{ kind: 'html', path: 'gxhzwplacelist.html', confidence: 'high' }],
87
+ },
88
+ ],
89
+ grids: [
90
+ {
91
+ id: 'datagrid',
92
+ columns: [],
93
+ rowActions: [
94
+ {
95
+ label: '查看',
96
+ locator: "xpath=//i[@data-tooltip='查看']",
97
+ availability: 'auto_extractable',
98
+ sources: [{ kind: 'html', path: 'gxhzwplacelist.html', confidence: 'high' }],
99
+ },
100
+ ],
101
+ availability: 'auto_extractable',
102
+ sources: [{ kind: 'html', path: 'gxhzwplacelist.html', confidence: 'high' }],
103
+ },
104
+ ],
105
+ dialogs: [],
106
+ sources: [{ kind: 'html', path: 'gxhzwplacelist.html', confidence: 'high' }],
107
+ },
108
+ },
109
+ };
110
+ const candidates = collectActionCandidates(contract);
111
+ expect(candidates.map((item) => item.actionId)).toEqual([
112
+ 'zwplace.gxhzwplacelist.page_button.432c456e',
113
+ 'zwplace.gxhzwplacelist.page_button.da3d238e',
114
+ 'zwplace.gxhzwplacelist.row_action.807d8599',
115
+ ]);
116
+ expect(candidates.find((item) => item.label === '同步窗口管理系统')).toMatchObject({
117
+ moduleId: 'zwplace',
118
+ pageRole: 'list',
119
+ actionType: 'page_button',
120
+ onclick: 'syncWindowManagement()',
121
+ });
122
+ expect(candidates.find((item) => item.label === '同步窗口管理系统')?.evidence[0]).toMatchObject({
123
+ kind: 'html_button',
124
+ evidenceText: '同步窗口管理系统',
125
+ });
126
+ });
127
+ it('builds skeleton coverage from contract flows and ModuleHints aliases', () => {
128
+ const contract = {
129
+ flows: {
130
+ create: { entryButton: { pageRole: 'list', label: '新增场所信息管理', sources: [] } },
131
+ search: { submitControl: { pageRole: 'list', label: '搜索', sources: [] } },
132
+ update: { entryAction: { pageRole: 'list', label: '修改', sources: [] } },
133
+ delete: { entryButton: { pageRole: 'list', label: '删除选定', sources: [] } },
134
+ },
135
+ };
136
+ const coverage = createSkeletonCoverage(contract, {
137
+ buttonAliases: {
138
+ export: ['导出'],
139
+ },
140
+ });
141
+ expect(coverage.coveredWorkflows).toEqual([
142
+ 'crud.create',
143
+ 'crud.read',
144
+ 'crud.update',
145
+ 'crud.delete',
146
+ 'export.run',
147
+ ]);
148
+ expect(coverage.coveredActionLabels).toMatchObject({
149
+ create: ['新增场所信息管理'],
150
+ read: ['搜索'],
151
+ update: ['修改'],
152
+ delete: ['删除选定'],
153
+ export: ['导出'],
154
+ });
155
+ expect(coverage.coveredActionLabels.internal).toEqual(['保存并关闭', '确定', '取消', '关闭']);
156
+ });
157
+ });
158
+ function minimalContractWithSyncButton() {
159
+ return {
160
+ contractVersion: 'crud-business-module/v1',
161
+ module: { id: 'zwplace', label: '场所窗口信息管理' },
162
+ pages: {
163
+ list: {
164
+ role: 'list',
165
+ pageId: 'gxhzwplacelist',
166
+ title: '场所信息管理列表',
167
+ iframeSrcKeyword: 'gxhzwplacelist',
168
+ fields: [],
169
+ buttons: [
170
+ {
171
+ id: 'create',
172
+ label: '新增场所信息管理',
173
+ locator: "xpath=//a[normalize-space()='新增场所信息管理']",
174
+ availability: 'auto_extractable',
175
+ sources: [{ kind: 'html', path: 'gxhzwplacelist.html', confidence: 'high' }],
176
+ },
177
+ {
178
+ id: 'sync',
179
+ label: '同步窗口管理系统',
180
+ onclick: 'syncWindowManagement()',
181
+ locator: "xpath=//a[normalize-space()='同步窗口管理系统']",
182
+ availability: 'auto_extractable',
183
+ sources: [{ kind: 'html', path: 'gxhzwplacelist.html', confidence: 'high' }],
184
+ },
185
+ ],
186
+ grids: [],
187
+ dialogs: [],
188
+ sources: [{ kind: 'html', path: 'gxhzwplacelist.html', confidence: 'high' }],
189
+ },
190
+ },
191
+ dataKey: {
192
+ field: 'placename',
193
+ label: '场所名称',
194
+ availability: 'auto_extractable',
195
+ generationStrategy: 'timestamp_prefix',
196
+ sources: [],
197
+ },
198
+ flows: {
199
+ create: { entryButton: { pageRole: 'list', label: '新增场所信息管理', sources: [] }, targetPageId: 'add', saveButton: { pageRole: 'add', label: '保存并关闭', sources: [] }, overrideFields: [] },
200
+ search: { searchField: { pageRole: 'list', field: 'placename', sources: [] }, submitControl: { pageRole: 'list', label: '搜索', sources: [] }, resultGrid: 'datagrid' },
201
+ update: { entryAction: { pageRole: 'list', label: '修改', sources: [] }, targetPageId: 'edit', updateField: { pageRole: 'edit', field: 'placename', sources: [] }, saveButton: { pageRole: 'edit', label: '保存并关闭', sources: [] } },
202
+ delete: { entryButton: { pageRole: 'list', label: '删除选定', sources: [] }, expectedOutcome: 'blocked_by_business_rule' },
203
+ },
204
+ assertions: [],
205
+ businessRules: [],
206
+ unresolvedSlots: [],
207
+ };
208
+ }
209
+ describe('gap planner', () => {
210
+ it('marks custom workflow as planned when hints provide intent and Java Action provides assertion message', () => {
211
+ const workflows = planAgentInferredWorkflows({
212
+ contract: minimalContractWithSyncButton(),
213
+ actions: [
214
+ {
215
+ path: 'GxhZwPlaceListAction.java',
216
+ controllerName: 'gxhzwplacelistaction',
217
+ methods: ['syncWindowManagement'],
218
+ messages: ['同步完成'],
219
+ },
220
+ ],
221
+ hints: {
222
+ buttonAliases: { export: ['导出'] },
223
+ customWorkflows: [
224
+ {
225
+ label: '同步窗口管理系统',
226
+ expectedIntent: '同步窗口管理系统数据',
227
+ include: true,
228
+ },
229
+ ],
230
+ },
231
+ });
232
+ expect(workflows).toHaveLength(1);
233
+ expect(workflows[0]).toMatchObject({
234
+ workflowId: 'zwplace.custom_da3d238e',
235
+ workflowKind: 'custom',
236
+ executionSource: 'agent_inferred',
237
+ status: 'planned',
238
+ businessIntent: { status: 'resolved', value: '同步窗口管理系统数据' },
239
+ assertionExpectation: { status: 'resolved', value: '同步完成' },
240
+ });
241
+ expect(workflows[0]?.evidence.map((item) => item.kind)).toContain('java_action_method');
242
+ expect(workflows[0]?.evidence.map((item) => item.kind)).toContain('test_policy_hint');
243
+ expect(workflows[0]?.evidence.map((item) => item.kind)).toContain('java_callback_message');
244
+ });
245
+ it('marks a custom button as candidate when only HTML button evidence exists', () => {
246
+ const workflows = planAgentInferredWorkflows({
247
+ contract: minimalContractWithSyncButton(),
248
+ actions: [],
249
+ });
250
+ expect(workflows).toHaveLength(1);
251
+ expect(workflows[0]).toMatchObject({
252
+ workflowId: 'zwplace.custom_da3d238e',
253
+ status: 'candidate',
254
+ reviewReason: '已发现「同步窗口管理系统」动作,但缺少业务意图或断言预期证据。',
255
+ });
256
+ });
257
+ it('uses actionAlias to match custom workflow hints', () => {
258
+ const workflows = planAgentInferredWorkflows({
259
+ contract: minimalContractWithSyncButton(),
260
+ actions: [],
261
+ hints: {
262
+ customWorkflows: [
263
+ {
264
+ label: '窗口同步',
265
+ actionAlias: ['同步窗口管理系统'],
266
+ expectedIntent: '同步窗口管理系统数据',
267
+ expectedAssertion: '出现同步完成提示',
268
+ include: true,
269
+ },
270
+ ],
271
+ },
272
+ });
273
+ expect(workflows[0]).toMatchObject({
274
+ workflowId: 'zwplace.custom_da3d238e',
275
+ status: 'planned',
276
+ });
277
+ });
278
+ it('returns no workflow when ModuleHints explicitly excludes the action', () => {
279
+ const workflows = planAgentInferredWorkflows({
280
+ contract: minimalContractWithSyncButton(),
281
+ actions: [],
282
+ hints: {
283
+ customWorkflows: [
284
+ {
285
+ label: '同步窗口管理系统',
286
+ include: false,
287
+ excludeReason: '该按钮由人工专项验证覆盖',
288
+ },
289
+ ],
290
+ },
291
+ });
292
+ expect(workflows).toEqual([]);
293
+ });
294
+ it('returns an empty diff when all page actions are covered by skeleton coverage', () => {
295
+ const workflows = planAgentInferredWorkflows({
296
+ contract: minimalContractWithSyncButton(),
297
+ actions: [],
298
+ hints: {
299
+ buttonAliases: {
300
+ create: ['新增场所信息管理'],
301
+ search: ['搜索'],
302
+ edit: ['修改'],
303
+ delete: ['删除选定'],
304
+ export: ['导出', '同步窗口管理系统'],
305
+ },
306
+ },
307
+ });
308
+ expect(workflows).toEqual([]);
309
+ });
310
+ });
311
+ describe('agent inferred workflow script generator', () => {
312
+ it('renders a Custom test with runRecordedWorkflow and repo-prefixed locator variable', () => {
313
+ const workflow = {
314
+ workflowId: 'zwplace.custom_da3d238e',
315
+ workflowKind: 'custom',
316
+ executionSource: 'agent_inferred',
317
+ status: 'resolved',
318
+ businessIntent: { status: 'resolved', value: '同步窗口管理系统数据' },
319
+ elementActions: { status: 'resolved', value: ['点击「同步窗口管理系统」按钮'] },
320
+ assertionExpectation: { status: 'resolved', value: '出现同步完成提示' },
321
+ actionCandidates: [{
322
+ actionId: 'zwplace.gxhzwplacelist.page_button.da3d238e',
323
+ moduleId: 'zwplace',
324
+ pageId: 'gxhzwplacelist',
325
+ pageRole: 'list',
326
+ actionType: 'page_button',
327
+ label: '同步窗口管理系统',
328
+ locator: "xpath=//a[contains(@class,'mini-button')][normalize-space()='同步窗口管理系统']",
329
+ evidence: [],
330
+ sourceRefs: [],
331
+ }],
332
+ triggerReason: '该动作不属于 CRUD + Export 骨架覆盖范围。',
333
+ evidence: [
334
+ {
335
+ kind: 'html_button',
336
+ path: 'gxhzwplacelist.html',
337
+ locator: "xpath=//a[contains(@class,'mini-button')][normalize-space()='同步窗口管理系统']",
338
+ evidenceText: '同步窗口管理系统',
339
+ confidence: 'high',
340
+ },
341
+ {
342
+ kind: 'java_callback_message',
343
+ path: 'GxhZwPlaceListAction.java',
344
+ evidenceText: '同步完成',
345
+ confidence: 'medium',
346
+ },
347
+ {
348
+ kind: 'test_policy_hint',
349
+ path: 'ModuleHints.customWorkflows',
350
+ evidenceText: '同步窗口管理系统数据',
351
+ confidence: 'high',
352
+ },
353
+ ],
354
+ reviewReason: '由 gap-planner 识别为自定义业务动作。',
355
+ resolvedRuntime: {
356
+ locator: "xpath=//a[contains(@class,'mini-button')][normalize-space()='同步窗口管理系统']",
357
+ locatorStrategy: 'html_static',
358
+ iframeSrcKeyword: 'gxhzwplacelist.html',
359
+ exploration: {
360
+ workflowId: 'zwplace.custom_da3d238e',
361
+ buttonLabel: '同步窗口管理系统',
362
+ locator: "xpath=//a[contains(@class,'mini-button')][normalize-space()='同步窗口管理系统']",
363
+ locatorStrategy: 'html_static',
364
+ buttonVisible: true,
365
+ buttonClickable: true,
366
+ confirmDialogPresent: true,
367
+ confirmText: '确认要从窗口管理系统同步数据吗?',
368
+ confirmExecuted: true,
369
+ signalType: 'toast',
370
+ observedMessage: '同步功能暂未实现,请联系管理员配置窗口管理系统接口。',
371
+ pageChanged: false,
372
+ downloadTriggered: false,
373
+ networkResponses: ['200 https://example.test/epoint-web/zwplace/syncWindowSystem'],
374
+ suggestedIntent: '同步窗口管理系统数据',
375
+ suggestedAssertion: '同步功能暂未实现,请联系管理员配置窗口管理系统接口。',
376
+ confidence: 'medium',
377
+ exploredAt: '2026-06-12T00:00:00.000Z',
378
+ },
379
+ playwrightTestTitle: 'Custom:同步窗口管理系统',
380
+ },
381
+ };
382
+ const script = renderAgentWorkflowTest({
383
+ moduleId: 'zwplace',
384
+ suiteTitle: '场所窗口信息管理 CRUD + Export',
385
+ workflow,
386
+ });
387
+ expect(script).toContain("test('Custom:同步窗口管理系统'");
388
+ expect(script).toContain("workflowId: 'zwplace.custom_da3d238e'");
389
+ expect(script).toContain("executionSource: 'agent_inferred'");
390
+ expect(script).toContain("evidenceSources: ['upstream_html', 'upstream_java_action', 'module_hints', 'runtime_page', 'agent_reasoning']");
391
+ expect(script).toContain("file: 'gxhzwplacelist.html'");
392
+ expect(script).toContain("file: 'GxhZwPlaceListAction.java'");
393
+ expect(script).toContain("source: 'runtime_page', note: 'gap-executor 在真实页面确认按钮可见并完成定位收敛'");
394
+ expect(script).toContain("selector: 'iframe[src*=\"gxhzwplacelist.html\"]', note: 'gap-executor 确认该动作位于业务 iframe 内'");
395
+ expect(script).toContain('运行态观察到网络响应:200 https://example.test/epoint-web/zwplace/syncWindowSystem');
396
+ expect(script).toContain("source: 'agent_reasoning', note: 'gap-executor 在真实页面确认按钮可见并可点击'");
397
+ expect(script).toContain("gapFinding: {");
398
+ expect(script).toContain("kind: 'feature_incomplete'");
399
+ expect(script).toContain("observed: '同步功能暂未实现,请联系管理员配置窗口管理系统接口。'");
400
+ expect(script).toContain('const repoIFrame同步窗口管理系统 = page.locator(\'iframe[src*="gxhzwplacelist.html"]\').contentFrame();');
401
+ expect(script).toContain('const repoBtn同步窗口管理系统 = repoIFrame同步窗口管理系统.locator("xpath=//a[contains(@class,\'mini-button\')][normalize-space()=\'同步窗口管理系统\']");');
402
+ expect(script).toContain('const repoBtn同步窗口管理系统确认 = repoIFrame同步窗口管理系统');
403
+ expect(script).toContain("normalize-space()='确定' or normalize-space()='确认'");
404
+ expect(script).toContain('const repoMsgBox同步窗口管理系统反馈 = repoIFrame同步窗口管理系统.locator');
405
+ expect(script).toContain("同步窗口管理系统未达成业务预期:期望出现「出现同步完成提示」,系统实际反馈「");
406
+ expect(script).toContain('await runRecordedWorkflow(');
407
+ });
408
+ it('rejects candidate workflow without resolved slots', () => {
409
+ const workflow = {
410
+ workflowId: 'zwplace.custom_da3d238e',
411
+ workflowKind: 'custom',
412
+ executionSource: 'agent_inferred',
413
+ status: 'candidate',
414
+ businessIntent: {
415
+ status: 'unresolved',
416
+ unresolved: {
417
+ reason: '缺少业务意图证据',
418
+ expectedProvider: 'tester',
419
+ suggestedFormat: '{ "customWorkflows": [{ "expectedIntent": "..." }] }',
420
+ },
421
+ },
422
+ elementActions: { status: 'resolved', value: ['点击「同步窗口管理系统」按钮'] },
423
+ assertionExpectation: {
424
+ status: 'unresolved',
425
+ unresolved: {
426
+ reason: '缺少断言预期证据',
427
+ expectedProvider: 'tester',
428
+ suggestedFormat: '{ "customWorkflows": [{ "expectedAssertion": "..." }] }',
429
+ },
430
+ },
431
+ actionCandidates: [{
432
+ actionId: 'zwplace.gxhzwplacelist.page_button.da3d238e',
433
+ moduleId: 'zwplace',
434
+ pageId: 'gxhzwplacelist',
435
+ pageRole: 'list',
436
+ actionType: 'page_button',
437
+ label: '同步窗口管理系统',
438
+ onclick: 'syncWindowManagement()',
439
+ locator: "xpath=//a[contains(@class,'mini-button')][normalize-space()='同步窗口管理系统']",
440
+ evidence: [],
441
+ sourceRefs: [],
442
+ }],
443
+ triggerReason: '该动作不属于 CRUD + Export 骨架覆盖范围。',
444
+ evidence: [],
445
+ };
446
+ expect(() => renderAgentWorkflowTest({
447
+ moduleId: 'zwplace',
448
+ suiteTitle: '场所窗口信息管理 CRUD + Export',
449
+ workflow,
450
+ })).toThrow('不能生成 Custom test');
451
+ });
452
+ it('appends Custom test before describe closing brace and avoids duplicates', () => {
453
+ const source = `test.describe('场所窗口信息管理 CRUD + Export', () => {
454
+ test('Export:导出场所窗口信息列表并校验下载文件', async ({ page }) => {
455
+ });
456
+ });
457
+ `;
458
+ const customTest = `test('Custom:同步窗口管理系统', async ({ page }) => {
459
+ // workflowId: zwplace.custom_da3d238e
460
+ });
461
+ `;
462
+ const first = appendAgentWorkflowTest({
463
+ source,
464
+ workflowId: 'zwplace.custom_da3d238e',
465
+ testTitle: 'Custom:同步窗口管理系统',
466
+ testSource: customTest,
467
+ });
468
+ const second = appendAgentWorkflowTest({
469
+ source: first,
470
+ workflowId: 'zwplace.custom_da3d238e',
471
+ testTitle: 'Custom:同步窗口管理系统',
472
+ testSource: customTest,
473
+ });
474
+ expect(first).toContain("test('Custom:同步窗口管理系统'");
475
+ expect(second.match(/Custom:同步窗口管理系统/g)).toHaveLength(1);
476
+ });
477
+ });
478
+ describe('gap list 人工筛选 gate', () => {
479
+ it('planned 建议 infer、candidate 建议 explore', () => {
480
+ const base = minimalContractWithSyncButton();
481
+ const planned = planAgentInferredWorkflows({
482
+ contract: base,
483
+ actions: [
484
+ { path: 'X.java', controllerName: 'x', methods: ['syncWindowManagement'], messages: ['同步完成'] },
485
+ ],
486
+ hints: {
487
+ customWorkflows: [
488
+ { label: '同步窗口管理系统', expectedIntent: '同步窗口管理系统数据', include: true },
489
+ ],
490
+ },
491
+ });
492
+ const plannedItems = listGapWorkflows({ ...base, agentInferredWorkflows: planned });
493
+ const syncPlanned = plannedItems.find((item) => item.label === '同步窗口管理系统');
494
+ expect(syncPlanned).toMatchObject({
495
+ status: 'planned',
496
+ hasIntent: true,
497
+ hasAssertion: true,
498
+ suggestedMode: 'infer',
499
+ });
500
+ const candidate = planAgentInferredWorkflows({ contract: base, actions: [], hints: {} });
501
+ const candidateItems = listGapWorkflows({ ...base, agentInferredWorkflows: candidate });
502
+ const syncCandidate = candidateItems.find((item) => item.label === '同步窗口管理系统');
503
+ expect(syncCandidate).toMatchObject({
504
+ status: 'candidate',
505
+ hasIntent: false,
506
+ suggestedMode: 'explore',
507
+ });
508
+ });
509
+ it('无 agentInferredWorkflows 时返回空清单', () => {
510
+ expect(listGapWorkflows(minimalContractWithSyncButton())).toEqual([]);
511
+ });
512
+ });
513
+ /**
514
+ * 可观测 coverage diff 测试。
515
+ *
516
+ * 验证 createCoverageDiff 把骨架覆盖动作、gap 候选、嵌套 CRUD 候选分类输出,
517
+ * 并与现有 gap planner 基线 workflow 做集合差异对比(新旧并行,不互相替换)。
518
+ */
519
+ describe('observable coverage diff', () => {
520
+ it('keeps skeleton-covered actions separate from gap candidates and nested CRUD candidates', () => {
521
+ const diff = createCoverageDiff({
522
+ moduleId: 'zwplace',
523
+ actionCandidates: [
524
+ {
525
+ actionId: 'zwplace.list.create',
526
+ moduleId: 'zwplace',
527
+ pageId: 'gxhzwplacelist',
528
+ pageRole: 'list',
529
+ actionType: 'page_button',
530
+ label: '新增场所信息管理',
531
+ evidence: [],
532
+ sourceRefs: [],
533
+ },
534
+ {
535
+ actionId: 'zwplace.add.addWindow',
536
+ moduleId: 'zwplace',
537
+ pageId: 'gxhzwplaceadd',
538
+ pageRole: 'add',
539
+ actionType: 'page_button',
540
+ label: '添加窗口',
541
+ evidence: [],
542
+ sourceRefs: [],
543
+ },
544
+ ],
545
+ skeletonCoverage: {
546
+ coverageId: 'crud-plus-export/v1',
547
+ coveredWorkflows: ['crud.create', 'crud.read', 'crud.update', 'crud.delete', 'export.run'],
548
+ coveredActionLabels: {
549
+ create: ['新增场所信息管理'],
550
+ delete: ['删除选定'],
551
+ export: ['导出'],
552
+ internal: ['保存并关闭', '确定', '取消', '关闭'],
553
+ },
554
+ },
555
+ baselineWorkflows: [
556
+ {
557
+ workflowId: 'zwplace.add.addWindow',
558
+ workflowKind: 'custom',
559
+ executionSource: 'agent_inferred',
560
+ status: 'candidate',
561
+ businessIntent: {
562
+ status: 'unresolved',
563
+ unresolved: { reason: '缺少业务意图证据', expectedProvider: 'tester', suggestedFormat: '{}' },
564
+ },
565
+ elementActions: { status: 'resolved', value: ['点击「添加窗口」按钮'] },
566
+ assertionExpectation: {
567
+ status: 'unresolved',
568
+ unresolved: { reason: '缺少断言预期证据', expectedProvider: 'tester', suggestedFormat: '{}' },
569
+ },
570
+ actionCandidates: [],
571
+ triggerReason: '该动作不属于 CRUD + Export 骨架覆盖范围。',
572
+ evidence: [],
573
+ },
574
+ ],
575
+ pageStructures: [
576
+ {
577
+ pageId: 'gxhzwplaceadd',
578
+ readSearchCandidate: true,
579
+ createCandidate: true,
580
+ deleteCandidate: true,
581
+ updateOrDetailCandidate: false,
582
+ nestedCrudCandidate: true,
583
+ evidenceLabels: ['弹窗页面中存在 mini-datagrid'],
584
+ risks: ['子表断言未确认'],
585
+ },
586
+ ],
587
+ });
588
+ expect(diff.covered.map((item) => item.label)).toEqual(['新增场所信息管理']);
589
+ expect(diff.gapCandidates.map((item) => item.label)).toEqual(['添加窗口']);
590
+ expect(diff.baseline.candidateWorkflows).toEqual(['zwplace.add.addWindow']);
591
+ expect(diff.comparison.onlyGeneralized).toEqual([]);
592
+ expect(diff.nestedCrudCandidates[0]).toMatchObject({
593
+ pageId: 'gxhzwplaceadd',
594
+ unresolvedReason: '子表断言未确认',
595
+ });
596
+ });
597
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=generalization.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generalization.test.d.ts","sourceRoot":"","sources":["../../test/generalization.test.ts"],"names":[],"mappings":""}