@buaa_smat/hometrans 0.1.0 → 0.1.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 (128) hide show
  1. package/README.md +141 -124
  2. package/agents/self-tester.md +259 -233
  3. package/agents/test-tools/autotest/README.md +223 -0
  4. package/agents/test-tools/autotest/config.yaml.example +58 -0
  5. package/agents/test-tools/autotest/pyproject.toml +16 -0
  6. package/agents/test-tools/autotest/report_tool.py +759 -0
  7. package/agents/test-tools/autotest/self_test_runner.py +773 -0
  8. package/agents/test-tools/autotest/testcases_schema.md +143 -0
  9. package/agents/test-tools/autotest/testcases_tool.py +215 -0
  10. package/agents/test-tools/autotest/uv.lock +3156 -0
  11. package/agents/test-tools/harmony_autotest-0.1.0-py3-none-any.whl +0 -0
  12. package/agents/test-tools/hypium-6.1.0.210-py3-none-any.whl +0 -0
  13. package/agents/test-tools/hypium_mcp-0.6.5-py3-none-any.whl +0 -0
  14. package/agents/test-tools/xdevice-6.1.0.210-py3-none-any.whl +0 -0
  15. package/agents/test-tools/xdevice_devicetest-6.1.0.210-py3-none-any.whl +0 -0
  16. package/agents/test-tools/xdevice_ohos-6.1.0.210-py3-none-any.whl +0 -0
  17. package/dist/cli/config-store.js +27 -2
  18. package/dist/cli/config.js +17 -6
  19. package/dist/cli/index.js +3 -2
  20. package/dist/cli/init.js +135 -22
  21. package/dist/cli/mcp.js +2 -2
  22. package/dist/context/index.js +165 -69
  23. package/package.json +59 -60
  24. package/skills/code-dev-review-fix/SKILL.md +279 -0
  25. package/skills/code-dev-review-fix-workspace/evals/evals.json +56 -0
  26. package/skills/code-dev-review-fix-workspace/iteration-1/routing-results.md +23 -0
  27. package/skills/convert_pipeline/SKILL.md +423 -439
  28. package/skills/hmos-resources-convert/SKILL.md +623 -0
  29. package/skills/hmos-resources-convert/evals/evals.json +171 -0
  30. package/skills/hmos-resources-convert/references/conversion-rules.md +663 -0
  31. package/skills/hmos-resources-convert/references/dependency-analysis-rules.md +388 -0
  32. package/skills/hmos-resources-convert/references/resource-mapping-rules.md +457 -0
  33. package/skills/hmos-resources-convert/references/xml-drawable-to-svg-rules.md +513 -0
  34. package/skills/hmos-resources-convert/template/AppScope/app.json5 +10 -0
  35. package/skills/hmos-resources-convert/template/AppScope/resources/base/element/string.json +8 -0
  36. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/background.png +0 -0
  37. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/foreground.png +0 -0
  38. package/skills/hmos-resources-convert/template/AppScope/resources/base/media/layered_image.json +7 -0
  39. package/skills/hmos-resources-convert/template/build-profile.json5 +42 -0
  40. package/skills/hmos-resources-convert/template/code-linter.json5 +32 -0
  41. package/skills/hmos-resources-convert/template/entry/build-profile.json5 +33 -0
  42. package/skills/hmos-resources-convert/template/entry/hvigorfile.ts +6 -0
  43. package/skills/hmos-resources-convert/template/entry/obfuscation-rules.txt +23 -0
  44. package/skills/hmos-resources-convert/template/entry/oh-package.json5 +10 -0
  45. package/skills/hmos-resources-convert/template/entry/src/main/ets/entryability/EntryAbility.ets +48 -0
  46. package/skills/hmos-resources-convert/template/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets +16 -0
  47. package/skills/hmos-resources-convert/template/entry/src/main/ets/pages/Index.ets +23 -0
  48. package/skills/hmos-resources-convert/template/entry/src/main/module.json5 +55 -0
  49. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/color.json +8 -0
  50. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/float.json +8 -0
  51. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/element/string.json +16 -0
  52. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/background.png +0 -0
  53. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/foreground.png +0 -0
  54. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/layered_image.json +7 -0
  55. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/media/startIcon.png +0 -0
  56. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/backup_config.json +3 -0
  57. package/skills/hmos-resources-convert/template/entry/src/main/resources/base/profile/main_pages.json +5 -0
  58. package/skills/hmos-resources-convert/template/entry/src/main/resources/dark/element/color.json +8 -0
  59. package/skills/hmos-resources-convert/template/entry/src/mock/mock-config.json5 +2 -0
  60. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/Ability.test.ets +35 -0
  61. package/skills/hmos-resources-convert/template/entry/src/ohosTest/ets/test/List.test.ets +5 -0
  62. package/skills/hmos-resources-convert/template/entry/src/ohosTest/module.json5 +16 -0
  63. package/skills/hmos-resources-convert/template/entry/src/test/List.test.ets +5 -0
  64. package/skills/hmos-resources-convert/template/entry/src/test/LocalUnit.test.ets +33 -0
  65. package/skills/hmos-resources-convert/template/hvigor/hvigor-config.json5 +23 -0
  66. package/skills/hmos-resources-convert/template/hvigorfile.ts +6 -0
  67. package/skills/hmos-resources-convert/template/oh-package-lock.json5 +28 -0
  68. package/skills/hmos-resources-convert/template/oh-package.json5 +10 -0
  69. package/skills/hmos-resources-convert/tools/apktool.bat +85 -0
  70. package/skills/hmos-resources-convert/tools/apktool_3.0.1.jar +0 -0
  71. package/skills/hmos-ui-align/SKILL.md +182 -0
  72. package/skills/hmos-ui-align/config-example.json +11 -0
  73. package/skills/hmos-ui-align/config.json +11 -0
  74. package/skills/hmos-ui-align/diff_analysis.md +53 -0
  75. package/skills/hmos-ui-align/page_align.md +62 -0
  76. package/skills/hmos-ui-align/readme.md +231 -0
  77. package/skills/hmos-ui-align/references/Comparison_Template.md +2 -0
  78. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  79. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  80. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  81. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  82. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  83. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  84. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  85. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  86. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  87. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  88. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217V1.md +911 -0
  89. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  90. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  91. package/skills/hmos-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  92. package/skills/hmos-ui-align/references/UI_Analysis_Template.md +4 -0
  93. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2535 -0
  94. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  95. package/skills/hmos-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  96. package/skills/hmos-ui-align/scripts/app_feature_verify.py +443 -0
  97. package/skills/hmos-ui-align/scripts/navigation-capure.md +37 -0
  98. package/skills/hmos-ui-align/scripts/page_capture.py +592 -0
  99. package/skills/hmos-ui-align-batch/SKILL.md +99 -0
  100. package/skills/hmos-ui-align-batch/references/conversion-procedure.md +180 -0
  101. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2535 -0
  102. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
  103. package/skills/hmos-ui-align-batch/references/mappings/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
  104. package/skills/hmos-ui-align-batch/references/mvvm/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
  105. package/skills/hmos-ui-align-batch/references/mvvm/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
  106. package/skills/hmos-ui-align-batch/references/mvvm/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
  107. package/skills/hmos-ui-align-batch/references/mvvm/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
  108. package/skills/hmos-ui-align-batch/references/mvvm/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
  109. package/skills/hmos-ui-align-batch/references/mvvm/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
  110. package/skills/hmos-ui-align-batch/references/mvvm/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
  111. package/skills/hmos-ui-align-batch/references/mvvm/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
  112. package/skills/hmos-ui-align-batch/references/mvvm/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
  113. package/skills/hmos-ui-align-batch/references/mvvm/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
  114. package/skills/hmos-ui-align-batch/references/mvvm/MVVM/346/250/241/345/274/217/357/274/210V1/357/274/211.md +911 -0
  115. package/skills/hmos-ui-align-batch/references/mvvm/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
  116. package/skills/hmos-ui-align-batch/references/mvvm//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
  117. package/skills/hmos-ui-align-batch/scripts/android_parse_fast.py +1606 -0
  118. package/skills/self-test/SKILL.md +369 -0
  119. package/skills/self-test/readme.md +309 -0
  120. package/skills/spec-generator-skill/SKILL.md +332 -0
  121. package/skills/spec-generator-skill/references/android-platform-tokens.md +105 -0
  122. package/skills/spec-generator-skill/references/spec-sample-1.md +78 -0
  123. package/skills/spec-generator-skill/references/spec-sample-2.md +58 -0
  124. package/skills/spec-generator-skill/references/spec-sample-3.md +116 -0
  125. package/skills/spec-generator-skill/references/step4-report-template.md +33 -0
  126. package/agents/self-test-setup.md +0 -165
  127. package/dist/context/resources/sdkConfig.json +0 -24
  128. package/src/context/resources/sdkConfig.json +0 -24
@@ -0,0 +1,143 @@
1
+ # testcases.json 生成规范
2
+
3
+ > 本文件是 `testcases.json` 的**唯一权威格式定义**。
4
+ > 生成 `testcases.json` 时,必须严格遵循本规范。
5
+
6
+ ---
7
+
8
+ ## 1. 文件结构
9
+
10
+ 根元素必须是 **JSON 数组 `[]`**,每个元素是一个测试用例对象。
11
+
12
+ ```json
13
+ [
14
+ { ... },
15
+ { ... }
16
+ ]
17
+ ```
18
+
19
+ ## 2. 测试用例对象(4 个必填字段)
20
+
21
+ | 字段 | 类型 | 说明 | 示例 |
22
+ |------|------|------|------|
23
+ | `case_name` | `string` | 测试场景标题,来自 `### Scenario:` | `"冷启动初始化后正常进入主页面"` |
24
+ | `bundle_name` | `string` | HarmonyOS 应用包名 | `"com.example.tuku"` |
25
+ | `app_name` | `string` | 应用显示名称(不要翻译,保持原始值) | `"Tuku"` |
26
+ | `test_steps` | `string` | 测试步骤文本(见下方模板) | 见第 3 节 |
27
+
28
+ > **不允许**出现其他字段(如 `id`、`priority`、`preconditions`、`spec_id` 等)。
29
+
30
+ ## 3. `test_steps` 字符串模板
31
+
32
+ 每个测试用例的 `test_steps` 必须严格按以下模板生成:
33
+
34
+ ```
35
+ 动作:{actions}
36
+ 预期结果:{expected_results}
37
+ ```
38
+
39
+ `test_steps` 中**只包含** `动作:` 和 `预期结果:` 两行,不包含任何段落头(如 `【说明】`、`【待测应用信息】`、`【测试用例】` 等)。
40
+
41
+ ### 变量说明
42
+
43
+ | 变量 | 来源 | 处理规则 |
44
+ |------|------|----------|
45
+ | `{actions}` | `test_case.md` 中的 `- 动作:` 行 | 所有应用名引用(如"图库应用"、"Tuku"等)替换为 `bundle_name` |
46
+ | `{expected_results}` | `test_case.md` 中的 `- 预期结果:` 行 | 原样使用,同样执行 app_name → bundle_name 替换 |
47
+
48
+ ## 4. 完整示例
49
+
50
+ ```json
51
+ [
52
+ {
53
+ "case_name": "冷启动初始化后正常进入主页面",
54
+ "bundle_name": "com.example.tuku",
55
+ "app_name": "Tuku",
56
+ "test_steps": "动作:打开com.example.tuku\n预期结果:文件夹网格主页面成功展示文件夹列表"
57
+ },
58
+ {
59
+ "case_name": "首次打开应用-授权通过-展示所有文件夹",
60
+ "bundle_name": "com.example.tuku",
61
+ "app_name": "Tuku",
62
+ "test_steps": "动作:打开com.example.tuku -> 点击允许按钮授予媒体访问权限\n预期结果:权限授予成功,跳转到文件夹网格主页面,可见所有包含媒体的文件夹"
63
+ }
64
+ ]
65
+ ```
66
+
67
+ ## 5. 禁止字符串
68
+
69
+ 以下字符串**不得出现在 `test_steps` 中的任何位置**。如果 `test_case.md` 源文件中包含这些内容,提取时必须删除或改写:
70
+
71
+ | 禁止字符串 | 原因 | 处理方式 |
72
+ |------------|------|----------|
73
+ | `前置条件` | 前置条件不应进入测试步骤 | 删除含该词的句子 |
74
+ | `Spec:` | Spec 引用不应出现 | 删除 |
75
+ | `Scenario:` | Scenario 引用不应出现 | 删除 |
76
+ | `ability_name:` | 不允许出现在 test_steps 中 | 删除 |
77
+ | `module_name:` | 不允许出现在 test_steps 中 | 删除 |
78
+
79
+ > **不得出现任何 `【xxx】` 段落头**(如 `【说明】`、`【待测应用信息】`、`【测试用例】`、`【前置条件】` 等)。`test_steps` 只包含 `动作:` 和 `预期结果:` 两行。
80
+
81
+ ## 6. 错误格式示例
82
+
83
+ ### 错误 1:使用结构化对象数组代替纯文本字符串
84
+
85
+ ```json
86
+ {
87
+ "test_steps": [
88
+ { "step": 1, "action": "launch_app", "params": { "bundle_name": "..." } }
89
+ ]
90
+ }
91
+ ```
92
+ **正确做法**: `test_steps` 必须是字符串,不是数组。
93
+
94
+ ### 错误 2:根元素是对象而非数组
95
+
96
+ ```json
97
+ {
98
+ "spec_id": "SPEC-01",
99
+ "test_cases": [ ... ]
100
+ }
101
+ ```
102
+ **正确做法**: 根元素必须是 `[ ... ]`。
103
+
104
+ ### 错误 3:包含段落头
105
+
106
+ ```json
107
+ {
108
+ "test_steps": "【说明】\n...\n\n【待测应用信息】\napp_name: Tuku\nbundle_name: com.example.tuku\n\n【测试用例】\n动作:...\n预期结果:..."
109
+ }
110
+ ```
111
+ **正确做法**: `test_steps` 中不允许任何 `【xxx】` 段落头,只包含 `动作:` 和 `预期结果:` 两行。
112
+
113
+ ### 错误 4:包含额外字段
114
+
115
+ ```json
116
+ {
117
+ "id": "TC-001",
118
+ "priority": "P0",
119
+ "preconditions": ["..."],
120
+ "case_name": "...",
121
+ "test_steps": "..."
122
+ }
123
+ ```
124
+ **正确做法**: 只允许 `case_name`、`bundle_name`、`app_name`、`test_steps` 四个字段。
125
+
126
+ ### 错误 5:缺少必要行
127
+
128
+ ```json
129
+ {
130
+ "test_steps": "打开图库应用 -> 检查主页面"
131
+ }
132
+ ```
133
+ **正确做法**: 必须包含 `动作:` 和 `预期结果:` 两行。
134
+
135
+ ## 7. 验证
136
+
137
+ 生成后必须运行验证脚本:
138
+
139
+ ```bash
140
+ cd "<autotest_dir>" && uv run python testcases_tool.py validate "<path-to-testcases.json>"
141
+ ```
142
+
143
+ 只有输出 `VALIDATION PASSED` 才算生成成功。
@@ -0,0 +1,215 @@
1
+ #!/usr/bin/env python3
2
+ """Generate or validate testcases.json for the Self-Testing Agent.
3
+
4
+ Subcommands:
5
+ generate <input-json> <output-json> [--validate]
6
+ Rewrite app_name → bundle_name and compose `test_steps` strictly
7
+ ("动作:..." / "预期结果:..."). Optionally re-validate the output.
8
+
9
+ validate <testcases-json>
10
+ Check a `testcases.json` (array of cases) against the schema in
11
+ `testcases_schema.md`. Exit 0 = PASSED, exit 1 = FAILED.
12
+
13
+ The forbidden-strings and section-header rules live in this file as the single
14
+ source of truth — generate enforces them at write time, validate re-checks them
15
+ on existing files.
16
+ """
17
+
18
+ import argparse
19
+ import json
20
+ import os
21
+ import re
22
+ import sys
23
+
24
+ # Ensure UTF-8 output on Windows
25
+ os.environ.setdefault("PYTHONIOENCODING", "utf-8")
26
+
27
+
28
+ # =============================================================================
29
+ # Shared schema invariants — used by both generate and validate
30
+ # =============================================================================
31
+
32
+ # Substrings that must not appear anywhere in `test_steps`. Keep the validator
33
+ # in lock-step with the generator: anything that the generator refuses to emit
34
+ # must also be rejected by the validator.
35
+ FORBIDDEN_STRINGS = ("前置条件", "Spec:", "Scenario:", "ability_name:", "module_name:")
36
+
37
+ # `test_steps` must contain only the two lines starting with `动作:` and
38
+ # `预期结果:`. `【…】` section headers (e.g. 【说明】, 【待测应用信息】) are forbidden.
39
+ SECTION_HEADER_RE = re.compile(r"【(.+?)】")
40
+
41
+ REQUIRED_LINES = ("动作:", "预期结果:")
42
+
43
+ TEST_STEPS_TEMPLATE = """\
44
+ 动作:{actions}
45
+ 预期结果:{expected_results}"""
46
+
47
+
48
+ # =============================================================================
49
+ # Generate
50
+ # =============================================================================
51
+
52
+ def rewrite_app_name(text: str, app_name: str, bundle_name: str) -> str:
53
+ """Safety net: replace remaining app_name references with bundle_name."""
54
+ if app_name and app_name in text:
55
+ text = text.replace(app_name, bundle_name)
56
+ return text
57
+
58
+
59
+ def compose_test_steps(app_name: str, bundle_name: str,
60
+ actions: str, expected_results: str) -> str:
61
+ """Compose a compliant test_steps string with only 动作 and 预期结果."""
62
+ rewritten_actions = rewrite_app_name(actions, app_name, bundle_name)
63
+ rewritten_expected = rewrite_app_name(expected_results, app_name, bundle_name)
64
+
65
+ test_steps = TEST_STEPS_TEMPLATE.format(
66
+ actions=rewritten_actions,
67
+ expected_results=rewritten_expected,
68
+ )
69
+ # Final safety: rewrite once more in case app_name only became visible after composition.
70
+ test_steps = rewrite_app_name(test_steps, app_name, bundle_name)
71
+
72
+ for forbidden in FORBIDDEN_STRINGS:
73
+ if forbidden in test_steps:
74
+ raise ValueError(
75
+ f"FATAL: forbidden string '{forbidden}' found in generated test_steps. "
76
+ f"This is a bug in testcases_tool.py."
77
+ )
78
+ for section in SECTION_HEADER_RE.findall(test_steps):
79
+ raise ValueError(
80
+ f"FATAL: unexpected section header 【{section}】 in generated test_steps. "
81
+ f"test_steps should only contain 动作 and 预期结果."
82
+ )
83
+ return test_steps
84
+
85
+
86
+ def generate(input_data: dict) -> list[dict]:
87
+ """Generate compliant testcases array from input data."""
88
+ bundle_name = input_data["bundle_name"]
89
+ app_name = input_data["app_name"]
90
+ cases = input_data["cases"]
91
+
92
+ result = []
93
+ for case in cases:
94
+ test_steps = compose_test_steps(
95
+ app_name=app_name,
96
+ bundle_name=bundle_name,
97
+ actions=case["actions"],
98
+ expected_results=case["expected_results"],
99
+ )
100
+ result.append({
101
+ "case_name": case["case_name"],
102
+ "bundle_name": bundle_name,
103
+ "app_name": app_name,
104
+ "test_steps": test_steps,
105
+ })
106
+ return result
107
+
108
+
109
+ # =============================================================================
110
+ # Validate
111
+ # =============================================================================
112
+
113
+ def validate_testcases(filepath: str) -> list[str]:
114
+ """Return a list of validation error messages. Empty list means PASSED."""
115
+ with open(filepath, "r", encoding="utf-8") as f:
116
+ cases = json.load(f)
117
+
118
+ if not isinstance(cases, list):
119
+ return ["Root element is not a JSON array"]
120
+
121
+ errors: list[str] = []
122
+ for i, c in enumerate(cases):
123
+ ts = c.get("test_steps", "")
124
+ name = c.get("case_name", f"case_{i}")
125
+ app_name = c.get("app_name", "")
126
+ bundle_name = c.get("bundle_name", "")
127
+ prefix = f"Case {i + 1} [{name}]"
128
+
129
+ # Required lines
130
+ for required in REQUIRED_LINES:
131
+ if required not in ts:
132
+ errors.append(f"{prefix}: MISSING {required} line")
133
+
134
+ # Forbidden substrings (single source of truth shared with the generator)
135
+ for forbidden in FORBIDDEN_STRINGS:
136
+ if forbidden in ts:
137
+ errors.append(f"{prefix}: CONTAINS {forbidden} (must be excluded)")
138
+
139
+ # Forbidden section headers
140
+ for section in SECTION_HEADER_RE.findall(ts):
141
+ errors.append(
142
+ f"{prefix}: CONTAINS unexpected section header 【{section}】 "
143
+ f"(test_steps should only contain 动作 and 预期结果)"
144
+ )
145
+
146
+ # App name must be rewritten to bundle_name
147
+ if app_name and bundle_name and app_name != bundle_name and app_name in ts:
148
+ errors.append(
149
+ f"{prefix}: test_steps contains app_name '{app_name}' instead of bundle_name "
150
+ f"'{bundle_name}'. All app name references in test_steps must use bundle_name."
151
+ )
152
+
153
+ return errors
154
+
155
+
156
+ # =============================================================================
157
+ # CLI
158
+ # =============================================================================
159
+
160
+ def cmd_generate(args) -> None:
161
+ with open(args.input, "r", encoding="utf-8") as f:
162
+ input_data = json.load(f)
163
+
164
+ testcases = generate(input_data)
165
+
166
+ with open(args.output, "w", encoding="utf-8") as f:
167
+ json.dump(testcases, f, ensure_ascii=False, indent=2)
168
+
169
+ print(f"Generated {len(testcases)} test cases -> {args.output}")
170
+
171
+ if args.validate:
172
+ errors = validate_testcases(args.output)
173
+ if errors:
174
+ print("VALIDATION FAILED:")
175
+ for e in errors:
176
+ print(f" [FAIL] {e}")
177
+ sys.exit(1)
178
+ print(f"VALIDATION PASSED: all {len(testcases)} cases conform to the schema")
179
+
180
+
181
+ def cmd_validate(args) -> None:
182
+ errors = validate_testcases(args.path)
183
+ if errors:
184
+ print("VALIDATION FAILED:")
185
+ for e in errors:
186
+ print(f" [FAIL] {e}")
187
+ sys.exit(1)
188
+ with open(args.path, "r", encoding="utf-8") as f:
189
+ cases = json.load(f)
190
+ print(f"VALIDATION PASSED: all {len(cases)} cases have correct test_steps format")
191
+
192
+
193
+ def main() -> None:
194
+ parser = argparse.ArgumentParser(
195
+ description="Generate or validate testcases.json for the Self-Testing Agent."
196
+ )
197
+ sub = parser.add_subparsers(dest="command", required=True)
198
+
199
+ p_gen = sub.add_parser("generate", help="Generate compliant testcases.json from an extracted input")
200
+ p_gen.add_argument("input", help="Path to the extracted input JSON")
201
+ p_gen.add_argument("output", help="Path to write the compliant testcases.json")
202
+ p_gen.add_argument("--validate", action="store_true",
203
+ help="After writing, re-validate the output against the schema")
204
+ p_gen.set_defaults(func=cmd_generate)
205
+
206
+ p_val = sub.add_parser("validate", help="Validate an existing testcases.json against the schema")
207
+ p_val.add_argument("path", help="Path to the testcases.json to validate")
208
+ p_val.set_defaults(func=cmd_validate)
209
+
210
+ args = parser.parse_args()
211
+ args.func(args)
212
+
213
+
214
+ if __name__ == "__main__":
215
+ main()