# 帆幕(Fanmu)产品全流程链路测试报告 - **测试日期**:2026-09-26 - **测试方式**:真实浏览器(Playwright)逐流程操作 + 接口层复现取证 - **测试环境**: - 主测实例:`http://localhost:4321`(Docker 容器,生产构建 `NODE_ENV=production`) - 对照实例:`http://127.0.0.1:4327`(本地 `astro dev`,用于定位根因) - 片源夹具:临时本地 WebDAV(`tmp-e2e-dav/`,2 个 webm 样例视频),测试后已停止 - **测试账号**:`e2e_tester`、`e2e_mail`(主实例)、`dev2`、`dev3`(dev 实例,密码已重置为 `Devpass456`) - **注意**:主实例中 `e2e_tester` 的挂载已在「删除挂载」测试中删除;媒体库残留 1 条刮削条目(见问题 P2-5) --- ## 一、测试结论摘要 **主链路可以走通**:注册登录 → 挂载添加/测试连接 → 目录浏览 → 扫描入库 → 媒体库(列表/海报墙/匹配刮削)→ 播放(流代理、进度保存)→ 弹幕(导入、渲染、开关/密度/透明度)→ 登出/再登录回跳,均实测通过,且鉴权回跳、多用户隔离、i18n 切换等细节闭环做得不错。 **但存在 3 处闭环断裂(P1)**: 1. 登录后访问 `/dashboard` 必现 500(用户菜单「仪表盘」是死链); 2. **生产模式下找回密码流程无法完成**(无令牌、无邮件通道); 3. **播放页导入的弹幕刷新即丢**(服务端只存了字节数,不存内容)。 另有删除挂载无确认/留孤儿数据、校验错误暴露原始 JSON 等一批 P2 可用性问题。 **第二轮(播放能力专项,见第七章)**:流代理 Range/206/416 全部规范;进度上报与续播精确(首页/媒体库双入口带百分比);多集切换与中间集结束引导完整;弹幕时间轴联动正常。新增发现:**孤儿集条目混入剧集列表、点中即黑屏且无错误提示(P2 升级)**、`/api/stream` 错误码语义问题,以及快捷键 f/m 不可用、末集无结束引导等 P3 项。 --- ## 二、全流程闭环矩阵 | # | 流程 | 实测结果 | 判定 | |---|------|---------|------| | 1 | 首页 → 各导航入口 | 5 个主入口全部可达,编号导航清晰 | ✅ 闭环 | | 2 | 游客访问受保护页(/library 等) | 302 → `/auth/login?next=…`,登录后回跳原页(实测回跳 `/mounts` 成功) | ✅ 闭环 | | 3 | 错误凭据登录 | toast「用户名或密码不正确」 | ✅ 闭环 | | 4 | 注册(弱密码 123) | 浏览器原生校验拦截(min=8),但表单无前置规则提示 | ⚠️ 可用 | | 5 | 注册(合法) | 成功 → 首页 → 顶栏显示用户名,自动登录 | ✅ 闭环 | | 6 | 挂载:非法地址保存 | 400 返回,但 toast 显示**原始 JSON 数组** | ❌ 不友好 | | 7 | 挂载:空名称点保存 | 无任何提示,仅静默聚焦到名称字段 | ⚠️ 隐晦 | | 8 | 挂载:保存成功 | 列表即时刷新、计数+1,但**无成功 toast** | ⚠️ 反馈弱 | | 9 | 挂载:测试连接(坏地址/好地址) | 内联显示「连接失败 / 连接成功」,失败无原因说明 | ⚠️ 可用 | | 10 | 挂载:编辑(含密码留空语义) | 进入编辑态、卡片标「编辑中」、密码占位「留空保持不变」 | ✅ 闭环 | | 11 | 文件浏览:目录/面包屑/上一级/根目录 | 全部正常,中文目录名处理正确 | ✅ 闭环 | | 12 | 文件浏览:目录读取失败 | 显示「目录读取失败」,无重试按钮、无具体原因 | ⚠️ 弱 | | 13 | 扫描入库 | 「扫描完成:2 个文件」+ CTA「去媒体库整理」 | ✅ 闭环(优秀) | | 14 | 媒体库:列表视图 | 已扫条目齐全,带「播放」「匹配」操作 | ✅ 闭环 | | 15 | 媒体库:海报墙(未刮削时) | 空白 + 文案;不提示切列表视图;**搜索无结果复用同一文案** | ⚠️ 误导 | | 16 | 匹配刮削(Bangumi 搜索→绑定) | 搜索结果正常,绑定后改名、入海报墙分组、匹配按钮消失 | ✅ 闭环(优秀) | | 17 | 播放(流代理 + Range) | 正常播放,时长正确,进度到 00:04/00:04 | ✅ 闭环 | | 18 | 播放进度持久化 | 服务端 `positionMs=4000` 每 5s/暂停/离开时上报;看完整片从头播(合理) | ✅ 闭环 | | 19 | 弹幕导入 XML | 「弹幕 5 条」+ 逐条渲染上屏 | ⚠️ **刷新即丢**(P1) | | 20 | 弹幕设置(开关/密度/透明度) | 保存 toast「已保存(本地会话)」,播放页即时生效 | ✅ 闭环(本地) | | 21 | 修改密码(错误/正确当前密码) | 「当前密码不正确」/「密码已更新」,当前会话保留 | ✅ 闭环 | | 22 | 登出 | 顶栏恢复 登录/注册,数据清空 | ✅ 闭环 | | 23 | 找回密码(不存在邮箱) | 「若邮箱存在,重置申请已受理」(防枚举正确) | ✅ 闭环 | | 24 | 找回密码(存在邮箱,**生产实例**) | 响应 `{}`:无令牌、无邮件通道 → **流程无法完成** | ❌ **断裂** | | 25 | 找回密码(dev 实例) | 令牌 +「使用此令牌重置」→ 重置页令牌预填 → 新密码登录成功 | ✅ 闭环(仅 dev) | | 26 | 删除挂载 | **无二次确认、无成功提示,直接删除** | ❌ 不安全 | | 27 | 删除挂载后播放旧条目 | 流 502,播放器只显示「重新连接: 5」,无业务解释 | ❌ 孤儿数据 | | 28 | 用户菜单「仪表盘」 | `/zh-CN/dashboard` **500**(dev:ResponseSentError) | ❌ **断裂** | | 29 | 404 页 | 正确 404 状态码 + 说明 + 返回首页 | ✅ 闭环 | | 30 | 中英文切换 | cookie 写入、路径保留、英文界面翻译完整 | ✅ 闭环 | | 31 | 多用户数据隔离 | `e2e_mail` 看不到 `e2e_tester` 的挂载 | ✅ 闭环 | | 32 | 媒体库搜索 | 命中正常;**无结果时文案误导**(见 P3-11) | ⚠️ 弱 | | 33 | 无挂载空态(浏览页) | 禁用下拉 + 「添加挂载」CTA 跳转正确 | ✅ 闭环(优秀) | --- ## 三、问题清单 ### P1 — 功能闭环断裂(必须修复) **P1-1 登录后访问仪表盘必现 500(用户菜单死链)** - 复现:登录任意账号 → 顶栏头像菜单 →「仪表盘」→ 页面显示 Internal server error。 - 根因(dev 实例报错页证实):`app/web/src/views/dashboard.astro` 在流式渲染中执行 `return Astro.redirect(…)`,抛出 `ResponseSentError: The response has already been sent`(`astro/render.js:158`)。 - 影响:菜单常驻入口是死链;现有 e2e 未覆盖登录后 dashboard,故未被发现。 - 建议:把重定向移到 `pages/[locale]/dashboard.astro`(页面级、渲染前)或中间件层;或干脆让 `/dashboard` 直接渲染媒体库内容。并补一条登录态 e2e。 **P1-2 生产模式下「找回密码」无法完成** - 复现(生产实例):注册带邮箱账号 → 申请重置 → 接口 200 但响应 `{}`(`auth.service.ts` 中 `if (IS_PROD) return {}`),页面无令牌、后端无邮件发送实现 → 用户拿不到重置令牌,流程终止。 - dev 实例有「开发环境令牌(生产请接入邮件发送)」兜底,生产没有任何兜底。 - 建议:接入邮件发送(SMTP/API),或至少在生产日志/管理端暴露令牌并给出明确的「请联系管理员」降级提示;上线前把该流程列入验收。 **P1-3 播放页导入弹幕刷新即丢** - 复现:播放页「导入本地 XML」→ 显示「弹幕 5 条」并渲染 → 刷新/离开页面 → 变回「暂无在线弹幕」。 - 根因:`danmakuService.importMeta` 只校验 `byteSize` 后返回 `{ok:true}`,**XML 内容不落库也不落 localStorage**;内容仅存在 `WatchBody` 组件 state。 - 影响:核心卖点「弹幕观影」的导入闭环断裂,且用户被成功 toast 误导认为已保存。 - 建议:将 XML 存入服务端(按 `mediaItemId` 持久化,或复用 `danmakuCacheDao`),或至少写 localStorage;并把 `danmakuSaveSettings/GetSettings` 的服务端桩实现补真。 ### P2 — 可用性与数据安全(应当修复) **P2-4 删除挂载无确认、无成功反馈** - 点「删除」立即生效:无确认对话框、无撤销、无 toast,列表直接清空。挂载配置录入成本高,误触即丢。 - 建议:加确认(名称确认或二次弹窗)+ 删除成功 toast。 **P2-5 删除挂载产生孤儿媒体条目** - 实测:删除挂载后媒体库海报/剧集仍在,点播放 → `/api/stream` 502,播放器仅显示「重新连接: 5」重试耗尽,无「片源已失效/挂载已删除」说明,也无处理入口(删除/重新挂载后重扫)。 - 建议:删除挂载时提示影响范围(N 个条目将失效)并可选级联清理;条目侧检测挂载缺失时给出业务级错误页(带「返回媒体库」)。 **P2-6 校验错误暴露原始 JSON** - 实测 toast 内容:`[ { "code": "invalid_format", "format": "url", "path": ["baseUrl"], "message": "需要合法 URL" } ]`。 - 建议:客户端统一提取 zod 错误的 `message`(或按字段渲染行内错误),不要把序列化结果直接展示。 **P2-7 非 JSON 错误响应未兜底** - 实测曾出现一次:`/api/trpc/media.mountCreate` 返回 500 HTML 错误页 → toast 显示 `Unexpected token '<', " 环境:重建 WebDAV 夹具(新增 6s 的 EP02 用于多集测试),重建挂载并重新扫描;接口层用 curl 带会话 Cookie 直测 `/api/stream`;播放器行为用浏览器自动化逐项实测。 ### 7.1 流代理 `/api/stream`(接口层) | 场景 | 结果 | 判定 | |------|------|------| | 无 Range GET | 200,`Accept-Ranges: bytes`,`video/webm`,长度正确 | ✅ | | `Range: bytes=0-99` | 206,`Content-Range: bytes 0-99/112733`,实际 100 字节 | ✅ | | `Range: bytes=100-199`(seek 分段) | 206,精确 100 字节 | ✅ | | `Range: bytes=-50`(尾部) | 206,`bytes 112683-112732/112733` | ✅ | | 越界 Range | 416 + `Content-Range: bytes */total`(规范) | ✅ | | 分段内容一致性 | 前 100 字节与源文件 `cmp` 一致 | ✅ | | 并发 5 路 Range | 全部 206 | ✅ | | 无会话 Cookie | 401 | ✅ | | 不存在的 mediaId | **502** + 正文「媒体或挂载不存在」 | ⚠️ 应为 404 | | 挂载已删的孤儿条目 | **502** + 同上文案 | ⚠️ 条目明明在列表中,文案自相矛盾 | ### 7.2 播放器交互 | 能力 | 实测 | 判定 | |------|------|------| | 播放 / 暂停(UI 按钮) | 正常,暂停即时上报进度(实测 2.1s → 服务端 2103ms 精确) | ✅ | | 进度条点击 seek | 点 50% → t=2.0(总长 4s)精确 | ✅ | | 进度条拖拽 seek | 拖至 75% → t=3.0 精确 | ✅ | | 倍速(设置面板 0.5–2.0) | 选 2.0 后 `playbackRate=2` 生效 | ✅ | | 音量(hover 面板 / 图标静音) | 面板可见可调;图标切换 `muted` 生效 | ✅ | | 全屏按钮 | 点击进入 `fullscreenElement`,Escape 退出 | ✅ | | 画中画按钮 | 按钮存在,环境 `pictureInPictureEnabled=true` | ✅ | | 键盘 Space | 播放/暂停切换生效 | ✅ | | 键盘 ← → | 快退/快进(±5s,越界钳制到 0/duration)生效 | ✅ | | 键盘 ↑ ↓ | 音量 ± 生效 | ✅ | | 键盘 f / m | **无效** —— ArtPlayer 默认 hotkey 不注册这两个键(源码确认),UI 按钮可用;属体验建议而非故障 | ⚠️ P3 | | 弹幕快速开关(播放器内按钮) | 切换生效,`artplayerPluginDanmuku:config` 回写 localStorage 偏好,下次进入继承 | ✅ | ### 7.3 进度与续播体系(全链路) | 场景 | 实测 | 判定 | |------|------|------| | UI 暂停上报 | 位置精确(2103ms) | ✅ | | 离开页面(pagehide beacon) | 暂停态 seek 1.5s 后离开 → 服务端 1500ms | ✅ | | 重新进入续播 | 恢复 1.5s / 2.1s / 5.19s 三次均精确,就绪后暂停等用户播放 | ✅ | | 看完再进 | position ≥ duration 不跳转,从头播 | ✅ 合理 | | 首页「继续观看」 | 有未完成进度时出现;点击跳 watch 精确续播 | ✅ | | 媒体库「继续播放 · 87%」 | 带百分比;点击 → t=5.19/6.0 精确 | ✅ | | 已看完条目 | 正确从「继续观看」中消失(`position < duration` 过滤) | ✅ | | 程序化 `video.pause()`(系统媒体键等) | **不触发上报**——代码监听的是组件事件 `art.on("pause")`,原生暂停只发 `video:pause` | ⚠️ P3-3 | | 5s 定时 + 播放/暂停/结束多时机上报 | 均实测生效 | ✅ | ### 7.4 多集系列与结尾引导 | 场景 | 实测 | 判定 | |------|------|------| | EP01/EP02 绑定同一 Bangumi | 海报墙聚合「进击的巨人 2 集」 | ✅ | | 系列详情 | 集列表(EP1/EP2)+ 每集播放/匹配 | ✅ | | 播放页剧集导航 | 「上一集/下一集」可用性正确(首集禁上、末集禁下),当前集高亮 | ✅ | | 下一集切换 | 跳 EP02,时长 6s 正确,URL 带 `from=series&g=` | ✅ | | 中间集播放结束 | 出现「第 2 集 / 播放下一集」引导卡,点击跳转成功 | ✅ | | 「返回系列」 | 回 `/library?g=55770&...` 系列详情 | ✅ | | 末集 / 单集播放结束 | **无任何结束引导**(无重播、无返回系列 CTA),隐性入口仅中央播放键 | ⚠️ P3-4 | | 点「播放下一集」后 URL | 丢失 `g/from` 参数(分享该 URL 丢失上下文;页内返回按钮仍可用) | ⚠️ P3 | | ⚠️ 剧集列表出现**两个「第 1 集」** | 挂载删除产生的孤儿条目与新条目同 epNumber 并列 | ❌ P2-5 升级 | | 点中孤儿集(列表第一项) | 流 502 → `MEDIA_ELEMENT_ERROR: Format error` → duration=NaN;重连计数结束后**静默黑屏,无错误文案、无重试、无出路** | ❌ **P2-5 最严重表现** | ### 7.5 弹幕 × 播放 | 场景 | 实测 | 判定 | |------|------|------| | 导入后渲染(官方插件 artplayer-plugin-danmuku) | 文本节点实际出现 | ✅ | | 时间轴同步 | t≈1.5 弹幕在 1.5–2.3s 出现;seek 到 4.3 后 4.6s 弹幕出现 | ✅ | | seek 后重新定位 | 回拖后对应时刻弹幕重新发出 | ✅ | | 插件设置面板 | 按类型屏蔽(滚动/顶部/底部)、防止弹幕重叠、同步视频速度、透明度 | ✅ 能力超预期 | | 弹幕开关联动 | `dm.show()/hide()` + 偏好回写 localStorage | ✅ | ### 7.6 第二轮新增问题清单 | 级别 | 问题 | 建议 | |------|------|------| | **P2(升级)** | 孤儿条目混入剧集列表且与正常集重名,点中即黑屏(502→Format error→静默),用户无法分辨也无法自救 | 根治:删除挂载时级联清理/标记失效条目(同 P2-5);播放侧:加载失败时展示业务级错误卡(原因+重试+返回),不要停在 00:00/00:00 | | **P2** | `/api/stream` 对「不存在」与「挂载失效」统一 502,且正文文案「媒体或挂载不存在」与媒体库列表可见相矛盾 | 分别返回 404 / 410(或 502+结构化 code),UI 消费文案生成对应提示 | | P3 | 原生 `video.pause()` 不触发进度上报(仅组件事件 `art.on("pause")`) | 追加 `art.on("video:pause")` 上报(主路径已正常,覆盖系统媒体键/锁屏场景) | | P3 | 末集/单集播放结束无引导 | 结束后展示「重播 / 返回系列」动作卡 | | P3 | 快捷键 f(全屏)/ m(静音)不可用 | 自定义注册 hotkey,或在提示中标注可用按键 | | P3 | 「播放下一集」跳转后 URL 丢 `g/from` | openEpisode 保留 series 上下文参数 | | P3 | 筛选无结果时空态文案复用「暂无媒体,请先在文件浏览中扫描挂载」(第二轮再次复现,叠加挂载 tab 过滤时更易触发) | 区分「筛选无结果」与「确实无数据」两套文案 | ### 7.7 第二轮正面结论 - **流代理的 HTTP 语义是全项目最扎实的部分**:Range/206/416/鉴权/字节一致性全部规范。 - **进度体系闭环质量高**:多时机上报 + 精确恢复 + 双入口带百分比 + 已看完过滤,实测无一次偏差。 - **多集导航与结束引导**(中间集)完整;弹幕与播放的时间轴联动、偏好持久化均正常。 - 主路径(UI 按钮暂停、seek、切集)进度上报全部精确——上报缺口仅限程序化暂停的边缘场景。 --- ## 八、优化修复与回归验证(第三轮) > 修复范围:两轮报告全部 P1/P2/P3 项。验证环境为本地 dev 实例(127.0.0.1:4327,重启后加载新构建);**4321 Docker 实例跑的是旧镜像,需重新构建部署才能获得以下修复**。 ### 8.1 修复对照表 | 级别 | 问题 | 修复方式 | 回归结果 | |------|------|---------|---------| | P1-1 | /dashboard 登录态 500 | 重定向移到**页面级** `pages/[locale]/dashboard.astro`(渲染前),删除子视图内 redirect 与死代码(views/dashboard、DashboardBody、UserList) | ✅ /dashboard → 302 → /library | | P1-2 | 生产找回密码无交付通道 | 新增 `deliverResetToken`:配置 `PASSWORD_RESET_WEBHOOK_URL` 时 POST `{email,username,token,resetPath}`;未配置时**写服务日志**(含令牌与链接);UI 提示文案更新;`.env.example` 登记 | ✅ 日志输出 `[password-reset] user=dev3 … token=… link=…`;toast 新文案 | | P1-3 | 弹幕导入刷新即丢 | 导入 XML 按 `media-danmaku-xml:` 写 localStorage(2MB 上限),进入/切换条目时恢复 | ✅ 导入2条 → 刷新仍「弹幕 2 条」 | | P2-4 | 删除挂载无确认 | 两步确认(按钮变「确认删除?」+ 影响提示),成功 toast 带级联数量 | ✅ 确认态/「挂载已删除」toast | | P2-5 | 孤儿条目(黑屏/剧集重复/列表僵尸) | ① `mountDelete` **级联删除**媒体条目+播放进度;② `libraryService` list/groups/get/series 前**惰性清理孤儿**;③ WatchBody 监听 `video:error` 弹出**业务级错误卡**(原因+重试+返回媒体库) | ✅ 删除后条目/进度全清;手工插入孤儿 → 自动清理 + 页面显示「加载失败」卡(不再黑屏);停 WebDAV 播放 → 错误卡 → 恢复后「重试」成功 | | P2-6 | 校验错误暴露原始 JSON | 新增 `formatTrpcError`(提取 zod 数组消息/HTML 兜底),挂载保存/删除等 onError 全部接入 | ✅ | | P2-7 | 非 JSON 错误响应未兜底 | `lib/trpc.ts` 自定义 `safeFetch`:非 JSON 错误响应转换为结构化 tRPC 错误 | ✅(typecheck/链路验证) | | P2-8 | 保存成功无反馈 | 挂载创建/更新/删除 toast 链路核验 | ✅ 「挂载已保存」「挂载已更新」「挂载已删除」 | | P2-9 | 测试连接失败无原因 | `mountService.test` 归一化错误(认证失败/无法解析地址/连接被拒绝/超时/证书/非 WebDAV) | ✅ 显示「连接被拒绝,请检查地址和端口」 | | P2-10 | 表单静默校验 | 行内红字(名称必填、http(s) 合法性)+ 焦点定位,非法提交**零网络请求** | ✅ 两条红字 + 请求数 0 | | P2 | stream 错误码语义 | `StreamSourceError`:媒体不存在 404 / 挂载失效 410 / 上游失败 502;`mountListDir` 失败改 BAD_GATEWAY+具体原因 | ✅ 孤儿条目接口 404 NOT_FOUND | | P3-11 | 空结果文案误导 | 区分「搜索无结果(可清除搜索)/筛选无结果(可切全部)/真无数据(引导扫描)」 | ✅ 三层文案与恢复按钮 | | P3-12 | 视图偏好不持久 | `media-library-view` localStorage | ✅ 跨页面刷新保持「列表」 | | P3-13 | 已登录首页仍显示登录 CTA | `auth.me` 判断登录态,隐藏「登录 / 注册」 | ✅ | | P3-14 | 找回密码邮箱 label | forgot 模式专用「注册邮箱」 | ✅ | | P3-15 | 弹幕关闭文案 | 已导入时显示「弹幕已关闭,可在上方按钮或弹幕设置中开启」 | ✅ | | P3-16 | 404 语言不随路径 | 404 页优先取 URL locale 段 | ✅ en cookie 下 `/zh-CN/*` 显示中文、`/en/*` 英文 | | P3-17 | 密码规则不可见 | 注册/改密/重置密码框下常显「至少 8 位字符」 | ✅ | | P3-19 | 末集结束无引导 | 结束卡:「本集已播放完毕」+ 重新播放 + 返回系列/媒体库 | ✅ | | 播放 | 原生暂停不报进度 | 监听改为 `video:play` / `video:pause`(原生事件转发,覆盖系统媒体键) | ✅ 程序化 pause → positionMs=702 精确上报 | | 播放 | 快捷键 f/m 缺失 | 构造后注册 `hotkey KeyF/KeyM`(全屏/静音) | ✅ m 静音切换、f 进全屏 | | 播放 | 目录读取失败无重试 | 浏览页错误行显示服务端原因 + 「重试」按钮 | ✅ | ### 8.2 修复过程中发现并解决的阻塞问题 1. **工作区存在未提交 WIP**(models 新增 `dandanplay_episode_id`/`matched_hash` 列),但**未执行 `yarn db:push`**——导致媒体库查询/写入全部 500、扫描假成功。已执行 `db:push`(schema 变更已应用),媒体库恢复正常。**注意:Docker 实例的数据库需在其环境单独执行迁移。** 2. WIP 代码 `danmaku.service.ts` 存在 `exactOptionalPropertyTypes` 类型错误,已修复。 3. 运行期 Vite 依赖预构建过期(504 Outdated Optimize Dep)导致页面水合失败,重启 dev server 恢复。 ### 8.3 最终质量门禁 - `yarn typecheck`:19/19 任务通过 - `yarn lint`:11/11 通过(仅 3 条存量 CSS specificity warning,非本次引入) - `yarn build:web`:构建成功 - 浏览器回归(dev 实例):8.1 表中全部 ✅ 项逐一手动验证 ### 8.4 暂缓项与生效说明 | 项 | 说明 | |----|------| | P3-18 弹幕设置服务端持久化 | **暂缓**:需新增数据库表 + 双环境迁移;当前 localStorage 方案已用「本地会话」文案如实标注,单设备闭环完整。建议随下个 schema 变更窗口一并实施 | | 4321 Docker 实例 | 仍是**旧构建**:本轮修复需重新 `docker build` 并对容器内数据库执行 `db:push` 后生效 | | 现有 e2e 套件 | 仍指向 4321(旧镜像)且只覆盖游客路径;建议将 baseURL 指向新部署并补充登录态冒烟链(覆盖 dashboard/挂载/播放) | | m/f 快捷键的全屏退出 | headless 环境下 Escape 未观察到退出全屏(进入正常);真实浏览器行为待人工确认 | --- ## 附录:测试现场说明 - 临时 WebDAV 已停止;如需复测:`"$MIMO_PYTHON" tmp-e2e-dav/dav_server.py "$PWD/tmp-e2e-dav/dav-root" 8099`,挂载地址 `http://192.168.31.125:8099`(localhost 被 SSRF 策略禁止,须用局域网 IP)。 - 样例视频位于 `tmp-e2e-dav/dav-root/片库/`(EP01 4s、EP02 6s、示例电影),弹幕样例 `tmp-e2e-dav/sample-danmaku.xml`。 - 主实例遗留状态:`e2e_tester` 名下 2 条孤儿媒体条目(挂载删除残留;其中孤儿 EP1 与新 EP01 已同绑「进击的巨人」,即 7.4 节剧集列表重复的复现条件)+ 3 条有效条目;新增账号 `e2e_mail`。 - 第二轮测试结束后 WebDAV 服务已停止,复测前需按第一条命令重启。