@harmonyos-arkts/d2h 0.1.2 → 0.1.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 (71) hide show
  1. package/README.md +78 -44
  2. package/agents/android-to-hmos-00-orchestrator.md +34 -28
  3. package/agents/d2h.md +266 -95
  4. package/dist/bin/install-opencode.js +137 -43
  5. package/dist/src/install-opencode.js +44 -0
  6. package/dist/src/install-vscode.js +226 -0
  7. package/dist/src/toolchain.js +83 -0
  8. package/docs/environment-check.md +86 -0
  9. package/opencode/agents.json +1 -1
  10. package/opencode/vscode-d2h-package.json +7 -0
  11. package/opencode/vscode-d2h-plugin.mjs +29 -0
  12. package/package.json +6 -4
  13. package/schemas/task-envelope.schema.json +58 -4
  14. package/schemas/task-plan.schema.json +281 -63
  15. package/scripts/check-environment.mjs +321 -0
  16. package/skills/android2hmos_resources_convert/SKILL.md +37 -19
  17. package/skills/appgraph-rule-audit/SKILL.md +24 -5
  18. package/skills/d2h-hypium-forge/SKILL.md +252 -0
  19. package/skills/d2h-hypium-forge/references/functional-spec-template.md +140 -0
  20. package/skills/d2h-hypium-forge/references/official-api.md +135 -0
  21. package/skills/d2h-hypium-forge/references/pitfalls.md +98 -0
  22. package/skills/d2h-hypium-forge/references/testability-template.md +71 -0
  23. package/skills/d2h-hypium-forge/scripts/_common_template.py +614 -0
  24. package/skills/d2h-hypium-forge/scripts/analyze_results.py +156 -0
  25. package/skills/d2h-hypium-forge/scripts/assert_audit.py +107 -0
  26. package/skills/d2h-hypium-forge/scripts/collect_runtime_pages.py +169 -0
  27. package/skills/d2h-hypium-forge/scripts/forge_report.py +325 -0
  28. package/skills/d2h-hypium-forge/scripts/inject_resource_ids.py +114 -0
  29. package/skills/d2h-hypium-forge/scripts/lint_asserts.py +80 -0
  30. package/skills/d2h-hypium-forge/scripts/lint_locate.py +60 -0
  31. package/skills/d2h-hypium-forge/scripts/merge_appgraph.py +249 -0
  32. package/skills/d2h-hypium-forge/scripts/precondition.py +140 -0
  33. package/skills/d2h-hypium-forge/scripts/preinject_ids.py +232 -0
  34. package/skills/d2h-hypium-forge/scripts/run_direct.py +392 -0
  35. package/skills/d2h-hypium-forge/scripts/run_parallel.py +121 -0
  36. package/skills/d2h-hypium-forge/scripts/scan_features.py +255 -0
  37. package/skills/d2h-hypium-forge/scripts/upgrade_asserts.py +66 -0
  38. package/skills/d2h-hypium-forge/scripts/validate_scenarios.py +84 -0
  39. package/skills/d2h-utg-ai-generate/SKILL.md +128 -0
  40. package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +76 -0
  41. package/skills/d2h-utg-ai-generate/references/extraction-guide.md +34 -0
  42. package/skills/d2h-utg-ai-generate/references/facts-contract.md +96 -0
  43. package/skills/d2h-utg-ai-generate/references/page-schema.json +61 -0
  44. package/skills/d2h-utg-ai-generate/references/utg-schema.json +89 -0
  45. package/skills/d2h-utg-ai-generate/scripts/build_page_json.py +314 -0
  46. package/skills/d2h-utg-ai-generate/scripts/build_utg.py +123 -0
  47. package/skills/d2h-utg-ai-generate/scripts/capture_from_page_json.ts +280 -0
  48. package/skills/d2h-utg-ai-generate/scripts/utg_common.py +524 -0
  49. package/skills/d2h-utg-ai-generate/scripts/utg_html.py +489 -0
  50. package/skills/d2h-utg-ai-generate/scripts/validate_utg.py +69 -0
  51. package/skills/d2h-utg-ai-generate/scripts/vendor/D3-LICENSE +13 -0
  52. package/skills/d2h-utg-ai-generate/scripts/vendor/d3-7.9.0.min.js +2 -0
  53. package/skills/d2h-utg-ai-generate/tests/test_pipeline.py +369 -0
  54. package/skills/d2h-utg-static-generate/SKILL.md +73 -0
  55. package/skills/d2h-utg-static-generate/references/discoveries.md +38 -0
  56. package/skills/d2h-utg-static-generate/scripts/generate.py +499 -0
  57. package/skills/d2h-utg-static-generate/tests/test_static.py +335 -0
  58. package/skills/d2h-utg-static-generate/tests/verify-offline-html.py +60 -0
  59. package/skills/d2h-utg-static-generate/tests/verify-produced-fixture.py +131 -0
  60. package/skills/harmonyos-migration-playbook/SKILL.md +30 -3
  61. package/skills/harmonyos-migration-playbook/references/lesson-lifecycle.md +4 -4
  62. package/skills/harmonyos-migration-playbook/references/ui-automation.md +4 -3
  63. package/skills/harmonyos-migration-playbook/references/windows-environment.md +2 -2
  64. package/skills/maintaining-migration-report/SKILL.md +88 -99
  65. package/skills/maintaining-migration-report/references/final-acceptance.md +48 -0
  66. package/skills/maintaining-migration-report/references/source-provenance.md +66 -0
  67. package/skills/managing-d2h-toolchain/SKILL.md +23 -0
  68. package/skills/managing-d2h-toolchain/environment-requirements.json +16 -0
  69. package/skills/managing-d2h-toolchain/toolchain.json +16 -0
  70. package/skills/preparing-migration-workspace/SKILL.md +76 -69
  71. package/skills/preparing-migration-workspace/toolchain.json +0 -43
@@ -0,0 +1,252 @@
1
+ ---
2
+ name: d2h-hypium-forge
3
+ description: 鸿蒙应用/元服务测试用例设计与 hypium 执行(两阶段流水线第二段,部署于 .opencode 迁移 agent)。宿主 agent 代码理解产出功能场景库(功能/说明/是否已转化)→ 本 skill 按场景测试目的选断言范式、LLM 白盒精写 Python 脚本 → 裸跑执行与失败四簇分析。适用于任何 ArkTS 工程的功能测试自动化。触发词:hypium-forge、功能用例设计、测试用例生成执行、覆盖度评估、鸿蒙 UI 测试全流程。
4
+ ---
5
+
6
+ # d2h-hypium-forge — LLM 白盒精写 → 裸跑执行
7
+
8
+ ## 体系边界(先分清)
9
+
10
+ 本 skill **只服务 Python 版 Hypium**(devicetest 框架,`testcases/*.py`,UiDriver 经 uitest RPC 驱动设备)。
11
+ **ArkTS @ohos/hypium 是另一条链路,不在本 skill 范围**——模拟器上应用内 uitest napi 是 stub
12
+ (`Driver.create()` 恒 null,ENV 实证),ArkTS 用例仅适用于真机且需独立工程配置;文档中两套
13
+ API 同名不同构(BY 定位/断言语义有差异),混用必然执行失败。涉及 ArkTS 用例需求请走
14
+ 单独链路(ohosTest 模板),禁止在本 skill 产物目录混建。
15
+
16
+ ## 三条铁律
17
+
18
+ 1. **必须先预置控件 ID(写脚本之前)**:`preinject_ids.py` 对全部交互点机械注入,
19
+ 脚本只认 ids.json/既有 ID(BY.key)。文本定位(BY.text)慢 10~100 倍且有歧义。
20
+ 2. **Level 0 前置自适应(冷启三件套,launch 原语内置,用例不得自带)**:
21
+ 首启弹窗 dismiss(多文案轮询幂等)+ 首页 tab 归位(HOME_TAB 应用级配置,
22
+ 冷启默认 tab 常与目标 tab 不一致)+ 语义锚等待(禁依赖注入 id 等 Loading 层控件)。
23
+ 隐私弹窗常为内存态(冷启必弹)——每条冷启都必须 dismiss(TD-007)。
24
+ 3. **权限和账号前置完成**:precondition.py 预授可授权限/登录策略/首启弹窗 dismiss,
25
+ 绝不留给用例执行时撞。**登录态跨用例保持**:软隔离(stop_app/start_app)不清应用数据;
26
+ `reset_data=True`(bm clean)只用于首启态用例且**须排尾/独立分组执行**——与登录用例
27
+ 交错会反复清登录态导致重复登录注册(CloudReader TD-004 实证:交错执行重复注册 30 次)。
28
+
29
+ ## 五步流水线
30
+
31
+ 设 `$F = 本skill/scripts`,工程 `$P`,产物目录 `$P/.hypium_forge/`。
32
+
33
+ ### 1. 功能场景库(测试设计的基础输入)
34
+
35
+ 本 skill 是两阶段流水线的第二段:代码理解由宿主 agent(.opencode 迁移 agent)完成,
36
+ 产出功能场景清单后调用本 skill 生成并执行测试。**场景库是测试设计的唯一基础**——
37
+ 它的完备性与准确度决定下游全部产出的质量,宁可在此多花时间。
38
+
39
+ **阶段一(宿主 agent)**——按 `references/functional-spec-template.md` 产出功能规格
40
+ `functional_spec.md`,再翻译为 `scenarios.json`:
41
+ ```json
42
+ {"id":"WF-01","name":"搜索选中城市并收藏","domain":"位置管理",
43
+ "desc":"用户在搜索页输入关键字,点选结果后返回管理页,列表新增该城市并提示收藏成功",
44
+ "converted":true, "source_ref":"SearchActivity.java:181-195",
45
+ "test_intent":"功能交互类"}
46
+ ```
47
+ 生成规则:
48
+ - 场景单位 = **用户动宾行为**(禁 Activity/Fragment/surface 类名当场景);
49
+ - `desc` 写清「输入→动作→**可观察业务效果**」,效果描述到可翻译成断言的程度;
50
+ - 行为预期从源码推导(默认值/联动/守卫),不从直觉——直觉必错;
51
+ - `converted` 与迁移报告对齐,未转化标 false 并跳过。
52
+
53
+ **阶段二(本 skill)**——先过**场景库质量门禁**再消费:
54
+ ```bash
55
+ python3 $F/validate_scenarios.py scenarios.json # 不通过项入 scenarios_blocker.md
56
+ ```
57
+ 校验:①必备字段(id/name/desc/converted/test_intent)②desc 含可观察业务效果关键词
58
+ (新增/删除/修改/保存/收藏/取消/成功/失败/提示/计数/状态/切换——纯"进入XX页"类导航词
59
+ 无效果=拦截)③入口可定位(深层链路 desc 须写前置路径)④行为预期有源码/PRD 依据标注
60
+ ⑤converted=false 必须带 reason(未迁移/能力缺失/暂不测试)。**blocker 场景不进用例生成**,
61
+ 退回宿主 agent 修复。通过后:converted=true 逐场景设计用例
62
+ (按 test_intent 选断言范式)→ 白盒精写 → 执行 → 分诊 → defects/报告。
63
+ 用例与场景一一对应,无场景不写用例。
64
+
65
+ ### 1.5 机械扫描(覆盖性评估的补充)
66
+
67
+ ```bash
68
+ python3 $F/scan_features.py $P --out $P/.hypium_forge/features.json
69
+ ```
70
+ 产出:页面(路由+@Entry)× 交互点 × 状态矩阵 + 权限/登录检测。有安卓迁移现场时
71
+ `merge_appgraph.py --features ... --facts <android>/.migration/spec/facts` 注入权威行为语义
72
+ (handler 链揭示联动场景)。
73
+
74
+ **定位是补充而非基础**:features 条目是定位事实不是业务功能,机械扫描可靠性不足,
75
+ 不得直接作为测试设计输入。两个用途:
76
+ 1. **覆盖校验**:对照 scenarios.json,找出未被任何场景覆盖的页面/交互点 →
77
+ 反馈宿主 agent 裁决「补场景」或「登记不测并说明理由」——遗漏即测试盲区;
78
+ 2. **ID 预置清单**:交互点列表供步骤 3 preinject_ids.py 机械注入。
79
+
80
+ 覆盖结论写入 `coverage.md`,**双口径分列(防机械冒充功能覆盖)**:
81
+ - **场景覆盖度(主口径)** = 被采纳用例触达的场景数 / scenarios.json 中 converted=true 场景数;
82
+ - **控件触达率(辅助口径)** = 已触达交互点 / 全部交互点——仅暴露机械盲区,**不得单独
83
+ 作为功能覆盖率汇报**(把扫到的控件数当功能覆盖是自欺);
84
+ coverage.md 逐条列出并按规则处置:
85
+ - 未覆盖场景(如 WF-12, WF-19)→ 必须反馈宿主 agent 补场景或登记不测;
86
+ - 未触达交互点(如 HomePage.ad_banner)→ 不一定补用例,人工裁决是否为
87
+ 广告位/运营位/未迁移能力(判定理由随条目记录)。
88
+ 未覆盖交互点及其裁决结果。
89
+
90
+ ### 2. 运行时文本采集
91
+ ```bash
92
+ python3 $F/collect_runtime_pages.py --device <sn> --bundle <b> --out runtime_pages.json
93
+ ```
94
+ 脚本引用的控件文本**只允许**来自采集结果;断言锚定业务数据,禁止导航回声断言。
95
+ **并行提速**:首页文本采集完成后即可开始写该页用例(其余页面采集继续)——采集与编写
96
+ 流水线并行,禁串行等全量采集完再动笔。
97
+ 启动参数门控应用:先 `aa start --ps k v` 预热,singleton 的 onNewWant 不重置状态可继续采集。
98
+
99
+ ### 3. ID 预置(铁律①,先于脚本编写)——机械注入的边界与补强
100
+ ```bash
101
+ python3 $F/preinject_ids.py --features features.json --out ids.json # 交互点清单来自 §1.5
102
+ # 之后 rebuild + uninstall&&install
103
+ ```
104
+ 批量注入 `tc_<page>_<n>`,产出 ids.json 映射表。脚本定位只认 ids.json/既有 ID(BY.key);
105
+ 仅输入框用 BY.hint。
106
+
107
+ **静态注入三盲区**(preinject 只扫静态代码,以下场景必然漏注):
108
+ 1. 动态列表控件(ForEach 运行时生成,静态无挂载点)→ 用 `collect_runtime_pages.py`
109
+ 的 dumpLayout 控件树交叉核对:dump 中 key 为空但语义可定位的条目,回填至
110
+ runtime_pages.json 供 BY.text 兜底并登记例外;
111
+ 2. 条件渲染分支(if/else 二选一支路)→ 场景变体执行前先 dump 该分支实况;
112
+ 3. 异步加载页(网络数据到位才渲染)→ 依赖步骤 2 采集的等待锚,禁用盲等。
113
+
114
+ **无 ID 例外审批制**:例外必须三件套——docstring 理由 + runtime_pages.json 中的实测
115
+ 证据(该控件确实无 id 且文案唯一)+ lint_locate 通过;例外清单汇总进 coverage.md
116
+ 「无 ID 例外」章节备查,审计时逐条复核(防例外变成 BY.text 后门)。
117
+
118
+ ### 3.5 输入契约与空库守卫(宿主 agent ↔ 本 skill 的稳定边界)
119
+
120
+ **只接受以下输入**:scenarios.json / features.json / ids.json / runtime_pages.json /
121
+ _common_template.py / ArkTS 源码路径。**场景库为空或全部 converted=false 时直接报错
122
+ 退出,不生成空用例集**(空跑=伪覆盖)。生成规则见步骤 4;执行后必须产出:
123
+ testcases/*.py、results.json、coverage.md、summary.json、test-report.html、失败截图与 hilog 片段。
124
+
125
+ ### 4. LLM 白盒精写 testcases/*.py(核心)
126
+
127
+ 从 `$F/_common_template.py` 复制为 `testcases/_common.py`(launch 带 `--ps k v` 空格分隔、
128
+ 3 轮重试探活、首启弹窗 dismiss、强断言原语全套——**写前必读**),逐条编写 `TCxx_<slug>.py`。
129
+
130
+ **编写规则:**
131
+
132
+ - **先读源码**:默认值/联动规则/时序以 ArkTS 源码为准——臆断默认值必返工;
133
+ - **按场景测试目的选断言范式**(问"本场景要证明什么"再选型):
134
+
135
+ | 场景类型 | 测试目的 | 正确断言范式 |
136
+ |---|---|---|
137
+ | 资源渲染类 | 图标/文案加载成功 | exist/equals 即目的本身 |
138
+ | 功能交互类 | 操作产生业务效果 | 效果断言(值变化/计数增减/checked 翻转/absent) |
139
+ | 配置生效类 | 设置改变全局行为 | 生效链断言(改设置→回业务页→断言输出变化) |
140
+ | 导航流转类 | 状态迁移到目的地 | 终点效果(城市选中→顶栏变新城市名) |
141
+ | 状态机类 | 状态转移规律 | 前置态断言→触发→后置态断言(成对) |
142
+ | 数据输入类 | 输入被正确处理 | 输入→输出值断言(搜索词→结果计数) |
143
+
144
+ - **强断言比例按 test_intent 分档**(全局硬门禁 ≥30% 整体维持 40~70%;比例是目的的
145
+ 结果不是目标——**禁止加"页面存在/按钮可见"类弱断言凑比例**,单条低于档位下限报
146
+ "弱断言风险"提示、超上限检查是否断言了实现细节):
147
+
148
+ | test_intent | 强断言区间 | 说明 |
149
+ |---|---|---|
150
+ | 资源渲染 | 20~40% | 证明图标/文案/占位正确,exist/equals 即目的 |
151
+ | 功能交互 | 50~70% | 必须断言业务效果,不能只断言按钮存在 |
152
+ | 配置生效 | 60~80% | 改设置→回业务页→验证输出变化的完整生效链 |
153
+ | 导航流转 | 40~60% | 终点页面 + 关键状态都要验证 |
154
+ | 状态机 | 60~80% | 前置态、触发、后置态成对断言 |
155
+ | 数据输入 | 50~70% | 输入→结果列表/计数/排序必须验证 |
156
+
157
+ 核心判据:**断言必须证明场景声明的目的**——目的是"功能发生"时仅判存在 = 无效用例;
158
+ - **Toast** 用 hilog 关键字轮询或 toast_text_seen(dump 轮询)、**剪贴板**用 hdc pasteboard、
159
+ 对话框流转逐步断言;
160
+ - **视口外控件**必须用 `touch(BY.text("xx"), scroll_target=BY.type("Scroll"))` 原生滚动查找,
161
+ 禁止手工 swipe + find 循环(时序敏感且不稳定);
162
+ - **深链路用例内显式 setup**:上游状态准备写成用例内步骤,禁止假设页面已就位;
163
+ - **失败 = 信息**:产品缺陷保持 FAIL 暴露,绝不弱化断言换绿灯;用例自身错误可修(≤2 轮);
164
+ - **用例数量由功能场景覆盖决定**,禁止固定条数或上限——每个 converted=true 场景至少
165
+ 1 条用例真实触达;**不允许脱离场景新增用例、不允许把页面跳转当最终业务效果**;
166
+ 文本断言只能来自 runtime_pages.json 实采,不能臆造文案;
167
+ - **定位门禁**:`lint_locate.py --dir testcases --ids ids.json`,text 定位 >20% FAIL——
168
+ LLM 常绕过预置 ID 用 text_click,唯机械门禁有效;
169
+ - 每脚本 docstring 写清行为链与覆盖的功能场景 id;
170
+ - 执行后产出 `coverage.md`(含「总覆盖度: N%」行)与 `summary.json`。
171
+
172
+ ### 4.5 前置条件分组编排(共用前置,提升执行效率)
173
+
174
+ 用例文件声明 `PRECOND`(缺省 anonymous),run_direct 按 **anonymous → logged_in →
175
+ first_launch** 分组排序执行(破坏性递增,bm clean 类强制排尾不再回头破坏登录态)。
176
+ 组内软隔离共享前置:`launch()` 默认 `reset_data=False`,preferences 保留 → 组首一次
177
+ 完整 `ensure_login`,组内后续幂等复用。组首执行时 `FORGE_GROUP_FIRST=1`。
178
+ results.json 每条附 `group` 字段可分组统计。**禁把 reset_data=True 用在 logged_in 组**。
179
+
180
+ ### 5. 裸跑执行 + 四簇分析 + HTML 报告
181
+ ```bash
182
+ python3 $F/run_direct.py --all --dir testcases --device <sn> # 自动采集 end/fail 截图
183
+ python3 $F/analyze_results.py --results results.json --runtime-pages runtime_pages.json
184
+ python3 $F/forge_report.py # 产出 test-report.html(Allure 风格:概况/用例卡片/关键截图内嵌)
185
+ ```
186
+ 失败四簇:DEVICE(环境,重跑)/ DESIGN-ERROR(脚本定位/步骤错误,修脚本 ≤2 轮)/
187
+ NAV-CONTEXT-LOSS(页面上下文丢失,hilog 定案)/ ASSERT-FAIL(**优先怀疑产品缺陷,保留 FAIL**)。
188
+
189
+ **自愈边界(允许 vs 禁止,修≠换绿灯)**:
190
+ - 允许:修正错误控件 ID;补缺失等待条件;修正滚动/输入/弹窗关闭方式;调整用例内前置步骤。
191
+ - 禁止:强断言→弱断言;`equals`→`contains` 规避差异;固定等待无限加长;模糊 text 匹配
192
+ 绕过定位失败;把产品缺陷标 SKIP/PASS;**删除失败断言而不是修复原因**。
193
+ - **FAIL 现场证据自动附言**:run_direct 失败时 msg 自动附带当前页面 Text 摘要
194
+ (`| 现场Text: …`)——下轮分诊直读证据,禁重复现场取证。
195
+ - **原始失败记录保留**:修复重跑前 run_direct 自动把 results.json 滚动备份为
196
+ results.prev.json——修复轨迹可审计(改了什么、为什么改),防"驯化测试"。
197
+ - **分诊三律(TD-018,防定案实验失控)**:①**同消息聚类**——多例同错误消息(如
198
+ text not found: X×N)视为同族,一次分析一次定案,禁逐条重复实验;②**同族定案实验
199
+ ≤2 次**(清数据/冷启/对照类),超限即按现有证据登记缺陷转人工——保留 FAIL 是合规
200
+ 产出,不是未完成;③**成功模式对照先行**——定案前先查同场景已 PASS 用例的断言
201
+ 范式(如冷启持久化断言),判据升级优先于现场重取证。
202
+ - **修复轮增量重试**:`run_direct.py --retry-failed` 只重跑 FAIL/SKIP 条(PASS 保留
203
+ 原结果合并写出)——禁无谓全量重跑浪费设备时间(首轮 11 条全量 286s,增量仅重跑 2 条
204
+ ~52s,省 80%)。**编写期自检批量做**:全部脚本写完统一 lint+语法批检,禁逐条单跑
205
+ 调试(单条 LLM 往返间隔是隐形大头)。
206
+ - **增量重试回归门禁(TD-019,防旧 PASS 掩盖回归)**:`run_direct.py` 自动对
207
+ `entry/src/main/ets` 计算内容指纹(srcHash,存 `results.meta.json` 侧车)。增量重试前
208
+ 重算比对:产品代码有变更 / 无上轮指纹 / 定位不到源码目录 → **自动升级全量重跑**
209
+ (实证:Profiles TC09/TC14 PASS→FAIL 靠全量重跑才抓到,走增量通道会被旧 PASS 掩盖);
210
+ 确认仅测试脚本变更可 `--force-incremental` 强制增量。顺带修复:--retry-failed 现写入
211
+ results.json(旧版误写 results-single.json)。
212
+
213
+ ## 度量与沉淀
214
+
215
+ - 记录:用例数、覆盖度、耗时、首轮通过率、调试轮次、强断言占比。
216
+ - 失败模式沉淀 testability-requirements.md(可测试性需求)。
217
+
218
+ ## References(按需加载)
219
+
220
+ | 文件 | 内容 | 何时读 |
221
+ |---|---|---|
222
+ | `references/official-api.md` | Hypium 官方 API 速查(BY 定位/模糊匹配/触摸/滑动/输入/断言 Checker/截图) | 编写脚本前查阅定位与断言 API 用法 |
223
+ | `references/pitfalls.md` | 7 项目实证踩坑清单(swipe 百分比/BY.text 全匹配/press_back 语义/系统子窗口不可见/视口外滚动/DatePicker) | 遇到定位/滑动/输入/Toast 问题时查阅 |
224
+ | `references/functional-spec-template.md` | 功能规格文档模板(9 段结构 + test_intent 选型表 + 边界异常) | 阶段一宿主 agent 产出规格时 |
225
+ | `references/testability-template.md` | 可测试性需求模板 | 沉淀失败模式时 |
226
+
227
+ ## 断言能力速查
228
+
229
+ | 层 | 能力 |
230
+ |---|---|
231
+ | 存在/消失 | BY.key/id/text/hint/type + absent |
232
+ | 属性状态 | BY.checked/selected/enabled/focused/clickable/scrollable |
233
+ | 值断言 | Checker:equal/not_equal/contains/match_regexp/greater/less |
234
+ | 几何关系 | getBounds()/中心点/相对定位 |
235
+ | Toast/系统级 | hilog 轮询 / hdc pasteboard / 外部跳转 absent 级 |
236
+ | 结构断言 | find_all_components 计数 |
237
+ | 边界 | 颜色/像素 diff/性能时序(勿硬造) |
238
+
239
+ ## 定位原语速查
240
+
241
+ | 场景 | 原语 |
242
+ |---|---|
243
+ | 已注入 ID | `BY.key(tid)` |
244
+ | 输入框 | `BY.hint('占位符')`(BY.text 命中 hint 节点静默失败) |
245
+ | 底部 Tab | find_all(BY.text CONTAINS) 取最靠下 bounds 中心 |
246
+ | 视口外 | `touch(BY.text("xx"), scroll_target=BY.type("Scroll"))` 原生滚动查找 |
247
+ | mock 数据 | `hdc rport tcp:8787 tcp:8787` |
248
+
249
+ ## 相关 skill
250
+
251
+ - `hypium-id-test`:无业务语义 ID 冒烟(转化后即跑)
252
+ - `hypium-test-gen`:白盒逐条精写(7 项目踩坑清单)
@@ -0,0 +1,140 @@
1
+ # 功能规格文档(Functional Spec)— 模板与填写指南
2
+
3
+ > **用途**:转化完成后的功能验证基准。每份 spec 对应一个业务功能域,由**代码理解阶段**产出,
4
+ > 作为 **d2h-hypium-forge 用例设计**的唯一输入。规格 = 测试预期的唯一来源。
5
+
6
+ ---
7
+
8
+ ## 使用流程
9
+
10
+ ```
11
+ 安卓源码 → [宿主 agent 代码理解] → functional_spec.md(本文档格式)
12
+ ↓
13
+ scenarios.json(场景清单)
14
+ ↓
15
+ [d2h-hypium-forge] → testcases/*.py → 执行 → 标定
16
+ ```
17
+
18
+ ---
19
+
20
+ ## 模板
21
+
22
+ ```markdown
23
+ # 功能规格:<功能域名>
24
+
25
+ ## 元信息
26
+ | 字段 | 值 |
27
+ |---|---|
28
+ | spec_id | FS-<模块>-<序号>(如 FS-goods-001) |
29
+ | 业务域 | <如:商品/交易/账户/消息> |
30
+ | 安卓源模块 | <如 feature/goods> |
31
+ | 鸿蒙源模块 | <如 features/goods> |
32
+ | 置信度 | <已读全/部分/骨架> |
33
+ | 转化状态 | <已转化/部分转化/未转化> |
34
+ | 可测试性级别 | <A/B/C(对照 testability-requirements.md T-10)> |
35
+
36
+ ## 1. 功能概述
37
+ <一段话:用户视角这个功能做什么、核心业务价值、入口路径>
38
+
39
+ ## 2. 业务规则(BR)
40
+ | 规则 ID | 规则描述 | 源码依据(安卓) | 源码依据(鸿蒙) | 测试含义 |
41
+ |---|---|---|---|---|
42
+ | BR-01 | <默认值/联动/互斥/守卫> | file.java:L120 | Index.ets:L45 | <翻译成什么断言> |
43
+ | BR-02 | ... | ... | ... | ... |
44
+
45
+ ## 3. 状态机
46
+ | 当前态 | 事件/动作 | 次态 | 可观察效果(断言锚点) |
47
+ |---|---|---|---|
48
+ | <初始态> | <用户操作> | <目标态> | <UI 变化/数据变更/系统事件> |
49
+
50
+ ## 4. 数据流
51
+ | 输入 | 处理 | 输出 | 断言方式 |
52
+ |---|---|---|---|
53
+ | <用户输入/系统事件> | <业务逻辑> | <UI 变化/持久化/网络请求> | <equals/checked/hilog/absent> |
54
+
55
+ ## 5. 断言锚点清单
56
+ | 锚点 | 类型 | 控件 ID / 文本 / hilog | 断言范式 |
57
+ |---|---|---|---|
58
+ | <如:价格文本> | 文本值 | tc_goods_price | equals |
59
+ | <如:加入购物车按钮> | 控件状态 | btn_add_cart | enabled/disabled |
60
+ | <如:加入成功 Toast> | 瞬态浮层 | hilog tag=GoodsVM | hilog_tail |
61
+ | <如:购物车数量> | 结构计数 | goods_item | find_all count |
62
+
63
+ ## 6. 边界与异常
64
+ | 场景 | 输入条件 | 预期行为 | 断言要点 |
65
+ |---|---|---|---|
66
+ | <如:库存为 0> | stock=0 | 购买按钮禁用 | checked/disabled |
67
+ | <如:网络断开> | 离线 | 显示空态页 | exist(EmptyNetwork) |
68
+ | <如:未登录> | token 过期 | 跳登录页 | 路由断言 |
69
+
70
+ ## 7. 测试场景(直接消费)
71
+ | 场景 ID | 场景描述 | test_intent | 断言范式 | 优先级 |
72
+ |---|---|---|---|---|
73
+ | S-01 | <动宾结构描述> | 功能交互类 | 效果断言 | P0 |
74
+ | S-02 | ... | 配置生效类 | 生效链断言 | P0 |
75
+ | S-03 | ... | 导航流转类 | 终点效果 | P1 |
76
+
77
+ ## 8. 依赖与环境
78
+ | 依赖项 | 状态 | 说明 |
79
+ |---|---|---|
80
+ | 登录 | <不需要/Mock 可测/需真实账号> | ... |
81
+ | 网络 | <不需要/Mock Server/真实 API> | ... |
82
+ | 权限 | <无/相机/位置/...> | ... |
83
+ | mock 通道 | <有/无>(如有,说明激活方式) | ... |
84
+
85
+ ## 9. 可测试性缺口
86
+ | 缺口 ID | 缺 TR 编号 | 描述 | 影响 | 建议修复 |
87
+ |---|---|---|---|---|
88
+ | G-01 | TR-0501 | Base URL 硬编码 | 无法切 Mock Server | 加配置变量 |
89
+ ```
90
+
91
+ ---
92
+
93
+ ## 填写规则
94
+
95
+ ### BR(业务规则)——从源码推导,不从直觉
96
+
97
+ | 来源 | ✅ 正确 | ❌ 错误 |
98
+ |---|---|---|
99
+ | 默认值 | `settings.getBoolean('answer', true)` → "默认开启" | "直觉上应该默认关闭" |
100
+ | 联动 | `if (!answer) { allAngles.setEnabled(false) }` → "关闭应答联动禁用任意角度" | "一般这种开关会联动" |
101
+ | 守卫 | `if (stock <= 0) { button.disabled = true }` → "库存≤0 按钮禁用" | "没库存应该不能买" |
102
+
103
+ > **历史教训**:RTA TC05(守卫条件记错)与 GW 搜索(选中不切主页),两次实证直觉必错。
104
+
105
+ ### test_intent 分类与断言范式选型
106
+
107
+ | test_intent | 测试目的 | 正确断言范式 | 反例 |
108
+ |---|---|---|---|
109
+ | 资源渲染类 | 图标/文案加载成功 | exist/equals 即目的本身 | — |
110
+ | 功能交互类 | 操作产生业务效果 | 效果断言(值变化/计数增减/checked 翻转) | 仅 exist |
111
+ | 配置生效类 | 设置改变全局行为 | 生效链(改设置→回业务页→断言输出变化) | 仅在设置页断言 |
112
+ | 导航流转类 | 状态迁移到正确目的地 | 终点效果(城市选中→顶栏变新城市名) | 仅"目标页可达" |
113
+ | 状态机类 | 状态转移规律 | 前置态断言→触发→后置态断言(成对) | 只断后置态 |
114
+ | 数据输入类 | 输入被正确处理 | 输入→输出值断言(搜索词→结果计数) | 仅"页面有结果" |
115
+
116
+ ### 优先级
117
+
118
+ | 级别 | 含义 | 分配原则 |
119
+ |---|---|---|
120
+ | P0 | 核心正向路径 | 每个业务域至少 1 条 |
121
+ | P1 | 重要分支/边界 | 联动/互斥/持久化/异常处理 |
122
+ | P2 | 补充场景 | 边缘功能/极端边界 |
123
+
124
+ ---
125
+
126
+ ## 示例:FS-goods-001 规格选择弹窗(CoolMall 节选)
127
+
128
+ > 完整版见 `ForgeDemos/CoolMall/docs/functional-spec-goods-001.md`
129
+
130
+ | 规则 ID | 规则描述 | 测试含义 |
131
+ |---|---|---|
132
+ | BR-01 | 初始未选规格,数量默认 1 | 打开弹窗断言"请选择规格"存在 + 数量=1 |
133
+ | BR-02 | 未选规格或库存≤0 → 购买按钮 disabled | `BY.key('btn_buy').enabled(False)` |
134
+ | BR-03 | 价格 fallback:spec.price → goods.price | 选规格后 equals 新价 |
135
+
136
+ | 场景 ID | 场景 | test_intent | 断言 | 优先级 |
137
+ |---|---|---|---|---|
138
+ | S-01 | 选中规格后价格/库存/按钮联动 | 功能交互类 | 值变化 + enabled 翻转 | P0 |
139
+ | S-02 | 立即购买跨页一致性 | 配置生效类 | 订单确认页 equals 规格/单价/数量 | P0 |
140
+ | S-03 | stock=0 时购买拦截 | 功能交互类 | disabled + 点击无跳转 | P1 |
@@ -0,0 +1,135 @@
1
+ # Hypium 官方 API 速查(提炼自 developer.huawei.com 应用UI测试(基于Python)文档)
2
+
3
+ > 源文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/hypium-python-guidelines(2026-06-05 版)
4
+ > 本文只保留生成脚本高频用到的部分;完整语义以官方文档为准。
5
+
6
+ ## 工程结构
7
+
8
+ ```
9
+ project/
10
+ ├── testcases/ # 用例 XXX.py + 同名 XXX.json
11
+ ├── resource/ # 模板图片等资源(get_resource_path 优先搜此)
12
+ ├── config/user_config.xml
13
+ └── main.py # 入口
14
+ ```
15
+
16
+ 用例生命周期:`setup`(前置)/ `process`(步骤)/ `teardown`(清理)。Step/CheckPoint/MESSAGE 会进报告。测试套模式:TestSuite 类 + TC_ 前缀自动扫描用例。
17
+
18
+ ## 控件定位(BY)
19
+
20
+ **优先级:属性 > 图片 > 比例坐标**(坐标点击前后各做一次属性断言)。
21
+
22
+ ```python
23
+ from hypium import BY, UiDriver
24
+ from hypium.model import MatchPattern, UiParam, KeyCode
25
+
26
+ driver.find_component(BY.text("蓝牙")) # 第一个匹配
27
+ driver.find_all_components(BY.type("Button")) # 全部匹配,取 [n]
28
+ driver.touch(BY.text("蓝牙")) # 查找并点击
29
+
30
+ # 模糊匹配(第二个参数)
31
+ BY.text("看过", MatchPattern.CONTAINS) # 模板文案/长标题必用
32
+ BY.text("今天星期", MatchPattern.STARTS_WITH)
33
+ BY.text(".+", MatchPattern.REGEXP)
34
+
35
+ # 组合
36
+ BY.text("蓝牙").type("Button").key("bluetooth_switch")
37
+
38
+ # 相对位置(锚点不可再用相对定位)
39
+ BY.type("Button").isAfter(BY.text("显示通知图标")) # 控件树前序遍历顺序
40
+ BY.type("Image").isBefore(BY.text("账号"))
41
+ BY.type("Image").within(BY.key("nav_container"))
42
+ BY.text("蓝牙").inWindow("com.huawei.hmos.settings") # 限定窗口
43
+
44
+ # XPath(不能与其他属性混用,较慢,兜底用)
45
+ BY.xpath("//*[@text='可用 WLAN']/ancestor::List/ListItemGroup/ListItem[1]//Text/following::Image")
46
+ ```
47
+
48
+ 支持属性:text/key/type/hint(支持模糊);checkable/longClickable/clickable/scrollable/enabled/focused/selected/checked(bool)。
49
+
50
+ ## 图片与坐标
51
+
52
+ ```python
53
+ driver.touch_image("/path/template.jpeg") # 需 opencv-python;只匹配最佳一处
54
+ b = driver.find_image("template.jpeg") # 返回 Rect
55
+ driver.touch((0.52, 0.98)) # 比例坐标
56
+ ```
57
+
58
+ ## 窗口
59
+
60
+ ```python
61
+ from hypium.model import WindowFilter
62
+ w = driver.find_window(WindowFilter().bundle_name("com.x").actived(True))
63
+ driver.check_window(WindowFilter().focused(True), bundle_name="com.x")
64
+ ```
65
+
66
+ ## 触控
67
+
68
+ ```python
69
+ driver.touch(target, mode=UiParam.NORMAL|LONG|DOUBLE, scroll_target=BY.type("Scroll"), wait_time=0.1)
70
+ # scroll_target:在滚动容器内查找并点击——视口外控件首选,勿手工预滑动
71
+ driver.long_click(target, press_time=2, offset=(0.1, 0.1)) # offset 为控件内比例
72
+ driver.swipe(UiParam.UP, distance=40, area=BY.type("Scroll"), side=UiParam.RIGHT, start_point=(0.8,0.8), swipe_time=0.3)
73
+ # ⚠ distance=滑动区域总长百分比(1-100),非像素
74
+ driver.slide((100,200), (300,400), slide_time=0.3) # 精确滑动,起止可为 BY/控件/坐标
75
+ driver.drag(start, end, area=BY.type("Slider"), press_time=1, drag_time=1)
76
+ driver.pinch_in/out(BY.type("Image"), direction="horizontal")
77
+ driver.multi_finger_touch([(0.1,0.2),(0.3,0.4)])
78
+ # 自定义手势
79
+ from hypium.uidriver import Gesture
80
+ g = Gesture().start(pos, 2).move_to(p2).pause(1)
81
+ driver.inject_gesture(g) # 多指:inject_multi_finger_gesture([g1,g2])
82
+ ```
83
+
84
+ ## 输入与按键
85
+
86
+ ```python
87
+ from hypium.model import InputTextMode
88
+ driver.input_text(BY.type("TextInput"), "hello") # 默认先清空;中文剪贴板粘贴
89
+ driver.input_text(c, "x", mode=InputTextMode().paste(True).addition(True))
90
+ driver.clear_text(BY.type("TextInput"))
91
+ driver.press_key(KeyCode.POWER, mode=UiParam.LONG)
92
+ driver.press_combination_key(KeyCode.CTRL_LEFT, KeyCode.SHIFT_LEFT, KeyCode.F)
93
+ ```
94
+
95
+ ## 应用/设备管理
96
+
97
+ ```python
98
+ driver.start_app(bundle, ability, wait_time=1)
99
+ driver.stop_app(bundle)
100
+ driver.install_app(path, "-r")
101
+ driver.uninstall_app(bundle)
102
+ driver.clear_app_data(bundle) # 清应用数据(重置持久化状态首选)
103
+ driver.hdc("list targets")
104
+ driver.shell("ls -l") # 设备侧
105
+ host.shell("dir") # PC 侧
106
+ driver.push_file(local, device_path); driver.pull_file(device, local); driver.has_file(p)
107
+ ```
108
+
109
+ ## 断言
110
+
111
+ ```python
112
+ host.check_equal(a, b, "a != b"); host.check_greater(a, b)
113
+
114
+ driver.check_component_exist(BY.type("Button"), expect_exist=True, wait_time=5,
115
+ scroll_target=BY.type("Scroll")) # 负断言 expect_exist=False
116
+ driver.check_component(BY.key("xxx"), checked=True) # 属性断言 kwargs: id/text/key/type/
117
+ # enabled/focused/clickable/scrollable/checked/checkable
118
+ driver.check_image_exist("t.jpeg", similarity=0.95, timeout=3, mode="template"|"sift")
119
+ ```
120
+
121
+ ## 等与轮询
122
+
123
+ ```python
124
+ comp = driver.wait_for_component(BY.text("xx"), 5) # 秒;找不到返回 None
125
+ ```
126
+
127
+ ## 执行与报告
128
+
129
+ ```bash
130
+ python -m hypium # 交互控制台;run/list/help
131
+ # 常用:run -l Case1;Case2 -sn SN1 -rp report路径 -ta screenshot:true
132
+ # harmonyrun 执行器等价:cd 项目目录 && harmonyrun hypium run . -d SN --case XXX_001
133
+ ```
134
+
135
+ 报告:`reports/<时间戳>/`(details/ 步骤截图、result/*.xml、log/、summary_report.html、summary.ini、task_info.record)。xdevice 执行器下为 report_data.json(含逐条耗时)。
@@ -0,0 +1,98 @@
1
+ # Hypium 高频踩坑清单(7 项目实证 + 官方文档修正)
2
+
3
+ 来源:agc-template-market 7 个项目(DailySchedule/CarBeautyCare/Recipes/HomeDecoration/WebShortDrama/Recycling/DigitalNewspaper)白盒开发全记录。完整数据见 TestDemo 仓库 `reports/HYPIUM-DEV-COST-ANALYSIS.md`。
4
+
5
+ ## 框架/API 层
6
+
7
+ ### 1. `swipe` distance 是百分比(4/7 项目首踩)
8
+ `driver.swipe(direction, distance)` 的 distance 是**滑动区域总长的 1%-100%**,非像素。按像素写 250 会越界报 invalid/负坐标。direction 用常量 `UiParam.LEFT/RIGHT/UP/DOWN`。精确滑动用 `slide(start, end)`;长滑抽屉类(swipeAction 露出按钮)要 ~40% 屏宽。
9
+
10
+ ### 2. `BY.text` 默认全匹配(5/7 项目)
11
+ 模板文案(`${used}看过`)、mock 长标题("非常好吃上头的的可乐鸡翅~~")全匹配必失败。**修正**:`MatchPattern.CONTAINS`(官方支持,无需自写子串遍历——早期项目自写了 find_text_contains,其实框架原生支持)。还支持 STARTS_WITH/ENDS_WITH/REGEXP/REGEXP_ICASE。
12
+
13
+ ### 3. `press_back()` 语义不唯一(RCY 6 次失败中 3 次)
14
+ 收软键盘与 pop 页面不区分:编辑页上收键盘会把整页 pop 掉。**绕法**:点击页面标题空白处失焦收键盘。框架暂无 dismiss_keyboard()。
15
+
16
+ ### 4. 系统子窗口对组件树不可见(6/7 项目)
17
+ Toast(promptAction)、系统权限弹窗、输入法窗口、MapKit 级联面板均拿不到。**对策**:Toast 断言改界面持久变化;权限弹窗用 `hdc shell atm` 预授权或 setup 处理一次;输入法遮挡按钮→先收键盘再点。**A 线(LLM 截图)反而能读 Toast**——融合分工点。
18
+
19
+ ### 5. 视口外控件(2/7 项目)
20
+ 不需要手工滑动!`touch(BY.text("xx"), scroll_target=BY.type("Scroll"))` 与 `check_component_exist(..., scroll_target=...)` 原生支持滚动查找。仅当控件被布局裁剪出屏(如 CBC_008 `height('100%')` 缺陷)才完全不可达→登记应用缺陷+降级断言。
21
+
22
+ ### 6. DatePicker/滚轮无列级 API(2/7)
23
+ 几何坐标模拟:取 DatePicker bounds 右侧 15% 处为日列中心,小距离 swipe 后**读取高亮值校验,过冲则反向回滑一格**(闭环纠偏)。
24
+
25
+ ### 7. Rect/bounds 便捷方法缺失(3/7)
26
+ 无 get_left 等;用 get_center()/get_size() 或直接 r.left/r.right 字段。`.id()` 不一定暴露(应用没写 test id 时);Span 拼接文本拿不到完整串→用父 Text CONTAINS。
27
+
28
+ ### 8. `input_text` 行为细节
29
+ 默认**先清空再输入**;中文走剪贴板一次粘贴;英文 <200 字符逐键。粘贴板输入后软键盘可能不弹出——某些校验依赖输入事件的表单要留意。
30
+
31
+ ## 应用/数据层
32
+
33
+ ### 9. PersistenceV2 登录态跨用例残留(RCY 6 次失败的另一半)
34
+ "not in logout state" 类失败的根因。**标准解**:setup 里 `driver.clear_app_data(PACKAGE)`(官方 API)或 `hdc shell bm clean -n <pkg> -d`,然后重新 mock_login。
35
+
36
+ ### 10. 纯内存数据不可跨用例继承
37
+ VM 里普通数组(如地址列表)每次启动为空——同用例 setup 内造数,勿依赖其他用例。
38
+
39
+ ### 11. 华为账号 Kit 模拟器必失败
40
+ 模板 app 普遍有模拟登录降级:一键登录失败 → 弹"是否先模拟登录成功?/温馨提示" → 点确认 → isLogin=true。common 库封装 `mock_login()`,注意有的要先勾协议 Checkbox(软键盘可能遮挡它——先收键盘)。
41
+
42
+ ### 12. press_back 的另一面:Web 页面 go_back 被 Web 历史消费
43
+ 详情页是 Web 组件时,press_back 先退 Web 历史;EMBEDDED sheet 返回首页后不自动关闭会遮挡底栏——回首页后显式关 sheet。
44
+
45
+ ### 13. 异步导航延迟(WSD-014 flaky)
46
+ 登录成功后 app 数秒后自动跳首页,会把校验通过的页面"带走"。**标准解**:每步操作前 `ensure_page()` 自愈式重进。
47
+
48
+ ### 14. 同名控件歧义
49
+ 订单状态 Tab 与卡片状态文案同名→按 y 最小取顶部;弹窗"取消"与顶栏"取消"→按 y 区分或用 within/inWindow 缩范围。
50
+
51
+ ### 15. mock 数据语义陷阱
52
+ 搜索可能是 `name 子串 || labels 精确`(热词"华为手机"零命中,"手机"命中 labels);列表最新在前;首条地址自动设默认。**读 mock/VM 源码是唯一可靠途径**。
53
+
54
+ ## 执行器层(harmonyrun)
55
+
56
+ ### 16. 全量模式 xdevice bug
57
+ `harmonyrun hypium run .`(不带 --case)报 "Test source 'testcases' does not exist"——即使 cwd 正确。只能 `--case XXX_001` 逐条,每条 ~40s 框架开销(安装/报告),15 条多花 ~7min。
58
+
59
+ ### 17. 必须先 cd 到 hypium 项目目录
60
+ 相对路径解析依赖 cwd;后台命令的 cd 不影响后续 shell——写 run.sh 固定 cwd。
61
+
62
+ ### 18. 报告位置
63
+ xdevice 报告在 `<项目>/reports/<时间戳>/`,summary_report.html / report_data.json(逐条耗时在 report_data.json)。控制台管道 tail 会截日志,排障去报告目录取全量日志。
64
+
65
+ ## SchoolLife 对照实验新增(2026-09-16,N1~N10)
66
+
67
+ ### 19. isAfter/isBefore 是"整体在上方/下方",非同行左右
68
+ 表单同行布局(label 左、输入框右)用 isAfter 定位会命中下一行的框。同行控件用 y 区间过滤或 within(容器);xpath `//TextInput[n]` 序号 >1 不可用。
69
+
70
+ ### 20. 组件树包含被 sheet/弹窗遮挡的背景控件
71
+ sheet 打开时背景页控件仍可被 find 命中,点击打在遮挡层上。弹窗内操作前先断言弹窗标题存在,或校验目标 bounds 是否被遮挡。
72
+
73
+ ### 21. AlertDialog 文本命中返回整窗容器
74
+ 点容器中心未必命中按钮。用 find_all 取容器内 Button 子控件再点。
75
+
76
+ ### 22. 坐标是 px 不是 vp(vp×3.5)
77
+ 源码 vp 尺寸直接当坐标用会全偏。common 库封装 vp→px 换算;无文字图标按钮先几何过滤再算中心。
78
+
79
+ ### 23. BY.text 的 MatchPattern 必须位置传参
80
+ `BY.text(match_pattern=MatchPattern.CONTAINS)` 关键字传参被拒,必须 `BY.text(s, MatchPattern.CONTAINS)`。
81
+
82
+ ### 24. input_text 偶发不生效
83
+ 封装"输入后 getText 回读校验 + 重试"。
84
+
85
+ ### 25. 用例间业务数据冲突
86
+ 带唯一性/重叠校验的功能(如课程时段),每条用例分配互斥数据;全套重跑前卸载重装或 clear_app_data。
87
+
88
+ ### 26. 功能被登录态门控但用例集未体现
89
+ control-facts 阶段必须查每个功能入口的 isLogin 分支(SchoolLife 签到需"模拟关联"后才能用)。
90
+
91
+ ### 27. 模拟器锁屏/崩溃无预案会报废整轮
92
+ 每次 setup 先亮屏(press_key POWER / 检查 display 状态),崩溃后重试;atm perm 预授权在部分镜像不存在,勿依赖。
93
+
94
+ ### 28b. 权限弹窗保护规则(用户指令 2026-09-16)
95
+ 执行中出现任何权限确认弹窗(位置/相机/麦克风/相册/通知,"允许/仅本次允许/禁止"),**一律点击同意/允许继续**——除非该用例明确设计为"无权限状态下的测试"(如验证拒绝后的降级 UI/引导页)。落法:common 库 launch/setup 加弹窗处理循环(检测弹窗文案→点允许→重检到无弹窗)。注意系统权限弹窗是系统子窗口、组件树可能不可见(见 #4),不可见时用坐标点击或 `hdc shell atm` 预授权兜底。
96
+
97
+ ### 28. key 定位现状:模板 app 几乎无 .id()
98
+ 实测 8 项目 .id() 密度 0~9 处(集中在个别安全组件)。遇到有 id 的模块优先 BY.key(SchoolLife 签到 4 条一次通过即证据);脚本生成时输出"可测性改造建议"(给关键控件打语义化 .id())作为交付物之一。