提交 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
此差异已折叠。
# -*- 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论