Skip to content
Merged
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
64 changes: 64 additions & 0 deletions tests/manual/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# tests/manual/ — 真机回归手册

> mobile/ 模块依赖 UIAutomator2 直连真机,自动化测试以 Mock 为主(`tests/conftest.py`)。
> **本目录**承担纯单测覆盖不到的真机验证:UI hierarchy、Damai App 实际响应、抢票热路径性能。

## 目录结构

```
tests/manual/
├── README.md 本文件 — 入口与使用方法
├── runbook.md 标准回归流程(每次发版前跑)
├── android_compatibility_matrix.md 跨 Android 版本兼容性记录(持续更新)
├── templates/ 可复制模板
│ ├── _smoke_log.md 冒烟测试模板
│ ├── _nlp_5_show_log.md 5 演出 NLP 测试矩阵模板
│ ├── _multi_session_log.md 多场次测试矩阵模板
│ ├── _p2_scenarios_log.md P2 边界场景模板
│ └── _findings.md 发现新 issue 的归档模板
└── history/ 历史回归记录(按 YYYY-MM-DD 归档)
├── 2026-05-15-W2-v0.4.0-rc1.md NLP autodetect (#26) + price 越界守护 (#31)
├── 2026-05-15-findings.md W2 真机发现归档
├── 2026-05-22-W3-multi.md 多场次回归 (#25)
├── 2026-05-22-W3-p2.md P2 三连 (#28 #23 #24)
└── 2026-05-22-W3-summary.md W3 综合回归汇总
```

## 使用方法

### 一次完整回归(每次发版前)

1. 打开 [`runbook.md`](runbook.md),按 6 步流程逐步执行。
2. 每步从 [`templates/`](templates/) 复制对应模板到工作副本,填数据。
3. 完成后把工作副本 `git mv` 到 [`history/`](history/),命名 `YYYY-MM-DD-<release-or-tag>.md`。
4. 更新 [`android_compatibility_matrix.md`](android_compatibility_matrix.md) 一行。
5. 任何 ❌ 的问题在 [`templates/_findings.md`](templates/_findings.md) 复制 Finding 模板登记 → `gh issue create` 上报。

### 临时只跑一类场景

如:仅验证某个 P0 hotfix → 直接复制 `templates/_smoke_log.md` 跑冒烟即可,归档到 `history/<日期>-hotfix-<issue#>.md`。

## 触发条件(详见 runbook.md)

- 任一 P0 / P1 PR 合入
- 准备打 tag 发版
- 收到大麦 App 更新通知(高优先级,可能需要立即兼容性回归)

## SLA(详见 runbook.md)

- P0 PR:24h 内完成回归
- P1 PR:48h 内完成回归
- 其他:与 sprint 同步

## 保密红线

- 演出名脱敏(首字母 + 类型,如 "XX 演唱会"、"ZZ 音乐节")
- 不写真实观演人姓名 / 手机号 / 身份证号 / 订单号
- 截图、dump 文件、config 摘录均需脱敏后再纳入文档或 issue

## 关联文档

- [`reference/06-test-gap-analysis.md`](../../reference/06-test-gap-analysis.md):测试缺口与维护节奏
- [`reference/04-issues-matrix.md`](../../reference/04-issues-matrix.md):每个 issue 的真机验证状态
- [`reference/08-ops-runbook.md`](../../reference/08-ops-runbook.md):抢票现场与回滚流程
- [`mobile/scripts/benchmark_hot_path.sh`](../../mobile/scripts/benchmark_hot_path.sh):抢票热路径性能基准
33 changes: 33 additions & 0 deletions tests/manual/android_compatibility_matrix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Android × Damai App 兼容性矩阵

> 持续更新。每次发版前的真机回归完成后,把环境维度追加 / 更新一行。
> 用于:判断某次失败是否归因于已知组合;指导未来购买测试设备 / 选 emulator 镜像。

## 通过状态图例

- ✅:全部场景通过
- ⚠️:部分场景失败,但有 workaround 或不影响主流程(详见对应 history 记录)
- ❌:主流程失败(需 hotfix)
- 空:尚未测试

## 矩阵

| 测试日期 | Android | 真机/模拟器 | Damai App | HaTickets commit | 冒烟 | NLP | 多场次 | P2 | 发现的 issue | 历史记录 |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| _ | _ | _ | _ | _ | _ | _ | _ | _ | _ | _ |

<!-- 视场景调整:当真机/Android/Damai 版本组合首次出现时新增一行;同组合复测可在「历史记录」列追加多个 link 而不是新增行 -->

## 当前已知风险点

> 在某个组合上反复出现的、尚未在 reference/ 中归档的兼容性问题。
> 写法:`<Android 版本> + <Damai 版本>: <一句话症状> → <issue # 或 TBD>`。

- _

## 矩阵维护规则

- 每次回归后必更新本表(即使全部通过也要新增一行)
- "发现的 issue" 列填 `gh` 创建的 issue 编号;无则写 `none`
- "历史记录" 列引用 `history/<日期>-<标签>.md`
- 出现 ❌ 时,须在 [`reference/04-issues-matrix.md`](../../reference/04-issues-matrix.md) 同步登记
File renamed without changes.
75 changes: 75 additions & 0 deletions tests/manual/runbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# 真机回归 Runbook

> 每次发版前必跑。从模板复制 → 填数据 → 归档到 `history/`。

## 触发条件

- 任一 P0 / P1 PR 合入
- 准备打 tag 发版
- 收到大麦 App 更新通知

## 流程(每次回归 1-2h)

### 1. 冒烟(10 min)

复制 `templates/_smoke_log.md` → 填:

- 设备 / Android / Damai 版本
- `poetry install` 通过
- `mobile/scripts/start_ticket_grabbing.sh --probe --yes` 进到详情页

### 2. NLP 5 演出(30 min)

复制 `templates/_nlp_5_show_log.md` → 跑 5 个不同演出。覆盖:

- #26 NLP autodetect 4 类正常输入 + 1 类故意缺信息
- #31 price_index 越界守护 3 子场景
- 无价格卡片 / 缺货 dump

### 3. 多场次(20 min)

复制 `templates/_multi_session_log.md` → 跑 ≥2 个多场次活动。覆盖:

- #25 多日同城 / 多日多城 / 单日多场
- `rush_mode` alias 兼容
- `rush_skip_session` 单场次/多场次行为

### 4. P2 边界(30 min)

复制 `templates/_p2_scenarios_log.md` → 跑三类场景:

- #28 `wait_for_home_ready` 超时与 dump
- #23 `select_search_result` 0/1/N 分流(含 strict 模式)
- #24 `PageProbe` unknown_threshold 阈值与 `force_state`

### 5. 性能基准(10 min)

```bash
bash mobile/scripts/benchmark_hot_path.sh --runs 5
```

把中位数填到本次冒烟日志最末「性能数据」段。

### 6. 归档

- 把填好的文件 `git mv` 到 `history/<日期>-<标签>.md`,命名形如 `2026-MM-DD-<release-or-tag>.md`、`2026-MM-DD-findings.md`、`2026-MM-DD-summary.md`。
- 更新 `android_compatibility_matrix.md` 一行:本次设备 / Android / Damai 版本 + 通过/失败摘要。
- 把 findings 中的新 issue 用 `gh issue create` 上报,并在对应 PR 描述中回链。

## SLA

- P0 PR:24h 内完成回归
- P1 PR:48h 内完成回归
- 其他:与 sprint 同步

## 不做的事

- ❌ 不在模板 / 历史日志中含真实演出名 / 真实姓名 / 手机号 / 身份证号 / 订单号
- ❌ 不删除已有 history(只 `git mv` 重组)
- ❌ 不下调 80% 覆盖率门槛来"绕过"失败(应在本 PR 增补单测)

## 关联

- `reference/06-test-gap-analysis.md`:测试维护节奏
- `reference/05-fix-plans/`:每个 issue 的修复计划与回归点
- `reference/08-ops-runbook.md`:抢票现场与回滚流程
90 changes: 90 additions & 0 deletions tests/manual/templates/_findings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# 真机回归 Findings 归档(_findings.md)

<!-- 复制时删除以下 [TODO] 标记 -->

> **用途**: 记录本次真机回归(冒烟 / NLP / 多场次 / P2 边界)中发现的、不在预期内的失败、回归或可用性问题。
> **流程**: 每发现一个新问题 →
> 1. 在本文件追加一条「Finding 模板」并填写。
> 2. 用 `gh issue create` 上报(含 OS / Android / Damai App 版本 / 脱敏 config / 错误日志摘要)。
> 3. 在对应 PR 描述中引用新 issue 编号。
> **保密要求**: 截图、日志、config 必须脱敏(演出名首字母 + 类型,无姓名 / 手机号 / 身份证号 / 订单号)。

---

## 已发现 Findings

> [TODO] 暂无;qa 真机执行后追加。

---

## Finding 模板(复制后填写)

```markdown
### Finding YYYY-MM-DD-NN:<一句话标题>

- **发现时间**: YYYY-MM-DD HH:MM (Asia/Shanghai)
- **执行人 ID(脱敏)**: qa-XX
- **真机型号 / Android**: _
- **Damai App 版本**: _
- **仓库 commit**: `git rev-parse --short HEAD` 输出
- **触发场景**: (冒烟 Task A/B / NLP Task A #N / NLP Task B-N / 多场次 #N / P2 #28-N / P2 #23-N / P2 #24-N / 其它)
- **复现步骤**:
1. _
2. _
3. _
- **预期**: _
- **实际**: _
- **关键日志(脱敏)**:
```
<stderr / 关键 stack trace>
```
- **附件**:
- tmp/price_dump_*.xml(路径,已脱敏)
- tmp/home_probe_*.xml / search_probe_*.xml / page_probe_unknown_*.xml(路径)
- tmp/failure_*.zip(W4-02 自动留档;路径)
- 截图链接(脱敏后存放位置)
- **严重度**: P0 / P1 / P2 / P3
- **建议归类**: bug / docs / UX / 第三方变更
- **关联 issue**: #N(gh issue create 创建后回填)
- **关联 PR**: #N(修复 PR,事后回填)
```

---

## gh issue 上报示例(便于 qa 复用)

```bash
gh issue create \
--title "[真机回归] <一句话现象>" \
--label "bug,real-machine" \
--body "$(cat <<'EOF'
## 现象
<一句话描述>

## 复现步骤
1. ...
2. ...

## 环境
- 真机 / Android: _
- Damai App: _
- HaTickets commit: _
- config 关键字段(脱敏): price_index=__, keyword=__

## 关键日志
<脱敏后的 stderr / stack trace>

## 关联
- 真机日志: tests/manual/history/<本次回归文件名>
- 详细记录: tests/manual/history/<本次 findings 文件名> (Finding __)
EOF
)"
```

---

## 归档完成判定

- [ ] 本文件每条 Finding 都已建对应 GitHub issue(除非确认为操作失误)
- [ ] 每条 Finding 都标注严重度
- [ ] 截图与 dump 路径不含真实演出名 / 姓名 / 手机号 / 身份证号 / 订单号
76 changes: 76 additions & 0 deletions tests/manual/templates/_multi_session_log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 多场次回归矩阵(_multi_session_log.md)

<!-- 复制时删除以下 [TODO] 标记 -->

> **范围**: 多场次场景下场次选择 (#25) 的真机回归。
> **预算**: 20 min。
> **保密要求**: 演出名脱敏(首字母 + 类型),不写真实观演人姓名 / 手机号 / 身份证号 / 订单号。
> **上游模板**: 抽自 `history/2026-05-22-W3-multi.md`。
> **关联实现**: `mobile/event_navigator.py:select_session`。

---

## 测试环境

| 项 | 值(待 qa 填写) |
| --- | --- |
| 真机型号 / Android 版本 | [TODO] _ |
| Damai App 版本 | [TODO] _ |
| 仓库 commit (`git rev-parse --short HEAD`) | [TODO] _ |
| 操作者(脱敏 ID,如 qa-01) | [TODO] _ |
| 执行日期(YYYY-MM-DD) | [TODO] _ |

---

## Task A:多场次 5 场景回归矩阵

> **填表说明**
> - `命中场次`:从 `mobile/scripts/start_ticket_grabbing.sh --probe --yes` 的日志中读取 `select_session: ... 命中 idx=K/N` 字段,记 `idx (K/N) - <场次描述>`,如 `0 (0/3) - 04.05 上海`。
> - `通过` 列写 ✅/❌;❌ 行须在 `_findings.md` 开 issue 并回链。
> - `# 4` 与 `# 5` 不需要真机活动,可在任意已 reach `sku_page` 的演出上验证;重点验证配置项的兼容/语义。
> <!-- 视场景调整:若 W4 后续引入新别名,把 alias 行扩展为 4-N -->

| # | 演出名(脱敏) | 类型 | date 配置 | city 配置 | 命中场次 | 通过 | 备注 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 1 | [TODO] 巡演 A 上海站 | 多日同城(同一城市多个日期) | [TODO] `04.05` | [TODO] `上海` | [TODO] _ | [TODO] _ | 期望唯一命中 04.05 |
| 2 | [TODO] 巡演 B 全国 | 多日多城(多个城市各一日期) | [TODO] `04.10` | [TODO] `北京` | [TODO] _ | [TODO] _ | 期望命中 北京 04.10;只填 date 应 ambiguous → 必须配合 city |
| 3 | [TODO] 音乐节 C 单日 | 多艺人单日(单日多场次卡片) | [TODO] `05.01` | [TODO] `上海` | [TODO] _ | [TODO] _ | 单日内若 SESSION_PICKER 不弹出(只有 1 场次),日志应说明 "skipped session selection";fallback_index 失效 |
| 4 | [TODO] rush_mode alias 兼容 | _ | _ | _ | _ | [TODO] _ | `config` 中 `rush_mode: true` 启动期应自动展开为三个子开关默认值,且日志显示 `rush_skip_session=false / rush_skip_price_dump=true / rush_aggressive_retry=true` |
| 5 | [TODO] rush_skip_session=true(单场次场景) | _ | _ | _ | _ | [TODO] _ | 单场次演出 + `rush_skip_session: true`:跳过场次选择直接 sku_page;多场次演出 + `rush_skip_session: true`:日志应有 warning 或 fail-fast |

---

## Task B:执行步骤(人工 qa 真机现场操作)

```bash
cd /Users/andrew/Documents/GitHub/HaTickets
git checkout master && git pull

# 准备 config.local.jsonc,填入对应行的 keyword / date / city
# 然后逐场景执行:
bash mobile/scripts/start_ticket_grabbing.sh --probe --yes 2>&1 | tee tmp/multi_session_case_$N.log
# 把 select_session 命中行复制到 Task A 表格对应行

# 场景 4 验证:直接构造 alias 配置
# config.local.jsonc:
# {"rush_mode": true, ...}
# 启动后日志应有:rush_mode alias → rush_skip_session=false, rush_skip_price_dump=true, rush_aggressive_retry=true

# 场景 5:手动构造 rush_skip_session=true 单场次/多场次 各一次
```

---

## Task C:发现问题汇总

> 任一行 ❌ 都要在此区列出 issue 链接 + 一句话症状。
> 若已知症状但暂无 issue,先标记 "TBD" 并在 `_findings.md` 中提请创建。

- [TODO] _

---

## 归档

- 完成填写后,将本文件 `git mv` 到 `history/<日期>-<标签>.md`。
- 若 5 场景全部 ✅,在 #25 issue 下评论:「真机回归全部通过 — log: history/<新文件名>@<commit>」。
Loading
Loading