@harmonyos-arkts/d2h 0.1.3 → 0.1.6
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.
- package/README.md +78 -44
- package/agents/android-to-hmos-00-orchestrator.md +34 -28
- package/agents/d2h.md +266 -95
- package/dist/bin/install-opencode.js +137 -43
- package/dist/src/install-opencode.js +44 -0
- package/dist/src/install-vscode.js +226 -0
- package/dist/src/toolchain.js +87 -0
- package/docs/environment-check.md +86 -0
- package/opencode/agents.json +1 -1
- package/opencode/vscode-d2h-package.json +7 -0
- package/opencode/vscode-d2h-plugin.mjs +29 -0
- package/package.json +6 -4
- package/schemas/task-envelope.schema.json +58 -4
- package/schemas/task-plan.schema.json +281 -63
- package/scripts/check-environment.mjs +321 -0
- package/skills/d2h-hypium-forge/SKILL.md +252 -0
- package/skills/d2h-hypium-forge/references/functional-spec-template.md +140 -0
- package/skills/d2h-hypium-forge/references/official-api.md +135 -0
- package/skills/d2h-hypium-forge/references/pitfalls.md +98 -0
- package/skills/d2h-hypium-forge/references/testability-template.md +71 -0
- package/skills/d2h-hypium-forge/scripts/_common_template.py +614 -0
- package/skills/d2h-hypium-forge/scripts/analyze_results.py +156 -0
- package/skills/d2h-hypium-forge/scripts/assert_audit.py +107 -0
- package/skills/d2h-hypium-forge/scripts/collect_runtime_pages.py +169 -0
- package/skills/d2h-hypium-forge/scripts/forge_report.py +325 -0
- package/skills/d2h-hypium-forge/scripts/inject_resource_ids.py +114 -0
- package/skills/d2h-hypium-forge/scripts/lint_asserts.py +80 -0
- package/skills/d2h-hypium-forge/scripts/lint_locate.py +60 -0
- package/skills/d2h-hypium-forge/scripts/merge_appgraph.py +249 -0
- package/skills/d2h-hypium-forge/scripts/precondition.py +140 -0
- package/skills/d2h-hypium-forge/scripts/preinject_ids.py +232 -0
- package/skills/d2h-hypium-forge/scripts/run_direct.py +392 -0
- package/skills/d2h-hypium-forge/scripts/run_parallel.py +121 -0
- package/skills/d2h-hypium-forge/scripts/scan_features.py +255 -0
- package/skills/d2h-hypium-forge/scripts/upgrade_asserts.py +66 -0
- package/skills/d2h-hypium-forge/scripts/validate_scenarios.py +84 -0
- package/skills/d2h-utg-ai-generate/SKILL.md +128 -0
- package/skills/d2h-utg-ai-generate/references/click-paths-schema.json +76 -0
- package/skills/d2h-utg-ai-generate/references/extraction-guide.md +34 -0
- package/skills/d2h-utg-ai-generate/references/facts-contract.md +96 -0
- package/skills/d2h-utg-ai-generate/references/page-schema.json +61 -0
- package/skills/d2h-utg-ai-generate/references/utg-schema.json +89 -0
- package/skills/d2h-utg-ai-generate/scripts/build_page_json.py +314 -0
- package/skills/d2h-utg-ai-generate/scripts/build_utg.py +123 -0
- package/skills/d2h-utg-ai-generate/scripts/capture_from_page_json.ts +280 -0
- package/skills/d2h-utg-ai-generate/scripts/utg_common.py +524 -0
- package/skills/d2h-utg-ai-generate/scripts/utg_html.py +489 -0
- package/skills/d2h-utg-ai-generate/scripts/validate_utg.py +69 -0
- package/skills/d2h-utg-ai-generate/scripts/vendor/D3-LICENSE +13 -0
- package/skills/d2h-utg-ai-generate/scripts/vendor/d3-7.9.0.min.js +2 -0
- package/skills/d2h-utg-ai-generate/tests/test_pipeline.py +369 -0
- package/skills/d2h-utg-static-generate/SKILL.md +73 -0
- package/skills/d2h-utg-static-generate/references/discoveries.md +38 -0
- package/skills/d2h-utg-static-generate/scripts/generate.py +499 -0
- package/skills/d2h-utg-static-generate/tests/test_static.py +335 -0
- package/skills/d2h-utg-static-generate/tests/verify-offline-html.py +60 -0
- package/skills/d2h-utg-static-generate/tests/verify-produced-fixture.py +131 -0
- package/skills/harmonyos-migration-playbook/references/ui-automation.md +4 -3
- package/skills/harmonyos-migration-playbook/references/windows-environment.md +1 -1
- package/skills/managing-d2h-toolchain/SKILL.md +23 -0
- package/skills/managing-d2h-toolchain/environment-requirements.json +16 -0
- package/skills/managing-d2h-toolchain/toolchain.json +16 -0
- package/skills/preparing-migration-workspace/SKILL.md +9 -7
- package/agents/d2h_v2.md +0 -351
- package/skills/preparing-migration-workspace/toolchain.json +0 -19
|
@@ -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())作为交付物之一。
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# 可测试性需求(Design for Testability)— 模板与累积清单
|
|
2
|
+
|
|
3
|
+
> 调测中发现的「让 hypium 测试变慢/不准」的工程特征,反哺为对应用开发的需求。
|
|
4
|
+
> 每条格式:现象(实测) → 根因 → 可测试性需求(对开发的要求) → 收益。
|
|
5
|
+
|
|
6
|
+
## 累积清单(按工程验证时追加)
|
|
7
|
+
|
|
8
|
+
### DFT-001 控件必须预置稳定 ID
|
|
9
|
+
- 现象:无 .id() 控件用 BY.text 定位,文案随数据变化 → 脚本重试多轮、不稳定(实测:首个转化工程首轮全部超时)。
|
|
10
|
+
- 根因:ArkTS 开发普遍省略 .id(),uitest 只能按 text/type 匹配。
|
|
11
|
+
- 需求:**所有可交互控件(Button/Toggle/TextInput/入口 Image)必须写 .id()**,命名 `<语义>_<页面>`(如 btn_settings_index)。
|
|
12
|
+
- 收益:脚本零重试定位;用例与实现解耦(文案改了脚本不断)。
|
|
13
|
+
|
|
14
|
+
### DFT-002 条件渲染控件需可达路径
|
|
15
|
+
- 现象:`if(testMode)` 内控件默认态不存在,存在性断言误报失败(实测 START TEST 按钮)。
|
|
16
|
+
- 需求:条件功能的入口开关应在**入口页可达**(≤2 步操作),或提供 deeplink/want 参数直达。
|
|
17
|
+
- 收益:状态覆盖用例可自动执行,不依赖复杂前置链。
|
|
18
|
+
|
|
19
|
+
### DFT-003 运行时权限在模拟器的可预授性
|
|
20
|
+
- 现象:首启权限弹窗阻塞 BFS/用例执行(实测 RaiseToAnswer 电话权限)。
|
|
21
|
+
- 需求:应用不应在首帧同步弹多个权限弹窗;权限申请延后到功能触发点。
|
|
22
|
+
- 收益:precondition 批量预授后用例零弹窗干扰。
|
|
23
|
+
|
|
24
|
+
### DFT-004 登录需提供测试通道
|
|
25
|
+
- 现象:真实登录页在模拟器无法自动完成 → 登录后功能全部 BLOCKED。
|
|
26
|
+
- 需求:提供 mock 登录开关(want 参数/环境开关)或测试账号常量。
|
|
27
|
+
- 收益:登录态用例可全自动执行。
|
|
28
|
+
|
|
29
|
+
### DFT-005 首启弹窗需可编程处置
|
|
30
|
+
- 现象:隐私协议/引导页弹窗文案各异,逐个适配成本高。
|
|
31
|
+
- 需求:首启弹窗按钮文案使用标准词(同意并继续/跳过/我知道了);提供"不再提醒"。
|
|
32
|
+
- 收益:统一 dismiss 片段即可覆盖。
|
|
33
|
+
|
|
34
|
+
(新工程验证时按编号追加,注明来源工程与实测数据)
|
|
35
|
+
|
|
36
|
+
### DFT-006 HAP 更新必须卸载重装(模拟器缓存怪癖)
|
|
37
|
+
- 现象:`install -r` 同版本号替换后,注入的 .id 运行时全部丢失(abc 缓存不失效,实测 News 工程)。
|
|
38
|
+
- 需求/skill 规则:**注入 ID 重打包后一律 `hdc uninstall <bundle> && hdc install <hap>`**。
|
|
39
|
+
|
|
40
|
+
### DFT-007 部分 HAR 模块 .id 运行时丢弃(待官方解释)
|
|
41
|
+
- 现象:同一 HAP 内 commons/features-home 的 .id 生效,features-mine 的全部不生效(abc 字节串在、属性指令无效,跨 4 种注入位置复现)。
|
|
42
|
+
- 需求:**smart_check/smart_click 必须带 orig_target 文本兜底**(key 失败转文本/类型断言)。
|
|
43
|
+
- 来源:News 工程(2026-09-23 实测)。
|
|
44
|
+
|
|
45
|
+
### DFT-008 同控件多次注入会叠 id(仅首个生效)
|
|
46
|
+
- 现象:多条用例引用同一控件时注入器产生 `.id(a).id(b)`,运行时仅第一个可查。
|
|
47
|
+
- 需求/skill 规则:**用例引用统一到首注入 ID(`_1` 后缀规约)**;注入器应检测已有 tc_ id 并复用。
|
|
48
|
+
|
|
49
|
+
### DFT-009 Tab 切换的可靠路径
|
|
50
|
+
- 现象:BY.text 精确/模糊点击 Tab 均可能点到正文同名文本;比例坐标受 Tab 数量与手势条影响。
|
|
51
|
+
- 需求/skill 规则:**click_tab 用 find_all_components 取「最靠下的同名文本」的 bounds 中心 touch**(News/SchoolLife/CoolMall 2/3/4-Tab 全验证通过)。
|
|
52
|
+
|
|
53
|
+
### DFT-010 资源化文案使静态覆盖度失准
|
|
54
|
+
- 现象:CoolMall(70k 行)源码文案全部 `$r()` 资源化,scan 的交互点与运行时文本不匹配 → 静态覆盖度 0%。
|
|
55
|
+
- 需求/skill 规则:大型工程覆盖度评估降级为**运行时可达性口径**(用例通过率 + dump 文本对照);源码覆盖度仅对硬编码文案工程有效。
|
|
56
|
+
|
|
57
|
+
### DFT-011 用例间需间歇(teardown 冷启动时序)
|
|
58
|
+
- 现象:批量连跑时下一条 setup 的 start_app 未完成即断言(TC01 偶发失败)。
|
|
59
|
+
- skill 规则:批量执行循环 **sleep 3s** + launch 等待 2.5s 起步。
|
|
60
|
+
|
|
61
|
+
### DFT-012 输入法首用向导是隐藏前置(CoolMall 实测)
|
|
62
|
+
- 现象:input_text 前拉起「小艺输入法」声明弹窗 + 键盘布局向导(多步),挡住输入与提交按钮;向导期间点登录无效。
|
|
63
|
+
- skill 规则:precondition 增加 IME 预置步骤——检测并依次点掉「同意/下一步/完成」;输入后 **press_back 收键盘再点提交按钮**(否则按钮被键盘遮挡)。
|
|
64
|
+
|
|
65
|
+
### DFT-013 输入框定位用 BY.hint 不用 BY.text
|
|
66
|
+
- 现象:`BY.text('请输入手机号')` 匹配到的是 hint 展示文本节点而非 TextInput 本体,input_text 无效且无报错。
|
|
67
|
+
- skill 规则:**带占位符的输入框一律 `BY.hint('<placeholder>')` 定位**(hypium 专属匹配器)。
|
|
68
|
+
|
|
69
|
+
### DFT-014 设备访问 PC mock 需 rport(fport 是反向)
|
|
70
|
+
- 现象:`hdc fport` 是 PC→设备方向;设备 App 访问 127.0.0.1:8787 的 PC mock 必须用 `hdc rport tcp:8787 tcp:8787`(CoolMall BASE_URL 已预指 mock)。
|
|
71
|
+
- 附:mock 重启后 App 需 aa force-stop 冷启动才重新拉数据(失败态有缓存)。
|