Files
EchoChat/.playwright-mcp/task16/scenarios/04-host-toolkit-and-transfer.md
bujinyuan 5ed14c2aac 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
2026-04-23 17:21:42 +08:00

140 lines
5.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 场景 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 处理锁应保证单节点唯一处理 |