docs(meeting): Task 16 E2E 剧本落盘 + .gitignore 精细化

Playwright MCP 4 场景剧本(.playwright-mcp/task16/):
- README.md:总览 + 账号/环境/资源断言指南
- scenarios/01-create-and-join-by-code.md:创建→会议号加入→媒体打通→结束,含 Redis/MySQL/media-server 三维资源断言
- scenarios/02-invite-link-with-password.md:邀请链接带密码 + P0-4 "密码不走 URL query" 回归
- scenarios/03-notify-quickjoin.md:通知中心「立即加入」跳转 + 过期卡片降级
- scenarios/04-host-toolkit-and-transfer.md:主持人四件套 + 宽限期重连/到期自动转让(P1-1 原子性 + P1-7 处理锁验证)
- scenarios/05-reopen-5times-resource-cleanup.md:反复开关 5 次 + 前端 heap 快照/ Redis 累积对比/ media-server 观测端点对比,验证 Step A(f5ae095)资源清理修复

手工回归点(manual-regression.md):17 项 MCP 难以稳定覆盖的交互场景
(HTTPS 自签 / 跨网段 iOS / 网络抖动 / 物理返回 / 跨 tab 并发 / 摄像头抢占 等)

.gitignore 调整:
- 放行 .playwright-mcp/task16/ 下的 Markdown 剧本文档进入版本控制
- 仍忽略运行时产物(*.log/*.png/*.yml/截图)
- task15/ 保持未跟踪状态,避免历史截图噪声入库

Made-with: Cursor
This commit is contained in:
bujinyuan
2026-04-23 17:21:42 +08:00
parent f5ae095033
commit 5ed14c2aac
8 changed files with 737 additions and 1 deletions

View File

@@ -0,0 +1,139 @@
# 场景 04主持人四件套 + 宽限期自动转让
## 目标
- **验证 Task 15 主持人四件套**:请他静音、请他开麦、转让主持人、踢出会议
- **验证 Task 8 host 宽限期状态机**P1-1 原子转让 + P1-7 处理锁):
- host 主动断开 WS → 宽限期内重连 → 撤销转让
- host 宽限期到期 → 自动转让给最早加入的参会人
- 覆盖设计 §6.5生命周期状态机、§6.6(权限矩阵)
## 前置条件
依 [`../README.md`](../README.md) 通用前置;**需 3 个账号 Alice/Bob/Carol 同时入会**。
## MCP 执行脚本 A主持人四件套
### 步骤 1三人入会
1. Alice 创建即时会议 → `$ROOM_CODE`
2. Bob 用会议号加入
3. Carol 用会议号加入
4. 截图 → `01-three-joined.png`
### 步骤 2Alice 请他静音 Bob
1. Alice tab 打开成员面板 → Bob 右侧 「⋯」 菜单 → 「请他静音」
2. Bob tab 期望:
- Toast「主持人已将你的麦克风关闭」
- `localAudioEnabled=false`
- 成员面板自己的 AV 图标变灰
3. Alice tab 期望Bob 行 AV 图标变灰
4. 截图 → `02-bob-muted-by-host.png`
### 步骤 3Alice 请他开麦 Bob
1. Alice tab → Bob 「⋯」菜单 → 「请他开麦」
2. Bob tab 期望:
- 弹窗请求确认「主持人请你开启麦克风,是否同意?」
- 点「同意」后 `localAudioEnabled=true`
3. 截图 → `03-bob-ask-unmute.png`
### 步骤 4Alice 踢出 Carol
1. Alice tab → Carol 「⋯」菜单 → 「踢出会议」
2. 弹窗确认「踢出 Carol」→ 确认
3. Carol tab 期望:
- 收到 `meeting.member.kicked` 广播
- Toast「主持人已将你移出会议」
- 跳回会议首页
4. Alice / Bob tab 期望Carol 行从成员列表消失
5. 截图 → `04-carol-kicked.png`
### 步骤 5Alice 转让主持人给 Bob
1. Alice tab → Bob 「⋯」菜单 → 「转让主持人」
2. 弹窗确认 → 确认
3. **原子性验证**P1-1
```bash
mysql -e "SELECT host_id, (SELECT role FROM meeting_participants WHERE room_id=r.id AND user_id=55) AS alice_role, \
(SELECT role FROM meeting_participants WHERE room_id=r.id AND user_id=56) AS bob_role \
FROM meeting_rooms r WHERE room_code='$ROOM_CODE'" echochat
```
期望同时满足:`host_id=56` + `alice_role=participant` + `bob_role=host`(同一事务原子更新)
4. Alice tab / Bob tab 工具栏切换Alice 看到"结束会议"按钮消失Bob 看到它出现)
5. 截图 → `05-host-transferred.png`
## MCP 执行脚本 B宽限期自动转让
### 前置:接续场景 A 的会议;此时 Bob 是主持人Alice 是参会人
### 步骤 6Bob 断开 WS模拟网络断开
1. Bob tab DevTools → Network 面板 → Throttling 切换到 **Offline**
2. 等待 WS 心跳超时(~15sBob 客户端 `_onWsDisconnected` 触发
3. 期望 Redis 立即写入 `echo:meeting:host_grace:$ROOM_CODE`
```bash
redis-cli GET "echo:meeting:host_grace:$ROOM_CODE"
# 期望JSON {host_id:56, started_at:..., grace_until:...}
redis-cli TTL "echo:meeting:host_grace:$ROOM_CODE"
# 期望:约 120 秒(配置 `meeting.host_grace_seconds`
```
4. Alice tab 期望:工具栏显示「主持人掉线中(宽限期 2:00」倒计时若 UI 已实现)
### 步骤 7a宽限期内重连撤销转让
1. 15 秒后 Bob tab Network 恢复 Online
2. 期望 `_onWsReconnected` 触发,执行 `OnHostReconnect`
```bash
redis-cli EXISTS "echo:meeting:host_grace:$ROOM_CODE"
# 期望0key 已 DEL
```
3. Alice tab 工具栏倒计时消失
4. 查日志:
```bash
tail -50 backend/go-service/logs/*.log | grep "host 宽限期内重连"
# 期望:`host 宽限期内重连,撤销转让计划`
```
### 步骤 7b宽限期到期自动转让
**若步骤 7a 已执行,请先让 Bob 重新 Offline 并等待整个 120s 周期完成。**
1. Bob tab Offline保持 120+s
2. 期望本地 timer 到期触发 `HandleHostGraceExpired`
- Alice tab 收到 `meeting.host.changed` 广播,`auto_reason='host_grace_expired'`
- 工具栏/成员面板 Alice 头像变为主持人标识
- `meeting_rooms.host_id=55` + `meeting_participants.role` 原子变更
3. **处理锁验证P1-7**
```bash
# 宽限期到期瞬间抓一下处理锁
redis-cli GET "echo:meeting:host_grace_handling:$ROOM_CODE"
# 期望:`1`SETNX 成功 60s TTL60s 后自然过期
```
4. 截图 → `06-host-auto-transferred.png`
### 步骤 8清理
Alice现任主持人结束会议走场景 01 的资源清理断言。
## 预期结果汇总
| 断言 | 预期 |
|---|---|
| 请他静音 | Bob `localAudioEnabled=false` + toast |
| 请他开麦 | Bob 弹窗确认后 `localAudioEnabled=true` |
| 踢出 | Carol 跳首页 + 成员列表移除 |
| 主动转让原子性 | `host_id` + 两行 `role` 同事务更新 |
| host 宽限期 Redis | key 写入 + TTL≈120s |
| 宽限内重连 | key DEL + 本地 timer cancel |
| 宽限到期自动转让 | 最早加入者成为新 host + 广播 `host.changed` |
| 处理锁 SETNX | `host_grace_handling:*` 存在 60s |
## 失败排查
| 现象 | 定位 |
|---|---|
| 转让后 `host_id` 和 `role` 不一致 | P1-1 回归:检查 `TransferHost` 是否包在单事务 |
| 宽限到期但 host 不变 | 看 handling 锁是否被"自然过期 + DEL=0"误拦P1-7 应已修) |
| 多节点部署下双重转让 | SETNX 处理锁应保证单节点唯一处理 |