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

fix(ui): 5.44登录落首页与MySQL写库截断修复 + 5.60部署记录

- playwright_executor: 登录成功后显式 navigate 到首页并等 .block,避免
  登录态步骤因首页 DOM 未渲染而失败
- execution_service: error_message 按 60000 字节截断、steps log/error 按
  20000 截断,规避 MySQL TEXT 65535 上限;pass_rate 改用滚动整数计数,
  不再读过期 ORM 列(MissingGreenlet)
- config: 补 PLATFORM_BASE_URL 远程字段(钉钉报告链接用)
- HANDOFF_UI自动化: 记录会话53(5.44 P0 修复 + 5.60 部署验证)
Co-Authored-By: 's avatarClaude Code <noreply@anthropic.com>
上级 0dff7768
# HANDOFF — UI自动化测试交接文档 # HANDOFF — UI自动化测试交接文档
> **生成时间**: 2026-09-07 > **生成时间**: 2026-09-08
> **当前分支**: `platform-auto-test` > **当前分支**: `platform-auto-test`
> **最近提交**: `b80abae3` feat(recorder): 真录制器与定位质量提升 Phase 1-5(引擎/路由/E2E/前端) > **最近提交**: `b80abae3` feat(recorder): 真录制器与定位质量提升 Phase 1-5(引擎/路由/E2E/前端)(会话 53 改动未提交,工作区待提交)
> **状态**: 🟢 **会话52 完成(2026-09-07):真录制器 Phase 5 —— recorder.py /save 三重 P0 修复、recorder_engine.py 主循环事件饥饿修复(page.is_closed() 不泵事件队列)、新增 test_recorder_e2e.py(10 测试,后端 408/408 全绿)、Recorder.vue TS 修复(togglePause 前端暂停语义),前端 build 零报错,已提交推送。上一会话51:定时任务截图瘦身已部署 5.44,其遗留 P0=5.44 全量套件登录落首页失败排查中**(历史会话见下章节) > **状态**: 🟢 **会话53 完成(2026-09-08):5.44 登录/落库双 P0 攻坚闭环(do_login→_navigate_to_home 主动落首页 + error_message 60000B 截断 + MissingGreenlet 绕过),5 条核心用例 3轮回归从全失败→4/5 通过,仅剩看板断言文案 1 条('.block' 实际文本「新建会议」≠ 预期「今日会议」);同步部署 5.60(7 后端文件 + 前端 dist 95 文件,健康检查全绿,RECORDER_ENABLED 生效)**(历史会话见下章节)
--- ---
## 📊 当前状态(会话 52,2026-09-07 · 真录制器 Phase 5:E2E 测试 + 两个 P0 修复) ## 📊 当前状态(会话 53,2026-09-08 · 5.44 登录/落库 P0 攻坚闭环 + 5.60 部署)
**背景**:承接会话 51 遗留 P0(5.44 全量套件 ~100% 失败,`do_login` 后落不到首页,卡 `.nav_right_list.el-dropdown-selfdefine` 等待),本次在 5.44 容器内完成根因定位、代码修复、用例数据修复与回归,并将全部修复同步部署到 5.60。
### ✅ P0 修复 1:登录落首页(`playwright_executor.py`,MD5 b6205b21 已上 5.44/5.60)
- **根因**`do_login` 只保证「离开登录路由」(URL 判据)→ 首页 DOM(`.nav_right_list` 等)尚未渲染就进入后续等待 → 全部依赖登录态用例超时。
- **修复**`need_auto_login` 且首次登录成功分支,登录后显式调用 `self._navigate_to_home()`(等 `.block`)兜底;协议弹窗经 `confirm_texts` 循环点击(容器内实测弹窗文本「点击确定表示您已阅读并同意…」)。
- **实测**:auto_login 登录 11s 达首页,`.block` 出现;巡检报表 3 步 9.37s passed。
### ✅ P0 修复 2:MySQL 写库崩溃(`execution_service.py`,MD5 80fd2224 已上 5.44/5.60)
- **root cause A**:断言失败把整段页面 body/CSS 带进 `error_message`,超 MySQL TEXT 65535B → `Data too long`(1406) 使本用例结果写库失败。
- 修复:`_persist_case_result``error_message` 截断至 **60000 bytes**`steps_result` 的 log/error 各截断至 **20000**(附「...[截断]」标记)。
- **root cause B**`execution.calculate_pass_rate()` 读已过期 ORM 列 → `MissingGreenlet`;rollback 后 Session 进入 prepared 态连锁 500。
- 修复:改用滚动 `_count_results` 出的整数,显式赋值 `passed/failed/skipped/total_cases/pass_rate`,不再读列计算。
### ✅ 用例数据修复(`fix_544_login_cases.py`,5.44 已执行,409 用例)
| 用例 | 修复 |
|------|------|
| 3 条登录验证(case_178f6ee1/e044fc4d/abee2116) | 重写为 auto_login + 等 `.block` + 断言 `.block` visible(原非法 input 动作 / 错误验证码选择器 / 等 `.nav_right_list` 全废弃) |
| 巡检报表(case_588392a4) | 改 auto_login + 首页入口 `[id='data_list.inspect_report']` 点击(monitor 直达竞态经验沉淀) |
| 首页看板(case_6699d6dd) | 等 `.nav_right_list` 去掉,改等 `.block` + visible 断言 + text 断言 |
| 删除 4 条 | 垃圾用例 case_2d4dd2ed + 3 条旧 drawer 版会议巡检(case_b9e24309/17aade9c/eb161109) |
### ✅ 回归结果(exec_09f4a61b 第 3 轮)
**4 passed + 1 failed**(登录×3 ✅ / 巡检报表 ✅ / 首页看板 ✗)—— 唯一失败:步骤 3 断言 `.block` 文本期望「今日会议」,**容器内 DOM 探测实测 .block 文本 = 「新建会议」**(首页常用功能卡片首项为「新建会议」)。
### ✅ 5.60 部署(2026-09-08)
- 部署前 MD5/diff 核对,排除其他窗口 performance/project WIP(main.py 无需改,已注册 projects)。
- 上传 7 后端文件(config/recorder/element_locator/recorder_engine/playwright_executor/execution_service/database)+ 前端 dist 95 文件。
- **config.py 保留远程独有 `PLATFORM_BASE_URL`**(钉钉报告链接用,5.60 实测值 `http://192.168.5.60`),与本地 RECORDER/Screenshot 配置合并。
- 验证:容器 py_compile 7/7 OK → restart → healthy;`/health` 200;`RECORDER_ENABLED=True`/TTL=300/CANDIDATE_LIMIT=4;`/api/recorder/status?session_id=test_check_only` 返回 404「录制会话不存在」(路由已生效);`/api/element-locator/pages` 200;Recorder JS chunk 200(前端已加载)。
### ⚠️ 待办
- **5.44 看板用例收尾**:将 case_6699d6dd 步骤 3 断言修正为「新建会议」或 visible 断言 → 5/5 回归确认
- **5.202 部署**本改动(会话 51 遗留,本次仅部署 5.44/5.60)
- **5.60 录制器真实环境验收**(会话 52 P2:有头浏览器自动登录 → 录制 → 保存 → 执行回放)
- **git 提交**本会话改动(playwright_executor.py / execution_service.py / config.py / HANDOFF + fix 脚本)
- 清理诊断脚本(diag_544_*.py、dump_544_cases.py、dump9_cases.json、tmp_wait_deploy_544.py 等)
---
## 📊 历史状态(会话 52,2026-09-07 · 真录制器 Phase 5:E2E 测试 + 两个 P0 修复)
**背景**:继续 `_PRD/需求文档/用例管理/_PRD_真录制器与定位质量提升_计划执行.md` Phase 5(Phase 1-4 引擎/路由/捕获脚本/前端已在工作区实现,未验证)。 **背景**:继续 `_PRD/需求文档/用例管理/_PRD_真录制器与定位质量提升_计划执行.md` Phase 5(Phase 1-4 引擎/路由/捕获脚本/前端已在工作区实现,未验证)。
...@@ -215,4 +256,4 @@ ...@@ -215,4 +256,4 @@
--- ---
*本文档由 Claude Code 于 2026-09-07 更新(会话 52:真录制器 Phase 5 完成 —— recorder.py /save 三重 P0 修复(孤儿 except 语法错误 + 用例创建漏出 else 分支 + case_service NameError,重写为 CaseService.create + auto_login 注入)、recorder_engine.py 主循环事件饥饿修复(page.is_closed() 不泵事件队列 → page.evaluate("1") 真实 CDP 往返泵送 expose_binding 回调)、新增 test_recorder_e2e.py 10 测试(本地 mock 页 + 独立 SQLite 全离线隔离)、Recorder.vue 两个 TS 编译错误修复(togglePause 前端暂停语义 + 删 stepCounter)。验证:后端 408/408 全绿 + 前端 npm run build 零报错,提交 `b80abae3` 已推送。历史:会话 51 截图瘦身部署 5.44(`228c732a`+`45a247b8`,遗留 5.44 登录落首页 P0);会话 50 会议巡检 navigate 直达 3/3 通过 `1924d77f`+`d2660651`;会话 49 视讯设备直达升级 11/11 `35a58b2e`;会话 48 看门狗修复 + 缺口补齐 8/8 `720bf764`)。* *本文档由 Claude Code 于 2026-09-08 更新(会话 53:5.44 登录/落库双 P0 闭环(do_login 后 _navigate_to_home 主动落首页 + error_message 60000B/log-error 20000 截断 + 滚动计数绕过 MissingGreenlet),fix_544_login_cases.py 重写 5 用例+删 4 垃圾用例(409 总数),3 轮回归 4/5(看板 .block 文本实测「新建会议」);5.60 部署 7 后端文件 + dist 95 文件全绿(RECORDER_ENABLED 生效、PLATFORM_BASE_URL 保留)。历史:会话 52 真录制器 Phase 5(`b80abae3`);会话 51 截图瘦身部署 5.44(`228c732a`+`45a247b8`);会话 50 会议巡检 navigate 直达 3/3 通过 `1924d77f`+`d2660651`;会话 49 视讯设备直达升级 11/11 `35a58b2e`;会话 48 看门狗修复 + 缺口补齐 8/8 `720bf764`)。*
...@@ -94,6 +94,9 @@ class Settings: ...@@ -94,6 +94,9 @@ class Settings:
# 被测系统配置 # 被测系统配置
TARGET_URL: str = os.getenv("TARGET_URL", "https://192.168.5.44") TARGET_URL: str = os.getenv("TARGET_URL", "https://192.168.5.44")
# 平台对外访问地址(钉钉报告通知拼"查看完整报告"链接用,如 http://192.168.5.44)
PLATFORM_BASE_URL: str = os.getenv("PLATFORM_BASE_URL", "")
# 认证接口路径配置(默认对齐被测系统微前端路径) # 认证接口路径配置(默认对齐被测系统微前端路径)
AUTH_LOGIN_PATH: str = os.getenv("AUTH_LOGIN_PATH", "/platform/api/auth/login") AUTH_LOGIN_PATH: str = os.getenv("AUTH_LOGIN_PATH", "/platform/api/auth/login")
AUTH_CAPTCHA_PATH: str = os.getenv("AUTH_CAPTCHA_PATH", "/platform/api/code") AUTH_CAPTCHA_PATH: str = os.getenv("AUTH_CAPTCHA_PATH", "/platform/api/code")
......
...@@ -1389,6 +1389,10 @@ class PlaywrightExecutor: ...@@ -1389,6 +1389,10 @@ class PlaywrightExecutor:
login_success = self.do_login() login_success = self.do_login()
if not login_success: if not login_success:
logger.warning("自动登录失败,可能影响需要登录态的测试步骤") logger.warning("自动登录失败,可能影响需要登录态的测试步骤")
else:
# 登录成功后主动落到首页:do_login 只保证离开登录路由(URL 判据),
# 首页 DOM(.nav_right_list 等)可能尚未渲染,显式导航 + 等 .block 兜底
self._navigate_to_home()
elif need_auto_login and self._is_logged_in: elif need_auto_login and self._is_logged_in:
# 已登录但需要确保从主页开始(上一个用例可能停留在子页面) # 已登录但需要确保从主页开始(上一个用例可能停留在子页面)
self._navigate_to_home() self._navigate_to_home()
......
...@@ -706,17 +706,34 @@ class ExecutionService: ...@@ -706,17 +706,34 @@ class ExecutionService:
) )
case_result_obj.end_time = datetime.now() case_result_obj.end_time = datetime.now()
case_result_obj.retry_count = result.retry_count case_result_obj.retry_count = result.retry_count
case_result_obj.error_message = result.error_message # 截断错误信息:断言失败会把整段页面 body/CSS 带进 error_message,
# MySQL TEXT 列仅 65535 字节,超长直接 1406 导致本用例结果写库失败。
error_msg = result.error_message
if error_msg:
error_msg = (error_msg.encode("utf-8", "replace")[:60000]
.decode("utf-8", "ignore"))
case_result_obj.error_message = error_msg
case_result_obj.steps_result = [ case_result_obj.steps_result = [
{"order": s.order, "name": s.name, "action": s.action, {"order": s.order, "name": s.name, "action": s.action,
"status": s.status, "duration": s.duration, "status": s.status, "duration": s.duration,
"screenshot": s.screenshot, "log": s.log, "error": s.error} "screenshot": s.screenshot,
"log": (s.log[:20000] + "...[截断]" if s.log and len(s.log) > 20000 else s.log),
"error": (s.error[:20000] + "...[截断]" if s.error and len(s.error) > 20000 else s.error)}
for s in result.steps_result for s in result.steps_result
] ]
execution.passed = await self._count_results(execution_id, "passed") passed = await self._count_results(execution_id, "passed")
execution.failed = await self._count_results(execution_id, "failed") failed = await self._count_results(execution_id, "failed")
execution.skipped = await self._count_results(execution_id, "skipped") skipped = await self._count_results(execution_id, "skipped")
execution.pass_rate = execution.calculate_pass_rate() # 不读 execution 列属性计算通过率:失败回滚后 ORM 实例已过期,
# calculate_pass_rate() 读列会触发同步 lazy load → MissingGreenlet。
execution.passed = passed
execution.failed = failed
execution.skipped = skipped
# 显式覆盖 total_cases(passed+failed+skipped),计算通过率不再依赖
# calculate_pass_rate() 读 self.total_cases 列(commit 后 ORM 实例已过期,
# 读列会触发同步 lazy load → MissingGreenlet)。
execution.total_cases = passed + failed + skipped
execution.pass_rate = round(passed / (passed + failed + skipped) * 100, 2) if (passed + failed + skipped) else 0.0
await self.db.commit() # 立即提交,前端轮询可见 await self.db.commit() # 立即提交,前端轮询可见
await manager.broadcast(execution_id, { await manager.broadcast(execution_id, {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论