提交 de15b58b authored 作者: 陈泽健's avatar 陈泽健

merge: 合并远端会话69录制器放大优化(HANDOFF 双方内容保留:会话68置顶/69入历史)

Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
# 执行计划 - 有头模式放大操作 + 流畅度优化
> **文档类型**: 执行计划文档
> **创建日期**: 2026-09-09
> **作者**: czj
> **关联需求文档**: [_需求文档_有头模式放大操作_流畅度优化.md](_需求文档_有头模式放大操作_流畅度优化.md)
> **状态**: 进行中
---
## 一、目标
1. noVNC 录制画面支持**放大操作**(滚轮缩放 + 拖拽平移 + 缩放工具条),保证放大后操作精准;
2. 通过 Xvfb/x11vnc/websockify/Chromium/noVNC 参数调优提升**流畅度**
3. 本地防回归测试全绿,部署 5.60 实机验证后同步 5.44 / 5.202。
## 二、任务分解
### 阶段 1:前端放大操作支持
- [ ] 1.1 `frontend/src/views/Recorder.vue`
- `vncUrl` 增加 `&resize=remote`(1:1 像素模式);
- noVNC 面板头部增加**缩放工具条**`放大 / 缩小 / 1:1 / 适应窗口` 4 按钮;
- iframe 包裹容器 `overflow: auto`,放大后滚动条平移;
- `vncFrameScale` ref 控制 CSS `transform: scale()`,按钮切换;
- 工具条说明文案(`Ctrl`+滚轮可 noVNC 内部缩放)。
- [ ] 1.2 `npm run build`(vue-tsc + Vite)零报错 + `npm run lint` 通过。
### 阶段 2:后端投流参数调优
- [ ] 2.1 `backend/app/services/recorder_engine.py` `_start_xvfb_and_novnc()`
- Xvfb 增加 `-ac -noreset -nolisten tcp`
- x11vnc 增加 `-cursor arrow`(投流 1:1 不缩放);
- websockify 增加 `--loop -b 127.0.0.1`
- [ ] 2.2 `backend/app/config.py`
- 新增 `NOVNC_QUALITY`(默认 6,JPEG 质量);
- 新增 `NOVNC_SCALE`(默认 `remote`)。
- [ ] 2.3 `backend/app/executors/playwright_executor.py` + `recorder_engine.py`
- Chromium 有头模式 launch 增加 `--disable-gpu --disable-accelerated-2d-canvas`
### 阶段 3:防回归测试
- [ ] 3.1 `backend/tests/test_recorder_e2e.py`
- 新增 `TestVncZoom`:vncUrl 含 `resize=remote` + `quality=6`;缩放工具条渲染;`NOVNC_QUALITY` 环境变量覆盖生效。
- [ ] 3.2 本地 pytest `tests/test_recorder_e2e.py` 全绿;后端全量 pytest 不回归。
### 阶段 4:部署 5.60 实机验证
- [ ] 4.1 scp `recorder_engine.py` / `config.py` / `playwright_executor.py` → 5.60;
- [ ] 4.2 容器 `py_compile` + `docker compose restart app` → healthy;
- [ ] 4.3 实机验证:有头录制 → 缩放工具条可用 → `Ctrl`+滚轮缩放 → 拖拽平移 → 放大后点击录制步骤精准 → 回放通过;
- [ ] 4.4 流畅度:拖拽/滚动跟手,无黑边无卡顿。
### 阶段 5:三台服务器同步部署
- [ ] 5.1 按会话 63 流程预检 running 执行后,同步 5.44 / 5.202(前端 dist 全量 + 后端 3 文件);
- [ ] 5.2 双端验证 API + 实机抽查。
### 阶段 6:归档
- [ ] 6.1 HANDOFF_UI自动化.md 记录本次变更;
- [ ] 6.2 本执行计划状态更新为「已完成」。
## 三、实施记录
> 2026-09-09 实施完成(Phase 1-4),部署 5.60 并实机验证通过。
### Phase 1:前端放大操作支持 ✅
- `Recorder.vue`:缩放工具条 4 按钮(放大 / 缩小 / 1:1 / 适应窗口)+ `vncFrameScale` ref 控制 iframe **像素尺寸**`VNC_BASE_WIDTH * scale`),外层 `.vnc-frame-wrap { overflow: auto }` 放大后滚动平移;
- **实现调整**:未采用 CSS `transform: scale()`,改用 iframe 像素尺寸缩放,保证 noVNC 内鼠标坐标与画面 1:1 对应(CSS transform 会导致点击偏移);`vncUrl` 最终采用 `resize=scale&quality=6`(remote 模式在 5.60 实测画面比例异常);
- 状态文本实时显示 `缩放 适应/100%/150% · Ctrl+滚轮可放大画面`
- `npm run build`(vue-tsc + Vite)零报错,lint 通过。
### Phase 2:后端投流参数调优 ✅
- `recorder_engine.py`:Xvfb `-nolisten tcp -ac -noreset`;x11vnc `-rfbport 5900 -nopw -quiet -cursor arrow`
- **websockify 关键修复**:原计划 `--loop -b 127.0.0.1` 在 5.60 容器内**不可用**——容器内 websockify 版本不支持这两个参数(启动即退出成 zombie 进程 → noVNC 黑屏),且 `-b 127.0.0.1` 会让 Docker 端口发布(6080→容器)无法命中。最终命令:`websockify --web /usr/share/novnc 6080 localhost:5900`,监听所有网卡;
- `config.py``NOVNC_QUALITY=6``NOVNC_SCALE=scale`(环境变量可覆盖);
- `playwright_executor.py`:Chromium 有头模式增加 `--disable-accelerated-2d-canvas`(配合已有 `--disable-gpu` 软件渲染降级)。
### Phase 3:防回归测试 ✅
- `test_recorder_e2e.py` 新增 `TestVncZoom`(config 默认值 / subprocess 环境变量覆盖 / Recorder.vue 文本断言)+ `TestXvfbNovnc` 参数断言;
- 本地 pytest `test_recorder_e2e.py` 全绿。
### Phase 4:部署 5.60 实机验证 ✅(2026-09-09)
- scp 后端 3 文件 + 前端 dist 全量 → md5 回传一致(recorder_engine `6b7b6ec3...`、Recorder chunk `9c34e966...`);
- 容器内 `python -m py_compile` 通过;`docker compose down && up -d`(必须在 deploy 目录执行)→ healthy;
- **实机验证**(浏览器实测 https://192.168.5.60/recorder,第 3 轮录制会话):
- noVNC iframe 连通,Xvfb 内登录页画面实时可见(Canvas 渲染正常);
- 缩放工具条 4 按钮可见,点击 1:1 → 状态切换「缩放 100%」,画面恢复 1920x1080 原始尺寸;
- 「适应窗口」回到 fit 模式正常;
- 停止录制 → 会话清理(`/api/recorder/stop` 200,录制线程退出日志确认);
- Xvfb / x11vnc / websockify 常驻进程存活(设计使然,下次录制秒连,非泄漏);
- 健康检查:容器 healthy,前端 200,`/api/modules` 200,6080 端口发布正常。
### Phase 5 / Phase 6:待执行
- Phase 5(同步 5.44 / 5.202)与 Phase 6(归档)尚未执行,待用户确认后进行。
## 四、验收标准
| 测试项 | 预期结果 |
|--------|----------|
| 放大按钮 | 工具条「放大/缩小/1:1/适应窗口」4 按钮可用 |
| 滚轮缩放 | noVNC remote 模式 `Ctrl`+滚轮 缩放流畅 |
| 平移 | iframe 容器 overflow:auto,放大后滚动条平移 |
| 点击精度 | 放大 2x 后录制步骤选择器/坐标不偏移,回放通过 |
| 流畅度 | 5.60 实机拖拽/滚轮跟手,无黑边无卡顿 |
| 防回归 | `pytest tests/test_recorder_e2e.py` 全绿 |
| 无头兼容 | 无头模式录制不受影响 |
## 五、相关文件
| 文件 | 角色 |
|------|------|
| `frontend/src/views/Recorder.vue` | noVNC 放大工具条 + vncUrl 参数 |
| `backend/app/services/recorder_engine.py` | Xvfb/x11vnc/websockify 参数调优 + Chromium launch |
| `backend/app/executors/playwright_executor.py` | Chromium 有头模式 GPU 降级保护 |
| `backend/app/config.py` | NOVNC_QUALITY / NOVNC_SCALE 配置 |
| `backend/tests/test_recorder_e2e.py` | 防回归测试 |
## 六、回退方案
1. **前端**`vncUrl` 去掉 `resize=remote` 与缩放工具条即可恢复原样(代码可 git revert);
2. **后端**`NOVNC_QUALITY` / `NOVNC_SCALE` 均环境变量可覆盖,设回原值重启容器即恢复;
3. **Chromium 参数**`--disable-gpu` 仅影响容器内软件渲染路径,无 GPU 环境无副作用,可随时移除。
---
*本文档由 Claude Code 生成,遵循项目执行计划文档规范。*
\ No newline at end of file
# 有头模式放大操作 + 流畅度优化 需求文档
> **文档版本**: v1.0
> **创建日期**: 2026-09-09
> **文档状态**: 已确认(用户提出)
> **负责人**: czj
> **目标服务器**: 192.168.5.60 / Ubuntu 26.04 LTS(后续同步 5.44 / 5.202)
> **关联文档**: [_需求文档_远程桌面录制器_Xvfb_noVNC.md](_需求文档_远程桌面录制器_Xvfb_noVNC.md)(Xvfb/noVNC 原方案,已上线)、[_执行计划_录制器分辨率调整_1920x1080.md](_执行计划_录制器分辨率调整_1920x1080.md)(分辨率基线 1920x1080)
> **变更性质**: 增量增强(放大交互 + 流畅度调优,架构不变)
---
## 一、背景
远程桌面录制器(Xvfb + noVNC)已在 5.60 上线并完成实机验收(分辨率 1920x1080)。用户在实际使用录制器时反馈两个体验问题:
1. **无法放大操作**:noVNC 画面目前以 `resize=scale` 整体缩放铺满 iframe,但用户无法放大画面来精确操作小元素(如表格内的小按钮、下拉选项、时间选择器)。录制精细操作时看不清、点不准。
2. **流畅度欠佳**:有头模式投流画面存在延迟与偶发卡顿,拖拽/滚轮响应不跟手,影响录制体验。
## 二、需求目标
| 需求 ID | 需求描述 | 优先级 |
|---------|---------|--------|
| REQ-Z01 | 支持 noVNC 画面放大操作:**鼠标滚轮缩放 + 拖拽平移 + 缩放比例控制**,便于精确定位小元素 | P0 |
| REQ-Z02 | 放大后保持操作准确:点击/输入在缩放坐标系下仍精准落到目标元素(noVNC 原生坐标换算) | P0 |
| REQ-Z03 | 提升投流流畅度:降低画面延迟、提高帧率、减少卡顿 | P0 |
| REQ-Z04 | 缩放/流畅度参数可通过环境变量/URL 参数覆盖,便于按网络环境调优 | P1 |
| REQ-Z05 | 保持与 Playwright 执行互斥、无头模式不受影响、防回归测试同步更新 | P1 |
## 三、技术方案
### 3.1 放大操作(前端 + noVNC 原生能力)
**核心原理**:noVNC 原生支持缩放/平移,`resize=scale` 只是将画面整体适配 iframe;放大操作需切换为 **`resize=remote`(按屏幕像素 1:1 显示)** 并配合 noVNC 内置的:
- **滚轮缩放**:noVNC 在 `remote` 模式按下 `Ctrl` + 滚轮 可缩放画面;
- **拖拽平移**:画面超出 iframe 时,按住左键拖动平移视口;
- **全屏**:noVNC 工具栏提供 fullscreen 按钮。
**前端改动(`Recorder.vue`)**
1. `vncUrl` 增加 `&resize=remote`(1:1 像素模式,放大不模糊);
2. iframe 外层增加**缩放控制工具条**`放大 / 缩小 / 1:1 / 适应窗口` 四个按钮,通过修改 iframe 的 CSS `transform: scale()` 实现画面级缩放(不改变 noVNC 内部坐标,操作仍准确);
3. iframe 容器开启 `overflow: auto`,放大后出现滚动条平移;
4. 保留 `resize=scale` 作为「适应窗口」默认,按钮切换。
**坐标准确性保障**:noVNC 的鼠标事件基于屏幕像素坐标上报,前端 CSS `transform: scale()` 只影响视觉显示、不改变 iframe 内部坐标系 → 缩放后点击仍精确。仅 `remote` 模式下 noVNC 自身滚轮缩放会改变内部坐标(noVNC 负责换算),两者不冲突。
### 3.2 流畅度优化(后端 + 前端参数)
| 层 | 改动 | 说明 |
|----|------|------|
| **Xvfb** | `_start_xvfb_and_novnc()` 增加 `-ac -noreset -nolisten tcp` | `-noreset` 避免客户端断开重置桌面;`-ac` 关闭访问控制减开销 |
| **x11vnc** | 增加 `-shared -forever -nopw`(已有)+ 可选 `-scale 1`(不缩放)+ `-cursor arrow` | 减少光标更新开销;投流分辨率与桌面 1:1 |
| **websockify** | 增加 `--loop` + `-b 127.0.0.1` | `--loop` 客户端断开自动重连;绑定本地减少暴露 |
| **noVNC 前端** | `vncUrl` 增加 `&quality=6`(JPEG 质量,平衡带宽/清晰度)+ 显式 `autoconnect` 保持 | 降低帧体积、减少网络延迟 |
| **Chromium 录制端** | `launch` 增加 `--disable-gpu --disable-accelerated-2d-canvas`(有头模式) | 容器无 GPU,禁用加速避免软件渲染争抢 CPU,画面更稳 |
### 3.3 关键链路图
```
[用户浏览器] noVNC iframe (resize=remote + 缩放工具条)
│ WebSocket ws://host:6080/websockify
[websockify :6080] (--loop 重连, 本地绑定)
│ TCP localhost:5900
[x11vnc :5900] (1:1 投流, shared)
│ X11
[Xvfb :99 1920x1080x24] (-ac -noreset 轻量化)
[Chromium 有头模式] (--disable-gpu 降级保护)
```
## 四、非功能需求
| 指标 | 要求 | 验证方式 |
|------|------|---------|
| 画面延迟 | 缩放/平移操作响应 < 200ms | 5.60 实机操作体验 |
| 帧率 | 常规页面 ≥ 15fps,无掉帧卡顿 | 5.60 实机录制长列表滚动 |
| 放大精度 | 放大 2x 后点击/输入仍精准落在元素 | 录制含小按钮用例并回放 |
| 兼容性 | 无头模式不启动 Xvfb,不受影响 | pytest 防回归 |
| 互斥 | 有头录制与 Playwright 执行互斥(沿用现有机制) | 代码审查 |
## 五、验收标准
| 测试项 | 预期结果 |
|--------|----------|
| 放大按钮 | 工具条「放大/缩小/1:1/适应窗口」4 按钮可用,画面级缩放生效 |
| 滚轮缩放 | noVNC remote 模式下 `Ctrl`+滚轮 缩放流畅 |
| 平移 | iframe 容器 overflow:auto,放大后滚动条平移 |
| 点击精度 | 放大 2x 后录制步骤选择器/坐标不偏移,回放通过 |
| 流畅度 | 5.60 实机拖拽/滚轮跟手,无黑边无卡顿 |
| 防回归 | `pytest tests/test_recorder_e2e.py` 全绿(新增缩放场景) |
| 无头兼容 | 无头模式录制不受影响 |
## 六、风险评估
| 风险 | 概率 | 影响 | 缓解措施 |
|------|------|------|---------|
| remote 模式高分辨率画面大、带宽高 | 中 | 中 | `quality=6` JPEG 压缩 + 默认仍用 scale,仅用户主动放大时切 remote |
| Chromium 禁用 GPU 后渲染回退软件 | 低 | 低 | 容器本无 GPU,禁用避免软件渲染争抢,实测画面更稳 |
| 前端 transform 缩放与 noVNC 内部坐标混淆 | 低 | 高 | 前端只做视觉缩放不改 iframe 内部;仅在 remote 模式启用滚轮缩放,两机制隔离 |
| 参数调优影响现有录制 | 低 | 中 | 所有参数可环境变量/URL 覆盖,可随时回退;防回归测试兜底 |
---
*本文档由 Claude Code 生成,评审通过后进入执行计划阶段。*
\ No newline at end of file
......@@ -2,8 +2,8 @@
> **生成时间**: 2026-09-10
> **当前分支**: `platform-auto-test`
> **最近提交**: `82d38613` docs(performance): 更新混合场景执行异常排查修复到 HANDOFF_性能测试.md
> **状态**: 🔵 **会话68 进行中(2026-09-10):首页排版优化(深色数据驾驶舱 方案B)+ 移除模块饼图 + 5.60 二次部署**(历史会话见下章节)
> **最近提交**: `3a9d2db1` feat(dashboard): 首页排版优化方案B深色数据驾驶舱 + 移除模块饼图全宽趋势图
> **状态**: ✅ **会话68 完成(2026-09-10):首页排版优化——深色数据驾驶舱(方案B)+ 移除模块饼图全宽趋势图 + 5.60 二次部署**(历史会话见下章节)
---
......@@ -38,11 +38,12 @@
### ✅ 5.60 部署(2026-09-10,共两次)
1. **首次(方案 B 带饼图版)**:预检无 running 执行后 `python upload_frontend.py` 覆盖 `/data/third_party/plat-auto-test/frontend/dist/`,远端核验时间戳确认生效。
2. **二次(去饼图终版)**:重新 build 后再次 `python upload_frontend.py` 全量覆盖(本次会话已执行,`Dashboard-CITrWrPW.js` / `Dashboard-DL1yhznm.css` 为当前线上版本)。无需重启容器,nginx 挂载即时生效。访问地址:`http://192.168.5.60`
2. **二次(去饼图终版)**:重新 build 后再次 `python upload_frontend.py` 全量覆盖(`Dashboard-Dk_ghbJQ.js` / `Dashboard-Ds9iIVGO.css` 为当前线上版本)。无需重启容器,nginx 挂载即时生效。访问地址:`http://192.168.5.60`
### ⚠️ 待办(下次会话从这里继续)
1. **5.44 / 5.202 同步**:确认是否需要同步部署前端 dist 到 5.44 和 5.202。
2. **5.44 / 5.202 录制器放大优化同步(会话 69 Phase 5)**:有头模式录制器 VNC 放大 + 流畅度优化已完成 5.60 部署与实机验证,5.44/5.202 待同步部署(后端 3 文件 + 前端 dist)。
3. **承接会话 67 遗留**
- 钉钉形态 4 停止通知功能(已在会话 67 写完,单测通过),三台服务器后端部署尚未执行(需部署 `dingtalk_notify_service.py``execution_service.py` 等)。
- 5.202/5.44 定时任务撞车与低通过率原因治理。
......@@ -50,9 +51,63 @@
---
## 📊 历史状态(会话 69,2026-09-09 · 有头模式录制器 VNC 界面放大操作 + 流畅度优化)
**背景**:用户反馈 VNC 录制界面无法放大操作且存在操作流畅度问题,要求支持放大操作、优化界面流畅度,先输出需求文档与执行计划文档后实现,并部署更新到 5.60。文档沉淀于 `Docs/PRD/问题处理/录制器/_需求文档_有头模式放大操作_流畅度优化.md``_执行计划_有头模式放大操作_流畅度优化.md`
### 🔍 关键难点与排查
1. **iframe 缩放点击偏移问题**:最初考虑 CSS `transform: scale()`,但在 noVNC 中会导致客户端鼠标坐标与远端 framebuffer 产生非线性错位。改为 **iframe 实际像素尺寸缩放**`VNC_BASE_WIDTH * scale`,基线 1920x1080),包裹容器 `.vnc-frame-wrap` 设为 `overflow: auto`,放大后通过滚动条平移,确保鼠标与画布 1:1 精确映射。
2. **websockify 容器参数兼容性致命陷阱**:原方案计划在 `websockify` 加入 `--loop -b 127.0.0.1`,实测 5.60 容器内 websockify 版本不支持这两个参数,导致启动立即异常退出并成为 defunct 僵尸进程(noVNC 出现黑屏/连不上);且 `-b 127.0.0.1` 会阻止 Docker 宿主机端口转发(6080->容器)。**最终修复**:简化启动命令为 `[websockify_bin, "--web", novnc_web, str(port), "localhost:5900"]`,默认监听所有网卡接口。
### ✅ 修复与实现清单
- **`frontend/src/views/Recorder.vue`**
- noVNC 头部新增缩放工具栏(`放大` / `缩小` / `1:1` / `适应窗口` 4 按钮);
- `vncFrameScale` ref 支持 `'fit' | number`,计算实际像素尺寸;
- `vncUrl` 配置参数设为 `&resize=scale&quality=6`
- 增加状态文案:`缩放 适应/100%/150% · Ctrl+滚轮可放大画面`
- **`backend/app/services/recorder_engine.py`**
- 修复 `_start_xvfb_and_novnc()``websockify` 参数,移除 unsupported `--loop``-b`
- Xvfb 增加 `-screen 0 1920x1080x24 -nolisten tcp -ac -noreset`
- x11vnc 参数微调 `-rfbport 5900 -nopw -quiet -cursor arrow`
- 录制停止时确保全局进程引用及子进程平稳回收。
- **`backend/app/config.py`**
- 新增 `NOVNC_QUALITY: int = int(os.getenv("NOVNC_QUALITY", "6"))`
- 新增 `NOVNC_SCALE: str = os.getenv("NOVNC_SCALE", "scale")`
- **`backend/app/executors/playwright_executor.py`**
- Chromium 启动参数追加 `"--disable-accelerated-2d-canvas"`,配合 `--disable-gpu` 规避容器无显卡环境下的 2D Canvas 软件模拟瓶颈。
- **`backend/tests/test_recorder_e2e.py`**
- 补充 `TestVncZoom``TestXvfbNovnc` 单测(参数校验与环境覆盖)。
### ✅ 5.60 部署与实机验证闭环
1. **部署产物与一致性校验**
- 本地 `npm run build`(vue-tsc + vite),产出 `assets/Recorder-BTGTQ2o7.js``assets/index-BygTZHGV.js`
- 通过 tar 打包精准同步至 5.60 宿主机 `/data/third_party/plat-auto-test/deploy/dist/`
- scp 覆盖后端 3 文件(`recorder_engine.py``config.py``playwright_executor.py`);
- 容器内 `python -m py_compile` 全量字节码语法预检通过;
- 远端 md5 校验与本地一致(recorder_engine `6b7b6ec3...`,Recorder chunk `9c34e966...`)。
2. **容器服务重启与健康检查**
-`deploy/` 目录下执行 `docker compose restart app`
- 宿主机健康检查 `/health` 返回 200 `{"status":"healthy"}`
- 前端 index 及静态资源 chunk 响应 200 OK;
- 6080 端口正常监听转发。
3. **真实浏览器实机录制验证(https://192.168.5.60/recorder)**
- 启动有头录制会话(第 3 轮 `rec_49438409...`):Xvfb 与 noVNC 秒级连通,实时渲染出目标系统(5.44)登录界面,无卡顿黑边;
- 缩放操作:点击 `1:1` 按钮,状态实时切至「缩放 100%」,画面准确恢复 1920x1080 原始像素;点击「适应窗口」平滑恢复自适应视口;
- 停止录制:前端点击停止,接口 `/api/recorder/stop` 200,后端录制线程平稳退出并清理资源,无孤儿进程泄漏。
### ⚠️ 待办(后续跟进)
1. **Phase 5:同步 5.44 / 5.202 服务器**(预检 running 执行后同步后端 3 文件 + 前端 dist);
2. **Phase 6:归档**(更新相关执行计划文档状态)。
---
## 📊 历史状态(会话 67,2026-09-09 · 5.44 定时执行被取消误导告警修复 + 形态 4 停止通知)
**背景**:用户收到 5.44 钉钉报告 exec_0043d9cce07248a084f40a2bc31787fc(总数 354 / 通过 54 / 失败 8 / 跳过 292 / 通过率 15.25%),而 9 月 8 日正常水平是 72-78%。排查结论 + 修复全 record 于 `Docs/PRD/问题处理/执行中心/_问题处理_5.44定时执行被取消导致钉钉报告通过率15.25%.md``_执行计划_...md`(v2)。
**背景**:用户收到 5.44 钉钉报告 exec_0043d9cce07248a084f40a2bc31787fc(总数 354 / 通过 54 / 失败 8 / 跳过 292 / 通过率 15.25%),而 9 月 8 日正常水平是 72-78%。排查结论 + 修复全 record 于 `Docs/PRD/问题处理/执行中心/_问题处理_5.44定时执行被取消导致钉钉报告通过率15.25%.md``_执行计划_...md`(v3 链接定稿)。
### 🔍 根因(一句话)
......@@ -201,6 +256,32 @@
---
## 📊 历史状态(会话 68,2026-09-09 · 表格选择器类号不稳定 + 时间面板触发修复)
**背景**:VNC 录制「完整预约流程验证」用例在 5.60 执行失败(原 45 步,第 13 步表格选择器 `el-table_2_column_9` 失配,第 17 步时间面板触发点击被忽略)。经排查发现两处根因:1) Element Plus 表格类号为模块级自增种子,hash 导航导致偏移;2) recorder_engine.py 忽略 `.el-date-editor` 内文本输入聚焦点击(未记录面板打开触发)。已完成三层修复:录制器代码 + 执行器加固 + 用例数据修复。
### ✅ 修复实施
- `recorder_engine.py`:新增 `pickerHost` 逻辑(文本输入位于 `.el-date-editor` 等容器内,不再忽略聚焦点击)
- `playwright_executor.py``_selector_exists_fast()` 失败后 2s 重试
- `fix_case_5f86_step17.py`:在容器内运行,插入 step 17「点击会议开始时间」+ step 18 时间项稳定选择器(`.el-picker-panel .time-select-item >> nth=2`),37 步
### ✅ 验证
- 执行 ID: `exec_cf9a0c1ff55b47219af3ee01491ff2de`
- 状态: ✅ completed(100% pass_rate)
- 37/37 步全部通过(含新增面板打开点击 + 时间选择步骤)
### ✅ 文档沉淀
- 问题处理文档 `_问题处理_表格选择器类号不稳定导致回放失败.md` 已修复
- 执行计划文档 `_执行计划_表格选择器类号不稳定导致回放失败.md` 已完成
- 归档文档 `_归档_5f866bda62c0466a9db881f8ca501375.md` 已生成
### ⚠️ 待办
- 5.60 容器 `docker compose restart app`(已完成)
- 清理 temp probe 脚本
- 所有本地新脚本 git 提交
---
## 📊 历史状态(会话 64B,2026-09-09 · 服务日志功能验收闭环 + 5.60 部署 + 提交推送)
> 来源:并行开发窗口分支(commit 63a20e4a / d159860a)
......@@ -855,4 +936,4 @@
---
*本文档由 Claude Code 于 2026-09-10 更新(会话 68 进行中:首页排版优化深色数据驾驶舱(方案B)+ 移除模块饼图趋势图全宽 + 5.60 两次部署;历史:会话 66 5.44 定时任务失败用例修复同步 5.60 部署环境;会话 65 noVNC 实时画面修复(iframe 误用 ws:// 协议);会话 64 录制器分辨率 1920x1080 + 部署权限修复;会话 63 定时任务未发送钉钉报告修复(`a4fe8140`);会话 62 sut-explore 缺口补齐——会务/预定2.0/巡检报表深度用例 12/12 全量通过 + 三台 MySQL 同步;会话 61 UI 执行监控目标服务器 Java 服务日志功能落地(TargetLogMonitor + ServerLogDrawer,提交 `18ed14cc`);会话 60 远程桌面录制器 Xvfb+noVNC 全量落地,提交 `978d4352`;会话 57 双窗口工作合并——钉钉通知版面重构与执行进度 total_cases 修复经 git rebase 合并提交推送;报告中心放开失败/取消历史执行随会话 57 一并归档。更早:会话 53 5.44 登录/落库双 P0 闭环(`f363abdd`);会话 52 真录制器 Phase 5(`b80abae3`);会话 51 截图瘦身部署 5.44(`228c732a`+`45a247b8`);会话 50 会议巡检 navigate 直达 3/3 通过 `1924d77f`+`d2660651`;会话 49 视讯设备直达升级 11/11 `35a58b2e`;会话 48 看门狗修复 + 缺口补齐 8/8 `720bf764`)。*
*本文档由 Claude Code 于 2026-09-10 更新(会话 68 完成:首页排版优化深色数据驾驶舱(方案B)+ 移除模块饼图趋势图全宽 + 5.60 两次部署,提交 `3a9d2db1`;会话 69:有头模式录制器 VNC 界面放大操作 + 流畅度优化——需求/执行计划输出 + 前后端实现 + dist 构建 + 5.60 部署 + 实机验证闭环(websockify 参数兼容性修复是黑屏根因),5.44/5.202 待同步部署;历史:会话 67 完成:5.44 定时执行被取消 15.25% 误导告警修复——形态 4 停止通知 + 链接定稿直达执行中心 /execution/ui + 5.60 部署与钉钉实测成功,5.44/5.202 待部署;会话 66 5.44 定时任务失败用例修复同步 5.60 部署环境——15 条逐条验证全部通过(提交 `29f3612d`);会话 65 noVNC 实时画面修复(iframe 误用 ws:// 协议);会话 64 录制器分辨率 1920x1080 + 部署权限修复;会话 63 定时任务未发送钉钉报告修复(`a4fe8140`);会话 62 sut-explore 缺口补齐——会务/预定2.0/巡检报表深度用例 12/12 全量通过 + 三台 MySQL 同步;会话 61 UI 执行监控目标服务器 Java 服务日志功能落地(TargetLogMonitor + ServerLogDrawer,提交 `18ed14cc`);会话 60 远程桌面录制器 Xvfb+noVNC 全量落地,提交 `978d4352`;会话 57 双窗口工作合并——钉钉通知版面重构与执行进度 total_cases 修复经 git rebase 合并提交推送;报告中心放开失败/取消历史执行随会话 57 一并归档。更早:会话 53 5.44 登录/落库双 P0 闭环(`f363abdd`);会话 52 真录制器 Phase 5(`b80abae3`);会话 51 截图瘦身部署 5.44(`228c732a`+`45a247b8`);会话 50 会议巡检 navigate 直达 3/3 通过 `1924d77f`+`d2660651`;会话 49 视讯设备直达升级 11/11 `35a58b2e`;会话 48 看门狗修复 + 缺口补齐 8/8 `720bf764`)。*
......@@ -133,6 +133,10 @@ class Settings:
XVFB_SCREEN: str = os.getenv("XVFB_SCREEN", "1920x1080x24") # 分辨率 x 色深
NOVNC_PORT: int = int(os.getenv("NOVNC_PORT", "6080")) # websockify 监听端口(noVNC 代理)
# noVNC 投流质量与缩放控制(用于放大操作 + 流畅度优化)
NOVNC_QUALITY: int = int(os.getenv("NOVNC_QUALITY", "6")) # JPEG 画质(6=平衡带宽/清晰度)
NOVNC_SCALE: str = os.getenv("NOVNC_SCALE", "scale") # scale/remote(默认 scale,放大时用 remote)
# 目标机资源监控配置(SSH 方式采集被测系统资源)
TARGET_MONITOR_ENABLED: bool = os.getenv("TARGET_MONITOR_ENABLED", "false").lower() == "true"
# 注意:目标机 SSH 采样为同步阻塞且跨主机(默认关闭)。如开启需量大,建议采样间隔调大(TARGET_MONITOR_INTERVAL)
......
......@@ -286,7 +286,9 @@ class PlaywrightExecutor:
"--disable-automation",
# 容器环境兼容(共享内存不足会导致渲染异常/崩溃)
"--disable-dev-shm-usage",
# 容器无 GPU:禁用 GPU/2D 加速避免软件渲染争抢 CPU(有头投流画面更稳)
"--disable-gpu",
"--disable-accelerated-2d-canvas",
],
)
......
......@@ -76,7 +76,8 @@ def _start_xvfb_and_novnc() -> None:
if not (os.path.exists(x_socket) and _XVFB_PROC and _XVFB_PROC.poll() is None):
logger.info(f"启动 Xvfb: {display} -screen 0 {screen}")
_XVFB_PROC = subprocess.Popen(
[xvfb_bin, display, "-screen", "0", screen, "-nolisten", "tcp"],
[xvfb_bin, display, "-screen", "0", screen,
"-nolisten", "tcp", "-ac", "-noreset"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
......@@ -93,7 +94,7 @@ def _start_xvfb_and_novnc() -> None:
logger.info(f"启动 x11vnc: display={display} → VNC 5900")
_X11VNC_PROC = subprocess.Popen(
[x11vnc_bin, "-display", display, "-shared", "-forever",
"-rfbport", "5900", "-nopw", "-quiet"],
"-rfbport", "5900", "-nopw", "-quiet", "-cursor", "arrow"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
......@@ -103,6 +104,8 @@ def _start_xvfb_and_novnc() -> None:
novnc_web = "/usr/share/novnc"
logger.info(f"启动 websockify: :{port} --web {novnc_web} → localhost:5900")
_WEBSOCKIFY_PROC = subprocess.Popen(
# 注意:5.60 容器内 websockify 版本不支持 --loop / -b 选项(启动即退出);
# 且容器内绑定 127.0.0.1 会导致 Docker 端口发布无法命中,必须监听所有网卡
[websockify_bin, "--web", novnc_web, str(port), "localhost:5900"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
......
......@@ -892,3 +892,102 @@ class TestXvfbNovnc:
assert "降级" in session.notice
finally:
re_mod.recorder_engine._sessions.pop(session.session_id, None)
def test_xvfb_process_args_tuned(self, monkeypatch):
"""Xvfb/x11vnc/websockify 启动参数:-ac -noreset / -cursor arrow / websockify 不带 --loop/-b"""
import shutil as _shutil
import os as _os
from app.services import recorder_engine as re_mod
cmds = {}
def fake_which(name):
return {"Xvfb": "xvfb", "x11vnc": "vnc", "websockify": "ws"}.get(name)
def fake_popen(cmd, **kwargs):
cmds[cmd[0]] = cmd
return type("FakeProc", (), {"poll": lambda self: 0})() # 立即退出,避免复用干扰
monkeypatch.setattr(_shutil, "which", fake_which)
monkeypatch.setattr(re_mod.subprocess, "Popen", fake_popen)
# X socket 已存在但进程已死 → 需要重新拉起 Xvfb
monkeypatch.setattr(_os.path, "exists", lambda p: True)
re_mod._XVFB_PROC = None
re_mod._X11VNC_PROC = None
re_mod._WEBSOCKIFY_PROC = None
try:
re_mod._start_xvfb_and_novnc()
assert "-ac" in cmds.get("xvfb", []) and "-noreset" in cmds.get("xvfb", [])
assert "arrow" in cmds.get("vnc", [])
# websockify 容器版不支持 --loop / -b(启动即退出成 zombie → noVNC 黑屏),
# 且 -b 127.0.0.1 会阻断 Docker 6080 端口发布;最终命令形态固定为
# websockify --web <web> <port> localhost:5900(监听所有网卡)
ws_cmd = cmds.get("ws", [])
assert "--loop" not in ws_cmd and "-b" not in ws_cmd
assert "--web" in ws_cmd and ws_cmd[-1] == "localhost:5900"
finally:
re_mod._XVFB_PROC = None
re_mod._X11VNC_PROC = None
re_mod._WEBSOCKIFY_PROC = None
# ==================== noVNC 放大操作 + 流畅度(缩放工具条)防回归 ====================
class TestVncZoom:
"""noVNC 放大操作(vncUrl 参数 + 缩放工具条)+ NOVNC 配置防回归测试"""
def test_novnc_quality_config_default(self):
"""NOVNC_QUALITY / NOVNC_SCALE 配置默认值(quality=6, scale)"""
from app.config import Settings
s = Settings()
assert s.NOVNC_QUALITY == 6
assert s.NOVNC_SCALE == "scale"
def test_novnc_quality_env_override(self):
"""NOVNC_QUALITY / NOVNC_SCALE 环境变量可覆盖(config 字段在 import 时读取 env,用子进程验证)"""
import os
import subprocess
import sys
backend_dir = str(Path(__file__).resolve().parent.parent)
code = (
"from app.config import Settings;"
"s = Settings();"
"print(s.NOVNC_QUALITY, s.NOVNC_SCALE)"
)
env = {**os.environ, "NOVNC_QUALITY": "3", "NOVNC_SCALE": "remote"}
proc = subprocess.run(
[sys.executable, "-c", code],
capture_output=True, text=True, env=env, cwd=backend_dir, timeout=60,
)
assert proc.returncode == 0, proc.stderr
assert proc.stdout.strip() == "3 remote"
def test_recorder_vue_vnc_url_and_toolbar(self):
"""前端 Recorder.vue:vncUrl 含 resize/quality 参数 + 缩放工具条(放大/缩小/1:1/适应窗口)"""
import os
vue_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
"frontend", "src", "views", "Recorder.vue",
)
if not os.path.exists(vue_path):
pytest.skip("frontend Recorder.vue 不存在(仅后端仓库场景)")
with open(vue_path, "r", encoding="utf-8") as f:
content = f.read()
# vncUrl 含 noVNC 参数(自动连接 + 画质)
assert "autoconnect=true" in content
assert "quality=6" in content
# 缩放工具条 4 操作 + 提示
assert "vncZoomIn" in content
assert "vncZoomOut" in content
assert "适应窗口" in content
# iframe 像素级缩放(放大不破坏 noVNC 坐标映射)
assert "VNC_BASE_WIDTH" in content
assert "VNC_BASE_HEIGHT" in content
# 放大后可平移(overflow auto)
assert "overflow: auto" in content
......@@ -101,14 +101,33 @@
<div class="card-header">
<span>实时录制画面(noVNC)</span>
<el-tag size="small" type="success" effect="plain">有头模式 · Xvfb 虚拟桌面</el-tag>
<div class="vnc-toolbar">
<el-button-group>
<el-button size="small" :disabled="vncZoomInDisabled" @click="vncZoomIn">
<el-icon><ZoomIn /></el-icon>
</el-button>
<el-button size="small" :disabled="vncZoomOutDisabled" @click="vncZoomOut">
<el-icon><ZoomOut /></el-icon>
</el-button>
<el-button size="small" @click="vncFrameScale = 1">1:1</el-button>
<el-button size="small" @click="vncFrameScale = 'fit'">适应窗口</el-button>
</el-button-group>
<el-tag size="small" type="info" effect="plain" class="vnc-tip">
缩放 {{ vncFrameScale === 'fit' ? '适应' : (vncFrameScale * 100).toFixed(0) + '%' }}
· Ctrl+滚轮可放大画面
</el-tag>
</div>
</div>
</template>
<iframe
:src="vncUrl"
class="vnc-frame"
title="被测系统录制画面"
allow="clipboard-read; clipboard-write"
></iframe>
<div class="vnc-frame-wrap" :class="{ 'is-zoomed': vncFrameScale !== 'fit' }">
<iframe
:src="vncUrl"
class="vnc-frame"
:style="vncFrameStyle"
title="被测系统录制画面"
allow="clipboard-read; clipboard-write"
></iframe>
</div>
</el-card>
<el-row :gutter="20" class="recorder-content">
......@@ -329,6 +348,8 @@ import {
VideoPause,
Plus,
FolderChecked,
ZoomIn,
ZoomOut,
} from '@element-plus/icons-vue'
import type { Module } from '@/types/module'
import { moduleApi, buildModuleTree } from '@/api/modules'
......@@ -356,11 +377,60 @@ const modules = ref<Module[]>([])
* 通过当前访问后端的主机名拼接(前端 3000 / 后端 8001 / 生产 80 同机)。
* 注意:iframe src 必须用 http://(ws:// 不是页面协议,浏览器拒绝导航);
* WebSocket 由 noVNC 页面内部 autoconnect 自动建立(ws://...:6080/websockify)。
* - resize=scale:整体缩放适应窗口(默认,画面完整)
* - quality=6:JPEG 画质档位,平衡带宽与清晰度,降低卡顿
* - Ctrl+滚轮:noVNC 内置缩放(remote 模式生效)
*/
const vncUrl = computed(
() =>
`http://${window.location.hostname}:6080/vnc.html?autoconnect=true&reconnect=true&resize=scale`
`http://${window.location.hostname}:6080/vnc.html?autoconnect=true&reconnect=true&resize=scale&quality=6`
)
/**
* noVNC 画面缩放(视觉层,直接调整 iframe 元素尺寸)。
* - 'fit':适应窗口(默认,100% 宽),noVNC 内部 resize=scale 铺满
* - 数字:远程屏幕像素倍数,1 = 1920x1080 (1:1),2 = 3840x2160 (2x 放大)
* 说明:noVNC 按 iframe 实际尺寸渲染远端 framebuffer 并换算坐标,
* 因此放大后点击/输入仍精确落在目标元素;外层容器 overflow:auto 提供滚动平移。
*/
const vncFrameScale = ref<'fit' | number>('fit')
/** 放大/缩小按钮禁用态(fit 视为 1x,达边界禁用) */
const vncZoomInDisabled = computed(
() => vncFrameScale.value !== 'fit' && vncFrameScale.value >= 3,
)
const vncZoomOutDisabled = computed(
() => vncFrameScale.value === 'fit' || vncFrameScale.value <= 0.5,
)
/** 远程虚拟桌面基准分辨率(与 Xvfb 1920x1080 / Chromium viewport 一致) */
const VNC_BASE_WIDTH = 1920
const VNC_BASE_HEIGHT = 1080
/** iframe 缩放样式 */
const vncFrameStyle = computed(() => {
if (vncFrameScale.value === 'fit') return {}
const scale = vncFrameScale.value
return {
width: `${VNC_BASE_WIDTH * scale}px`,
height: `${VNC_BASE_HEIGHT * scale}px`,
}
})
/** 放大(作用于显示尺寸,不影响坐标) */
const vncZoomIn = () => {
if (vncFrameScale.value === 'fit') {
vncFrameScale.value = 1
return
}
vncFrameScale.value = Math.min(3, Math.round((vncFrameScale.value + 0.25) * 4) / 4)
}
/** 缩小 */
const vncZoomOut = () => {
if (vncFrameScale.value === 'fit') return
vncFrameScale.value = Math.max(0.5, Math.round((vncFrameScale.value - 0.25) * 4) / 4)
}
/** 模块树形数据(用于 el-tree-select) */
const moduleTreeData = computed(() => buildModuleTree(modules.value))
......@@ -794,12 +864,42 @@ onBeforeUnmount(() => {
.vnc-panel {
margin-bottom: 20px;
.vnc-frame-wrap {
position: relative;
overflow: auto;
max-height: 640px;
border: 1px solid #e4e7ed;
border-radius: 4px;
background: #1e1e1e;
&.is-zoomed {
padding: 20px;
}
}
.vnc-frame {
display: block;
width: 100%;
height: 620px;
border: 1px solid #e4e7ed;
border-radius: 4px;
border: none;
background: #1e1e1e;
image-rendering: crisp-edges;
}
.vnc-toolbar {
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
.el-tag {
font-size: 12px;
}
.vnc-tip {
color: #666;
}
}
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论