Files
EchoChat/.playwright-mcp/task16/scenarios/01-create-and-join-by-code.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

148 lines
6.6 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.

# 场景 01创建会议 → 会议号加入 → 双向音视频打通 → 主持人结束
## 目标
- 验证 `POST /api/v1/meeting/rooms`(创建)+ `POST /api/v1/meeting/rooms/:code/join`(加入)+ mediasoup 信令全链路打通
- 覆盖设计 §6.1REST 建房、§6.3WS 信令 11 事件、§6.6NodeClient 媒体编排)
- 配套断言 Step A 资源清理:结束会议后 Redis / MySQL / media-server 全部归零
## 前置条件
依 [`../README.md`](../README.md) 通用前置;额外确认:
- 两个浏览器上下文MCP 多 tab分别以 `task15_a` / `task15_b` 登录
- Alice tab 为主持人Bob tab 为参会人
## MCP 执行脚本
### 步骤 1Alice 登录并创建即时会议
1. `browser_navigate``https://localhost:5173/#/pages/auth/login`
2. `browser_fill_form` → 账号 `task15_a` / 密码 `Task15@2026` → 点 "登录"
3. 断言首页进入「消息」tab
4. 底部切换到「会议」tab`pages/meeting/index`
5. 点「创建即时会议」
6. 设备预览页:默认勾选麦克风 + 摄像头;点「立即进入」
7. **进入 `pages/meeting/room`**,截图 → `01-alice-created.png`
8. 读取 URL query 得到 `roomCode`,记录到上下文变量 `$ROOM_CODE`
### 步骤 2Bob 通过会议号加入
1. 开第二个 browser context`browser_navigate``https://localhost:5173/#/pages/auth/login`
2.`task15_b` 登录
3. 切到「会议」tab点「通过会议号加入」
4. 输入 `$ROOM_CODE`
5. 预览页 → 「立即加入」
6. 截图 → `02-bob-joined.png`
### 步骤 3双向音视频打通断言
- Alice tab 成员面板应展示 2 个成员Bob 的 AV 图标为蓝色(开启状态)
- Bob tab 成员面板同理
- Alice 的视频宫格应能看到 Bob 的视频流(`VideoTile[user_id=56] video.srcObject` 非空)
- 控制台验证 mediasoup 关键日志:
- `[MediaEngine] producer 创建成功 kind=audio`
- `[MediaEngine] producer 创建成功 kind=video`
- `[MediaEngine] consumer 创建成功`
- `browser_evaluate`:
```js
const store = window.__pinia?.state?.value?.meeting
return {
participantCount: store.participants.length,
localAudio: store.localAudioEnabled,
localVideo: store.localVideoEnabled,
remoteUserCount: Object.keys(store.remoteConsumers).length,
}
```
期望:`participantCount=2`、`localAudio=true`、`localVideo=true`、`remoteUserCount=1`
### 步骤 4资源创建态断言
在第三个终端执行:
```bash
# media-server 应有 1 Router + 4 Transport2 users × send/recv+ 4 Producer + 4 Consumer
curl -s -H "X-Internal-Token: $INTERNAL_TOKEN" http://localhost:3300/internal/info | jq .stats
# Redis 应有两个用户的资源追踪 Set
redis-cli KEYS "echo:meeting:resource:$ROOM_CODE:*"
# 期望输出echo:meeting:resource:xxx:55 / echo:meeting:resource:xxx:56
# 每个 SMEMBERS 应有 5 条transport×2 + producer×2 + consumer×2 之类,具体取决于 producer.new 收敛顺序)
redis-cli SMEMBERS "echo:meeting:resource:$ROOM_CODE:55"
redis-cli SMEMBERS "echo:meeting:resource:$ROOM_CODE:56"
```
### 步骤 5Alice 结束会议
1. Alice tab 工具栏 → 「结束会议」→ 弹窗确认「主持人结束」
2. 截图 → `03-alice-ended.png`(页面转为「会议已结束」提示页)
3. Bob tab 应收到 WS `meeting.room.ended` 广播展示「会议已结束」toast1.2s 后跳回会议首页
4. 截图 → `04-bob-received-ended.png`
### 步骤 6资源释放断言核心验证 Step A
```bash
# 1. Redis 清理验证P1-4 + Step A B1/B3 的核心断言)
redis-cli KEYS "echo:meeting:resource:$ROOM_CODE:*" # 期望:(empty array)
redis-cli KEYS "echo:meeting:member_state:$ROOM_CODE:*" # 期望:(empty array)
redis-cli KEYS "echo:meeting:host_grace:$ROOM_CODE" # 期望:(nil)
redis-cli KEYS "echo:meeting:empty_ttl:$ROOM_CODE" # 期望:(nil)
# 2. MySQL 状态验证
mysql -e "SELECT status, ended_reason FROM meeting_rooms WHERE room_code='$ROOM_CODE'" echochat
# 期望status=ended, ended_reason=host_ended
mysql -e "SELECT user_id, left_at, left_reason FROM meeting_participants WHERE room_id=(SELECT id FROM meeting_rooms WHERE room_code='$ROOM_CODE')" echochat
# 期望Alice + Bob 都有 left_at ≠ NULL, left_reason='host_end'
# 3. media-server Router / Transport / Producer / Consumer 全部归零
curl -s -H "X-Internal-Token: $INTERNAL_TOKEN" http://localhost:3300/internal/info | jq '.stats | .routers, .transports, .producers, .consumers'
# 期望:对应 $ROOM_CODE 的 Router 已销毁;整体 stats 可能非 0其他并行测试但本场 Router 必须不存在
```
### 步骤 7前端资源释放断言
在 Alice tab 执行 `browser_evaluate`:
```js
// 结束页停留时的 state 快照
const store = window.__pinia?.state?.value?.meeting
return {
localState: store.localState, // 期望:'ended'
engineExists: !!window.__engine, // 期望false_cleanupMedia 已置空)
participantCount: store.participants.length, // 结束页保留渲染数据,非 0
}
```
然后手动返回首页(模拟 `onUnload` 触发):
1. `browser_navigate_back` 或点结束页的「返回首页」按钮
2. 再次 `browser_evaluate` 断言 pinia state
```js
const store = window.__pinia?.state?.value?.meeting
return {
localState: store.localState, // 期望:'idle'
participantCount: store.participants.length, // 期望0
currentRoom: store.currentRoom, // 期望null
}
```
## 预期结果汇总
| 断言维度 | 预期 |
|---|---|
| WS 信令 | `room.join` / `producer.new` / `consume` 全链路无异常 |
| 双向媒体 | 两端视频宫格均能播放对方画面,`localAudioEnabled=true` 且 `remoteConsumers` 含对方 |
| Redis 清理 | `resource:*` / `member_state:*` / `host_grace` / `empty_ttl` 全空 |
| DB 一致 | `meeting_rooms.status=ended`、两行 `meeting_participants.left_at` 非空 |
| media-server | 对应 Router 已删除level transport/producer/consumer 观察端点无残留 |
| 前端 | `_engine=null` / `_pendingBroadcastTimers.size=0``onUnload` 后 pinia state 全部 reset |
## 失败排查
| 现象 | 可能原因 | 排查点 |
|---|---|---|
| Bob 看不到 Alice 的画面 | `pushExistingRoomState` 同步性Task 16 P1-8 修复) | `logs.Info` 搜 `room.join` 是否先于 `member.joined` |
| 结束后 Redis `resource:*` 仍存 | `EndRoom` 未走 `cleanupRoomRedisResidual`Step A B1 回归) | `grep "cleanupRoomRedisResidual" backend/go-service/app/meeting/service/meeting_service.go` |
| `localState` 卡在 `connected` | `_onRoomEnded` 未触发 | 检查 Bob tab 是否收到 `meeting.room.ended` WS 事件(网络面板 frame |