Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions docs/device-method-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,102 @@ packages/connect-examples/expo-example/src/testTools/deviceCompatibility/plugins
├── touch.ts
└── mini.ts
```

这些插件会在 `src/testTools/deviceCompatibility/plugins/index.ts` 中统一注册到 `compatibilityManager`。

---

# Overrides 规则模型

当前设备兼容层的核心结构如下:

```ts
interface DeviceCompatibilityOverride {
id: string;
methods: string | string[];
when?: (context) => boolean;
skip?: string;
expected?: boolean;
}
```

## 字段含义

| 字段 | 说明 |
|------|------|
| `methods` | 规则命中的方法名,支持单个字符串或字符串数组 |
| `when` | 可选附加条件;上下文里可读取 `method / key / path / params / testContext / features / deviceType` |
| `expected` | 覆盖默认期望值;常见场景是将默认失败改成成功,或将默认成功改成失败 |
| `skip` | 标记为跳过并返回原因 |

## 匹配规则

- 设备类型通过 `getDeviceType(features)` 判断
- 每个设备插件内部按 `overrides.find(...)` 顺序匹配
- **第一个命中的 override 生效**

这意味着:更具体的规则应放在更靠前的位置,避免被更宽泛的规则提前吞掉。

---

# 当前代码路径如何消费这些规则

## 1. Automation Test

`packages/connect-examples/expo-example/src/testTools/automationTest/useAutomationTest.ts`

当前自动化测试会在这些 suites 中读取 `compatibilityManager.getExpectedOverride(...)`:

- `sdkAddressBatch`
- `sdkPubkeyBatch`
- `specialPassphrase`

这里的作用不是隐藏失败,而是把“某设备当前预期应该失败/成功”的行为显式体现在结果里。

## 2. Blind Signature / Security Check

`packages/connect-examples/expo-example/src/testTools/securityCheckTest/blindSignature/utils.ts`

这里通过 `getDeviceExpected(...)` 包装 `getExpectedOverride(...)`,把默认预期与设备差异合并成最终断言结果。

例如:

- Classic 对 `evmSignTransaction + authorizationList` 期望失败
- Classic 1S / Pure 在 `securityChecksDisabled === true` 时,部分 `coinType=60` 用例期望成功

## 3. Hook API(skip 场景)

`src/testTools/deviceCompatibility/DeviceCompatibility.ts` 还暴露了:

- `useDeviceCompatibility(method)`
- `useBatchDeviceCompatibility(methods, pathsByMethod)`
- `compatibilityManager.checkMethod(...)`

这些 API 仍支持方法级、路径级 skip 判断,适合需要在 UI 或批量测试入口提前过滤能力的场景。

---

# 维护流程

当设备行为发生变化时,建议按下面顺序更新:

1. 修改对应设备插件里的 override
2. 保持“更具体的规则在前,更宽泛的规则在后”
3. 更新本文档中的设备差异说明
4. 到 expo-example 的相关测试工具里复跑受影响路径,确认结果区展示的是**真实错误**而不是被静默跳过

## 何时改 `expected`

优先用于这两类情况:

- 固件当前稳定返回失败,但测试不应把它记为回归
- 某些设备在特定参数或 test context 下,实际能力与默认期望不同

## 何时改 `skip`

仅在“执行本身没有意义”时使用,例如:

- 某设备明确不存在该能力
- 某路径组合在当前设备上不应被调用

如果只是“会失败,但这个失败本身就是当前已知行为”,优先使用 `expected=false`,这样结果页仍会保留真实错误信息。
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,49 @@
| `testTools/automationTest/scenarioResolver.ts` | Passphrase/数据集解析 |
| `testTools/automationTest/useAutomationTest.ts` | 执行主逻辑 |
| `atoms/automationAtoms.ts` | 状态管理 |
| `views/AutomationTestScreen.tsx` | UI |
| `views/AutomationTest/index.tsx` | UI |

---

## 0. 快速开始

### 入口与默认配置

| 项目 | 当前实现 |
|------|----------|
| 页面路由 | `expo-example/automation-test` |
| 配置持久化 | `localStorage` key: `automation-test-config` |
| 默认场景 | `ok26054_bip39_import_12`、`ok40090_slip39_import_20_1of1` |
| 默认 suites | `deviceFlow`、`sdkAddressBatch`、`sdkPubkeyBatch` |
| 默认 passphrase variants | `normal`、`passphrase_2` |
| 默认 PhonePilot 地址 | `EXPO_PUBLIC_PHONEPILOT_URL` 或 `http://localhost:3847` |
| 默认 runner 行为 | `stopOnFirstError=false`、`retryCount=1`、`delayBetweenTests=500`、`devicePreparationMode=full` |

### 启动步骤

1. 打开 `expo-example/automation-test`
2. 选择场景、suite、passphrase 变体
3. 若模式为 `full` 或 `deviceFlowOnly`,先连接 PhonePilot MCP
4. 确认状态区可见:
- `MCP: Ready`
- `OCR: Ready`(仅 create 场景强依赖)
5. 点击开始,查看实时报告与日志

### 设备准备模式怎么选

| 模式 | 何时使用 | 约束 |
|------|----------|------|
| `full` | 需要从设备流到 SDK 校验跑完整链路 | 必须连接 PhonePilot;重置策略由 `getFeatures()` 自动判断 |
| `sdkOnly` | 设备上已经是目标钱包,只想复跑 SDK suites | 不要求 PhonePilot 连接,但仍需要选择场景与 suite |
| `deviceFlowOnly` | 只验证创建/导入流程,不跑任何 SDK case | 必须连接 PhonePilot;可在不选择 SDK suite 时直接启动 |

### UI 自动响应

运行期间,Automation Runner 会自动处理常见 SDK UI 事件:

- `REQUEST_BUTTON` → PhonePilot `confirmAction()`
- `REQUEST_PIN` → PhonePilot `inputPin('1111')`
- `REQUEST_PASSPHRASE` → SDK `RECEIVE_PASSPHRASE`,随后由 PhonePilot 做物理确认

---

Expand Down Expand Up @@ -56,15 +98,15 @@ interface AutomationScenario {

| scenarioId | sequenceId | suites |
|------------|------------|--------|
| `ok26053_bip39_create_12` | `create-wallet` | deviceFlow · sdkAddressBatch · sdkPubkeyBatch · securityCheck |
| `ok26053_bip39_create_12` | `create-wallet` | deviceFlow · sdkAddressBatch · sdkPubkeyBatch |
| `ok26053_bip39_create_18` | `create-wallet-18` | 同上 |
| `ok26053_bip39_create_24` | `create-wallet-24` | 同上 |

**BIP39 Import(OK-26054)**

| scenarioId | sequenceId | suites |
|------------|------------|--------|
| `ok26054_bip39_import_12` | `one-normal-12` | deviceFlow · sdkAddressBatch · sdkPubkeyBatch · specialPassphrase · securityCheck |
| `ok26054_bip39_import_12` | `one-normal-12` | deviceFlow · sdkAddressBatch · sdkPubkeyBatch · specialPassphrase |
| `ok26054_bip39_import_12_two` | `two-normal-12` | 同上 |
| `ok26054_bip39_import_12_three` | `three-normal-12` | 同上 |
| `ok26054_bip39_import_18` | `one-normal-18` | 同上 |
Expand All @@ -79,7 +121,7 @@ interface AutomationScenario {

| scenarioId | sequenceId | suites |
|------------|------------|--------|
| `ok5504_slip39_create_20_1of1` | `create-slip39-single-template` | deviceFlow · sdkAddressBatch · sdkPubkeyBatch · securityCheck |
| `ok5504_slip39_create_20_1of1` | `create-slip39-single-template` | deviceFlow · sdkAddressBatch · sdkPubkeyBatch |
| `ok5504_slip39_create_20_2of2` | `create-slip39-multi-2of2-template` | 同上 |
| `ok5504_slip39_create_20_8of8` | `create-slip39-multi-8of8-template` | 同上 |
| `ok5504_slip39_create_20_16of2` | `create-slip39-multi-16of2-template` | 同上 |
Expand All @@ -88,7 +130,7 @@ interface AutomationScenario {

| scenarioId | sequenceId | dataset | suites |
|------------|------------|---------|--------|
| `ok40090_slip39_import_20_1of1` | `count20_one_normal` | `count20_one` | deviceFlow · sdkAddressBatch · sdkPubkeyBatch · securityCheck |
| `ok40090_slip39_import_20_1of1` | `count20_one_normal` | `count20_one` | deviceFlow · sdkAddressBatch · sdkPubkeyBatch |
| `ok40090_slip39_import_20_3of2` | `count20_two_normal` | `count20_two` | 同上 |
| `ok40090_slip39_import_20_16of16` | `count20_three_normal` | `count20_three` | 同上 |
| `ok40090_slip39_import_33_1of1` | `count33_one_normal` | `count33_one` | 同上 |
Expand Down Expand Up @@ -122,6 +164,8 @@ interface AutomationScenario {
sdkAddressBatch / sdkPubkeyBatch / specialPassphrase / securityCheck / chainMethodBatch
```

> `full` 模式下的 reset 不是固定执行。Runner 会先读取 `features.initialized` 和 `features.unlocked`,再决定是否跳过 reset,或者使用 `reset-wallet-locked` / `reset-wallet-unlocked`。

### suite 失败联动

| 情况 | 行为 |
Expand Down Expand Up @@ -155,6 +199,12 @@ interface AutomationScenario {
| `passphrase_1` | `asdfg7890` | `12345` |
| `passphrase_2` | `1234567890qwertyuiopasdfghjklzxcvbnm` | `onekey` |

### 当前实现约束

- `specialPassphrase` 仅在 **BIP39 import** 场景且场景自带 `bip39ImportMnemonicWords` 时可运行
- `securityCheck` 与 `chainMethodBatch` 仅挂在 `bip39_import_12_api` 场景上
- `SLIP39 create` 的 SDK 校验当前只使用 `normal` 与 `passphrase_2`

---

## 4. 配置与状态
Expand Down Expand Up @@ -196,3 +246,33 @@ TestReport
```

每个 `TestCaseResult` 包含 `title / method / expected / actual / passed / error / duration`。

---

## 5. 运行提示与排障

### 创建场景为什么比导入场景要求更高

create 场景执行完 PhonePilot sequence 后,会读取 `mnemonic-store`:

- BIP39 create:读取助记词词数
- SLIP39 create:额外记录 `shareCount` / `threshold`

因此 create 场景在执行前会检查 `health.ocrReady`。如果 OCR 未就绪,Runner 会直接终止该场景,而不是继续执行一个注定无法校验的流程。

### 常见失败信号

| 日志/现象 | 含义 | 优先检查 |
|-----------|------|----------|
| `PhonePilot server is not reachable` | 服务地址不可达 | `phonePilotUrl`、本地端口、服务是否启动 |
| `PhonePilot MCP connection failed` | `/health` 可达,但 MCP 初始化失败 | PhonePilot MCP transport 状态 |
| `sequence drift: ... is not present in PhonePilot /health sequenceIds` | 当前仓库引用的 `sequenceId` 不在服务端清单中 | PhonePilot 版本、sequence 配置是否同步 |
| `OCR not ready` | create 场景无法读取 mnemonic/share 数据 | OCR 依赖、模型、`/health` 返回的 `ocr.message` |
| `Warning: arm-connect failed` | MCP 已连通,但机械臂未接好 | 机械臂连接状态;依赖 arm 的 sequence 可能失败 |

### 其他行为细节

- `retryCount` 的实际最小值是 `1`
- `delayBetweenTests` 只作用在**场景之间**,不是单个 test case 之间
- 配置通过 `atomWithStorage` 持久化,刷新页面后会保留上次选择
- 若 `deviceFlow` 失败,后续 SDK suites 会标记为 `skipped`
Loading