﻿# Self-Test 使用文档

## 简介

**Self-Test** 是 HomeTrans 的端到端真机自测能力——你把一份 Markdown 测试用例和一个 HAP 交给我，我帮你把用例跑在鸿蒙真机或模拟器上，产出可读的测试报告。如果测试失败，我还能**自动分析原因、修改代码、重建 HAP、重新测试**，直到全部通过。测试和修复不是两个功能拼在一起，而是一条统一的工作流：你只需要说一句话，剩下的事情系统自动完成。

---

## 我能做什么

- **把 Markdown 测试用例变成机器可读的 JSON**：解析你写的 `test_case.md`，自动提取包名、替换应用名，生成 AutoTest 可执行的 `testcases.json`。
- **在鸿蒙真机或模拟器上跑自动化测试**：连接真机或启动模拟器，安装 HAP，逐条执行用例，生成带通过率和失败详情的报告。
- **测试失败后自动修复并重测**：读到失败报告 → 白盒审查源码 → 修改代码 → 重新编译 HAP → 重新测试 → 如果还有失败则继续循环，默认最多 3 轮（可通过 `max-rounds` 参数配置）。

---

## 快速开始

你说一句话，系统跑完全程。最简写法：

> 跑自测，自动修复

更完整的写法（指定所有路径）：

> 跑自测，测试用例在 `D:\project\test_case.md`，HAP 在 `D:\project\entry-default.hap`，输出到 `D:\project\output`

**然后会发生什么：**

1. **测试与修复循环** — 默认最多 3 轮（可通过 `max-rounds` 参数配置），每轮跑完后产物快照到 `output-path/round-{n}/`：
   - 首轮：解析 `test_case.md` → 写出 `output-path/testcases.json` 与 `app-metadata.json` → 连接真机或模拟器、安装 HAP、跑 AutoTest → 产出 `output-path/self-test-report.md`
   - 测试完成后，SKILL 把当轮的 `self-test-report.md`、`task/`、`_extracted.json`（若有）快照到 `output-path/round-{n}/`
   - Fix：白盒审查代码 → 修改源码 → 产出 `round-{n}/self-test-fix-report.md`
   - Build：调用 `hmos-fix-build-errors` 重新编译 → 从构建输出重新收集包集合并更新下一轮使用的 HAP/HSP
   - 后续轮：跳过解析阶段，直接读首轮已生成的 `testcases.json` + `app-metadata.json`，用新 HAP 跑测试 → 同样快照到对应轮 `round-{n}/`
2. **HAP 镜像** — 循环结束后，最后一轮的 HAP 复制到 `output-path/entry-default.hap`，方便直接取用

**最终产物**：都在 `output-path/` 下——`testcases.json` / `app-metadata.json`（根目录，跨轮共享）、`self-test-report.md` 与 `task/`（根目录，最新一轮）、`entry-default.hap`（循环结束后镜像），以及每轮历史快照 `round-{n}/`。完整清单见下方 [输出产物](#输出产物) 一节。

---

## 输入参数

### 测试参数

| 参数 | 是否必填 | 说明 |
|------|----------|------|
| `hap-path` | 必填 | 包路径，支持**一个或多个**（用逗号分隔）。每一项可以是 `.hap`/`.hsp` 文件，也可以是目录。所有项汇总起来要凑齐完整包集合：恰好一个 entry HAP + 其余 feature HAP / 应用内 HSP。**entry HAP 和 HSP 可以放在不同目录**——系统会把所有列出路径里的 `.hap`/`.hsp` 收集到一起，一次事务安装（应用内 HSP 不能单独装，必须和主包同一事务）。例：`D:\out\entry-default.hap, D:\hsp_out` |
| `output-path` | 可选，默认 = `test-case-path` 所在目录 | 所有产物输出到这个目录（`testcases.json`、`app-metadata.json`、`self-test-report.md`、`task/` 都写在此根目录）。不指定时直接写在用例文件同目录 |
| `project-dir` | 可选（自动从 `hap-path` / `test-case-path` 向上查找 `AppScope/app.json5` 推导；推导失败才询问） | HarmonyOS 工程根目录（含 `AppScope/app.json5`），用于解析 `bundle_name` / `app_name` |
| `test-case-path` | 必填 | `test_case.md` 的路径 |
| `pre-test-case-path` | 可选 | 前置用例 `pre_test_case.md` 的路径。不传会自动在 `test-case-path` 同目录查找 `pre_test_case.md` |
| `max-rounds` | 可选，默认 `3` | 测试修复循环的最大迭代轮数。必须为正整数（`>= 1`）。仅在启用修复循环时生效 |

> 首轮（round 1）解析 `test_case.md`（及发现的 `pre_test_case.md`）写出 `testcases.json` + `app-metadata.json` 再跑测试；后续轮（round 2+）跳过解析阶段，直接复用首轮写出的两个 JSON。两个 JSON 都不存在或为空时硬失败，需重跑首轮。

### 自动修复参数

以下参数仅在进入修复循环时使用，由 Skill 自动从 `app-metadata.json` 获取，你通常不需要手动指定：

| 参数 | 必填 | 说明 |
|------|------|------|
| `harmony-project-dir` | 是 | 自动从 `app-metadata.json` 的 `project_root` 字段读取 |
| `android-project-path` | 否 | Android 参考项目路径。提供后 fixer 会参考 Android 实现来修 bug，质量更高 |

---

## 输出产物

`testcases.json` 和 `app-metadata.json` 在首轮（round 1）写入 `output-path/` 根目录，后续轮直接复用，不再重新生成。`self-test-report.md` 与 `task/` 每轮都会在根目录覆盖更新，跑完每轮后由 SKILL 快照到 `output-path/round-{n}/`。循环结束后，最后一轮的 HAP 镜像到根目录。

| 文件 | 写入位置 | 内容 |
|------|----------|------|
| `testcases.json` | `output-path/` 根目录（首轮写入，跨轮共享） | 结构化的用例列表，供 AutoTest 逐条执行 |
| `app-metadata.json` | `output-path/` 根目录（首轮写入，跨轮共享） | `{ bundle_name, app_name, project_root }` |
| `_extracted.json` | `output-path/` 根目录（首轮写入，跨轮快照保留） | 用例提取的中间文件，排查问题时有用，通常不需要关注 |
| `self-test-report.md` | `output-path/` 根目录（每轮覆盖，跨轮快照保留） | 可读的测试报告，含通过率、失败原因、每条用例的 AutoTest 任务路径 |
| `task/task_<时间戳>/` | `output-path/task/`（每轮覆盖，跨轮快照保留） | 每条用例的 HTML 报告、截图、Agent 日志 |
| `self-test-fix-report.md` | `output-path/round-{n}/`（Fix 步骤，有失败的轮次） | 白盒审查结论、根因分析、修改内容、修复结果 |
| `self-test-fix-commit-info.md` | `output-path/round-{n}/`（Fix 步骤） | `commit_id: <hash>` 或 `commit_id: none` |
| `entry-default.hap` | 构建输出目录（Build 步骤）；循环结束后可镜像到 `output-path/` 根目录 | 修复后重新编译的 entry HAP |

> **提示**：`output-path/` 根目录下的报告和 task 始终是最近一轮的最新产物；要查看某一轮的历史快照，进入对应的 `round-{n}/` 目录即可。

---

## 工作流程

一次完整的 Self-Test 跑下来会经历下面这些阶段，简化的流程示意：

```
你提供: test_case.md + HAP
  │
  ▼
┌─ 解析阶段（仅首轮）─────────────────────────────────────────┐
│                                                              │
│  读取 test_case.md → LLM 提取动作/预期结果 → 替换应用名为包名  │
│  → testcases-tool 生成 testcases.json + 写入 app-metadata.json │
│                                                              │
└──────────────────────────────┬───────────────────────────────┘
                               │
                               ▼
┌─ 测试与修复循环（每轮跑完后快照到 output-path/round-{n}/）──┐
│                                                              │
│  ┌── Test ─────────────────────────────────────────────┐    │
│  │ 检测真机/模拟器连接 → 安装 HAP → AutoTest 批量执行用例   │    │
│  │ → 每 60 秒轮询 → 产出至 output-path/ → 快照 round-{n}/   │    │
│  └──────────────────────┬──────────────────────────────┘    │
│                         │                                    │
│                  全部通过?                                    │
│              ┌──────┴──────┐                                  │
│              ▼              ▼                                  │
│            是(退出循环)    否(进入修复)                         │
│                             │                                  │
│  ┌── Fix ──────────────────────────────────────────────┐    │
│  │ self-test-fixer 读报告 → 白盒审查代码                 │    │
│  │ → 区分 confirmed(真bug) / false_positive(误报)       │    │
│  │ → 只修改 confirmed → git commit                      │    │
│  │ → 产出 round-{n}/self-test-fix-report.md             │    │
│  └──────────────────────┬──────────────────────────────┘    │
│                         │                                    │
│                  有确认缺陷?                                   │
│              ┌──────┴──────┐                                  │
│              ▼              ▼                                  │
│            否(退出循环)    是(进入编译)                         │
│                             │                                  │
│  ┌── Build ────────────────────────────────────────────┐    │
│  │ 调 `hmos-fix-build-errors` 重新编译 → 从构建输出重新收集 entry/HSP │    │
│  │ → 组装到 round-{n}/package-set/ 供下一轮安装           │    │
│  └──────────────────────┬──────────────────────────────┘    │
│                         │                                    │
│              未达上限? n++; 回到 Test                          │
│              ┌──────┴──────┐                                  │
│              ▼              ▼                                  │
│            是(下一轮)     否(退出循环)                         │
│                                                              │
└──────────────────────────────────────────────────────────────┘
                               │
                               ▼
               循环结束 → 仅镜像最终轮的签名安装包 (HAP/HSP) 到 output-path/；
               self-test-report.md 已位于 output-path/ 根目录，无需镜像
```

### 循环停止条件

满足以下任一条件，循环立即停止：

| 条件 | stop_reason | 说明 |
|------|-------------|------|
| 全部通过 | `all_passed` | 报告 `failed == 0`，所有用例通过 |
| 无确认缺陷 | `no_confirmed_defects` | fixer 判定所有失败都是测试误报（false positive），没有需要修改的代码缺陷 |
| 达到最大轮数 | `max_rounds_reached` | 已完成配置的最大轮数循环（`max-rounds`，默认 3），仍有失败未解决 |
| 编译未产出 HAP（异常） | `no_hap` | 重建后在构建输出中找不到 entry HAP，无法继续测试 |
| 用例列表为空（异常） | `no_testcases` | `testcases.json` 里 0 条用例，没有可跑的内容。常见原因：解析阶段没识别到 `### Scenario:` 区块 |
| 自测 agent 早退（异常） | `agent_early_exit` | 集成测试 skill 在跑用例之前就退出了（设备没连、模型 api_key 没配（`HOMETRANS_MODEL_API_KEY` 环境变量或 `~/.hometrans/autotest.yaml`）、`@autotest/agent` 未找到且自动安装失败、batch 启动失败 / 超时 / 崩溃、前置条件不满足但所需 JSON 缺失等），或根本没生成 `self-test-report.md`（skill 在产出报告前就失败退出）。报告首行会是 `status: FAIL`，第二行 `reason: <原因>`。**这种情况下不会进入 fix 循环**——这些是环境/前置条件问题，不是应用缺陷 |

---

## 前置用例（Pre-Cases）

如果 `test_case.md` 同目录下有 `pre_test_case.md`，系统会自动把它作为前置用例合并进去。前置用例是一些**环境准备操作**——比如授权弹窗、跳过引导、导入素材、授予权限。

- 前置用例在 `testcases.json` 中排在最前面，`case_name` 会自动加 `[PRE] ` 前缀
- 跑测试时最先执行，为后续用例准备环境
- **前置用例失败不代表应用有 bug**——通常是没素材、没权限、系统弹窗没弹等环境问题
- 测试报告会单独标注**常规通过率**（排除前置用例），这才是衡量应用质量的主要指标

---

## 报告解读

### 测试报告（self-test-report.md）

每份报告包含以下部分：

- **测试概览**：测试套件名、测试时间、设备序列号、应用名（含 bundle_name）、HAP 文件名、总用例数（前置+常规拆分）、通过/失败数（均拆分子项）、常规通过率、含前置通过率
- **前置用例**：`[PRE]` 开头的用例，与常规用例分开列出，各自从 1 开始编号（Pre 1、Pre 2... / Case 1、Case 2...）
- **用例详情**：每条用例的动作、预期结果、AutoTest 判定结果、失败原因
- **测试总结**：总计用例、常规通过率（反映需求质量）、前置通过率、总计通过/未通过、未通过用例列表、建议
- **每条用例都标注了 `**AutoTest 任务路径**`**：指向该用例的原始执行目录（HTML 报告、截图、日志）

**PASS / FAIL / UNKNOWN 的含义：**

- `PASS` — 测试通过
- `FAIL` — 测试失败或超时/崩溃，`reason` 字段说明原因
- `UNKNOWN` — 有产出报告但无法明确判定结果

`FAIL` 和 `UNKNOWN` 都应视为未通过。

### 修复报告（self-test-fix-report.md）

- **概览**：失败数 → 确认 bug 数 → 误报数 → 修复成功/失败数
- **白盒审查结果**：每条失败用例逐一分析，结论为 `confirmed`（代码真有问题）或 `false_positive`（测试误判）
- **修复计划**：按依赖关系排序的修复列表
- **修复详情**：每条修复的 Android 参考、根因、具体修改、有效尝试次数、结果
- **误报说明**：为什么判定为误报，测试 agent 出了什么问题
- **编译验证**：编译结果及 `hmos-fix-build-errors` 触发的编译修复情况（如有）
- **所有修改文件汇总**：文件路径、修改类型、关联 Scenario 的汇总表

### 编译阶段

编译阶段的关键结果是：项目是否重新编过、是否成功得到新的 entry HAP、以及下一轮测试使用的包集合是否已从构建输出重新收集完成。

---

## test_case.md 怎么写

完整格式定义见 `hmos-test-case-generation/references/contract.md`（`hmos-test-case-generation` skill 产出的就是该完整格式）。集成测试的解析器只从中提取 `### Scenario` 的 `动作`/`预期结果`，其余字段（`- 前置条件：`、`## 编号映射表`、`- 测试点：` 等）会被忽略——所以你既可以手写完整格式，也可以手写下面的最小子集。

完整格式（`hmos-test-case-generation` 产出，contract.md 节选）：

```markdown
# 测试套件名

## 编号映射表
| 功能名称 | SPEC 编号 | REQ 编号 |
|---------|-----------|----------|
| 新建歌单 | SPEC-01   | REQ      |

## Scenario List

### Scenario 1-1: 新建歌单输入空白名称点击确定时提示名称不得为空 [P0]
- 前置条件：
  - 条件1: 已安装 被测应用 并授予存储权限（AutoTest 自动处理）
- 动作：打开 被测应用 -> 进入歌单页面 -> 点击「新建歌单」按钮 -> 输入空白名称 -> 点击确定按钮
- 预期结果：弹出 toast「歌单名称不得为空」，且新建歌单对话框关闭
- 测试点：
  - TP-1: 弹出 toast「歌单名称不得为空」
  - TP-2: 新建歌单对话框关闭
```

最小手写子集（解析器同样接受，只提取动作/预期结果）：

```markdown
# 测试套件名

### Scenario: 新建歌单
- 动作：点击新建歌单按钮
- 预期结果：弹出新建歌单对话框
- 动作：输入歌单名称"测试歌单"
- 预期结果：歌单创建成功，出现在列表顶部
```

字段说明：
- `# 标题` → 测试套件名，会出现在报告里
- `### Scenario N-M:` → 一条用例（`N` = SPEC 号，`M` = 该 SPEC 内流水号；手写子集可省略编号写成 `### Scenario:`）
- `- 动作：` → 测试操作（多步用 ` -> ` 分隔）
- `- 预期结果：` → 期望结果（多行用中文逗号连接）
- `- 前置条件：` / `## 编号映射表` / `- 测试点：` → 仅供人工阅读，解析时忽略

**不需要手动替换应用名**。动作里写的显示名称（如"简单图库"、"Tuku"）系统会自动替换成包名（如 `com.example.tuku`），确保 AutoTest 能正确识别目标应用。

---

## 环境要求

### 基础环境

- 操作系统：**Windows** 是主要测试目标；macOS/Linux 上的核心命令（`hdc`、POSIX 工具链）也能跑，Skill 里涉及到的复制操作会按可用 shell 自适应
- `hdc` 已安装并在 PATH 中
- 模型已配置：`ht init` 导出 `HOMETRANS_MODEL_API_KEY` / `HOMETRANS_MODEL_NAME` / `HOMETRANS_MODEL_BASE_URL` 环境变量。集成测试首次运行时，skill 自动从这些环境变量生成 `~/.hometrans/autotest.yaml`（AutoTestAgent 原生格式），后续运行直接读取，不重复生成。用户可手动编辑此文件启用 layered 模式。
- **layered 多模型模式**（可选）：编辑 `~/.hometrans/autotest.yaml`，将 `agent.mode` 改为 `"layered"`，并添加 `execute` 和 `decision` 模型槽位。此模式下 AutoTest 使用 Planner + Executor 双 Agent 架构。两个槽位都需配置真实的 `name` / `api_key` / `base_url` / `provider`，否则 AutoTestAgent 启动时会报错。
- 鸿蒙真机或模拟器已连接，`npx --yes devecocli device list` 能看到设备
- `.hap` 文件（多模块应用：把 entry HAP + 应用内 HSP / feature HAP 一并传入；可以放进一个目录整目录传，也可以用逗号列出多个文件/目录，entry HAP 和 HSP 不在同一目录也行）

### 自动修复附加条件

- `app-metadata.json` 存在（round 1 解析阶段自动产出，内含 `project_root`）
- 建议项目在 Git 仓库中（不在也可以修复，但不会自动 commit；`commit_id: none`）

---

## 常见问题

**Q: 报 "No HarmonyOS device connected"？**

A: 检查设备/模拟器连接，跑 `npx --yes devecocli device list` 看看有没有设备 SN。真机请重新插拔 USB 并在设备上重新授权 USB 调试；模拟器请确认已启动。

**Q: 报 autotest 配置缺失 或 api_key 没填？**

A: 确认 `ht init` 已完成模型配置（导出 `HOMETRANS_MODEL_API_KEY` 等环境变量）。集成测试首次运行时会自动生成 `autotest.yaml`，如果环境变量缺失会提示先跑 `ht init`。

**Q: 启用 layered 模式后报模型配置不完整？**

A: layered 模式需要 `execute` 和 `decision` 两个模型槽位。编辑 `~/.hometrans/autotest.yaml`，在 `model:` 下添加：
```yaml
  execute:
    name: "gui-plus-2026-02-26"
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
    api_key: "sk-xxx"
    provider: "mai-ui"
  decision:
    name: "qwen3.7-plus"
    base_url: "https://dashscope.aliyuncs.com/compatible-mode/v1"
    api_key: "sk-xxx"
    provider: "openai"
```
并将 `agent.mode` 改为 `"layered"`。

**Q: 前置用例失败了要不要修代码？**

A: 不用。前置用例是环境准备脚本，失败意味着环境条件不满足（没素材、没权限、引导弹窗等），不影响应用的常规通过率。

**Q: PASS / FAIL / UNKNOWN 有什么区别？**

A: `PASS` — 通过；`FAIL` — 失败/超时/崩溃，有原因说明；`UNKNOWN` — 有报告但无法判定。后面两个都算未通过。

**Q: 测试跑多久？**

A: 每条用例大约 6 分钟（含 Agent 决策和执行时间）。整体超时上限 = 用例数 × 12 分钟（`--timeout auto` = N×720 秒，CLI 自动从用例数推导，含每条用例 10 分钟 CASE_TIMEOUT + 20% 余量），超过后自动终止并产出部分报告。

**Q: 怎么只重跑失败的用例？**

A: 目前每次都跑 `testcases.json` 的全部用例。你可以手动删掉已通过的条目，只保留失败的，然后重跑（round 2+ 会跳过解析阶段，直接读取 `<output-path>/testcases.json`）。

**Q: 产物在哪个目录？**

A: `testcases.json` 和 `app-metadata.json` 在 `output-path/` 根目录。测试和修复的每轮产物在 `output-path/round-1/`、`output-path/round-2/`... 子目录中。循环结束后，最终轮的关键文件（报告、HAP）会镜像到 `output-path/` 根目录，方便直接查看。

**Q: 怎么启用自动修复？**

A: 说"跑自测，自动修复"；或者跑完测试后看到有失败，Skill 会主动问你要不要启用，回复"是"就进去了。默认最多 3 轮，需要更多/更少可以加一句"最多 N 轮"（对应 `max-rounds` 参数）。

**Q: 自动修复会改我的代码吗？**

A: 会修改鸿蒙源码来修 bug，但只在**白盒审查确认是真问题**（`confirmed`）的情况下才会改。误报（`false_positive`）不改，测试用例不改。所有修改会 git commit，可随时 revert。如果不在 Git 仓库中，修改照常进行但不 commit。

**Q: 自动修复一轮多久？**

A: Fix（白盒分析 + 改代码）约 5-10 分钟，Build（编译）约 2-5 分钟，Retest（重新跑用例）约 N×12 分钟。可以随时中断。

**Q: 什么情况下循环会停？**

A: 正常退出 3 种：① 全部通过（`all_passed`）；② 所有失败都是误报，无代码缺陷需要修复（`no_confirmed_defects`）；③ 跑满 `max-rounds` 轮（默认 3）仍有失败（`max_rounds_reached`）。异常退出 3 种：编译未产出 HAP（`no_hap`）；`testcases.json` 为空（`no_testcases`）；自测 agent 早退，写出首行 `status: FAIL` 的 sentinel 报告（`agent_early_exit`，常见原因：设备没连、api_key 没填、batch 崩溃 / 超时）。异常退出不会进入 fix 循环——那些是环境问题，不是代码缺陷。

**Q: 需要提供 Android 项目路径吗？**

A: 不必须。提供了 fixer 会参考 Android 实现来修 bug，质量更高。不提供也能独立完成，基于白盒代码分析和 HarmonyOS API 文档修复。

**Q: 报 "Build did not produce a HAP"？**

A: 通常是编译失败或构建配置有误。检查 `hmos-fix-build-errors` 的编译输出，确认项目能正常构建后继续循环。
