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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/SKILL.md +27 -27
  2. package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -223
  3. package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -275
  4. package/codex-skill/ep-stage/glue-generate-testcase/examples/observable-testcase.json +40 -40
  5. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +95 -95
  6. package/codex-skill/ep-stage/glue-run-test/SKILL.md +273 -296
  7. package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -30
  8. package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -132
  9. package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -44
  10. package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
  11. package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +127 -166
  12. package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
  13. package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
  14. package/codex-skill/ep-stage/scripts/validate-skill.mjs +95 -98
  15. package/dist/src/cli/generate-testcase.d.ts +0 -1
  16. package/dist/src/cli/generate-testcase.d.ts.map +1 -1
  17. package/dist/src/cli/generate-testcase.js +1 -6
  18. package/dist/src/cli/index.d.ts.map +1 -1
  19. package/dist/src/cli/index.js +14 -38
  20. package/dist/src/cli/run.d.ts +0 -2
  21. package/dist/src/cli/run.d.ts.map +1 -1
  22. package/dist/src/cli/run.js +21 -70
  23. package/dist/src/cli/spec.d.ts.map +1 -1
  24. package/dist/src/cli/spec.js +0 -9
  25. package/dist/src/contracts/glue-contract-v1.d.ts +0 -35
  26. package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -1
  27. package/dist/src/extractors/code-list-json5.d.ts +1 -34
  28. package/dist/src/extractors/code-list-json5.d.ts.map +1 -1
  29. package/dist/src/extractors/code-list-json5.js +0 -1
  30. package/dist/src/extractors/html-page.d.ts +0 -9
  31. package/dist/src/extractors/html-page.d.ts.map +1 -1
  32. package/dist/src/extractors/html-page.js +2 -26
  33. package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
  34. package/dist/src/generators/stage-skeleton-script.d.ts +0 -50
  35. package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
  36. package/dist/src/generators/stage-skeleton-script.js +116 -449
  37. package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -1
  38. package/dist/src/runtime/ai-runtime-trace.js +0 -8
  39. package/dist/src/testcase/contract-testcase-assembler.d.ts +0 -28
  40. package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
  41. package/dist/src/testcase/contract-testcase-assembler.js +75 -329
  42. package/dist/src/validation/contract.d.ts.map +1 -1
  43. package/dist/src/validation/contract.js +0 -29
  44. package/dist/test/cli/generate-testcase-json5.test.d.ts +2 -0
  45. package/dist/test/cli/generate-testcase-json5.test.d.ts.map +1 -0
  46. package/dist/test/cli/generate-testcase-json5.test.js +154 -0
  47. package/dist/test/cli/index.test.d.ts +2 -0
  48. package/dist/test/cli/index.test.d.ts.map +1 -0
  49. package/dist/test/cli/index.test.js +114 -0
  50. package/dist/test/cli/run.test.d.ts +2 -0
  51. package/dist/test/cli/run.test.d.ts.map +1 -0
  52. package/dist/test/cli/run.test.js +429 -0
  53. package/dist/test/cli/spec.test.d.ts +2 -0
  54. package/dist/test/cli/spec.test.d.ts.map +1 -0
  55. package/dist/test/cli/spec.test.js +112 -0
  56. package/dist/test/code-list-json5.test.d.ts +2 -0
  57. package/dist/test/code-list-json5.test.d.ts.map +1 -0
  58. package/dist/test/code-list-json5.test.js +84 -0
  59. package/dist/test/code-list.test.d.ts +2 -0
  60. package/dist/test/code-list.test.d.ts.map +1 -0
  61. package/dist/test/code-list.test.js +39 -0
  62. package/dist/test/gap-inference.test.d.ts +2 -0
  63. package/dist/test/gap-inference.test.d.ts.map +1 -0
  64. package/dist/test/gap-inference.test.js +597 -0
  65. package/dist/test/generalization.test.d.ts +2 -0
  66. package/dist/test/generalization.test.d.ts.map +1 -0
  67. package/dist/test/generalization.test.js +73 -0
  68. package/dist/test/material-inventory.test.d.ts +2 -0
  69. package/dist/test/material-inventory.test.d.ts.map +1 -0
  70. package/dist/test/material-inventory.test.js +155 -0
  71. package/dist/test/observable-chain.test.d.ts +2 -0
  72. package/dist/test/observable-chain.test.d.ts.map +1 -0
  73. package/dist/test/observable-chain.test.js +123 -0
  74. package/dist/test/page-structure.test.d.ts +2 -0
  75. package/dist/test/page-structure.test.d.ts.map +1 -0
  76. package/dist/test/page-structure.test.js +45 -0
  77. package/dist/test/runtime/ai-runtime-trace.test.d.ts +2 -0
  78. package/dist/test/runtime/ai-runtime-trace.test.d.ts.map +1 -0
  79. package/dist/test/runtime/ai-runtime-trace.test.js +78 -0
  80. package/dist/test/scenario-inference.test.d.ts +2 -0
  81. package/dist/test/scenario-inference.test.d.ts.map +1 -0
  82. package/dist/test/scenario-inference.test.js +73 -0
  83. package/dist/test/stage-context.test.d.ts +2 -0
  84. package/dist/test/stage-context.test.d.ts.map +1 -0
  85. package/dist/test/stage-context.test.js +395 -0
  86. package/dist/test/testcase/testcase-markdown.test.d.ts +2 -0
  87. package/dist/test/testcase/testcase-markdown.test.d.ts.map +1 -0
  88. package/dist/test/testcase/testcase-markdown.test.js +48 -0
  89. package/dist/test/testcase-generator.test.d.ts +2 -0
  90. package/dist/test/testcase-generator.test.d.ts.map +1 -0
  91. package/dist/test/testcase-generator.test.js +276 -0
  92. package/dist/test/testcase-skeleton.test.d.ts +2 -0
  93. package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
  94. package/dist/test/testcase-skeleton.test.js +185 -0
  95. package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
  96. package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
  97. package/dist/test/testcase-spec-assembly.test.js +105 -0
  98. package/dist/test/util/credentials.test.d.ts +2 -0
  99. package/dist/test/util/credentials.test.d.ts.map +1 -0
  100. package/dist/test/util/credentials.test.js +64 -0
  101. package/dist/test/util/i18n-testcase.test.d.ts +2 -0
  102. package/dist/test/util/i18n-testcase.test.d.ts.map +1 -0
  103. package/dist/test/util/i18n-testcase.test.js +119 -0
  104. package/dist/test/util/softlink.test.d.ts +2 -0
  105. package/dist/test/util/softlink.test.d.ts.map +1 -0
  106. package/dist/test/util/softlink.test.js +82 -0
  107. package/dist/test/validation/contract.test.d.ts +2 -0
  108. package/dist/test/validation/contract.test.d.ts.map +1 -0
  109. package/dist/test/validation/contract.test.js +116 -0
  110. package/dist/test/validation/credentials.test.d.ts +2 -0
  111. package/dist/test/validation/credentials.test.d.ts.map +1 -0
  112. package/dist/test/validation/credentials.test.js +74 -0
  113. package/dist/test/validation/projects-index.test.d.ts +2 -0
  114. package/dist/test/validation/projects-index.test.d.ts.map +1 -0
  115. package/dist/test/validation/projects-index.test.js +48 -0
  116. package/dist/test/validation/testcase-v1-draft.test.d.ts +2 -0
  117. package/dist/test/validation/testcase-v1-draft.test.d.ts.map +1 -0
  118. package/dist/test/validation/testcase-v1-draft.test.js +173 -0
  119. package/dist/vitest.config.d.ts +3 -0
  120. package/dist/vitest.config.d.ts.map +1 -0
  121. package/dist/vitest.config.js +7 -0
  122. package/docs/README.md +124 -124
  123. package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
  124. package/examples/schemeresource.module-hints.json +38 -38
  125. package/examples/schemeresource.observable.code_list.md +37 -37
  126. package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
  127. package/examples/zwplace-placecategory-validation.json +29 -29
  128. package/examples/zwplace.module-hints.json +69 -69
  129. package/examples/zwplace.observable.code_list.md +37 -37
  130. package/package.json +19 -13
  131. package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +0 -72
  132. package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +0 -233
  133. package/codex-skill/ep-stage/page-element-scan/SKILL.md +0 -91
  134. package/codex-skill/ep-stage/page-element-scan/evals/evals.json +0 -37
  135. package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +0 -85
  136. package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +0 -153
  137. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +0 -233
  138. package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +0 -298
  139. package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +0 -749
  140. package/dist/src/cli/probe.d.ts +0 -46
  141. package/dist/src/cli/probe.d.ts.map +0 -1
  142. package/dist/src/cli/probe.js +0 -447
  143. package/dist/src/cli/run-report.d.ts +0 -46
  144. package/dist/src/cli/run-report.d.ts.map +0 -1
  145. package/dist/src/cli/run-report.js +0 -331
  146. package/dist/src/extractors/prd-extractor.d.ts +0 -70
  147. package/dist/src/extractors/prd-extractor.d.ts.map +0 -1
  148. package/dist/src/extractors/prd-extractor.js +0 -248
  149. package/dist/src/testcase/button-interaction-inference.d.ts +0 -37
  150. package/dist/src/testcase/button-interaction-inference.d.ts.map +0 -1
  151. package/dist/src/testcase/button-interaction-inference.js +0 -385
  152. package/dist/src/testcase/prd-button-mapper.d.ts +0 -72
  153. package/dist/src/testcase/prd-button-mapper.d.ts.map +0 -1
  154. package/dist/src/testcase/prd-button-mapper.js +0 -148
  155. package/dist/src/testcase/testcase-v2.d.ts +0 -50
  156. package/dist/src/testcase/testcase-v2.d.ts.map +0 -1
  157. package/dist/src/testcase/testcase-v2.js +0 -1
  158. package/docs/mvp-usage-guide.md +0 -298
@@ -1,69 +1,69 @@
1
- {
2
- "dataKey": {
3
- "selectedField": "placename",
4
- "generationStrategy": "timestamp_prefix"
5
- },
6
- "searchConditions": [
7
- {
8
- "field": "placename",
9
- "component": "input",
10
- "seedValue": "自动化测试名称",
11
- "strategy": "timestamp_prefix",
12
- "updatePrefix": "修改"
13
- },
14
- {
15
- "field": "placecategory",
16
- "component": "listbox",
17
- "value": "条线大厅",
18
- "createSelections": [
19
- {
20
- "field": "placecategory",
21
- "value": "条线大厅"
22
- }
23
- ]
24
- },
25
- {
26
- "field": "status",
27
- "component": "listbox",
28
- "value": "在用",
29
- "createSelections": [
30
- {
31
- "field": "placecategory",
32
- "value": "条线大厅"
33
- }
34
- ]
35
- }
36
- ],
37
- "buttonAliases": {
38
- "create": ["新增场所信息管理", "新增"],
39
- "saveAndClose": ["保存并关闭"],
40
- "search": ["搜索", "查询"],
41
- "edit": ["修改", "编辑"],
42
- "delete": ["删除选定"],
43
- "confirm": ["确定"]
44
- },
45
- "autofill": {
46
- "overrideFields": [
47
- {
48
- "field": "placename",
49
- "strategy": "timestamp_prefix",
50
- "value": "自动化测试场所名称"
51
- }
52
- ]
53
- },
54
- "deletePolicy": "blocked_by_business_rule",
55
- "assertionPolicy": {
56
- "afterCreate": "record_exists",
57
- "afterUpdate": "record_exists",
58
- "afterDelete": "toast_message"
59
- },
60
- "customWorkflows": [
61
- {
62
- "label": "同步窗口管理系统",
63
- "actionAlias": ["同步窗口管理系统"],
64
- "expectedIntent": "同步窗口管理系统数据",
65
- "expectedAssertion": "同步完成",
66
- "include": true
67
- }
68
- ]
69
- }
1
+ {
2
+ "dataKey": {
3
+ "selectedField": "placename",
4
+ "generationStrategy": "timestamp_prefix"
5
+ },
6
+ "searchConditions": [
7
+ {
8
+ "field": "placename",
9
+ "component": "input",
10
+ "seedValue": "自动化测试名称",
11
+ "strategy": "timestamp_prefix",
12
+ "updatePrefix": "修改"
13
+ },
14
+ {
15
+ "field": "placecategory",
16
+ "component": "listbox",
17
+ "value": "条线大厅",
18
+ "createSelections": [
19
+ {
20
+ "field": "placecategory",
21
+ "value": "条线大厅"
22
+ }
23
+ ]
24
+ },
25
+ {
26
+ "field": "status",
27
+ "component": "listbox",
28
+ "value": "在用",
29
+ "createSelections": [
30
+ {
31
+ "field": "placecategory",
32
+ "value": "条线大厅"
33
+ }
34
+ ]
35
+ }
36
+ ],
37
+ "buttonAliases": {
38
+ "create": ["新增场所信息管理", "新增"],
39
+ "saveAndClose": ["保存并关闭"],
40
+ "search": ["搜索", "查询"],
41
+ "edit": ["修改", "编辑"],
42
+ "delete": ["删除选定"],
43
+ "confirm": ["确定"]
44
+ },
45
+ "autofill": {
46
+ "overrideFields": [
47
+ {
48
+ "field": "placename",
49
+ "strategy": "timestamp_prefix",
50
+ "value": "自动化测试场所名称"
51
+ }
52
+ ]
53
+ },
54
+ "deletePolicy": "blocked_by_business_rule",
55
+ "assertionPolicy": {
56
+ "afterCreate": "record_exists",
57
+ "afterUpdate": "record_exists",
58
+ "afterDelete": "toast_message"
59
+ },
60
+ "customWorkflows": [
61
+ {
62
+ "label": "同步窗口管理系统",
63
+ "actionAlias": ["同步窗口管理系统"],
64
+ "expectedIntent": "同步窗口管理系统数据",
65
+ "expectedAssertion": "同步完成",
66
+ "include": true
67
+ }
68
+ ]
69
+ }
@@ -1,37 +1,37 @@
1
- # 场所窗口信息管理代码清单
2
-
3
- > 此文件是 `knowledge-project/epoint-web-v9.5.2/_docs/code_list.md` 的**可写镜像**。
4
- > 真实上游 `knowledge-project/.../_docs/code_list.md` 是当前唯一真相源;本文件只用于开发期模拟交互补齐后的回写结果,以及只读真相源无法落盘时的 e2e 入口验证。
5
-
6
- knowledgeRoot: ../../../knowledge-project/epoint-web-v9.5.2
7
- menu: 场所窗口信息管理>场所窗口信息列表
8
-
9
- ---
10
-
11
- ## 已补齐入口字段
12
-
13
- - `knowledgeRoot`:用于把 `code_list.md` 中的相对物料路径统一解析到真实上游物料根目录。
14
- - `menu`:用于默认入口直达菜单导航,避免链路停在菜单候选 gate。
15
- - `./zwplace.module-hints.json`:用于补齐 `ModuleHints`,避免 custom workflow 只停留在 candidate。
16
-
17
- ## 文档物料(模拟交互补齐后的可消费 docsDir)
18
-
19
- - `./zwplace-observable-docs/zwplace.context.md`
20
-
21
- ## 前端文件
22
-
23
- - `src/main/webapp/perpage/zwplace/gxhzwplacelist.html`
24
- - `src/main/webapp/perpage/zwplace/gxhzwplaceadd.html`
25
- - `src/main/webapp/perpage/zwplace/gxhzwplaceedit.html`
26
- - `src/main/webapp/perpage/zwplace/gxhzwplacedetail.html`
27
-
28
- ## 后端文件
29
-
30
- - `src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java`
31
- - `src/main/java/com/epoint/zwplace/action/GxhZwPlaceAddAction.java`
32
- - `src/main/java/com/epoint/zwplace/action/GxhZwPlaceEditAction.java`
33
- - `src/main/java/com/epoint/zwplace/action/GxhZwPlaceDetailAction.java`
34
-
35
- ## 提示物料
36
-
37
- - `./zwplace.module-hints.json`
1
+ # 场所窗口信息管理代码清单
2
+
3
+ > 此文件是 `knowledge-project/epoint-web-v9.5.2/_docs/code_list.md` 的**可写镜像**。
4
+ > 真实上游 `knowledge-project/.../_docs/code_list.md` 是当前唯一真相源;本文件只用于开发期模拟交互补齐后的回写结果,以及只读真相源无法落盘时的 e2e 入口验证。
5
+
6
+ knowledgeRoot: ../../../knowledge-project/epoint-web-v9.5.2
7
+ menu: 场所窗口信息管理>场所窗口信息列表
8
+
9
+ ---
10
+
11
+ ## 已补齐入口字段
12
+
13
+ - `knowledgeRoot`:用于把 `code_list.md` 中的相对物料路径统一解析到真实上游物料根目录。
14
+ - `menu`:用于默认入口直达菜单导航,避免链路停在菜单候选 gate。
15
+ - `./zwplace.module-hints.json`:用于补齐 `ModuleHints`,避免 custom workflow 只停留在 candidate。
16
+
17
+ ## 文档物料(模拟交互补齐后的可消费 docsDir)
18
+
19
+ - `./zwplace-observable-docs/zwplace.context.md`
20
+
21
+ ## 前端文件
22
+
23
+ - `src/main/webapp/perpage/zwplace/gxhzwplacelist.html`
24
+ - `src/main/webapp/perpage/zwplace/gxhzwplaceadd.html`
25
+ - `src/main/webapp/perpage/zwplace/gxhzwplaceedit.html`
26
+ - `src/main/webapp/perpage/zwplace/gxhzwplacedetail.html`
27
+
28
+ ## 后端文件
29
+
30
+ - `src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java`
31
+ - `src/main/java/com/epoint/zwplace/action/GxhZwPlaceAddAction.java`
32
+ - `src/main/java/com/epoint/zwplace/action/GxhZwPlaceEditAction.java`
33
+ - `src/main/java/com/epoint/zwplace/action/GxhZwPlaceDetailAction.java`
34
+
35
+ ## 提示物料
36
+
37
+ - `./zwplace.module-hints.json`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epoint-testtech/ep-stage-skill",
3
- "version": "0.0.10",
3
+ "version": "0.5.0-alpha.2",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -9,7 +9,11 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
- "codex-skill",
12
+ "codex-skill/ep-stage/glue-create-project",
13
+ "codex-skill/ep-stage/glue-generate-testcase",
14
+ "codex-skill/ep-stage/glue-run-test",
15
+ "codex-skill/ep-stage/recording-to-glue",
16
+ "codex-skill/ep-stage/scripts",
13
17
  "docs",
14
18
  "examples",
15
19
  "SKILL.md"
@@ -19,6 +23,18 @@
19
23
  "registry": "https://registry.npmjs.org/"
20
24
  },
21
25
  "description": "Agent skills and TypeScript tools for ep-stage glue-code workflows.",
26
+ "scripts": {
27
+ "typecheck": "tsc --noEmit",
28
+ "test": "vitest run",
29
+ "build": "tsc",
30
+ "prepare": "pnpm run build",
31
+ "prepack": "pnpm run build",
32
+ "generate:crud-contract": "node --import tsx src/cli/dev/extract-contract.ts",
33
+ "generate:playwright-tests": "node --import tsx src/cli/index.ts spec",
34
+ "run:gap-pipeline": "node --import tsx src/cli/run-gap-pipeline.ts",
35
+ "cli": "node --import tsx src/cli/index.ts",
36
+ "generate:testcase": "node --import tsx src/cli/index.ts testcase"
37
+ },
22
38
  "dependencies": {
23
39
  "cheerio": "^1.0.0",
24
40
  "json5": "^2.2.3",
@@ -29,15 +45,5 @@
29
45
  "tsx": "^4.20.0",
30
46
  "typescript": "^6.0.3",
31
47
  "vitest": "^4.0.0"
32
- },
33
- "scripts": {
34
- "typecheck": "tsc --noEmit",
35
- "test": "vitest run",
36
- "build": "tsc -p tsconfig.build.json",
37
- "generate:crud-contract": "node --import tsx src/cli/dev/extract-contract.ts",
38
- "generate:playwright-tests": "node --import tsx src/cli/index.ts spec",
39
- "run:gap-pipeline": "node --import tsx src/cli/run-gap-pipeline.ts",
40
- "cli": "node --import tsx src/cli/index.ts",
41
- "generate:testcase": "node --import tsx src/cli/index.ts testcase"
42
48
  }
43
- }
49
+ }
@@ -1,72 +0,0 @@
1
- <!--
2
- testcase.md 渲染模板(AI 用)。
3
-
4
- 用法(由 glue-generate-testcase SKILL Step 6 驱动):
5
- - 数据源:`<需求名>/testcase.json5`(cases 结构 = 事实源) + `<需求名>/.ep-stage/contract.json`(业务槽位)+ `<需求名>/credentials.json5`(环境信息)
6
- - AI 按本模板渲染 testcase.md,重跑覆盖
7
- - 忠实性约束:cases 数量 / caseId / title / reviewStatus / requiredRole 必须**逐字**取自 testcase.json5;步骤/断言/前置条件中的具体槽位标签(菜单/列表页/按钮文字/查询字段)必须**逐字**取自 contract.json,AI 只组织自然语言句式,不得新增/捏造 contract 没有的槽位
8
-
9
- 下面是格式骨架。`{{...}}` 是占位,AI 渲染时全部替换;不要把这段说明注释写进产出的 testcase.md。
10
- -->
11
-
12
- ---
13
- schemaVersion: {{testcase.schemaVersion}} # 取自 testcase.json5 顶层
14
- scenario: {{testcase.scenario}} # 取自 testcase.json5 顶层
15
- moduleId: {{testcase.moduleId}} # 取自 testcase.json5 顶层
16
- contractVersion: {{contract.contractVersion}} # 取自 contract.json
17
- ---
18
-
19
- # {{testcase.moduleName}}胶水测试用例
20
-
21
- ## 环境信息
22
-
23
- - 登录地址:{{credentials[0].url}}
24
- - 登录账号:{{credentials[0].username}}
25
- - 登录密码:(已配置在 credentials.json5,不在此展示)
26
- - 菜单路由:{{testcase.menuPath || "(未填写,待 testcase.json5 审阅补齐)"}}
27
- - 列表页:{{contract.pages.list.title}}
28
- - 查询字段:{{contract.flows.search.searchField.label}}
29
- - 操作角色:{{testcase.requiredRoles[0].role}}
30
- - 未解析槽位:{{contract.unresolvedSlots.length > 0 ? contract.unresolvedSlots.map(s => s.slotId).join("、") : "无"}}
31
-
32
- ## CRUD 冒烟用例
33
-
34
- | 用例 ID | 标题 | 前置条件 | 步骤 | 断言 |
35
- |---|---|---|---|---|
36
- | {{case.caseId}} | {{case.title}} | {{case.preconditions}} | {{case.steps_joined}} | {{case.assertions_joined}} |
37
-
38
- <!--
39
- 说明(仅供 AI 渲染时参考,不写入产出 md):
40
-
41
- 「步骤」「断言」「前置条件」列的填充规则——
42
-
43
- cases[].steps / cases[].assertions 当前若为占位(如「执行 create 主流程(详见 .spec.ts)」、「验证 create 完成后业务断言」),
44
- AI 应该**基于 contract.json 槽位组装具体的自然语言表述**:
45
-
46
- - create 步骤(caseId 含 .create):`进入菜单 {testcase.menuPath || "<未填写菜单>"};确认列表页 {contract.pages.list.title};点击 {contract.flows.create.entryButton.label};填写 {contract.dataKey.label} 等字段;点击 {contract.flows.create.saveButton.label}`
47
- - create 断言:`新增后可在 {contract.pages.list.title} 按 {contract.flows.search.searchField.label} 查询到记录`
48
- - create 前置条件:`已登录并进入 {testcase.menuPath || "<未填写菜单>"}`
49
-
50
- - read 步骤(caseId 含 .read):`进入菜单 {testcase.menuPath || "<未填写菜单>"};按查询字段:{contract.flows.search.searchField.label} 输入测试值;点击 {contract.flows.search.submitControl.label}`
51
- - read 断言:`查询结果包含 {contract.flows.search.searchField.label} 对应的测试记录`
52
- - read 前置条件:`已存在可查询测试数据`
53
-
54
- - update 步骤(caseId 含 .update):`在 {contract.pages.list.title} 中定位测试记录;点击 {contract.flows.update.entryAction.label};修改 {contract.dataKey.label} 字段;点击 {contract.flows.update.saveButton.label}`
55
- - update 断言:`修改后可按 {contract.flows.search.searchField.label} 查询到更新后的记录`
56
- - update 前置条件:`已定位到待修改记录`
57
-
58
- - delete 步骤(caseId 含 .delete):`在 {contract.pages.list.title} 中定位测试记录;点击 {contract.flows.delete.entryButton.label};按契约策略 {contract.flows.delete.expectedOutcome} 确认删除结果`
59
- - delete 断言:`删除结果符合 contract.flows.delete.expectedOutcome={contract.flows.delete.expectedOutcome} 的预期`
60
- - delete 前置条件:`已定位到待删除记录`
61
-
62
- - 非 CRUD 用例(uncoveredCandidates 转入的 candidate / planned 用例):AI 按 testcase.json5 cases[].businessIntent + assertionExpectation 直译;缺时填「待人工补充」,不发挥
63
-
64
- testcase.json5 的 cases[].evidence 不展示在 md 表格(人审不需要技术性溯源);
65
- 若 testcase.json5 cases 数量为 0,testcase.md 表格只保留表头,下方追加一段说明「本需求未生成任何用例,请审阅 .ep-stage/inference-trace.json 排查」。
66
- -->
67
-
68
- ## 审阅引导
69
-
70
- 1. 表格中**步骤、断言列若仍含占位**(如「执行 X 主流程」),表示 contract.json 对应槽位证据不足;请检查 `.ep-stage/inference-trace.json` 的 `moduleHintsInference.unresolved` 与 `contract.json.unresolvedSlots`,补齐后重跑。
71
- 2. 如对推断步骤/断言不认可,**修改 `testcase.json5` 的 cases[] 字段**(非本 md 文件),重跑 `/glue-generate-testcase` 重新渲染本 md。
72
- 3. 凭据信息在 `credentials.json5`;登录密码不显示在本审阅视图。
@@ -1,233 +0,0 @@
1
- /**
2
- * extract-page-elements-eval.mjs
3
- *
4
- * 专供 playwright-cli eval 注入使用的浏览器端扫描内核。
5
- * !!!此文件无任何 import/export,仅包含纯 JavaScript 函数。
6
- * !!!不要在 Node.js 环境中 import 此文件,会报 SyntaxError。
7
- *
8
- * 用法(shell 注入):
9
- * playwright-cli -s=$SESSION eval "< $(cat extract-page-elements-eval.mjs) ; print(JSON.stringify(extractPageElements()))"
10
- *
11
- * 函数签名:
12
- * extractPageElements() 当前 frame 扫描,返回 { title, elements[] }
13
- * extractAllFramesElementMap() 所有 frame(含子 iframe)聚合,返回 { [label]: { locator, controlType, visible } }
14
- */
15
-
16
- /* ─────────────────────────────────────────────────────────────────────────── */
17
-
18
- function norm(s) { return (s || '').replace(/\s+/g, ' ').trim(); }
19
-
20
- function isVisible(el) {
21
- const st = window.getComputedStyle(el);
22
- if (st.display === 'none' || st.visibility === 'hidden') return false;
23
- const r = el.getBoundingClientRect();
24
- return r.width > 0 && r.height > 0;
25
- }
26
-
27
- function isEnabled(el) {
28
- const cls = el.className || '';
29
- return !el.disabled
30
- && el.getAttribute('aria-disabled') !== 'true'
31
- && !String(cls).includes('mini-disabled')
32
- && !String(cls).includes('disabled');
33
- }
34
-
35
- function fieldLabel(el) {
36
- // 分页控件不在表单字段采集范围内
37
- if (el.classList.contains('pagination-number')) return '';
38
- if (el.closest('.mini-pagination, .pagination-pagerchange')) return '';
39
- const wrapper = el.closest('[label]');
40
- if (wrapper) return norm(wrapper.getAttribute('label'));
41
- if (el.getAttribute('aria-label')) return norm(el.getAttribute('aria-label'));
42
- if (el.id) {
43
- const lab = document.querySelector('label[for="' + el.id + '"]');
44
- if (lab) return norm(lab.innerText);
45
- }
46
- const containerLabel = el.closest('div, td').querySelector('label');
47
- if (containerLabel) return norm(containerLabel.innerText).replace(/[::]$/, '');
48
- const prev = el.closest('td, div').previousElementSibling;
49
- if (prev && /label|td/.test(prev.tagName.toLowerCase())) return norm(prev.innerText).replace(/[::]$/, '');
50
- return norm(el.placeholder || el.getAttribute('bind') || el.name || el.id || '').replace(/^[  ]+|[  ]+$/g, '') || '';
51
- }
52
-
53
- function controlType(el) {
54
- const cls = String(el.className || '');
55
- const mini = cls.match(/mini-[a-z]+/);
56
- if (mini) return mini[0];
57
- if (el.tagName === 'SELECT') return 'html-select';
58
- if (el.tagName === 'TEXTAREA') return 'html-textarea';
59
- return 'html-' + (el.type || 'input');
60
- }
61
-
62
- function hiddenInputLocator(el, label) {
63
- const itemDiv = el.closest('.mini-radiobuttonlist-item, .mini-checkbox');
64
- if (itemDiv && itemDiv.id) return '#' + itemDiv.id.replace(/[!\\#$%&'()*+,/:;<=>?@[\\]^`{|}~]/g, '\\$&');
65
- if (label) {
66
- const type = el.type === 'radio' ? 'mini-radiobuttonlist-item' : 'mini-checkbox';
67
- return "xpath=//div[contains(@class,'" + type + "')][normalize-space()='" + label + "']";
68
- }
69
- return null;
70
- }
71
-
72
- function fieldLocator(el, label) {
73
- const type = controlType(el);
74
- const tag = el.tagName.toLowerCase();
75
- if (label && type.startsWith('mini-')) {
76
- const exact = "xpath=//div[@label='" + label + "']//" + tag + "[contains(@class,'" + type + "') or contains(@class,'" + type + "-input')]";
77
- if (document.querySelector("div[label='" + label + "']")) return exact;
78
- var bareLabel = label.replace(/\d+/g, '').replace(/\s+/g, ' ').trim();
79
- return "xpath=//div[@label and contains(@label,'" + bareLabel.replace(/[^一-龥a-zA-Z]/g, '') + "')]//" + tag + "[contains(@class,'" + type + "') or contains(@class,'" + type + "-input')]";
80
- }
81
- if (el.id) {
82
- if (el.id.includes('$')) {
83
- var prev = hiddenInputLocator(el, label);
84
- if (prev) return prev;
85
- }
86
- return '#' + el.id.replace(/[!\\#$%&'()*+,/:;<=>?@[\\]^`{|}~]/g, '\\$&');
87
- }
88
- if (el.name) return tag + '[name="' + el.name + '"]';
89
- if (label) return "xpath=//div[@label='" + label + "']//" + tag;
90
- return tag;
91
- }
92
-
93
- function buttonLocator(el, label) {
94
- var tag = el.tagName.toLowerCase();
95
- var cls = String(el.className || '');
96
- if (cls.includes('mini-button')) return 'xpath=//' + tag + "[contains(@class,'mini-button')][normalize-space()='" + label + "']";
97
- if (cls.includes('cond-srh-btn')) return "xpath=//a[contains(@class,'cond-srh-btn')][normalize-space()='" + label + "']";
98
- if (el.id) return '#' + el.id;
99
- return 'role=button[name="' + label + '"]';
100
- }
101
-
102
- /* ─── extractPageElements() ─── */
103
-
104
- function extractPageElements() {
105
- var elements = [];
106
- var seen = new Set();
107
- function push(el) {
108
- var key = el.type + '|' + el.label + '|' + el.locator;
109
- if (seen.has(key)) return;
110
- seen.add(key);
111
- elements.push(el);
112
- }
113
-
114
- /* 按钮 */
115
- document.querySelectorAll('a.mini-button, button, input[type=button], input[type=submit], input[type=reset], [role=button], a.cond-srh-btn, a.mini-tools, span.mini-tools').forEach(function(el) {
116
- var label = norm(el.innerText || el.value || el.getAttribute('data-tooltip') || el.title || '');
117
- if (!label) return;
118
- push({ type: 'button', label: label, locator: buttonLocator(el, label), visible: isVisible(el), enabled: isEnabled(el) });
119
- });
120
-
121
- /* 表单字段 */
122
- document.querySelectorAll('input:not([type=button]):not([type=submit]):not([type=reset]):not([type=hidden]), textarea, select').forEach(function(el) {
123
- if (!isVisible(el) && el.closest('.mini-textbox, .mini-combobox, .mini-datepicker, .mini-spinner')) return;
124
- if ((el.type === 'radio' || el.type === 'checkbox') && !isVisible(el)) return;
125
- var label = fieldLabel(el);
126
- if (!label) return;
127
- push({ type: 'field', label: label, controlType: controlType(el), locator: fieldLocator(el, label), visible: isVisible(el), enabled: isEnabled(el) });
128
- });
129
-
130
- /* MiniUI RadioButtonList / CheckBox 独立控件 */
131
- document.querySelectorAll('.mini-radiobuttonlist-item, .mini-checkbox').forEach(function(el) {
132
- var input = el.querySelector('input[type=radio], input[type=checkbox]');
133
- if (!input) return;
134
- var label = norm(input.labels && input.labels[0] ? input.labels[0].innerText : el.innerText || '').replace(/[::]$/, '');
135
- if (!label) return;
136
- var numInput = el.querySelector('.mini-export-num');
137
- if (numInput) label = label.replace(/\d+/g, '');
138
- var isRadio = input.type === 'radio';
139
- var itemId = el.id;
140
- var locator = itemId ? ('#' + itemId.replace(/[!\\#$%&'()*+,/:;<=>?@[\\]^`{|}~]/g, '\\$&'))
141
- : "xpath=//div[contains(@class,'" + (isRadio ? 'mini-radiobuttonlist-item' : 'mini-checkbox') + "')][contains(normalize-space(),'" + label + "')]";
142
- push({ type: 'field', label: label, controlType: isRadio ? 'mini-radiobuttonlist' : 'mini-checkbox', locator: locator, visible: isVisible(el), enabled: isEnabled(el) });
143
- });
144
-
145
- /* 列表 */
146
- document.querySelectorAll('.mini-datagrid').forEach(function(grid) {
147
- var gridId = grid.id || 'datagrid';
148
- var headers = [].map.call(grid.querySelectorAll('.mini-grid-headertxt, .mini-grid-headerText, th'), function(h) { return norm(h.innerText); }).filter(Boolean);
149
- var rowActions = [].map.call(grid.querySelectorAll('[data-tooltip]'), function(a) { return norm(a.getAttribute('data-tooltip')); }).filter(Boolean);
150
- push({ type: 'grid', label: gridId, locator: '#' + gridId, visible: isVisible(grid), enabled: true, columns: headers, rowActions: rowActions });
151
- });
152
-
153
- /* 标签页 */
154
- document.querySelectorAll('.mini-tabs li, [role=tab]').forEach(function(el) {
155
- var label = norm(el.innerText);
156
- if (!label) return;
157
- push({ type: 'tab', label: label, locator: 'role=tab[name="' + label + '"]', visible: isVisible(el), enabled: isEnabled(el) });
158
- });
159
-
160
- /* 普通链接 */
161
- document.querySelectorAll('a[href]:not(.mini-button)').forEach(function(el) {
162
- var label = norm(el.innerText);
163
- if (!label || !isVisible(el)) return;
164
- push({ type: 'link', label: label, locator: 'role=link[name="' + label + '"]', visible: true, enabled: isEnabled(el), href: el.getAttribute('href') });
165
- });
166
-
167
- /* MiniUI 分页控件 */
168
- document.querySelectorAll('.mini-grid-pager').forEach(function(pager) {
169
- var parentGrid = pager.closest('.mini-datagrid');
170
- var gridId = (parentGrid && parentGrid.id) ? parentGrid.id : 'datagrid';
171
- var prefix = parentGrid ? ('#' + gridId.replace(/[!\\#$%&'()*+,/:;<=>?@[\\]^`{|}~]/g, '\\$&')) : '';
172
-
173
- /* 总条数文本 */
174
- var pagerInfo = pager.querySelector('.pagination-pagerinfo');
175
- if (pagerInfo) push({ type: 'pagination', label: norm(pagerInfo.innerText), locator: prefix + ' .pagination-pagerinfo', visible: true, enabled: true });
176
-
177
- /* 分页按钮 */
178
- var btns = pager.querySelectorAll('.pagination-buttons a');
179
- btns.forEach(function(btn) {
180
- var action = btn.getAttribute('action');
181
- var isEllipsis = btn.classList.contains('pagination-button-ellipsis');
182
- var label = norm(btn.innerText) || '';
183
- var subType = '', suffix = '';
184
- if (action === 'prev') { subType = 'pagination-prev'; label = '上一页'; suffix = '.pagination-buttons a[action=prev]'; }
185
- else if (action === 'next') { subType = 'pagination-next'; label = '下一页'; suffix = '.pagination-buttons a[action=next]'; }
186
- else if (isEllipsis || action === 'none') { subType = 'pagination-ellipsis'; label = '...'; suffix = '.pagination-button-ellipsis'; }
187
- else { subType = 'pagination-page'; label = norm(btn.innerText); suffix = '.pagination-buttons a.pagination-button:not(.pagination-button-ellipsis)'; }
188
- push({ type: 'pagination', subType: subType, label: label, locator: prefix + ' ' + suffix, visible: isVisible(btn), enabled: !btn.classList.contains('disabled') });
189
- });
190
-
191
- /* 页面尺寸下拉 */
192
- var pageSizeBtn = pager.querySelector('.mini-pagination > span.mini-buttonedit');
193
- if (pageSizeBtn && isVisible(pageSizeBtn)) {
194
- var hiddenVal = pageSizeBtn.querySelector('input[type=hidden]');
195
- push({ type: 'pagination', subType: 'pagination-size', label: '页面尺寸', locator: prefix + ' .mini-pagination > span.mini-buttonedit', value: hiddenVal ? hiddenVal.value : '', visible: true, enabled: true });
196
- }
197
-
198
- /* 页码跳转输入框 */
199
- var jumpInput = pager.querySelector('.pagination-pagerchange input.pagination-number');
200
- if (jumpInput && isVisible(jumpInput)) {
201
- push({ type: 'pagination', subType: 'pagination-jump', label: '跳至页', locator: prefix + ' .pagination-pagerchange input.pagination-number', visible: true, enabled: isEnabled(jumpInput) });
202
- }
203
- });
204
-
205
- return { title: document.title, elements: elements };
206
- }
207
-
208
- /* ─── extractAllFramesElementMap() ─── */
209
-
210
- function extractAllFramesElementMap() {
211
- var result = {};
212
- function merge(el) {
213
- if (!el.label) return;
214
- if (!result[el.label] || el.visible) result[el.label] = { locator: el.locator, controlType: el.controlType, visible: el.visible };
215
- }
216
-
217
- /* 主 frame */
218
- try { extractPageElements().elements.forEach(merge); } catch(e) {}
219
-
220
- /* 子 frame */
221
- try {
222
- for (var i = 0; i < window.frames.length; i++) {
223
- try {
224
- var fr = window.frames[i];
225
- if (fr.extractPageElements) {
226
- fr.extractPageElements().elements.forEach(merge);
227
- }
228
- } catch(e) {}
229
- }
230
- } catch(e) {}
231
-
232
- return result;
233
- }