@epoint-testtech/ep-stage-skill 0.0.4-alpha.0 → 0.0.5

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 (206) hide show
  1. package/SKILL.md +27 -27
  2. package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -186
  3. package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -199
  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 -112
  6. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +72 -0
  7. package/codex-skill/ep-stage/glue-run-test/SKILL.md +292 -249
  8. package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -0
  9. package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -145
  10. package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -43
  11. package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
  12. package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +166 -0
  13. package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +233 -0
  14. package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
  15. package/codex-skill/ep-stage/page-element-scan/SKILL.md +91 -0
  16. package/codex-skill/ep-stage/page-element-scan/evals/evals.json +37 -0
  17. package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +85 -0
  18. package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +153 -0
  19. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +233 -0
  20. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +298 -0
  21. package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +749 -0
  22. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
  23. package/codex-skill/ep-stage/scripts/validate-skill.mjs +98 -95
  24. package/dist/src/cli/generate-testcase.d.ts +18 -51
  25. package/dist/src/cli/generate-testcase.d.ts.map +1 -1
  26. package/dist/src/cli/generate-testcase.js +127 -131
  27. package/dist/src/cli/index.d.ts +11 -2
  28. package/dist/src/cli/index.d.ts.map +1 -1
  29. package/dist/src/cli/index.js +49 -16
  30. package/dist/src/cli/probe.d.ts +2 -0
  31. package/dist/src/cli/probe.d.ts.map +1 -1
  32. package/dist/src/cli/probe.js +239 -13
  33. package/dist/src/cli/run.d.ts +136 -29
  34. package/dist/src/cli/run.d.ts.map +1 -1
  35. package/dist/src/cli/run.js +494 -65
  36. package/dist/src/cli/spec.d.ts +18 -11
  37. package/dist/src/cli/spec.d.ts.map +1 -1
  38. package/dist/src/cli/spec.js +74 -20
  39. package/dist/src/contracts/crud-business-module.d.ts +6 -0
  40. package/dist/src/contracts/crud-business-module.d.ts.map +1 -1
  41. package/dist/src/contracts/glue-contract-v1.d.ts +98 -0
  42. package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -0
  43. package/dist/src/contracts/glue-contract-v1.js +9 -0
  44. package/dist/src/contracts/inference-trace.d.ts +223 -0
  45. package/dist/src/contracts/inference-trace.d.ts.map +1 -0
  46. package/dist/src/contracts/inference-trace.js +12 -0
  47. package/dist/src/contracts/observable-chain.d.ts +4 -0
  48. package/dist/src/contracts/observable-chain.d.ts.map +1 -1
  49. package/dist/src/extractors/code-list-json5.d.ts +192 -0
  50. package/dist/src/extractors/code-list-json5.d.ts.map +1 -0
  51. package/dist/src/extractors/code-list-json5.js +91 -0
  52. package/dist/src/extractors/code-list.d.ts +3 -3
  53. package/dist/src/extractors/code-list.d.ts.map +1 -1
  54. package/dist/src/extractors/code-list.js +46 -11
  55. package/dist/src/extractors/html-page.d.ts +9 -0
  56. package/dist/src/extractors/html-page.d.ts.map +1 -1
  57. package/dist/src/extractors/html-page.js +26 -2
  58. package/dist/src/extractors/prd-extractor.d.ts +70 -0
  59. package/dist/src/extractors/prd-extractor.d.ts.map +1 -0
  60. package/dist/src/extractors/prd-extractor.js +248 -0
  61. package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
  62. package/dist/src/generators/stage-skeleton-script.d.ts +87 -20
  63. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
  64. package/dist/src/generators/stage-skeleton-script.js +656 -238
  65. package/dist/src/index.d.ts +7 -2
  66. package/dist/src/index.d.ts.map +1 -1
  67. package/dist/src/index.js +3 -1
  68. package/dist/src/inference/module-hints-inference.d.ts +30 -0
  69. package/dist/src/inference/module-hints-inference.d.ts.map +1 -0
  70. package/dist/src/inference/module-hints-inference.js +363 -0
  71. package/dist/src/inference/source-analysis.d.ts +26 -0
  72. package/dist/src/inference/source-analysis.d.ts.map +1 -0
  73. package/dist/src/inference/source-analysis.js +258 -0
  74. package/dist/src/normalizers/crud-contract.d.ts +3 -0
  75. package/dist/src/normalizers/crud-contract.d.ts.map +1 -1
  76. package/dist/src/normalizers/crud-contract.js +3 -0
  77. package/dist/src/runtime/ai-runtime-trace.d.ts +88 -0
  78. package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -0
  79. package/dist/src/runtime/ai-runtime-trace.js +119 -0
  80. package/dist/src/testcase/button-interaction-inference.d.ts +37 -0
  81. package/dist/src/testcase/button-interaction-inference.d.ts.map +1 -0
  82. package/dist/src/testcase/button-interaction-inference.js +385 -0
  83. package/dist/src/testcase/contract-testcase-assembler.d.ts +44 -0
  84. package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -0
  85. package/dist/src/testcase/contract-testcase-assembler.js +806 -0
  86. package/dist/src/testcase/prd-button-mapper.d.ts +72 -0
  87. package/dist/src/testcase/prd-button-mapper.d.ts.map +1 -0
  88. package/dist/src/testcase/prd-button-mapper.js +148 -0
  89. package/dist/src/testcase/runtime-context.d.ts +9 -0
  90. package/dist/src/testcase/runtime-context.d.ts.map +1 -0
  91. package/dist/src/testcase/runtime-context.js +1 -0
  92. package/dist/src/testcase/test-access.d.ts +38 -0
  93. package/dist/src/testcase/test-access.d.ts.map +1 -0
  94. package/dist/src/testcase/test-access.js +106 -0
  95. package/dist/src/testcase/testcase-execution-fields.d.ts +10 -0
  96. package/dist/src/testcase/testcase-execution-fields.d.ts.map +1 -0
  97. package/dist/src/testcase/testcase-execution-fields.js +1 -0
  98. package/dist/src/testcase/testcase-generator.d.ts.map +1 -1
  99. package/dist/src/testcase/testcase-generator.js +6 -0
  100. package/dist/src/testcase/testcase-markdown.d.ts +11 -0
  101. package/dist/src/testcase/testcase-markdown.d.ts.map +1 -0
  102. package/dist/src/testcase/testcase-markdown.js +181 -0
  103. package/dist/src/testcase/testcase-skeleton.d.ts +4 -0
  104. package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -1
  105. package/dist/src/testcase/testcase-spec-assembly.d.ts +4 -0
  106. package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -1
  107. package/dist/src/testcase/testcase-v1-draft.d.ts +58 -0
  108. package/dist/src/testcase/testcase-v1-draft.d.ts.map +1 -0
  109. package/dist/src/testcase/testcase-v1-draft.js +13 -0
  110. package/dist/src/validation/contract.d.ts +14 -0
  111. package/dist/src/validation/contract.d.ts.map +1 -0
  112. package/dist/src/validation/contract.js +132 -0
  113. package/dist/src/validation/credentials.d.ts +7 -2
  114. package/dist/src/validation/credentials.d.ts.map +1 -1
  115. package/dist/src/validation/credentials.js +15 -4
  116. package/dist/src/validation/index.d.ts +2 -0
  117. package/dist/src/validation/index.d.ts.map +1 -1
  118. package/dist/src/validation/index.js +2 -0
  119. package/dist/src/validation/inference-trace.d.ts +13 -0
  120. package/dist/src/validation/inference-trace.d.ts.map +1 -0
  121. package/dist/src/validation/inference-trace.js +31 -0
  122. package/dist/src/validation/testcase.d.ts +9 -7
  123. package/dist/src/validation/testcase.d.ts.map +1 -1
  124. package/dist/src/validation/testcase.js +181 -34
  125. package/docs/README.md +124 -134
  126. package/docs/mvp-usage-guide.md +298 -298
  127. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
  128. package/examples/schemeresource.module-hints.json +38 -38
  129. package/examples/schemeresource.observable.code_list.md +37 -37
  130. package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
  131. package/examples/zwplace-placecategory-validation.json +29 -29
  132. package/examples/zwplace.module-hints.json +69 -69
  133. package/examples/zwplace.observable.code_list.md +37 -37
  134. package/package.json +2 -2
  135. package/dist/test/cli/extract-contract.test.d.ts +0 -2
  136. package/dist/test/cli/extract-contract.test.d.ts.map +0 -1
  137. package/dist/test/cli/extract-contract.test.js +0 -32
  138. package/dist/test/cli/generate-testcase.test.d.ts +0 -2
  139. package/dist/test/cli/generate-testcase.test.d.ts.map +0 -1
  140. package/dist/test/cli/generate-testcase.test.js +0 -130
  141. package/dist/test/cli/index.test.d.ts +0 -2
  142. package/dist/test/cli/index.test.d.ts.map +0 -1
  143. package/dist/test/cli/index.test.js +0 -93
  144. package/dist/test/cli/run.test.d.ts +0 -2
  145. package/dist/test/cli/run.test.d.ts.map +0 -1
  146. package/dist/test/cli/run.test.js +0 -149
  147. package/dist/test/cli/spec.test.d.ts +0 -2
  148. package/dist/test/cli/spec.test.d.ts.map +0 -1
  149. package/dist/test/cli/spec.test.js +0 -196
  150. package/dist/test/crud-contract.test.d.ts +0 -2
  151. package/dist/test/crud-contract.test.d.ts.map +0 -1
  152. package/dist/test/crud-contract.test.js +0 -819
  153. package/dist/test/gap-inference.test.d.ts +0 -2
  154. package/dist/test/gap-inference.test.d.ts.map +0 -1
  155. package/dist/test/gap-inference.test.js +0 -597
  156. package/dist/test/generalization.test.d.ts +0 -2
  157. package/dist/test/generalization.test.d.ts.map +0 -1
  158. package/dist/test/generalization.test.js +0 -73
  159. package/dist/test/material-inventory.test.d.ts +0 -2
  160. package/dist/test/material-inventory.test.d.ts.map +0 -1
  161. package/dist/test/material-inventory.test.js +0 -141
  162. package/dist/test/observable-chain.test.d.ts +0 -2
  163. package/dist/test/observable-chain.test.d.ts.map +0 -1
  164. package/dist/test/observable-chain.test.js +0 -123
  165. package/dist/test/observable-pipeline.test.d.ts +0 -2
  166. package/dist/test/observable-pipeline.test.d.ts.map +0 -1
  167. package/dist/test/observable-pipeline.test.js +0 -461
  168. package/dist/test/page-structure.test.d.ts +0 -2
  169. package/dist/test/page-structure.test.d.ts.map +0 -1
  170. package/dist/test/page-structure.test.js +0 -45
  171. package/dist/test/scenario-inference.test.d.ts +0 -2
  172. package/dist/test/scenario-inference.test.d.ts.map +0 -1
  173. package/dist/test/scenario-inference.test.js +0 -73
  174. package/dist/test/stage-context.test.d.ts +0 -2
  175. package/dist/test/stage-context.test.d.ts.map +0 -1
  176. package/dist/test/stage-context.test.js +0 -395
  177. package/dist/test/testcase-generator.test.d.ts +0 -2
  178. package/dist/test/testcase-generator.test.d.ts.map +0 -1
  179. package/dist/test/testcase-generator.test.js +0 -276
  180. package/dist/test/testcase-skeleton.test.d.ts +0 -2
  181. package/dist/test/testcase-skeleton.test.d.ts.map +0 -1
  182. package/dist/test/testcase-skeleton.test.js +0 -185
  183. package/dist/test/testcase-spec-assembly.test.d.ts +0 -2
  184. package/dist/test/testcase-spec-assembly.test.d.ts.map +0 -1
  185. package/dist/test/testcase-spec-assembly.test.js +0 -105
  186. package/dist/test/util/credentials.test.d.ts +0 -2
  187. package/dist/test/util/credentials.test.d.ts.map +0 -1
  188. package/dist/test/util/credentials.test.js +0 -64
  189. package/dist/test/util/i18n-testcase.test.d.ts +0 -2
  190. package/dist/test/util/i18n-testcase.test.d.ts.map +0 -1
  191. package/dist/test/util/i18n-testcase.test.js +0 -119
  192. package/dist/test/util/softlink.test.d.ts +0 -2
  193. package/dist/test/util/softlink.test.d.ts.map +0 -1
  194. package/dist/test/util/softlink.test.js +0 -82
  195. package/dist/test/validation/credentials.test.d.ts +0 -2
  196. package/dist/test/validation/credentials.test.d.ts.map +0 -1
  197. package/dist/test/validation/credentials.test.js +0 -72
  198. package/dist/test/validation/projects-index.test.d.ts +0 -2
  199. package/dist/test/validation/projects-index.test.d.ts.map +0 -1
  200. package/dist/test/validation/projects-index.test.js +0 -48
  201. package/dist/test/validation/testcase.test.d.ts +0 -2
  202. package/dist/test/validation/testcase.test.d.ts.map +0 -1
  203. package/dist/test/validation/testcase.test.js +0 -129
  204. package/dist/vitest.config.d.ts +0 -3
  205. package/dist/vitest.config.d.ts.map +0 -1
  206. package/dist/vitest.config.js +0 -7
@@ -1,130 +0,0 @@
1
- /**
2
- * generate-testcase.ts 单元测试。
3
- *
4
- * 覆盖:
5
- * - parseGenerateTestcaseArgs 缺参 / 非法参数对 / -- 分隔符
6
- * - decideScenario 3 档优先级(CLI 参数 / hints / 默认)
7
- * - renderTestcaseMarkdown:i18n 字段名 / 状态值;硬编码「未确认项」标题;
8
- * `unresolved` 字段标题与状态值映射不冲突。
9
- *
10
- * 端到端 testcase 子命令(含 probe / 文件落盘)依赖主仓 knowledge-project 物料,
11
- * 由 Phase 5 真机验收覆盖,此处不重复。
12
- */
13
- import { describe, expect, it } from 'vitest';
14
- import { decideScenario, parseGenerateTestcaseArgs, renderTestcaseMarkdown, } from '../../src/cli/generate-testcase.js';
15
- function makeTestcase(overrides = {}) {
16
- return {
17
- schemaVersion: 'v2',
18
- scenario: 'crud.single-page',
19
- moduleId: 'zwplace',
20
- moduleName: '场所窗口信息管理',
21
- menuPath: '业务管理 > 场所窗口信息',
22
- requiredSystems: [{ url: 'https://example.com', systemName: '示例系统' }],
23
- requiredRoles: [{ role: 'admin', hint: '后台管理员' }],
24
- coveredActions: [
25
- { actionId: 'create', kind: '新增', evidence: ['contract=crud-business-module/v1'] },
26
- ],
27
- uncoveredCandidates: [
28
- {
29
- actionId: 'export',
30
- label: '导出 CSV',
31
- status: 'planned',
32
- requiredRole: 'admin',
33
- businessIntent: '导出列表为 CSV 文件',
34
- assertionExpectation: '下载完成且文件非空',
35
- evidence: ['contract=crud-business-module/v1'],
36
- },
37
- ],
38
- cases: [
39
- {
40
- caseId: 'zwplace.create',
41
- title: '新增场所窗口',
42
- scenario: 'crud.single-page',
43
- reviewStatus: 'needs_review',
44
- requiredRole: 'admin',
45
- evidence: ['contract=crud-business-module/v1'],
46
- steps: ['打开新增弹窗', '提交表单'],
47
- assertions: ['弹窗关闭', '列表多一条'],
48
- unresolved: ['菜单路径待确认'],
49
- },
50
- ],
51
- reasoningSummary: {
52
- conclusion: '基于契约推理生成 1 条用例',
53
- evidenceChain: ['contract=crud-business-module/v1'],
54
- alternatives: [],
55
- confidence: 'medium',
56
- risks: [],
57
- needsHumanReview: true,
58
- },
59
- ...overrides,
60
- };
61
- }
62
- describe('parseGenerateTestcaseArgs', () => {
63
- it('缺 --code-list 时抛错', () => {
64
- expect(() => parseGenerateTestcaseArgs(['--tests-dir', '/x'])).toThrow(/缺少 --code-list/);
65
- });
66
- it('缺 --tests-dir 时抛错', () => {
67
- expect(() => parseGenerateTestcaseArgs(['--code-list', '/x'])).toThrow(/缺少 --tests-dir/);
68
- });
69
- it('参数对非法时抛错', () => {
70
- expect(() => parseGenerateTestcaseArgs(['--code-list'])).toThrow(/无效参数对/);
71
- });
72
- it('-- 分隔符被忽略', () => {
73
- const args = parseGenerateTestcaseArgs(['--', '--code-list', '/a', '--tests-dir', '/b']);
74
- expect(args.codeList).toBe('/a');
75
- expect(args.testsDir).toBe('/b');
76
- });
77
- it('解析 scenario / hints / project-dir / requirement-dir', () => {
78
- const args = parseGenerateTestcaseArgs([
79
- '--code-list', '/a',
80
- '--tests-dir', '/b',
81
- '--scenario', 'statistic',
82
- '--hints', '/h.json',
83
- '--project-dir', '/p',
84
- '--requirement-dir', '/r',
85
- ]);
86
- expect(args.scenario).toBe('statistic');
87
- expect(args.hints).toBe('/h.json');
88
- expect(args.projectDir).toBe('/p');
89
- expect(args.requirementDir).toBe('/r');
90
- });
91
- });
92
- describe('decideScenario 3 档优先级', () => {
93
- it('①档 --scenario CLI 参数优先', () => {
94
- expect(decideScenario({ codeList: '', testsDir: '', scenario: 'approval', hints: '/path?scenario=statistic' })).toBe('approval');
95
- });
96
- it('②档 hints 路径含 scenario=', () => {
97
- expect(decideScenario({ codeList: '', testsDir: '', hints: '/path?scenario=statistic.kpi' })).toBe('statistic.kpi');
98
- });
99
- it('③档默认 crud.single-page', () => {
100
- expect(decideScenario({ codeList: '', testsDir: '' })).toBe('crud.single-page');
101
- });
102
- it('hints 路径不含 scenario= 时回落默认', () => {
103
- expect(decideScenario({ codeList: '', testsDir: '', hints: '/h.json' })).toBe('crud.single-page');
104
- });
105
- });
106
- describe('renderTestcaseMarkdown', () => {
107
- it('字段名翻译为中文', () => {
108
- const md = renderTestcaseMarkdown(makeTestcase());
109
- expect(md).toMatch(/场景类型:单页 CRUD/);
110
- expect(md).toMatch(/菜单路径:业务管理/);
111
- expect(md).toMatch(/操作角色:admin/);
112
- expect(md).toMatch(/骨架已命中动作/);
113
- expect(md).toMatch(/骨架未命中候选/);
114
- });
115
- it('状态值翻译为中文', () => {
116
- const md = renderTestcaseMarkdown(makeTestcase());
117
- expect(md).toMatch(/状态:已计划/); // planned
118
- expect(md).toMatch(/审阅状态:待审阅/); // needs_review
119
- });
120
- it('未确认项字段标题硬编码「未确认项」(不被状态值映射污染)', () => {
121
- const md = renderTestcaseMarkdown(makeTestcase());
122
- expect(md).toMatch(/未确认项:菜单路径待确认/);
123
- // i18nTestcase('unresolved') 仍为「未解决」,不允许出现在字段标题位置
124
- expect(md).not.toMatch(/^\s*-\s+未解决:/m);
125
- });
126
- it('无 uncoveredCandidates 时显示「(无)」', () => {
127
- const md = renderTestcaseMarkdown(makeTestcase({ uncoveredCandidates: [] }));
128
- expect(md).toMatch(/骨架未命中候选\n(无)/);
129
- });
130
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=index.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../../test/cli/index.test.ts"],"names":[],"mappings":""}
@@ -1,93 +0,0 @@
1
- /**
2
- * src/cli/index.ts 路由测试(REQ-CLI-02 修订)。
3
- *
4
- * 覆盖:
5
- * - --help / -h 打印 testcase / spec / run 3 子命令且不含 contract
6
- * - 未知子命令 → process.exit(1)
7
- * - 路由到 parseGenerateTestcaseArgs / parseSpecArgs / parseRunArgs
8
- *
9
- * 用 vi.mock 替换 generate-testcase / spec / run 模块,避免占位 throw 干扰路由判定。
10
- */
11
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
12
- const generateTestcase = vi.hoisted(() => ({
13
- parseGenerateTestcaseArgs: vi.fn((argv) => ({ codeList: 'parsed', testsDir: argv.join(' ') })),
14
- runGenerateTestcase: vi.fn(() => 'TESTCASE_OK'),
15
- }));
16
- const spec = vi.hoisted(() => ({
17
- parseSpecArgs: vi.fn((argv) => ({ testcase: 'parsed', out: argv.join(' ') })),
18
- runSpec: vi.fn(() => 'SPEC_OK'),
19
- }));
20
- const run = vi.hoisted(() => ({
21
- parseRunArgs: vi.fn((argv) => ({ spec: 'parsed', testcase: argv.join(' '), headless: true })),
22
- runRun: vi.fn(async () => 'RUN_OK'),
23
- }));
24
- vi.mock('../../src/cli/generate-testcase.js', () => generateTestcase);
25
- vi.mock('../../src/cli/spec.js', () => spec);
26
- vi.mock('../../src/cli/run.js', () => run);
27
- const { main, printHelp } = await import('../../src/cli/index.js');
28
- describe('ep-stage-skill CLI 路由', () => {
29
- let logSpy;
30
- let errSpy;
31
- let exitSpy;
32
- beforeEach(() => {
33
- logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
34
- errSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
35
- exitSpy = vi.spyOn(process, 'exit').mockImplementation(((code) => {
36
- throw new Error(`__exit_${code ?? 0}__`);
37
- }));
38
- });
39
- afterEach(() => {
40
- logSpy.mockRestore();
41
- errSpy.mockRestore();
42
- exitSpy.mockRestore();
43
- vi.clearAllMocks();
44
- });
45
- it('--help 打印 3 子命令 testcase/spec/run 且不注册 contract 子命令', async () => {
46
- await main(['node', 'cli', '--help']);
47
- const printed = logSpy.mock.calls.flat().join('\n');
48
- expect(printed).toMatch(/ep-stage-skill testcase /);
49
- expect(printed).toMatch(/ep-stage-skill spec /);
50
- expect(printed).toMatch(/ep-stage-skill run /);
51
- // 反向断言:不存在 `ep-stage-skill contract` 子命令入口
52
- expect(printed).not.toMatch(/ep-stage-skill contract\b/);
53
- });
54
- it('-h 等价于 --help', async () => {
55
- await main(['node', 'cli', '-h']);
56
- expect(logSpy.mock.calls.flat().join('\n')).toMatch(/testcase/);
57
- });
58
- it('无参数时打印 help(不 exit)', async () => {
59
- await main(['node', 'cli']);
60
- expect(logSpy.mock.calls.flat().join('\n')).toMatch(/testcase/);
61
- expect(exitSpy).not.toHaveBeenCalled();
62
- });
63
- it('printHelp 不注册 contract 子命令', () => {
64
- printHelp();
65
- expect(logSpy.mock.calls.flat().join('\n')).not.toMatch(/ep-stage-skill contract\b/);
66
- });
67
- it('未知子命令 exit 1 + stderr', async () => {
68
- await expect(main(['node', 'cli', 'unknown-cmd'])).rejects.toThrow(/__exit_1__/);
69
- expect(errSpy.mock.calls.flat().join('\n')).toMatch(/未知子命令/);
70
- });
71
- it('testcase 子命令路由到 runGenerateTestcase', async () => {
72
- const result = await main(['node', 'cli', 'testcase', '--code-list', '/x']);
73
- expect(generateTestcase.parseGenerateTestcaseArgs).toHaveBeenCalledWith(['--code-list', '/x']);
74
- expect(generateTestcase.runGenerateTestcase).toHaveBeenCalled();
75
- expect(result).toBe('TESTCASE_OK');
76
- });
77
- it('spec 子命令路由到 runSpec', async () => {
78
- const result = await main(['node', 'cli', 'spec', '--testcase', '/t', '--out', '/o']);
79
- expect(spec.parseSpecArgs).toHaveBeenCalledWith(['--testcase', '/t', '--out', '/o']);
80
- expect(spec.runSpec).toHaveBeenCalled();
81
- expect(result).toBe('SPEC_OK');
82
- });
83
- it('run 子命令路由到 runRun', async () => {
84
- const result = await main(['node', 'cli', 'run', '--spec', '/s', '--testcase', '/t']);
85
- expect(run.parseRunArgs).toHaveBeenCalledWith(['--spec', '/s', '--testcase', '/t']);
86
- expect(run.runRun).toHaveBeenCalled();
87
- expect(result).toBe('RUN_OK');
88
- });
89
- it('-- 分隔符被忽略', async () => {
90
- await main(['node', 'cli', 'testcase', '--', '--code-list', '/x']);
91
- expect(generateTestcase.parseGenerateTestcaseArgs).toHaveBeenCalledWith(['--code-list', '/x']);
92
- });
93
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=run.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"run.test.d.ts","sourceRoot":"","sources":["../../../test/cli/run.test.ts"],"names":[],"mappings":""}
@@ -1,149 +0,0 @@
1
- /**
2
- * run.ts 单元测试(REQ-CHAIN-02 / REQ-SPEC-01)。
3
- *
4
- * 覆盖:
5
- * - parseRunArgs 缺参 / 非法 / --headless 默认 true / --headless false 关闭无头;
6
- * - runRun:
7
- * · v1 spec → 追加 AI 段产 v2(含 AI_APPEND_MARKER);
8
- * · 已有 AI 追加段时重跑覆盖(幂等);
9
- * · planned 为空时擦掉历史追加段(幂等收敛);
10
- * · 不存在 spec 时抛错;
11
- * · 报告目录基于 projectDir 推导(ISO 时间戳)。
12
- */
13
- import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
14
- import os from 'node:os';
15
- import path from 'node:path';
16
- import { beforeEach, describe, expect, it } from 'vitest';
17
- import { AI_APPEND_MARKER, parseRunArgs, runRun } from '../../src/cli/run.js';
18
- let tmpRoot;
19
- beforeEach(() => {
20
- tmpRoot = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-run-'));
21
- });
22
- function makeTestcase(plannedCount = 1) {
23
- const uncoveredCandidates = Array.from({ length: plannedCount }, (_, i) => ({
24
- actionId: `gap-${i}`,
25
- label: `导出 CSV ${i}`,
26
- status: 'planned',
27
- requiredRole: 'admin',
28
- businessIntent: `导出列表 ${i}`,
29
- assertionExpectation: `下载完成 ${i}`,
30
- evidence: ['contract=crud-business-module/v1'],
31
- }));
32
- return {
33
- schemaVersion: 'v2',
34
- scenario: 'crud.single-page',
35
- moduleId: 'zwplace',
36
- moduleName: '场所窗口信息管理',
37
- menuPath: '业务管理 > 场所',
38
- requiredSystems: [{ url: '' }],
39
- requiredRoles: [{ role: 'admin' }],
40
- coveredActions: [],
41
- uncoveredCandidates,
42
- cases: [
43
- {
44
- caseId: 'zwplace.create',
45
- title: '新增',
46
- scenario: 'crud.single-page',
47
- reviewStatus: 'needs_review',
48
- requiredRole: 'admin',
49
- evidence: ['contract=crud-business-module/v1'],
50
- steps: [],
51
- assertions: [],
52
- unresolved: [],
53
- },
54
- ],
55
- reasoningSummary: {
56
- conclusion: '',
57
- evidenceChain: [],
58
- alternatives: [],
59
- confidence: 'medium',
60
- risks: [],
61
- needsHumanReview: true,
62
- },
63
- };
64
- }
65
- function writeFixtures(opts) {
66
- const dir = path.join(tmpRoot, 'req');
67
- mkdirSync(dir, { recursive: true });
68
- const specPath = path.join(dir, 'crud.spec.ts');
69
- const testcasePath = path.join(dir, 'testcase.json5');
70
- writeFileSync(specPath, opts.specBody, 'utf8');
71
- writeFileSync(testcasePath, JSON.stringify(makeTestcase(opts.plannedCount ?? 1)), 'utf8');
72
- return { specPath, testcasePath };
73
- }
74
- describe('parseRunArgs', () => {
75
- it('缺 --spec 时抛错', () => {
76
- expect(() => parseRunArgs(['--testcase', '/x'])).toThrow(/缺少 --spec/);
77
- });
78
- it('缺 --testcase 时抛错', () => {
79
- expect(() => parseRunArgs(['--spec', '/x'])).toThrow(/缺少 --testcase/);
80
- });
81
- it('参数对非法时抛错', () => {
82
- expect(() => parseRunArgs(['--spec'])).toThrow(/无效参数对/);
83
- });
84
- it('--headless 默认 true', () => {
85
- const args = parseRunArgs(['--spec', '/s', '--testcase', '/t']);
86
- expect(args.headless).toBe(true);
87
- });
88
- it('--headless false 关闭无头', () => {
89
- const args = parseRunArgs(['--spec', '/s', '--testcase', '/t', '--headless', 'false']);
90
- expect(args.headless).toBe(false);
91
- });
92
- it('--project-dir 透传', () => {
93
- const args = parseRunArgs(['--spec', '/s', '--testcase', '/t', '--project-dir', '/p']);
94
- expect(args.projectDir).toBe('/p');
95
- });
96
- });
97
- describe('runRun', () => {
98
- it('v1 spec 追加 AI 段产 v2', async () => {
99
- const v1Body = '// === 胶水模式生成 ===\nconst x = 1;\n';
100
- const { specPath, testcasePath } = writeFixtures({ specBody: v1Body });
101
- const result = await runRun({
102
- spec: specPath,
103
- testcase: testcasePath,
104
- headless: true,
105
- projectDir: tmpRoot,
106
- });
107
- expect(result.version).toBe('v1-with-planned-stubs');
108
- const body = readFileSync(specPath, 'utf8');
109
- expect(body).toContain(AI_APPEND_MARKER);
110
- expect(body).toMatch(/test\("导出 CSV 0"/);
111
- expect(body).toMatch(/requiredRole: admin/);
112
- expect(result.reportDir.startsWith(path.join(tmpRoot, 'e2e/glue-code-mvp/glue-report'))).toBe(true);
113
- });
114
- it('已有 AI 追加段时重跑覆盖(幂等)', async () => {
115
- const v1Body = '// === 胶水模式生成 ===\nconst x = 1;\n';
116
- const oldAppended = `\n\n${AI_APPEND_MARKER}\ntest("旧追加段", async () => {});\n`;
117
- const { specPath, testcasePath } = writeFixtures({ specBody: v1Body + oldAppended });
118
- await runRun({ spec: specPath, testcase: testcasePath, headless: true });
119
- const body = readFileSync(specPath, 'utf8');
120
- // 旧追加段被擦掉,只剩当次推理结果
121
- expect(body).not.toMatch(/旧追加段/);
122
- // 标记只出现一次(重跑不叠加)
123
- expect(body.match(/AI 钻探追加/g)?.length).toBe(1);
124
- expect(body).toMatch(/test\("导出 CSV 0"/);
125
- });
126
- it('planned 为空时擦掉历史追加段(幂等收敛)', async () => {
127
- const v1Body = '// === 胶水模式生成 ===\nconst x = 1;';
128
- const oldAppended = `\n\n${AI_APPEND_MARKER}\ntest("旧", async () => {});\n`;
129
- const { specPath, testcasePath } = writeFixtures({
130
- specBody: v1Body + oldAppended,
131
- plannedCount: 0,
132
- });
133
- await runRun({ spec: specPath, testcase: testcasePath, headless: true });
134
- const body = readFileSync(specPath, 'utf8');
135
- expect(body).not.toContain(AI_APPEND_MARKER);
136
- expect(body).not.toMatch(/旧追加段|旧/);
137
- });
138
- it('spec 不存在时抛错', async () => {
139
- const dir = path.join(tmpRoot, 'r2');
140
- mkdirSync(dir, { recursive: true });
141
- const testcasePath = path.join(dir, 'testcase.json5');
142
- writeFileSync(testcasePath, JSON.stringify(makeTestcase()), 'utf8');
143
- await expect(runRun({
144
- spec: path.join(dir, 'missing.spec.ts'),
145
- testcase: testcasePath,
146
- headless: true,
147
- })).rejects.toThrow(/spec 不存在/);
148
- });
149
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=spec.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"spec.test.d.ts","sourceRoot":"","sources":["../../../test/cli/spec.test.ts"],"names":[],"mappings":""}
@@ -1,196 +0,0 @@
1
- /**
2
- * spec.ts 单元测试(REQ-SPEC-01)。
3
- *
4
- * 覆盖:
5
- * - parseSpecArgs 缺参 / 非法 / -- 分隔符
6
- * - runSpec:
7
- * · 缺 contract.json 时报错(引导用户先跑 testcase 子命令);
8
- * · 正确装配 v1 .spec.ts(含 SKELETON_MARKER_LINE 头部标记 + skeleton 主体);
9
- * · testcase.json5 不通过 validateTestcase 时抛错(透传 v2 schema 校验)。
10
- */
11
- import { mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
12
- import os from 'node:os';
13
- import path from 'node:path';
14
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
15
- import { parseSpecArgs, runSpec } from '../../src/cli/spec.js';
16
- let tmpRoot;
17
- beforeEach(() => {
18
- tmpRoot = mkdtempSync(path.join(os.tmpdir(), 'ep-stage-spec-'));
19
- });
20
- afterEach(() => {
21
- // 留 tmpdir 给宿主清理,避免删 fixture 时误命中其他测试。
22
- });
23
- function createContract() {
24
- return {
25
- contractVersion: 'crud-business-module/v1',
26
- module: { id: 'zwplace', label: '场所窗口信息管理' },
27
- pages: {
28
- list: {
29
- role: 'list',
30
- pageId: 'gxhzwplacelist',
31
- title: '场所窗口信息列表',
32
- iframeSrcKeyword: 'gxhzwplacelist',
33
- fields: [
34
- { name: 'windowname', label: '窗口名称', controlType: 'minitext', required: false, availability: 'auto_extractable', sources: [] },
35
- ],
36
- buttons: [],
37
- grids: [],
38
- dialogs: [],
39
- sources: [],
40
- },
41
- add: {
42
- role: 'add',
43
- pageId: 'gxhzwplaceadd',
44
- title: '新增场所窗口',
45
- iframeSrcKeyword: 'gxhzwplaceadd',
46
- fields: [
47
- { name: 'windowname', label: '窗口名称', controlType: 'minitext', required: true, availability: 'auto_extractable', sources: [] },
48
- ],
49
- buttons: [],
50
- grids: [],
51
- dialogs: [],
52
- sources: [],
53
- },
54
- edit: {
55
- role: 'edit',
56
- pageId: 'gxhzwplaceedit',
57
- title: '修改场所窗口',
58
- iframeSrcKeyword: 'gxhzwplaceedit',
59
- fields: [
60
- { name: 'windowname', label: '窗口名称', controlType: 'minitext', required: true, availability: 'auto_extractable', sources: [] },
61
- ],
62
- buttons: [],
63
- grids: [],
64
- dialogs: [],
65
- sources: [],
66
- },
67
- },
68
- dataKey: {
69
- field: 'windowname',
70
- label: '窗口名称',
71
- availability: 'derivable',
72
- generationStrategy: 'timestamp_prefix',
73
- sources: [],
74
- },
75
- searchConditions: [
76
- {
77
- field: 'windowname',
78
- label: '窗口名称',
79
- component: 'input',
80
- seedValue: '自动化测试窗口',
81
- generationStrategy: 'timestamp_prefix',
82
- sources: [],
83
- },
84
- ],
85
- flows: {
86
- create: {
87
- entryButton: { pageRole: 'list', label: '新增', sources: [] },
88
- targetPageId: 'gxhzwplaceadd',
89
- saveButton: { pageRole: 'add', label: '保存并关闭', sources: [] },
90
- overrideFields: [],
91
- },
92
- search: {
93
- searchField: { pageRole: 'list', field: 'windowname', label: '窗口名称', sources: [] },
94
- submitControl: { pageRole: 'list', label: '查询', sources: [] },
95
- resultGrid: 'datagrid',
96
- },
97
- update: {
98
- entryAction: { pageRole: 'list', label: '修改', sources: [] },
99
- targetPageId: 'gxhzwplaceedit',
100
- updateField: { pageRole: 'edit', field: 'windowname', label: '窗口名称', sources: [] },
101
- saveButton: { pageRole: 'edit', label: '保存并关闭', sources: [] },
102
- },
103
- delete: {
104
- entryButton: { pageRole: 'list', label: '删除', sources: [] },
105
- expectedOutcome: 'success_delete',
106
- confirmControl: { pageRole: 'list', label: '确定', sources: [] },
107
- },
108
- },
109
- assertions: [],
110
- businessRules: [],
111
- unresolvedSlots: [],
112
- };
113
- }
114
- function createTestcase() {
115
- return {
116
- schemaVersion: 'v2',
117
- scenario: 'crud.single-page',
118
- moduleId: 'zwplace',
119
- moduleName: '场所窗口信息管理',
120
- menuPath: '业务管理 > 场所窗口',
121
- requiredSystems: [{ url: '' }],
122
- requiredRoles: [{ role: 'admin' }],
123
- coveredActions: [
124
- { actionId: 'create', kind: '新增', evidence: ['contract=crud-business-module/v1'] },
125
- ],
126
- uncoveredCandidates: [],
127
- cases: [
128
- {
129
- caseId: 'zwplace.create',
130
- title: '新增',
131
- scenario: 'crud.single-page',
132
- reviewStatus: 'needs_review',
133
- requiredRole: 'admin',
134
- evidence: ['contract=crud-business-module/v1'],
135
- steps: [],
136
- assertions: [],
137
- unresolved: [],
138
- },
139
- ],
140
- reasoningSummary: {
141
- conclusion: '',
142
- evidenceChain: [],
143
- alternatives: [],
144
- confidence: 'medium',
145
- risks: [],
146
- needsHumanReview: true,
147
- },
148
- };
149
- }
150
- function writeFixtures() {
151
- const testsDir = path.join(tmpRoot, 'requirement-X');
152
- mkdirSync(path.join(testsDir, '.ep-stage'), { recursive: true });
153
- const testcasePath = path.join(testsDir, 'testcase.json5');
154
- const contractPath = path.join(testsDir, '.ep-stage', 'contract.json');
155
- writeFileSync(testcasePath, JSON.stringify(createTestcase()), 'utf8');
156
- writeFileSync(contractPath, JSON.stringify(createContract()), 'utf8');
157
- return { testcasePath, specOut: path.join(testsDir, 'crud.spec.ts') };
158
- }
159
- describe('parseSpecArgs', () => {
160
- it('缺 --testcase 时抛错', () => {
161
- expect(() => parseSpecArgs(['--out', '/x'])).toThrow(/缺少 --testcase/);
162
- });
163
- it('缺 --out 时抛错', () => {
164
- expect(() => parseSpecArgs(['--testcase', '/x'])).toThrow(/缺少 --out/);
165
- });
166
- it('参数对非法时抛错', () => {
167
- expect(() => parseSpecArgs(['--testcase'])).toThrow(/无效参数对/);
168
- });
169
- it('-- 分隔符被忽略', () => {
170
- const args = parseSpecArgs(['--', '--testcase', '/a', '--out', '/b']);
171
- expect(args).toEqual({ testcase: '/a', out: '/b' });
172
- });
173
- });
174
- describe('runSpec', () => {
175
- it('正常装配 v1 .spec.ts(含 SKELETON_MARKER_LINE)', () => {
176
- const { testcasePath, specOut } = writeFixtures();
177
- const result = runSpec({ testcase: testcasePath, out: specOut });
178
- expect(result).toEqual({ specPath: specOut, version: 'v1' });
179
- const body = readFileSync(specOut, 'utf8');
180
- expect(body).toMatch(/^\/\/ === 胶水模式生成(基于 contract \+ coveredActions)===/);
181
- // skeleton 主体(来自 generateStageSkeletonCrudSpec)应含 Playwright import 或 test 入口
182
- expect(body.length).toBeGreaterThan(100);
183
- });
184
- it('缺 .ep-stage/contract.json 时抛错引导', () => {
185
- const testsDir = path.join(tmpRoot, 'requirement-Y');
186
- mkdirSync(testsDir, { recursive: true });
187
- const testcasePath = path.join(testsDir, 'testcase.json5');
188
- writeFileSync(testcasePath, JSON.stringify(createTestcase()), 'utf8');
189
- expect(() => runSpec({ testcase: testcasePath, out: path.join(testsDir, 'out.spec.ts') })).toThrow(/未找到.*contract\.json/);
190
- });
191
- it('testcase.json5 schemaVersion 不是 v2 时由 validateTestcase 抛错', () => {
192
- const { testcasePath, specOut } = writeFixtures();
193
- writeFileSync(testcasePath, JSON.stringify({ schemaVersion: 'v1' }), 'utf8');
194
- expect(() => runSpec({ testcase: testcasePath, out: specOut })).toThrow(/schemaVersion 不是 v2/);
195
- });
196
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=crud-contract.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"crud-contract.test.d.ts","sourceRoot":"","sources":["../../test/crud-contract.test.ts"],"names":[],"mappings":""}