Files
wecom_it_smart_desk/docs/06-测试质量/方案A-消息发送延时-E2E验证报告-20260708.md
T
Simon e4e2de47bb docs: test reports + knowledge iteration design + PRDs
提交 OTP/RBAC/Tier0/Tier1/P0+P2 测试报告、方案A E2E 验证、知识库迭代设计(PRD/mermaid/html 原型)、项目状态看板更新; 根配置 docker-compose.yml/mkdocs.yml。
2026-07-09 11:50:19 +08:00

9.2 KiB
Raw Blame History

方案A 消息发送延时改造 — E2E 浏览器验证报告

验证日期:2026-07-08 验证方式:真实浏览器端到端实测(系统 Chrome 驱动 H5Playwright Python 验证目标:员工端发送消息瞬时返回不阻塞 UIAI 回复经 WebSocket 打字机流式推送(方案A 核心机制) 结论: 通过 — 发送即时、AI 经 WS 流式渲染、Duckula 头像/名称正常


1. 验真目标(方案A 是什么)

方案A 的核心改造:

  • 员工发送消息 → 后端 POST /h5/conversations/current/messages 立即返回ai_reply: null,消息落库 status: sent),不再同步等待 AI
  • AI 回复由 asyncio.create_task(process_h5_ai_reply) 异步生成,逐 chunk 通过 WebSocket 广播 ai_reply_chunk / ai_reply 给 H5
  • H5 监听 WS 帧,做打字机流式渲染WS 断连时降级为 3s 轮询

本次 E2E 要证明的是:UI 不阻塞(发送瞬时) + AI 回复走 WS 流式(非轮询兜底)


2. 测试环境

组件 版本/地址 说明
前端 H5 localhost:5174/itdesk/Vite dev docker-compose.dev.yml frontend-h5
后端 localhost:8000FastAPI, --reload dev 栈,单 worker
浏览器 系统 Chrome 150headless, Playwright 驱动) 走系统 Chrome,非下载 Chromium
AI Dify app-UaTWYdBSwN6VktKQlbh5YN5Hdev 临时切的可流式 app 用于验证 typewriter 主路径
登录 Mock 登录 POST /h5/mock-login dev 模式免企微 OAuth

3. 测试步骤

  1. 打开 H5 登录页 http://localhost:5174/itdesk/
  2. Mock 登录(employee_id=E2E_BROWSERemployee_name=浏览器实测
  3. 在输入框发送「打印机无法连接网络怎么办」
  4. 计时发送点击返回(验证 UI 不阻塞)
  5. 监听页面 WebSocket 接收帧,轮询 .chat-panel__messages 文本,捕获:
    • Duckula 头像/名称是否渲染
    • AI 回复是否经 ai_reply_chunk 流式到达(打字机证据)
    • 最终 AI 文本长度与真实内容

4. 验证结果(来自 e2e-screenshots/e2e_result.json

指标 判定
发送点击耗时 send_click_s 1.257s 瞬时(< 3s 阈值)
发送 UI 不阻塞 send_ui_instant true
AI 首屏渲染 ai_first_render_s 2.27s 发送后 2.3s 出现
WS 收到 ai_reply_chunk 帧数 343 流式打字机路径成立
打字机已证实 typewriter_proven true 非轮询兜底
最终消息文本长度 final_text_len 1469 字符 完整真实 AI 内容
含 Duckula 名称 final_has_duckula true 头像/名称渲染
含真实内容 final_has_real_content true ("问题描述/打印机/网络"
文本增长 len_grew true 流式累积
WS 打开总数 ws_open_total 2(含 1 个 Vite HMR 无握手失败

截图证据(真实浏览器会话):

  • e2e-screenshots/01-login.png — 登录页
  • e2e-screenshots/02-chat-after-login.png — 登录后进会话
  • e2e-screenshots/03-after-send-instant.png — 发送后即时(用户气泡出现,AI 占位)
  • e2e-screenshots/04-typewriter-mid.png — 打字机进行中
  • e2e-screenshots/05-final.png — AI 完整回复(Duckula 头像 + 1469 字)

结论:方案A 在真实浏览器中验证通过 — 发送不阻塞 UIAI 经 WebSocket 流式打字机渲染。


5. 验证过程中发现并修复的 4 个阻断问题

这些不是方案A 本身的缺陷,而是「dev 容器化栈跑真实浏览器」暴露的环境/代码阻断。其中第 4 条是真实后端 bug,会影响生产 WS。

① H5 应用无法挂载(Vite 资源解析失败)

  • 现象:打开 /itdesk/ 只剩骨架屏,<vite-error-overlay> 报错 Failed to resolve import "/duckula.webp"
  • 根因MessageBubble.vue / MessageItem.vue 引用 <img src="/duckula.webp">,该资源在容器镜像烘焙时尚未加入 public/,而 dev compose 只挂载了 src 没挂载 public/
  • 修复docker-compose.dev.yml frontend-h5 增加 - ./frontend-h5/public:/app/public(与 src 一致的热更新挂载)

② Mock 登录返回 500(Vite 代理目标错误)

  • 现象:浏览器点登录 → 后端 500;但纯 Python 直连 :8000 却 200
  • 根因vite.config.ts 代理 /apitarget: 'http://localhost:8000',但容器内 localhost 不是后端(是容器自己)→ ECONNREFUSED → Vite 返 500
  • 修复:代理目标改为可配置 process.env.VITE_PROXY_TARGET || 'http://localhost:8000'dev compose 注入 VITE_PROXY_TARGET=http://backend:8000(compose 服务名)。本地非 Docker 开发仍走默认 localhost:8000

③ CSP 阻断 dev WebSocket

  • 现象Connecting to 'ws://localhost:8000/ws/h5/...' violates CSP connect-src(握手前被拦)
  • 根因index.html CSP connect-src 仅允许 ws://localhost(默认端口 80),但 dev WS 用显式端口 8000,CSP 按端口精确匹配 → 视为不同源被拒
  • 修复index.html CSP connect-src 增加 ws://localhost:8000 ws://127.0.0.1:8000

⚠️ WebSocket 握手失败(真实后端 bug,生产相关)

  • 现象CSP 放开后握手仍失败 Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received
  • 根因:浏览器用子协议 Sec-WebSocket-Protocol: bearer.{token} 传递 token后端 ws_manager.connect() / connect_employee() 读取该头做认证,但 websocket.accept() 未回显子协议 → 浏览器严格拒绝握手
  • 影响:不仅 dev,生产环境坐席端/员工端 WS 同样会握手失败(之前被 3s 轮询兜底掩盖,导致 AI 回复实际走轮询而非流畅打字机)
  • 修复backend/app/services/ws_manager.pyconnect / connect_employee 增加可选 subprotocol 参数,accept(subprotocol=subprotocol if subprotocol else None)backend/app/api/ws.py 两处调用传入 subprotocol
  • 验证:修复后 WS 握手成功,ai_reply_chunk 343 帧正常流式到达

6. 遗留(非阻断)事项

  • CSP font-src 缺口:控制台仍有 data:font/woff2at.alicdn.com 字体被 CSP 拦截(仅影响字体显示,不影响功能)。如需消除,可在 CSP 增加 font-src 'self' data: https://at.alicdn.com
  • 404:一个资源 404(疑似 favicon 或字体文件),无害。
  • dev Dify key 临时切换docker-compose.dev.yml 中 Dify key 为验证 typewriter 主路径临时切到可流式 app,已在注释中标注。 已还原 (2026-07-09) 回 app-J3s8sHarZQ2SCaNF3xCppliLdev 该 app 经 dify2openai 返回空 SSE,本地 typewriter 主路径需改用 backend/.env 工作 app 才能看到流式效果)。

7. 结论

方案A(员工端消息发送即时返回 + AI 经 WS 打字机流式推送)在真实浏览器端到端验证通过。 发送 1.26s 即时返回、AI 回复 2.3s 起经 343 个 WS chunk 流式渲染、Duckula 头像与完整 1469 字回复正常显示。 同时修复了 1 个真实 WebSocket 握手 bug(生产相关,此前被轮询兜底掩盖)及 3 个 dev 容器化环境阻断,使「Docker dev 栈 + 真实浏览器 E2E」链路从此可用。


8. 生产部署验证 (2026-07-09)

WS 子协议修复(第④条 bug)按既定建议合入 main 并部署到生产服务器 itsupport.servyou.com.cn (10.90.5.110)。

8.1 部署方式

生产后端容器 wecom_it_backend 代码烘焙进镜像(唯一挂载是 uploads),无源码卷挂载。因此采用:

  1. 将 2 个修复文件打包上传至生产服务器 /tmp/jms_ops pack-upload
  2. docker cp 进运行容器:/app/app/services/ws_manager.py/app/app/api/ws.py
  3. 同步更新宿主机源码 /opt/wecom-it-desk/backend/app/...(供后续镜像重建)
  4. docker restart wecom_it_backend
  5. 原文件备份于 /tmp/ws_manager.py.bak/tmp/ws.py.bak(回滚点)

8.2 部署后验证(真实证据)

检查项 结果
容器状态 Up (healthy) — 重启后健康
修复代码就位 ws_manager.py:76:185 均为 accept(subprotocol=subprotocol if subprotocol else None)
真实 WS 连接(重启后) 日志显示 WebSocket /ws/h5/tangzhenzhen [accepted] + H5员工 WebSocket 连接建立: employee_id=tangzhenzhen,以及 WebSocket /ws/sxn [accepted] + 坐席连接建立
后端服务 GET /conversations ... 200 OK(正常服务流量)

关键证据:上述 H5 员工(tangzhenzhen)/坐席(sxn) 连接时间戳(01:15:03 / 01:15:06)均在容器重启(09:14:26之后。用旧 bug 代码,浏览器会因「未回显 subprotocol」直接拒绝握手,连接根本到不了 [accepted]。现在成功建立 = 修复在生产真实生效(旧代码下这些连接本应失败)。

8.3 Git 合并

  • 修复 commit bacd34cfeature/message-reliability
  • cherry-pick → 6db1c0emain),已推送 origin/main6277db3..6db1c0e
  • 生产部署与主干合并相互独立:部署用 docker cp(即时生效、重启保留),主干合并保证代码源一致