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

feat(ui-automation): 会议巡检升级为 navigate 直达用例(3/3 通过)

- 会议巡检 3 条 drawer+click 弱用例升级为 meetingV3 Meetinginspection navigate 直达(登录页重挂载 + 12s 日历渲染稳定)
- 页面无 .el-table/.el-pagination,断言改用 .el-calendar 日历标题 + 周期/会前巡检按钮(旧弱用例断言对象失效根因)
- generate_meeting_inspect_cases.py 幂等生成(页面访问/日历数据/日历导航),删旧弱用例 ID e161109d/17aade9c
- 探测结论固化:monitor/voice/meetingV2 微应用登录态不跨 hash 保留,运维区域/告警列表/转录列表/会议审批保持 drawer+click
- 探测脚本 5 个沉淀 + HANDOFF 会话50 记录;已同步三台 5.44/5.202→413、5.60→415(无重启)
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 8ed78453
# HANDOFF — UI自动化测试交接文档 # HANDOFF — UI自动化测试交接文档
> **生成时间**: 2026-09-01 > **生成时间**: 2026-09-02
> **当前分支**: `platform-auto-test` > **当前分支**: `platform-auto-test`
> **最近提交**: `35a58b2e` feat(ui-automation): 视讯设备升级为 navigate 直达用例(远程 HEAD `03ee164c`,并行会话 performance 提交叠加其后 > **最近提交**: `63ed9c69` docs(ui-automation): 精简 HANDOFF 文档至会话48核心内容(755行→66行)+会话49 视讯设备升级(远程 HEAD 叠加,未推送的本地工作区含:会话50 会议巡检升级 + 性能并行会话待提交改动
> **状态**: 🟢 **会话49:/sut-explore 全量盘点(首页 .block 24 入口 + 左侧抽屉 38 入口)——唯一可落地缺口=视讯设备(drawer+click 弱用例历史全中断),已升级为 navigate 直达 3 用例;「缺口+视讯设备」11 用例回归 100% 通过;已提交推送 `35a58b2e`、同步三台(5.44/5.202→410、5.60→412,无重启)**(历史会话见下章节) > **状态**: 🟡 **会话50:/sut-explore 弱模式入口直达探测——会议巡检 3 条 drawer+click 弱用例升级为 navigate 直达(meetingV3 微应用,执行 3/3 通过,已同步三台);其余弱入口(运维区域/告警列表/转录列表/会议审批)直达 URL 全跳登录页、保持弱模式;待办 P1=并行会话 performance 改动提交 + 本会话 UI 自动化改动提交推送**(历史会话见下章节)
--- ---
## 📊 当前状态(会话 49,2026-09-01 · sut-explore 全量盘点 + 视讯设备直达升级) ## 📊 当前状态(会话 50,2026-09-02 · 会议巡检 navigate 直达升级)
**/sut-explore 弱模式入口直达探测**:对 5 个 drawer+click 入口(运维区域/告警列表/会议巡检/转录列表/会议审批)做 navigate 直达 URL 升级探测。
**结论**:仅 **会议巡检**(meetingV3 微应用 `Meetinginspection`)可升级;monitor/voice/meetingV2 微应用登录态不跨 hash 保留,全部跳各自登录页,保持弱模式。
### ✅ 会议巡检 navigate 直达升级
- **真实直达 URL**`https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FMeetinginspection`
- **稳定路径**:登录页/干净上下文 → navigate 直达 → 等 12s 日历渲染(从 platform 已登录首页直接 goto 会落 `#/Invalid` + 「会话过期」)
- **DOM 特征**:无 `.el-table`/`.el-pagination`(旧弱用例断言对象失效的根因)。真实 DOM = `.el-calendar` + `.el-calendar__title`(「2026 年 9 月」)+ 周期巡检/会前巡检按钮 + 上个月/今天/下个月按钮
- **3 条用例**:页面访问验证 / 日历数据验证 / 日历导航验证(`generate_meeting_inspect_cases.py` 幂等,删旧 ID `case_e161109d4ef944fabd95b1003b9733c1``case_17aade9c8a124592944679a8a0e25364`
### ✅ 验证结果
| 执行 | 范围 | 结果 |
|------|------|------|
| 会议巡检 3 用例 | 页面访问 + 日历数据 + 日历导航 | **3/3 通过**`exec_11364e0e6c36474e8ac4a247ba3ce946`,时长 612s) |
### 📋 其余弱入口(不再重复探测)
- **运维区域 / 告警列表**:monitor 微应用,直达跳 platform 登录页
- **转录列表**:voice 微应用,独立登录页
- **会议审批**:meetingV2 微应用,跳 `#/login/logindf`
- 未逐个探测的 运维巡检/壁纸推送/脚本命令/软件卸载/设备网络/播放器发布:同属非 meetingV3,保持 drawer+click
**改动清单**
| 文件 | 说明 |
|------|------|
| `backend/data/test_platform.db` | 会议巡检 3 条用例(替换旧 drawer 版) |
| `backend/scripts/generate_meeting_inspect_cases.py` | 会议巡检用例生成(幂等) |
| `backend/scripts/probe_weak_*.py` / `probe_meeting_inspect_*.py` | 探测脚本沉淀(7 个) |
| `memory/weak-entry-direct-url-race.md` | 固化直达结论 + 会议巡检 DOM 特征 |
**待办**
- P1 提交本会话 UI 自动化改动(探测脚本 + 生成脚本 + HANDOFF)
- P1 并行会话 performance 改动(CSV 强制校验 + 登录监控)待提交,勿混入本会话
---
## 📊 历史状态(会话 49,2026-09-01 · sut-explore 全量盘点 + 视讯设备直达升级)
**/sut-explore 全量盘点**:首页 .block 24 个功能入口 + 左侧抽屉 38 个入口逐一对比用例库。 **/sut-explore 全量盘点**:首页 .block 24 个功能入口 + 左侧抽屉 38 个入口逐一对比用例库。
**结论**:全部入口名称覆盖,无"从零缺口";仅 **视讯设备** 存在可执行性缺口——旧 3 条 drawer+click 用例(首页→.home_nav_left→.el-drawer→点击)headless 下历史执行**全部中断/失败** **结论**:全部入口名称覆盖,无"从零缺口";仅 **视讯设备** 存在可执行性缺口——旧 3 条 drawer+click 用例(首页→.home_nav_left→.el-drawer→点击)headless 下历史执行**全部中断/失败**
...@@ -94,4 +131,4 @@ ...@@ -94,4 +131,4 @@
--- ---
*本文档由 Claude Code 于 2026-09-01 更新(会话 49:/sut-explore 全量盘点首页 .block 24 入口 + 抽屉 38 入口;唯一可落地缺口=视讯设备 drawr+click 弱用例历史全中断,升级为 meetingV3 TerminalMaintenance navigate 直达 3 用例;「会议运维 6 + 待办巡检 2 + 视讯设备 3」11 用例回归 100% 通过;已同步三台 5.44/5.202→410、5.60→412(无重启);探测脚本 7 个沉淀 + memory 固化。历史:会话 48 ①定时任务看门狗失败执行无报告输出修复已完成 A/B1/B2/B3 + 日志轮转 50m×5,部署三台 364/364 测试全绿;会话 48 ②sut-explore 缺口补齐会议运维 6 Tab + 待办巡检 2 用例 8/8 通过已推送 `720bf764`)。* *本文档由 Claude Code 于 2026-09-02 更新(会话 50:会议巡检 3 条 drawer+click 弱用例升级为 meetingV3 Meetinginspection navigate 直达(登录页重挂载 + 12s 日历渲染,页面无 .el-table/.el-pagination,改用 .el-calendar 日历与周期/会前巡检按钮断言),执行 3/3 通过,已同步三台 5.44/5.202→413、5.60→415(无重启);其余弱入口运维区域/告警列表/转录列表/会议审批 直达 URL 跳各自登录页,保持 drawer+click 弱模式(memory 固化);探测脚本 5 个沉淀 + 生成脚本幂等。历史:会话 49 /sut-explore 全量盘点 + 视讯设备 navigate 直达升级 3 用例 11/11 通过已推送 `35a58b2e`;会话 48 看门狗修复 + 缺口补齐 8/8 通过已推送 `720bf764`)。*
# -*- coding: utf-8 -*-
"""
模块名称: generate_meeting_inspect_cases.py
模块描述: 生成「会议巡检」navigate 直达用例(meetingV3 微应用)
替代现有 drawer+click 弱用例(首页→.home_nav_left→抽屉→点击,
headless 下不稳定)。会议巡检 = meetingV3 微应用 Meetinginspection
页面,可 navigate 直达(日历 12s 渲染,见 probe_meeting_inspect_*.py)。
页面特征:
- 顶部按钮:周期巡检 / 会前巡检(el-button--primary)
- 日历:.el-calendar + .el-calendar__title(文本如「2026 年 9 月」)
- 日历导航:上个月/今天/下个月(el-button--mini),标题随之变化
- 日历下方为巡检任务列表(无 .el-table,行用 .el-calendar__body 相关容器)
- 点「会前巡检」后「开始/修改」状态变化,但两个按钮始终共存,不适合作切换断言
- 当前无任务数据(稳定空态),断言以存在性为主
注意:本页面无 .el-table/.el-pagination(旧 drawer 用例断言对象已失效,
这是历史弱用例「表格数据/分页」断言选错对象的根因)。
作者: czj
创建日期: 2026-09-02
最后修改日期: 2026-09-02
用法:
cd backend
python scripts/generate_meeting_inspect_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_MEETING_OP = 'module_c9496f382be1430c958936d5cf473f62'
INSPECT_URL = "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FMeetinginspection"
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=15000):
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 click_sel(order, name, selectors, timeout=8000):
return {'order': order, 'name': name, 'action': 'click',
'params': {'selectors': selectors, 'timeout': timeout}}
# ================= 会议巡检(navigate 直达 + 日历导航) =================
# DOM 特征(探测 probe_meeting_inspect_stability/toggle/calendar.py):
# - 直达后约 12s 日历渲染(先 body → micro-app 挂载 → 按钮 → 日历)
# - 顶部按钮: 周期巡检/会前巡检,class=el-button--primary
# - 日历标题: .el-calendar__title 文本「2026 年 9 月」
# - 日历导航: 上个月/今天/下个月(el-button--mini)
# - 无 .el-table/.el-pagination —— 旧 drawer 用例的表格/分页断言对象不存在
base = [
nav(1, '导航到会议巡检页面', INSPECT_URL),
wait(2, '等待日历渲染', '.el-calendar', 30000),
]
# 1. 页面访问验证
steps = base + [
assert_exists(3, '验证周期巡检按钮存在', 'button:has-text("周期巡检")'),
assert_exists(4, '验证会前巡检按钮存在', 'button:has-text("会前巡检")'),
assert_exists(5, '验证日历组件存在', '.el-calendar__title'),
]
cases = [build_case(MODULE_MEETING_OP, '会议巡检-页面访问验证', steps,
'navigate 直达会议巡检页,验证周期/会前巡检按钮与日历渲染', 'P0',
['会议巡检', '页面访问', '日历'])]
# 2. 表格数据验证 → 改为日历标题与按钮数据区验证(原断言对象 .el-table 不存在)
steps = base + [
assert_exists(3, '验证日历标题存在', '.el-calendar__title'),
assert_exists(4, '验证上个月按钮存在', 'button:has-text("上个月")'),
assert_exists(5, '验证今天按钮存在', 'button:has-text("今天")'),
assert_exists(6, '验证下个月按钮存在', 'button:has-text("下个月")'),
]
cases.append(build_case(MODULE_MEETING_OP, '会议巡检-日历数据验证', steps,
'验证会议巡检日历标题与导航按钮(上个月/今天/下个月)渲染', 'P1',
['会议巡检', '日历', '数据验证']))
def assert_contains(order, name, selector, expected):
return {'order': order, 'name': name, 'action': 'assert',
'params': {'type': 'contains', 'selector': selector, 'expected': expected}}
# 3. 分页验证 → 改为日历导航验证(原断言对象 .el-pagination 不存在)
# 不硬编码月份:标题永远含「年」(如「2026 年 9 月」),导航后日历仍在即可;
# 点「今天」后回到当前月,标题仍含「年」。
steps = base + [
wait(3, '等待日历标题渲染', '.el-calendar__title', 15000),
assert_contains(4, '验证日历标题含年份', '.el-calendar__title', '年'),
click_sel(5, '点击下个月', ['button:has-text("下个月")']),
wait(6, '等待日历刷新', '.el-calendar__title', 8000),
assert_exists(7, '验证日历仍在', '.el-calendar'),
assert_contains(8, '验证导航后标题仍含年份', '.el-calendar__title', '年'),
click_sel(9, '点击今天回到本月', ['button:has-text("今天")']),
wait(10, '等待日历回到本月', '.el-calendar__title', 8000),
assert_exists(11, '验证回到本月后日历仍在', '.el-calendar'),
]
cases.append(build_case(MODULE_MEETING_OP, '会议巡检-日历导航验证', steps,
'点击下个月/今天,验证日历组件与年份标题在导航后仍存在', 'P1',
['会议巡检', '日历导航', '月份切换']))
# ================= 写入数据库(幂等:先删同名 + 旧弱用例 ID) =================
conn = sqlite3.connect(DB_PATH)
cur = conn.cursor()
# 旧 drawer+click 弱用例:同名覆盖 + 旧 ID 兜底删除(断言对象 .el-table/.el-pagination 本页不存在)
names = [c['name'] for c in cases]
LEGACY_IDS = [
'case_e161109d4ef944fabd95b1003b9733c1', # 会议巡检-表格数据验证(旧)
'case_17aade9c8a124592944679a8a0e25364', # 会议巡检-分页验证(旧)
]
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} 条')
id_ph = ','.join('?' for _ in LEGACY_IDS)
cur.execute(f"DELETE FROM test_cases WHERE id IN ({id_ph}) AND case_type='ui'", LEGACY_IDS)
print(f'已清理旧弱用例 ID {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✅ 共生成 {len(cases)} 个会议巡检用例')
# -*- coding: utf-8 -*-
"""
模块名称: 探测会议巡检日历导航 DOM
模块描述: navigate 直达 Meetinginspection,探测 .el-calendar__title 月份文本,
点击 下个月/上个月/今天 后标题与激活态变化,确定日历导航断言点。
作者: czj
创建日期: 2026-09-02
最后修改日期: 2026-09-02
"""
import json
import sys
from playwright.sync_api import sync_playwright
SUT = "https://192.168.5.44"
USERNAME = "admin@xty"
PASSWORD = "Ubains@13579"
CAPTCHA = "csba"
INSPECT_URL = "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FMeetinginspection"
def login(page):
page.goto(SUT, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(4000)
page.fill("input[placeholder='手机号/用户名/邮箱']", USERNAME)
page.fill("input[placeholder='密码']", PASSWORD)
page.fill("input[placeholder='图形验证']", CAPTCHA)
page.wait_for_timeout(500)
for b in page.query_selector_all("button"):
if (b.inner_text() or "").strip() == "登录":
b.click()
break
for _ in range(6):
page.wait_for_timeout(2000)
page.evaluate(
"""() => {
const btns = Array.from(document.querySelectorAll('button'))
.filter(b => (b.innerText||'').trim() === '确定');
if (btns.length) btns[btns.length-1].click();
}"""
)
page.wait_for_timeout(1500)
if "/login" not in page.url:
break
page.wait_for_timeout(4000)
def cal_title(page):
return page.evaluate(
"""() => {
const t = document.querySelector('.el-calendar__title');
return t ? t.innerText.trim() : null;
}"""
)
def click_btn(page, text):
return page.evaluate(
"""(text) => {
const b = Array.from(document.querySelectorAll('button'))
.find(b => (b.innerText||'').trim() === text);
if (b) { b.click(); return true; }
return false;
}""", text
)
def main():
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
ctx = browser.new_context(ignore_https_errors=True, viewport={"width": 1920, "height": 1080})
page = ctx.new_page()
page.set_default_timeout(15000)
login(page)
page.goto(INSPECT_URL, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(14000)
print("初始 title:", cal_title(page))
click_btn(page, "下个月")
page.wait_for_timeout(3000)
print("下个月后 title:", cal_title(page))
click_btn(page, "下个月")
page.wait_for_timeout(3000)
print("再下个月 title:", cal_title(page))
click_btn(page, "上个月")
page.wait_for_timeout(3000)
print("上个月后 title:", cal_title(page))
click_btn(page, "今天")
page.wait_for_timeout(3000)
print("今天后 title:", cal_title(page))
print("当前选中日期:", page.evaluate(
"""() => {
const els = Array.from(document.querySelectorAll('.el-calendar-day, td[class*=is-selected], .el-calendar__body td'));
const sels = Array.from(document.querySelectorAll('td.is-selected, td.is-today'));
return sels.map(e => (e.innerText||'').trim().slice(0,20));
}"""
))
browser.close()
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
main()
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
模块名称: 探测会议巡检 DOM 特征
模块描述: navigate 直达 Meetinginspection,dump 按钮/Tab/日历/表格,
确定稳定断言选择器。
作者: czj
创建日期: 2026-09-01
最后修改日期: 2026-09-01
"""
import json
import sys
from playwright.sync_api import sync_playwright
SUT = "https://192.168.5.44"
USERNAME = "admin@xty"
PASSWORD = "Ubains@13579"
CAPTCHA = "csba"
INSPECT_URL = "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FMeetinginspection"
def login(page):
page.goto(SUT, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(4000)
page.fill("input[placeholder='手机号/用户名/邮箱']", USERNAME)
page.fill("input[placeholder='密码']", PASSWORD)
page.fill("input[placeholder='图形验证']", CAPTCHA)
page.wait_for_timeout(500)
for b in page.query_selector_all("button"):
if (b.inner_text() or "").strip() == "登录":
b.click()
break
for _ in range(6):
page.wait_for_timeout(2000)
page.evaluate(
"""() => {
const btns = Array.from(document.querySelectorAll('button'))
.filter(b => (b.innerText||'').trim() === '确定');
if (btns.length) btns[btns.length-1].click();
}"""
)
page.wait_for_timeout(1500)
if "/login" not in page.url:
break
page.wait_for_timeout(4000)
def main():
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
ctx = browser.new_context(ignore_https_errors=True, viewport={"width": 1920, "height": 1080})
page = ctx.new_page()
page.set_default_timeout(15000)
login(page)
page.goto(INSPECT_URL, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(8000)
print("URL:", page.url[:180])
# 按钮
btns = page.evaluate(
"""() => Array.from(document.querySelectorAll('button')).map(b => ({
t: (b.innerText||'').trim().slice(0,20),
cls: (b.className||'').toString().slice(0,50),
})).filter(b => b.t)"""
)
print("buttons:", json.dumps(btns, ensure_ascii=False))
# 所有带文本的 span/div 标题
titles = page.evaluate(
"""() => Array.from(document.querySelectorAll('.el-tabs__item, .el-radio-button, [class*=tab], h1,h2,h3,.title'))
.map(e => (e.innerText||'').trim()).filter(Boolean).slice(0,20)"""
)
print("titles:", titles)
# 日历
cal = page.evaluate(
"""() => ({
hasCal: !!document.querySelector('.el-calendar, .fc-view, [class*=calendar]'),
calCls: Array.from(document.querySelectorAll('[class*=calendar], .el-calendar, .fc-view'))
.map(e => e.className.toString().slice(0,60)).slice(0,5),
radio: Array.from(document.querySelectorAll('.el-radio-button, .el-radio-group label'))
.map(e => (e.innerText||'').trim()).filter(Boolean),
})"""
)
print("日历:", json.dumps(cal, ensure_ascii=False))
# 点击「会前巡检」
print("\n=== 点击 会前巡检 ===")
clicked = page.evaluate(
"""() => {
const cands = Array.from(document.querySelectorAll('button, .el-radio-button, .el-tabs__item, span, div'));
for (const c of cands) {
if ((c.innerText||'').trim() === '会前巡检') { c.click(); return true; }
}
return false;
}"""
)
print("点击:", clicked)
page.wait_for_timeout(4000)
headers = page.evaluate(
"""() => Array.from(document.querySelectorAll('.el-table__header th'))
.map(t => (t.innerText||'').trim()).filter(Boolean)"""
)
print("切换后列头:", headers)
rows = page.evaluate("() => document.querySelectorAll('.el-table__row').length")
print("切换后行数:", rows)
body = page.evaluate("() => document.body ? document.body.innerText.slice(0, 400) : ''")
print("切换后 body:", body.replace("\n", " | ")[:400])
page.screenshot(path="tmp/meeting_inspect.png", full_page=False)
print("截图: tmp/meeting_inspect.png")
browser.close()
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
main()
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
模块名称: 探测会议巡检直达稳定性(长等待 + 多次采样)
模块描述: 登录后 goto Meetinginspection 直达 URL,每 3s 采样一次
URL/micro-app/body/表格,持续 24s,判断是永远空白还是竞态
(上轮出现 Invalid/会话过期,本轮出现微应用加载但内容空白)。
作者: czj
创建日期: 2026-09-02
最后修改日期: 2026-09-02
"""
import json
import sys
from playwright.sync_api import sync_playwright
SUT = "https://192.168.5.44"
USERNAME = "admin@xty"
PASSWORD = "Ubains@13579"
CAPTCHA = "csba"
INSPECT_URL = "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FMeetinginspection"
def login(page):
page.goto(SUT, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(4000)
page.fill("input[placeholder='手机号/用户名/邮箱']", USERNAME)
page.fill("input[placeholder='密码']", PASSWORD)
page.fill("input[placeholder='图形验证']", CAPTCHA)
page.wait_for_timeout(500)
for b in page.query_selector_all("button"):
if (b.inner_text() or "").strip() == "登录":
b.click()
break
for _ in range(6):
page.wait_for_timeout(2000)
page.evaluate(
"""() => {
const btns = Array.from(document.querySelectorAll('button'))
.filter(b => (b.innerText||'').trim() === '确定');
if (btns.length) btns[btns.length-1].click();
}"""
)
page.wait_for_timeout(1500)
if "/login" not in page.url:
break
page.wait_for_timeout(4000)
def sample(page, t):
cur = page.url[:160]
invalid = "Invalid" in cur
has_login = "/login" in cur
ma = page.evaluate(
"""() => {
const m = document.querySelector('micro-app');
if (!m) return {found: false};
const attrs = {};
for (const a of m.attributes) attrs[a.name] = a.value;
return {found: true, name: attrs['name'] || '', hasUrl: !!attrs['url']};
}"""
)
body = page.evaluate("() => document.body ? document.body.innerText.replace(/\\n/g,' | ').slice(0, 120) : ''")
headers = page.evaluate(
"""() => Array.from(document.querySelectorAll('.el-table__header th'))
.map(t => (t.innerText||'').trim()).filter(Boolean).slice(0,6)"""
)
rows = page.evaluate("() => document.querySelectorAll('.el-table__row').length")
cal = page.evaluate(
"""() => ({
hasCal: !!document.querySelector('.el-calendar, .fc-view, [class*=calendar]'),
radio: Array.from(document.querySelectorAll('.el-radio-button, .el-radio-group label'))
.map(e => (e.innerText||'').trim()).filter(Boolean),
})"""
)
print(f"[t={t}s] invalid={invalid} login={has_login} ma={ma.get('name') if ma.get('found') else None} "
f"rows={rows} cal={cal['hasCal']} radio={cal['radio']}")
print(f" body: {body}")
if headers:
print(f" headers: {headers}")
def main():
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
ctx = browser.new_context(ignore_https_errors=True, viewport={"width": 1920, "height": 1080})
page = ctx.new_page()
page.set_default_timeout(15000)
login(page)
print("登录后 URL:", page.url[:160])
page.goto(INSPECT_URL, wait_until="domcontentloaded", timeout=45000)
for t in range(3, 25, 3):
page.wait_for_timeout(3000 if t > 3 else 0)
sample(page, t)
page.screenshot(path="tmp/meeting_inspect_long.png")
print("截图: tmp/meeting_inspect_long.png")
browser.close()
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
main()
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
模块名称: 探测会议巡检「会前巡检」切换后的 DOM 变化
模块描述: navigate 直达 Meetinginspection,等日历渲染后点击「会前巡检」,
对比切换前后的 表格/日历/按钮/激活态,确定切换验证用例的断言点。
作者: czj
创建日期: 2026-09-02
最后修改日期: 2026-09-02
"""
import json
import sys
from playwright.sync_api import sync_playwright
SUT = "https://192.168.5.44"
USERNAME = "admin@xty"
PASSWORD = "Ubains@13579"
CAPTCHA = "csba"
INSPECT_URL = "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FMeetinginspection"
def login(page):
page.goto(SUT, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(4000)
page.fill("input[placeholder='手机号/用户名/邮箱']", USERNAME)
page.fill("input[placeholder='密码']", PASSWORD)
page.fill("input[placeholder='图形验证']", CAPTCHA)
page.wait_for_timeout(500)
for b in page.query_selector_all("button"):
if (b.inner_text() or "").strip() == "登录":
b.click()
break
for _ in range(6):
page.wait_for_timeout(2000)
page.evaluate(
"""() => {
const btns = Array.from(document.querySelectorAll('button'))
.filter(b => (b.innerText||'').trim() === '确定');
if (btns.length) btns[btns.length-1].click();
}"""
)
page.wait_for_timeout(1500)
if "/login" not in page.url:
break
page.wait_for_timeout(4000)
def snapshot(page, tag):
data = page.evaluate(
"""() => ({
btns: Array.from(document.querySelectorAll('button')).map(b => ({
t: (b.innerText||'').trim().slice(0,14),
cls: (b.className||'').toString().slice(0,70),
})).filter(b => b.t),
hasCal: !!document.querySelector('.el-calendar'),
hasTable: !!document.querySelector('.el-table'),
rows: document.querySelectorAll('.el-table__row').length,
headers: Array.from(document.querySelectorAll('.el-table__header th'))
.map(t => (t.innerText||'').trim()).filter(Boolean).slice(0,8),
})"""
)
print(f"\n--- {tag} ---")
print("btns:", json.dumps(data["btns"], ensure_ascii=False)[:600])
print("hasCal:", data["hasCal"], "hasTable:", data["hasTable"], "rows:", data["rows"], "headers:", data["headers"])
def main():
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
ctx = browser.new_context(ignore_https_errors=True, viewport={"width": 1920, "height": 1080})
page = ctx.new_page()
page.set_default_timeout(15000)
login(page)
page.goto(INSPECT_URL, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(14000)
snapshot(page, "初始(周期巡检 默认)")
# 点击 会前巡检
page.evaluate(
"""() => {
const b = Array.from(document.querySelectorAll('button'))
.find(b => (b.innerText||'').trim() === '会前巡检');
if (b) b.click();
}"""
)
page.wait_for_timeout(6000)
snapshot(page, "点击 会前巡检 后")
# 再点回 周期巡检
page.evaluate(
"""() => {
const b = Array.from(document.querySelectorAll('button'))
.find(b => (b.innerText||'').trim() === '周期巡检');
if (b) b.click();
}"""
)
page.wait_for_timeout(6000)
snapshot(page, "点回 周期巡检 后")
page.screenshot(path="tmp/meeting_inspect_toggle.png")
print("\n截图: tmp/meeting_inspect_toggle.png")
browser.close()
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
main()
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
模块名称: 探测会议巡检直达有效 URL
模块描述: 会议巡检直达 URL 捕获后直接 navigate,验证登录态跨 hash 保留
(上轮 /Meetinginspection 跳 login),如果有效则升级用例。
作者: czj
创建日期: 2026-09-01
最后修改日期: 2026-09-01
"""
import json
import sys
from playwright.sync_api import sync_playwright
SUT = "https://192.168.5.44"
USERNAME = "admin@xty"
PASSWORD = "Ubains@13579"
CAPTCHA = "csba"
# 上轮抽屉点击捕获的真实直达 URL
MONITOR_URL = "https://192.168.5.44/#/monitor?monitor=%2Fmonitor%2F%23%2Fmonitoring%2FMonitoringIndexStandard"
INSPECT_URL = "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FMeetinginspection"
def login(page):
page.goto(SUT, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(4000)
page.fill("input[placeholder='手机号/用户名/邮箱']", USERNAME)
page.fill("input[placeholder='密码']", PASSWORD)
page.fill("input[placeholder='图形验证']", CAPTCHA)
page.wait_for_timeout(500)
for b in page.query_selector_all("button"):
if (b.inner_text() or "").strip() == "登录":
b.click()
break
for _ in range(6):
page.wait_for_timeout(2000)
page.evaluate(
"""() => {
const btns = Array.from(document.querySelectorAll('button'))
.filter(b => (b.innerText||'').trim() === '确定');
if (btns.length) btns[btns.length-1].click();
}"""
)
page.wait_for_timeout(1500)
if "/login" not in page.url:
break
page.wait_for_timeout(4000)
def main():
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
ctx = browser.new_context(ignore_https_errors=True, viewport={"width": 1920, "height": 1080})
page = ctx.new_page()
page.set_default_timeout(15000)
login(page)
print("登录后 URL:", page.url[:180])
# 会议巡检
print("\n===== 会议巡检直达 =====")
page.goto(INSPECT_URL, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(8000)
print("URL:", page.url[:200])
if "/login" in page.url:
print(" ❌ 跳回登录页")
else:
ma = page.evaluate(
"""() => {
const m = document.querySelector('micro-app');
if (!m) return {found: false};
const attrs = {};
for (const a of m.attributes) attrs[a.name] = a.value;
return {found: true, name: attrs['name'] || '', url: attrs['url'] || ''};
}"""
)
print("micro-app:", json.dumps(ma, ensure_ascii=False)[:200])
headers = page.evaluate(
"""() => Array.from(document.querySelectorAll('.el-table__header th'))
.map(t => (t.innerText||'').trim()).filter(Boolean).slice(0,10)"""
)
print("表格列头:", headers)
rows = page.evaluate("() => document.querySelectorAll('.el-table__row').length")
print("表格行数:", rows)
tabs = page.evaluate(
"""() => Array.from(document.querySelectorAll('.el-tabs__item'))
.map(t => (t.innerText||'').trim()).filter(Boolean)"""
)
print("Tab:", tabs)
body = page.evaluate("() => document.body ? document.body.innerText.slice(0, 200) : ''")
print("body:", body.replace("\n", " | ")[:200])
browser.close()
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
main()
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
模块名称: 探测弱模式入口 navigate 直达稳定性
模块描述: 直接 navigate 上轮捕获的 6 个直达 URL(monitor/meetingV3/
meetingV2/voice 微应用),验证登录态跨 hash 保留 + 表格渲染,
决定哪些可升级为 navigate 直达用例。
作者: czj
创建日期: 2026-09-01
最后修改日期: 2026-09-01
"""
import json
import sys
from playwright.sync_api import sync_playwright
SUT = "https://192.168.5.44"
USERNAME = "admin@xty"
PASSWORD = "Ubains@13579"
CAPTCHA = "csba"
# 上轮抽屉点击捕获的真实直达 URL
TARGETS = [
("运维区域", "https://192.168.5.44/#/monitor?monitor=%2Fmonitor%2F%23%2Fmonitoring%2FMonitoringIndexStandard"),
("告警列表", "https://192.168.5.44/#/backend/monitor?monitor=%2Fmonitor%2F%23%2Fadminsystem%2Falarm%2FAlarmwarning"),
("会议巡检", "https://192.168.5.44/#/meetingV3?meetingV3=%2FmeetingV3%2F%23%2FMeetinginspection"),
("转录列表", "https://192.168.5.44/#/voice?voice=%2Fvoice%2F%23%2Fmaster"),
("会议审批", "https://192.168.5.44/#/meetingV2?meetingV2=%2FmeetingV2%2F%23%2Fmeeting%2Fapproval"),
]
def login(page):
page.goto(SUT, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(4000)
page.fill("input[placeholder='手机号/用户名/邮箱']", USERNAME)
page.fill("input[placeholder='密码']", PASSWORD)
page.fill("input[placeholder='图形验证']", CAPTCHA)
page.wait_for_timeout(500)
for b in page.query_selector_all("button"):
if (b.inner_text() or "").strip() == "登录":
b.click()
break
for _ in range(6):
page.wait_for_timeout(2000)
page.evaluate(
"""() => {
const btns = Array.from(document.querySelectorAll('button'))
.filter(b => (b.innerText||'').trim() === '确定');
if (btns.length) btns[btns.length-1].click();
}"""
)
page.wait_for_timeout(1500)
if "/login" not in page.url:
break
page.wait_for_timeout(4000)
def main():
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
ctx = browser.new_context(ignore_https_errors=True, viewport={"width": 1920, "height": 1080})
page = ctx.new_page()
page.set_default_timeout(15000)
login(page)
print("登录后 URL:", page.url[:180])
for name, url in TARGETS:
print(f"\n===== navigate 直达: {name} =====")
page.goto(url, wait_until="domcontentloaded", timeout=45000)
# 等 8s,观察是否跳登录页
page.wait_for_timeout(8000)
cur = page.url
print("URL:", cur[:200])
if "/login" in cur:
print(" ❌ 跳回登录页(登录态未保留)")
continue
ma = page.evaluate(
"""() => {
const m = document.querySelector('micro-app');
if (!m) return {found: false};
const attrs = {};
for (const a of m.attributes) attrs[a.name] = a.value;
return {found: true, name: attrs['name'] || '', url: attrs['url'] || ''};
}"""
)
print("micro-app:", json.dumps(ma, ensure_ascii=False)[:200])
headers = page.evaluate(
"""() => Array.from(document.querySelectorAll('.el-table__header th'))
.map(t => (t.innerText||'').trim()).filter(Boolean).slice(0,10)"""
)
print("表格列头:", headers)
rows = page.evaluate("() => document.querySelectorAll('.el-table__row').length")
print("表格行数:", rows)
tabs = page.evaluate(
"""() => Array.from(document.querySelectorAll('.el-tabs__item'))
.map(t => (t.innerText||'').trim()).filter(Boolean)"""
)
print("Tab:", tabs)
# 页面身份文本
body = page.evaluate("() => document.body ? document.body.innerText.slice(0, 200) : ''")
print("body:", body.replace("\n", " | ")[:200])
browser.close()
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
main()
\ No newline at end of file
# -*- coding: utf-8 -*-
"""
模块名称: 探测弱模式入口真实直达 URL
模块描述: 登录后展开左侧抽屉,逐个点击弱模式入口(运维区域/告警列表/
会议巡检/转录列表/会议审批 等),抓取真实双重 hash URL 与 DOM
特征,判断是否可 navigate 直达(meetingV3 微应用登录态跨 hash
通常可直达;monitor/backstage 微应用需验证)。
作者: czj
创建日期: 2026-09-01
最后修改日期: 2026-09-01
"""
import json
import sys
from playwright.sync_api import sync_playwright
SUT = "https://192.168.5.44"
USERNAME = "admin@xty"
PASSWORD = "Ubains@13579"
CAPTCHA = "csba"
# 弱模式入口(视讯设备已升级,不在此列)
TARGETS = [
("运维区域", "maintain_list.room_list"),
("告警列表", "maintain_list.alarm_list"),
("会议巡检", "maintain_list.meeting_inspect"),
("运维巡检", "maintain_list.inspect_list"),
("转录列表", "trans_list.table"),
("会议审批", "meetingApproval"),
]
def login(page):
page.goto(SUT, wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(4000)
page.fill("input[placeholder='手机号/用户名/邮箱']", USERNAME)
page.fill("input[placeholder='密码']", PASSWORD)
page.fill("input[placeholder='图形验证']", CAPTCHA)
page.wait_for_timeout(500)
for b in page.query_selector_all("button"):
if (b.inner_text() or "").strip() == "登录":
b.click()
break
for _ in range(6):
page.wait_for_timeout(2000)
page.evaluate(
"""() => {
const btns = Array.from(document.querySelectorAll('button'))
.filter(b => (b.innerText||'').trim() === '确定');
if (btns.length) btns[btns.length-1].click();
}"""
)
page.wait_for_timeout(1500)
if "/login" not in page.url:
break
page.wait_for_timeout(4000)
def dump_state(page, tag):
print(f"\n===== {tag} =====")
print("主 frame URL:", page.url[:240])
for f in page.frames:
try:
if f.url and f.url != "about:blank":
print(" frame:", f.url[:200])
except Exception:
pass
ma = page.evaluate(
"""() => {
const m = document.querySelector('micro-app');
if (!m) return {found: false};
const attrs = {};
for (const a of m.attributes) attrs[a.name] = a.value;
return {found: true, name: attrs['name'] || '', url: attrs['url'] || ''};
}"""
)
print("micro-app:", json.dumps(ma, ensure_ascii=False)[:300])
headers = page.evaluate(
"""() => Array.from(document.querySelectorAll('.el-table__header th'))
.map(t => (t.innerText||'').trim()).filter(Boolean).slice(0,12)"""
)
print("表格列头:", headers)
rows = page.evaluate("() => document.querySelectorAll('.el-table__row').length")
print("表格行数:", rows)
tabs = page.evaluate(
"""() => Array.from(document.querySelectorAll('.el-tabs__item'))
.map(t => (t.innerText||'').trim()).filter(Boolean)"""
)
print("Tab:", tabs)
body = page.evaluate("() => document.body ? document.body.innerText.slice(0, 400) : ''")
print("body:", body.replace("\n", " | ")[:400])
def click_drawer_item(page, did):
page.evaluate("() => { const el = document.querySelector('.home_nav_left'); if (el) el.click(); }")
page.wait_for_timeout(2500)
r = page.evaluate(
"""(did) => {
const el = document.querySelector(`[data-id="${did}"]`);
if (el) { el.click(); return true; }
return false;
}""", did
)
return r
def main():
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
ctx = browser.new_context(ignore_https_errors=True, viewport={"width": 1920, "height": 1080})
page = ctx.new_page()
page.set_default_timeout(15000)
login(page)
print("登录后 URL:", page.url[:180])
for name, did in TARGETS:
# 回首页再点,避免上一个微应用残留
page.goto(SUT + "/", wait_until="domcontentloaded", timeout=45000)
page.wait_for_timeout(4000)
clicked = click_drawer_item(page, did)
print(f"\n[click {name} {did}] clicked={clicked}")
page.wait_for_timeout(8000)
dump_state(page, name)
page.screenshot(path="tmp/weak_entries.png", full_page=False)
print("\n截图: tmp/weak_entries.png")
browser.close()
if __name__ == "__main__":
sys.stdout.reconfigure(encoding="utf-8")
main()
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论