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

feat(ui-automation): 补充会务管理/预定2.0/巡检报表深度用例(第3轮12/12通过)+ 同步三台

- 新增 12 条深度 UI 用例(会务统筹×3 + 会务工单×3 + 预定数据×3 + 巡检报表×3),第 3 轮执行 exec_8b0332dc 12/12 全量通过
- headless 微前端冷挂载修复:等待锚点改 :has-text 文本(inner route 挂载信号)+ 双等待点(文本 30s + canvas/.el-table 15-30s)
- 会务统筹 3 条旧用例直达 URL 修正 ConferenceManage→ConferenceManagement(旧 URL 404)
- 巡检报表走首页 inspect_report 入口点击,避开 monitor 直达 URL 竞态
- 用例已同步 5.44/5.202/5.60 三台 MySQL(各 INSERTED 12)
- backend/data/test_platform.db 解决 stash 冲突(取含 12 条新用例版本,与 HEAD 差异=+12 用例+3 条 URL 修复,无丢失)
- HANDOFF_UI自动化.md 记录会话 62(会话 61 TargetLogMonitor 章节保留归档)
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 18ed14cc
# HANDOFF — UI自动化测试交接文档
> **生成时间**: 2026-09-08
> **生成时间**: 2026-09-09
> **当前分支**: `platform-auto-test`
> **最近提交**: `978d4352` feat(recorder): 远程桌面录制器 Xvfb+noVNC 实现 + 部署 5.60 实机验收
> **状态**: 🟢 **会话60 完成(2026-09-08):远程桌面录制器 Xvfb+noVNC 实现 + 部署 5.60 实机验收通过**(历史会话见下章节)
> **最近提交**: `18ed14cc` feat(recorder): 远程桌面录制器 Xvfb+noVNC 协议修复 + 5.60 实机验收(会话 61)
> **状态**: 🟢 **会话62 完成(2026-09-09):sut-explore 缺口补齐——会务/预定2.0/巡检报表深度用例第3轮 12/12 全量通过 + 三台 MySQL 同步**(历史会话见下章节)
---
## 📊 当前状态(会话 60,2026-09-08 · 远程桌面录制器 Xvfb+noVNC 全量落地)
## 📊 当前状态(会话 62,2026-09-09 · sut-explore 缺口补齐:会务/预定2.0/巡检报表深度用例)
**背景**:sut-explore 盘点时发现会务管理 / 预定2.0 / 巡检报表三个模块用例数量偏少,经真实浏览器探测 DOM 后补充深度用例,headless 下迭代 3 轮修到全量通过。
### ✅ 本会话新增 12 条用例(第 3 轮 exec_8b0332dc 12/12 通过)
| 模块 | 用例 | 关键点 |
|------|------|--------|
| 会务管理(module_69cba085) | 会务统筹-页面访问与表格验证 / 操作按钮与检索区验证 / 历史会务Tab切换验证 | 直达 URL 修正为真实 `ConferenceManagement`(旧 `ConferenceManage` 404);`:has-text` 文本锚点等待 inner route 挂载 |
| 会务管理 | 会务工单-页面访问与列头验证 / 筛选区与分页组件验证 / 历史工单Tab切换验证 | 双等待点:`:has-text("会务工单")` + `.el-table` |
| 预定2.0(module_parent_yuding20) | 预定数据-核心指标卡验证 / 图表渲染数量验证(canvas≥5)/ 用户参会与云视频区块验证 | StatisticsModule 直达;冷挂载防护:文本锚点 30s + canvas 15s 分两步等待 |
| 运维管理(module_ddec220a) | 巡检报表-表格列头完整性验证 / 工具栏按钮验证 / 搜索筛选验证 | **稳定入口**:navigate 首页 → 点击 `[id="data_list.inspect_report"]`(避开 monitor 直达 URL 竞态,见 [[monitor-checklist-direct-url-race]]) |
### ✅ 迭代过程(3 轮)
- 第 1 轮:navigate 直达 URL 竞态 + 弱断言 → 修正真实 URL / 首页入口点击
- 第 2 轮 0/3:headless 下 micro-app 只渲染 shell,等待 `.el-table`/`canvas` 30s 超时 → 等待锚点改 `:has-text` 文本(inner route 真正挂载信号)+ 更长超时
- 第 3 轮 **exec_8b0332dc 12/12 通过**(会务统筹×3 ~11s / 会务工单×3 ~10-17s / 巡检×3 ~8-9s / 预定×3 ~15-16s)
### ✅ 三台服务器同步
`backend/scripts/sync_cases_to_servers.py`(paramiko SSH + docker exec + pymysql UPSERT):
- 5.44:409→421(INSERTED 12)
- 5.202:413→425(INSERTED 12)
- 5.60:415→427(INSERTED 12)
### 📦 提交记录
- 本会话提交:`generate_conference_cases.py` / `generate_yuding_inspection_deep_cases.py`(生成脚本)+ `backend/data/test_platform.db`(12 条新用例)+ 本文档
- **冲突说明**:拉取时 HANDOFF 头块与 `backend/data/test_platform.db` 处于 UU 未合并状态(stash 链残留,另一窗口会话 61 TargetLogMonitor 已并入远端 18ed14cc)。本窗口以 `git checkout-index --stage=3` 取包含本会话 12 条用例的版本解决 DB 冲突;HANDOFF 由外部窗口先行解决,本会话编号顺延为 **62** 归档,未覆盖会话 61 内容。
### 📁 生成脚本(可重复执行,幂等:先删同名再插入)
- `backend/scripts/generate_conference_cases.py`
- `backend/scripts/generate_yuding_inspection_deep_cases.py`
---
## 📊 历史状态(会话 61,2026-09-09 · UI 执行监控目标服务器 Java 服务日志功能落地)
**背景**:平台 UI 自动化执行后,前端只能看到 Playwright 步骤状态、截图与控制台信息,无法获知被测服务器(192.168.5.44)后端各 Java 微服务在用例时间窗口内报了什么 ERROR/Exception。排查失败用例需手动 SSH 登录翻多份日志文件,效率低下。本会话实现「用例级服务日志自动采集 + 前端可视化排查」全链路。
### ✅ 后端采集链路(TargetLogMonitor)
| 改动 | 说明 |
|------|------|
| `backend/app/executors/target_log_monitor.py`(新建) | SSH 单条复合命令一次性扫描 8 个 Java 服务日志(gateway / auth / modules-system / meeting-inner-api / quartz / mqtt / message-scheduling / api),`tail -n 500` + 时间窗口 grep 夹逼,单次采集 0.2~0.5s;异常堆栈按块分组返回 |
| `backend/app/models/case_result.py` | 新增 `server_log_errors` JSON 字段(默认 `{}`),`to_dict()` 透出 |
| `backend/app/database.py` | 启动时 `_ensure_columns` 兼容补列(SQLite `ALTER TABLE case_results ADD COLUMN server_log_errors JSON NULL`) |
| `backend/app/services/execution_service.py` | `run_all_cases_sync` 用例循环记录精确 `case_start/case_end`,用例间隙同步采集并落库,随 `case_complete` WebSocket 广播 |
| `backend/app/routers/executions.py` | 新增 `GET /api/executions/{id}/results/{case_id}/server-logs` 独立拉取接口 |
| `backend/app/config.py` | `TARGET_LOG_MONITOR_ENABLED`(默认开启)+ `TARGET_LOG_TAIL_LINES`(默认 500) |
### ✅ 前端可视化(ServerLogDrawer)
| 改动 | 说明 |
|------|------|
| `frontend/src/components/ServerLogDrawer.vue`(新建) | 70% 宽暗黑控制台风格 el-drawer:服务下拉(带错误数徽标)、日志级别过滤(全部/ERROR/WARN)、关键词搜索、一键复制全部日志;ERROR 块红色高亮 + 堆栈展示 |
| `frontend/src/views/Execution.vue` | 展开行新增「服务端异常」列(红色 Tag,前 2 服务 `meeting-inner-api:2` + 溢出计数)、操作列新增「服务日志」按钮(无数据时自动调 API 拉取) |
| `frontend/src/components/StepDetailPanel.vue` | 步骤概览右上角新增「查看对应服务端日志」入口,点击 emit `open-server-logs` 联动抽屉 |
| `frontend/src/api/executions.ts` | 新增 `getServerLogs(executionId, caseId)` API 封装 |
### ✅ 验证结果
- 后端:直接调用 `get_case_server_logs` 路由验证 `has_errors/total_errors/services` 结构与 404 分支 ✅
- DB:`server_log_errors` 列迁移补列成功,`case_complete` WebSocket 广播已含该字段 ✅
- 前端:`vue-tsc` 类型检查 0 错误 + `vite build` 通过 + ESLint 清理 ✅
- ⚠️ **8001 后端为 stale 进程**(未带 `--reload`,无新路由),需重启后才能进行真实用例 E2E 验收(采集耗时/服务 Tab 切换/搜索/复制)
### 📁 文档沉淀
- `Docs/PRD/需求文档/执行中心/_PRD_需求文档_UI执行监控目标服务器Java服务日志.md`(状态:已完成)
- `Docs/PRD/需求文档/执行中心/_执行计划_UI执行监控目标服务器Java服务日志.md`(状态:已完成)
### 📦 提交记录
- 本会话改动**尚未提交**(工作区含其他窗口 WIP:模块管理 project.*、diag_544_*、lzzsh_* 等,未纳入),待用户确认后按 /GitCommit 提交流程处理
### ⚠️ 遗留 / 注意事项
- **采集开销**:单用例窗口采集已控制在 0.5s 内,SSH 失败/目标机不可达时降级记录 debug 日志,不阻塞用例执行
- **服务清单**:当前 8 个服务为实机探测确定的日志路径,新增服务需在 `_SERVICE_LOG_FILES` 补充映射
---
## 📊 历史状态(会话 60,2026-09-08 · 远程桌面录制器 Xvfb+noVNC 全量落地)
**背景**:会话 58 遗留 P0——无头模式无法交互录制。用户确认采用方案 B(Xvfb + noVNC 虚拟桌面):容器内装 Xvfb + x11vnc + websockify + noVNC,录制器页面内嵌 noVNC 实时画面,用户直接在网页里操作 5.44 完成交互录制。
......@@ -459,4 +533,4 @@
---
*本文档由 Claude Code 于 2026-09-08 更新(会话 60:远程桌面录制器 Xvfb+noVNC 全量落地,提交 `978d4352` 11 文件未推送;历史:会话 57 双窗口工作合并——本窗口钉钉通知版面重构(三形态统一标题「UI自动化定时报告」+ 分区卡片式 blocks/`\n\n` 连接 + 移除失败明细仅一句统计提示,踩坑沉淀「钉钉 markdown 单 `\n` 折叠为空格须 `\n\n` 分行」,钉钉单测 29/29 + 后端全量 452/452 通过,已部署 5.60 MD5 `c954e976`)与另一窗口执行进度 total_cases 显示修复(会话 54 编号撞号,`b227d759` 437 测试全绿已部署 5.60)经 git rebase 合并提交推送;报告中心放开失败/取消历史执行(reports.py + Reports.vue,三台 total 59/59/994 全通)随本窗口一并归档。更早:会话 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-09 更新(会话 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`)。*
# -*- coding: utf-8 -*-
"""
模块名称: generate_conference_cases.py
模块描述: 生成「会务管理」(会务统筹 / 会务工单) 补充 UI 自动化用例
- 会务统筹直达 URL 修复为真实的 ConferenceManagement(旧用例 ConferenceManage 为 404)
- 深度覆盖:Tab切换(历史会务/历史工单)、搜索框与关键按钮存在性、分页与列头断言
作者: czj
创建日期: 2026-09-08
最后修改日期: 2026-09-08
用法:
cd backend
python scripts/generate_conference_cases.py
"""
import json
import os
import sqlite3
import sys
import uuid
from datetime import datetime
sys.stdout.reconfigure(encoding='utf-8')
DB_PATH = os.path.join(os.path.dirname(__file__), '..', 'data', 'test_platform.db')
MODULE_CONFERENCE = 'module_69cba085ed3c4b8288a363de7b97aa63'
CONF_MANAGE_URL = "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FConferenceManagement"
CONF_WORKORDER_URL = "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FConferenceWorkOrder"
now = datetime.now().isoformat(' ', 'seconds')
DEFAULT_CONFIG = {
"timeout": 120000,
"screenshot": True,
"auto_login": True,
"headless": True,
"step_retry_count": 2,
}
def gid(prefix='case'):
return f'{prefix}_{uuid.uuid4().hex}'
def build_case(module_id, name, steps, desc, priority='P1', tags=None):
return {
'id': gid(),
'module_id': module_id,
'name': name,
'description': desc,
'status': 'active',
'priority': priority,
'tags': json.dumps(tags or [], ensure_ascii=False),
'steps': json.dumps(steps, ensure_ascii=False),
'config': json.dumps(DEFAULT_CONFIG, ensure_ascii=False),
'depends_on': '[]',
'parameters': '[]',
'condition': '{}',
'order': 1,
'version': 1,
'created_at': now,
'updated_at': now,
'case_type': 'ui',
}
def nav(order, name, url):
return {'order': order, 'name': name, 'action': 'navigate', 'params': {'url': url}}
def wait(order, name, selector, timeout=20000):
return {'order': order, 'name': name, 'action': 'wait', 'params': {'selector': selector, 'timeout': timeout}}
def assert_exists(order, name, selector):
return {'order': order, 'name': name, 'action': 'assert', 'params': {'type': 'element_exists', 'selector': selector}}
def assert_min(order, name, selector, expected='0'):
return {'order': order, 'name': name, 'action': 'assert',
'params': {'type': 'element_count_min', 'selector': selector, 'expected': expected}}
def click_sel(order, name, selector, force=True, timeout=8000):
return {'order': order, 'name': name, 'action': 'click',
'params': {'selector': selector, 'force': force, 'timeout': timeout}}
cases = []
# ================= 1. 会务统筹用例 =================
cases.append(build_case(
MODULE_CONFERENCE,
'会务统筹-页面访问与表格验证',
[
nav(1, '导航到会务统筹页面', CONF_MANAGE_URL),
wait(2, '等待会务表格加载', '.el-table', 25000),
assert_exists(3, '验证会务统筹表格存在', '.el-table'),
assert_exists(4, '验证会议名称列存在', '.el-table :text("会议名称")'),
assert_exists(5, '验证办会人员列存在', '.el-table :text("办会人员")'),
assert_min(6, '验证表格数据行存在', '.el-table__row', '1'),
],
'navigate 直达会务统筹页面(URL 修复为 ConferenceManagement),验证表格及关键列渲染',
'P0', ['会务管理', '会务统筹', '表格']
))
cases.append(build_case(
MODULE_CONFERENCE,
'会务统筹-操作按钮与检索区验证',
[
nav(1, '导航到会务统筹页面', CONF_MANAGE_URL),
wait(2, '等待页面加载', '.el-table', 25000),
assert_exists(3, '验证关键字搜索输入框', 'input[placeholder*="请输入关键字"]'),
assert_exists(4, '验证添加条目按钮', 'button:has-text("添加条目")'),
assert_exists(5, '验证下载表格按钮', 'button:has-text("下载表格")'),
assert_exists(6, '验证批量删除按钮', 'button:has-text("批量删除")'),
],
'验证会务统筹页面的检索框以及添加/下载/删除功能按钮',
'P1', ['会务管理', '会务统筹', '操作区']
))
cases.append(build_case(
MODULE_CONFERENCE,
'会务统筹-历史会务Tab切换验证',
[
nav(1, '导航到会务统筹页面', CONF_MANAGE_URL),
wait(2, '等待页面加载', '.el-table', 25000),
assert_exists(3, '验证历史会务Tab存在', '.tab .item:has-text("历史会务"), :text("历史会务")'),
click_sel(4, '点击历史会务Tab', '.tab .item:has-text("历史会务")'),
wait(5, '等待历史会务数据表格刷新', '.el-table', 15000),
assert_exists(6, '验证历史会务表格存在', '.el-table'),
assert_min(7, '验证历史表格包含数据', '.el-table__row', '0'),
],
'点击历史会务Tab切换并验证对应表格展示',
'P1', ['会务管理', '会务统筹', 'Tab切换']
))
# ================= 2. 会务工单用例 =================
cases.append(build_case(
MODULE_CONFERENCE,
'会务工单-页面访问与列头验证',
[
nav(1, '导航到会务工单页面', CONF_WORKORDER_URL),
wait(2, '等待工单表格加载', '.el-table', 25000),
assert_exists(3, '验证工单表格存在', '.el-table'),
assert_exists(4, '验证会议名称列头', '.el-table :text("会议名称")'),
assert_exists(5, '验证会务员列头', '.el-table :text("会务员")'),
assert_exists(6, '验证开始时间列头', '.el-table :text("开始时间")'),
assert_min(7, '验证工单列表数据行', '.el-table__row', '1'),
],
'navigate 直达会务工单页面,验证表格、列头及工单数据展示',
'P0', ['会务管理', '会务工单', '表格']
))
cases.append(build_case(
MODULE_CONFERENCE,
'会务工单-筛选区与分页组件验证',
[
nav(1, '导航到会务工单页面', CONF_WORKORDER_URL),
# 冷挂载防护:页面标题文本出现=内层路由真正挂载(headless 下偶发仅 shell 无内容)
wait(2, '等待工单页面加载', ':has-text("会务工单")', 30000),
wait(3, '等待工单表格加载', '.el-table', 20000),
assert_exists(4, '验证关键字查询输入框', 'input[placeholder*="请输入关键字"]'),
assert_exists(5, '验证分页器存在', '.el-pagination'),
assert_exists(6, '验证分页总数描述', '.el-pagination__total'),
],
'验证会务工单的筛选搜索框以及分页控件完整性',
'P1', ['会务管理', '会务工单', '筛选分页']
))
cases.append(build_case(
MODULE_CONFERENCE,
'会务工单-历史工单Tab切换验证',
[
nav(1, '导航到会务工单页面', CONF_WORKORDER_URL),
wait(2, '等待工单表格加载', '.el-table', 25000),
assert_exists(3, '验证历史工单Tab存在', '.tab .item:has-text("历史工单"), :text("历史工单")'),
click_sel(4, '点击历史工单Tab', '.tab .item:has-text("历史工单")'),
wait(5, '等待历史工单表格刷新', '.el-table', 15000),
assert_exists(6, '验证历史工单表格存在', '.el-table'),
assert_min(7, '验证历史工单数据行', '.el-table__row', '0'),
],
'点击历史工单Tab切换并验证工单表格重新加载',
'P1', ['会务管理', '会务工单', 'Tab切换']
))
# ================= 写入数据库(幂等:先删同名) =================
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
names = [c['name'] for c in cases]
placeholders = ','.join('?' for _ in names)
cur.execute(f"DELETE FROM test_cases WHERE name IN ({placeholders}) AND case_type='ui'", names)
print(f'已清理同名旧用例 {cur.rowcount} 条')
for i, c in enumerate(cases, start=1):
c['order'] = i
cur.execute('''INSERT INTO test_cases
(id, module_id, name, description, status, priority, tags, steps, config, depends_on, parameters, condition, "order", version, created_at, updated_at, case_type)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
(c['id'], c['module_id'], c['name'], c['description'], c['status'], c['priority'],
c['tags'], c['steps'], c['config'], c['depends_on'], c['parameters'], c['condition'],
c['order'], c['version'], c['created_at'], c['updated_at'], c['case_type']))
print(f' OK: {c["name"]} ({c["id"]})')
conn.commit()
conn.close()
print(f'\n[OK] 共生成/更新 {len(cases)} 个会务管理用例')
# -*- coding: utf-8 -*-
"""
模块名称: generate_yuding_inspection_deep_cases.py
模块描述: 生成「预定2.0」与「巡检报表」补充 UI 自动化用例
- 预定数据(StatisticsModule):核心指标卡断言 + 图表 canvas 渲染数量验证
- 巡检报表(monitor Checklist):走已验证稳定的首页入口点击路径(避开直达 URL 竞态),
深度断言列头 / 工具栏 / 搜索筛选交互
作者: czj
创建日期: 2026-09-08
最后修改日期: 2026-09-08
用法:
cd backend
python scripts/generate_yuding_inspection_deep_cases.py
"""
import json
import os
import sqlite3
import sys
import uuid
from datetime import datetime
sys.stdout.reconfigure(encoding='utf-8')
DB_PATH = os.path.join(os.path.dirname(__file__), '..', 'data', 'test_platform.db')
# 预定数据用例归属 预定2.0 模块;巡检报表用例归属 运维管理 模块(与既有巡检报表用例同模块)
MODULE_YUDING = 'module_parent_yuding20'
MODULE_INSPECTION = 'module_ddec220aa05f4a4b9b3728a648dfa62a'
YUDING_URL = "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FStatisticsModule"
HOME_URL = "https://192.168.5.44/"
now = datetime.now().isoformat(' ', 'seconds')
DEFAULT_CONFIG = {
"timeout": 120000,
"screenshot": True,
"auto_login": True,
"headless": True,
"step_retry_count": 2,
}
def gid(prefix='case'):
return f'{prefix}_{uuid.uuid4().hex}'
def build_case(module_id, name, steps, desc, priority='P1', tags=None):
return {
'id': gid(),
'module_id': module_id,
'name': name,
'description': desc,
'status': 'active',
'priority': priority,
'tags': json.dumps(tags or [], ensure_ascii=False),
'steps': json.dumps(steps, ensure_ascii=False),
'config': json.dumps(DEFAULT_CONFIG, ensure_ascii=False),
'depends_on': '[]',
'parameters': '[]',
'condition': '{}',
'order': 1,
'version': 1,
'created_at': now,
'updated_at': now,
'case_type': 'ui',
}
def nav(order, name, url):
return {'order': order, 'name': name, 'action': 'navigate', 'params': {'url': url}}
def wait(order, name, selector, timeout=20000):
return {'order': order, 'name': name, 'action': 'wait', 'params': {'selector': selector, 'timeout': timeout}}
def assert_exists(order, name, selector):
return {'order': order, 'name': name, 'action': 'assert', 'params': {'type': 'element_exists', 'selector': selector}}
def assert_min(order, name, selector, expected='0'):
return {'order': order, 'name': name, 'action': 'assert',
'params': {'type': 'element_count_min', 'selector': selector, 'expected': expected}}
def click_sel(order, name, selector, force=True, timeout=8000):
return {'order': order, 'name': name, 'action': 'click',
'params': {'selector': selector, 'force': force, 'timeout': timeout}}
def fill(order, name, selector, value):
return {'order': order, 'name': name, 'action': 'fill', 'params': {'selector': selector, 'value': value}}
def press(order, name, key):
return {'order': order, 'name': name, 'action': 'press', 'params': {'key': key}}
cases = []
# ================= 1. 预定数据(StatisticsModule)深度用例 =================
cases.append(build_case(
MODULE_YUDING,
'预定数据-核心指标卡验证',
[
nav(1, '导航到预定数据页面', YUDING_URL),
# 冷挂载防护:标题文本出现=内层路由真正挂载(headless 下 canvas/.el-table 可能晚于容器文本)
wait(2, '等待统计模块渲染', ':has-text("预定会议数据")', 30000),
assert_exists(3, '验证使用会议室数据指标卡', ':has-text("使用会议室数据")'),
assert_exists(4, '验证满意度评价统计指标卡', ':has-text("满意度评价统计")'),
assert_exists(5, '验证会议通知发送统计指标卡', ':has-text("会议通知发送统计")'),
],
'navigate 直达预定数据页,验证四大核心指标卡区块渲染',
'P0', ['预定2.0', '预定数据', '指标卡']
))
cases.append(build_case(
MODULE_YUDING,
'预定数据-图表渲染数量验证',
[
nav(1, '导航到预定数据页面', YUDING_URL),
wait(2, '等待统计模块加载', ':has-text("预定会议数据")', 30000),
wait(3, '等待图表渲染', 'canvas', 15000),
assert_min(4, '验证图表 canvas 数量不少于5', 'canvas', '5'),
],
'验证预定数据页 9 个统计图表(canvas)正常渲染',
'P1', ['预定2.0', '预定数据', '图表']
))
cases.append(build_case(
MODULE_YUDING,
'预定数据-用户参会与云视频区块验证',
[
nav(1, '导航到预定数据页面', YUDING_URL),
wait(2, '等待统计模块加载', ':has-text("用户参会人次统计")', 30000),
assert_exists(3, '验证MCU视频会议使用时间统计区块', ':has-text("MCU视频会议使用时间统计")'),
assert_exists(4, '验证云视频会议场次统计区块', ':has-text("云视频会议场次统计")'),
assert_exists(5, '验证用户签到数据区块', ':has-text("用户签到数据")'),
],
'验证预定数据页次要统计区块(参会人次/MCU时长/云视频场次/签到)渲染',
'P1', ['预定2.0', '预定数据', '统计区块']
))
# ================= 2. 巡检报表(monitor Checklist)深度用例 =================
# 稳定入口:navigate 首页 → 点击 [id="data_list.inspect_report"](独立验证 100% 落 Checklist)
def inspection_entry_steps():
return [
nav(1, '导航到平台首页', HOME_URL),
wait(2, '等待首页加载', '.block', 20000),
click_sel(3, '点击首页巡检报表入口', '[id="data_list.inspect_report"]'),
wait(4, '等待巡检列表表格渲染', '.el-table', 30000),
]
cases.append(build_case(
MODULE_INSPECTION,
'巡检报表-表格列头完整性验证',
inspection_entry_steps() + [
assert_exists(5, '验证巡检名称列', '.el-table :text("巡检名称")'),
assert_exists(6, '验证区域名称列', '.el-table :text("区域名称")'),
assert_exists(7, '验证检测结果列', '.el-table :text("检测结果")'),
assert_exists(8, '验证执行方式列', '.el-table :text("执行方式")'),
assert_exists(9, '验证开始日期列', '.el-table :text("开始日期")'),
assert_min(10, '验证表格数据行存在', '.el-table__row', '1'),
],
'经首页入口进入巡检列表,验证 11 列关键表头与数据行渲染',
'P0', ['巡检报表', '表格', '列头']
))
cases.append(build_case(
MODULE_INSPECTION,
'巡检报表-工具栏按钮验证',
inspection_entry_steps() + [
assert_exists(5, '验证一键巡检按钮', '.company-edmit-right :text("一键巡检")'),
assert_exists(6, '验证新增按钮', '.company-edmit-right :text("新增")'),
assert_exists(7, '验证编辑按钮', '.company-edmit-right :text("编辑")'),
assert_exists(8, '验证删除按钮', '.company-edmit-right :text("删除")'),
assert_exists(9, '验证行内配置按钮', 'button:has-text("配置")'),
],
'验证巡检列表工具栏(一键巡检/新增/编辑/删除)与行内配置按钮',
'P1', ['巡检报表', '工具栏']
))
cases.append(build_case(
MODULE_INSPECTION,
'巡检报表-搜索筛选验证',
inspection_entry_steps() + [
assert_exists(5, '验证搜索输入框', 'input[placeholder*="巡检"]'),
fill(6, '输入巡检名称关键字', 'input[placeholder*="巡检"]', '急急急'),
press(7, '回车触发搜索', 'Enter'),
wait(8, '等待筛选结果刷新', '.el-table', 8000),
assert_exists(9, '验证筛选后目标行可见', '.el-table :text("急急急")'),
],
'在巡检列表搜索框输入关键字并回车,验证筛选结果包含目标巡检项',
'P1', ['巡检报表', '搜索筛选']
))
# ================= 写入数据库(幂等:先删同名) =================
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
names = [c['name'] for c in cases]
placeholders = ','.join('?' for _ in names)
cur.execute(f"DELETE FROM test_cases WHERE name IN ({placeholders}) AND case_type='ui'", names)
print(f'已清理同名旧用例 {cur.rowcount} 条')
for i, c in enumerate(cases, start=1):
c['order'] = i
cur.execute('''INSERT INTO test_cases
(id, module_id, name, description, status, priority, tags, steps, config, depends_on, parameters, condition, "order", version, created_at, updated_at, case_type)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)''',
(c['id'], c['module_id'], c['name'], c['description'], c['status'], c['priority'],
c['tags'], c['steps'], c['config'], c['depends_on'], c['parameters'], c['condition'],
c['order'], c['version'], c['created_at'], c['updated_at'], c['case_type']))
print(f' OK: {c["name"]} ({c["id"]})')
conn.commit()
conn.close()
print(f'\n[OK] 共生成/更新 {len(cases)} 个补充用例')
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论