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

feat(recorder): 远程桌面录制器 Xvfb+noVNC 协议修复 + 5.60 实机验收(会话 61)

Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 2755dfce
# -*- coding: utf-8 -*-
"""临时探测脚本(由 probe_44_202.py 上传到服务器):拉取容器日志中该执行/看门狗/报告相关记录"""
import pymysql, json, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
EXEC_ID = '__EXEC_ID__'
conn = pymysql.connect(host='mysql', user='platapp', password='PlatApp2026', database='plat_auto_test')
cur = conn.cursor()
def q(title, sql):
print('---- ' + title + ' ----')
try:
cur.execute(sql)
rows = cur.fetchall()
if not rows:
print('(无记录)')
else:
for r in rows:
print(json.dumps(list(r), default=str))
except Exception as e:
print('SQL ERR:', e)
# 该任务其它执行记录(看是否有报告成功的历史)
q('该任务最近 5 条执行', "SELECT id, name, status, passed, failed, duration, start_time, end_time, error_message FROM executions WHERE name LIKE '%每日定时自动化测试%' ORDER BY start_time DESC LIMIT 5")
# 是否有任何报告相关表/文件
q('executions 表是否存在 report 字段', "SHOW COLUMNS FROM executions LIKE '%report%'")
conn.close()
\ No newline at end of file
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""对 5.44 最新定时任务执行(exec_94ccad06efd643f49b1)的 90 条失败用例进行模式聚类
分析失败根因:被测系统问题 vs 用例定位/设计问题
"""
import sys
import json
import re
from collections import defaultdict
sys.stdout.reconfigure(encoding="utf-8")
with open("backend/scripts/probe_544_failed_cases_out.json", "r", encoding="utf-8") as f:
raw = json.load(f)
latest_exec_id = "exec_94ccad06efd643f49b1c642e47836883"
failed_list = raw["failed_detail"].get(latest_exec_id, [])
print(f"最新定时执行: {latest_exec_id},失败用例总数: {len(failed_list)}")
clusters = defaultdict(list)
for r in failed_list:
name = r["case_name"]
err = r.get("err_head") or ""
# 聚类规则
if ".el-drawer >> text=" in err:
m = re.search(r"\.el-drawer >> text='([^']+)'", err)
txt = m.group(1) if m else "抽屉菜单文本"
clusters[f"模式A: 抽屉菜单不可见/未打开 (选择器: .el-drawer >> text='{txt}')"].append((name, err))
elif "input[placeholder*=\"手机号\"]" in err:
clusters["模式B: 重复手工登录/老登录步骤卡输入 (选择器: input[placeholder*='手机号'])"].append((name, err))
elif "选择器不能为空" in err:
clusters["模式C: 用例数据缺陷:空选择器 (选择器不能为空)"].append((name, err))
elif "canvas" in err:
clusters["模式D: 统计图表等待超时 (选择器: canvas, 30s)"].append((name, err))
elif "不支持的动作类型" in err:
clusters["模式E: 用例动作类型错误 (如不支持 input)"].append((name, err))
elif "导航URL不能为空" in err:
clusters["模式F: 用例步骤配置缺失 (导航URL不能为空)"].append((name, err))
elif ".block" in err:
clusters["模式G: 首页/看板 .block 选择器或文本断言失败"].append((name, err))
elif ".el-table" in err:
clusters["模式H: 监控/报表/微应用 .el-table 表格等待超时"].append((name, err))
elif "admin@xty" in err:
clusters["模式I: 账号相关断言/选择器失效"].append((name, err))
elif "el-popover" in err or "el-dialog" in err or "el-message-box" in err:
clusters["模式J: 弹窗/气泡/确认框断言未出现"].append((name, err))
else:
clusters["模式K: 其他独立业务步骤失败"].append((name, err))
for c_name, items in sorted(clusters.items(), key=lambda x: -len(x[1])):
print(f"\n=======================================================")
print(f"【{c_name}】共 {len(items)} 条")
print(f"=======================================================")
for c_case, c_err in items[:15]:
print(f" - [{c_case}]")
clean_err = c_err.replace("\n", " ")[:160]
print(f" err: {clean_err}")
if len(items) > 15:
print(f" ... 以及其他 {len(items) - 15} 条")
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""快速诊断:容器名/启动时间/错误时间戳/执行状态"""
import sys
sys.stdout.reconfigure(encoding="utf-8")
import paramiko
HOSTS = [
{"host": "192.168.5.44", "user": "root", "pwd": "Ubains@123"},
{"host": "192.168.5.202", "user": "root", "pwd": "Ubains@123"},
{"host": "192.168.5.60", "user": "ubains", "pwd": "Ubains@123"},
]
CONTAINER = "plat-auto-test-app"
for h in HOSTS:
print(f"\n===== {h['host']} =====")
cli = paramiko.SSHClient()
cli.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
cli.connect(h["host"], username=h["user"], password=h["pwd"], timeout=20)
except Exception as e:
print(f" SSH 连接失败: {e}")
continue
def run(cmd, timeout=60):
stdin, stdout, stderr = cli.exec_command(cmd, timeout=timeout)
return stdout.read().decode("utf-8", errors="replace").strip(), \
stderr.read().decode("utf-8", errors="replace").strip()
out, _ = run("docker ps --format '{{.Names}} {{.Image}} {{.Status}}'")
print(f" 容器列表:\n " + "\n ".join(out.splitlines()))
out, _ = run(f"docker inspect {CONTAINER} --format '{{{{.State.StartedAt}}}}'")
print(f" 容器启动时间: {out}")
# 最近 3 条 MissingGreenlet 错误的时间戳
out, _ = run(f"docker logs -t {CONTAINER} 2>&1 | grep 'MissingGreenlet' | tail -3")
print(f" MissingGreenlet 最近3条:\n " + "\n ".join(out.splitlines()[-3:]))
out, _ = run(f"docker logs -t {CONTAINER} 2>&1 | grep '实时写入用例结果失败' | tail -2")
print(f" 写库失败 最近2条:\n " + "\n ".join(out.splitlines()[-2:]))
cli.close()
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
模块名称:diag_sut_500.py
模块描述:诊断 5.44/5.202 系统配置 500 根因
- 检查服务器 database.py 是否含 report_* 列补齐逻辑
- 检查服务器数据库 dingtalk_configs 表结构
- 复现 /api/system/sut-config 与 report-notify-config 500
作者:czj
创建日期:2026-09-07
最后修改:2026-09-07
"""
import sys
sys.stdout.reconfigure(encoding="utf-8")
import paramiko
HOSTS = ["192.168.5.44", "192.168.5.202"]
USER = "root"
PWD = "Ubains@123"
REMOTE_BASE = "/data/third_party/plat-auto-test"
def ssh_exec(client, cmd, timeout=60):
stdin, stdout, stderr = client.exec_command(cmd, timeout=timeout)
out = stdout.read().decode("utf-8", errors="replace").strip()
err = stderr.read().decode("utf-8", errors="replace").strip()
return out, err
def diag(host):
print(f"\n===== {host} =====")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, username=USER, password=PWD, timeout=30)
# 1. 服务器 database.py 是否含 report_* 补齐逻辑
out, _ = ssh_exec(client, f"grep -c 'report_webhook_url' {REMOTE_BASE}/backend/app/database.py")
print(f"database.py 含 report_webhook_url 补齐逻辑: {out} 处")
# 2. 服务器 dingtalk_config.py 是否含 report_* 字段
out, _ = ssh_exec(client, f"grep -c 'report_webhook_url' {REMOTE_BASE}/backend/app/models/dingtalk_config.py")
print(f"dingtalk_config.py 含 report_webhook_url 字段: {out} 处")
# 3. 数据库类型与表结构
out, _ = ssh_exec(client, "docker exec plat-auto-test-app printenv DATABASE_URL", timeout=15)
print(f"DATABASE_URL: {out}")
if "mysql" in out:
# MySQL:从 information_schema 查列(密码从容器 env 解析)
sql = (
"docker exec plat-auto-test-app python -c \""
"import os, asyncio, aiomysql; "
"u=os.environ['DATABASE_URL']; "
"import re; m=re.match(r'mysql\\+aiomysql://([^:]+):([^@]+)@([^:/]+):(\\d+)/(.+)', u); "
"user,pwd,host,port,db=m.groups(); "
"async def main(): "
"conn=await aiomysql.connect(host=host,port=int(port),user=user,password=pwd,db=db); "
"cur=await conn.cursor(); "
"await cur.execute(\\\"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=%s AND TABLE_NAME='dingtalk_configs' ORDER BY ORDINAL_POSITION\\\",(db,)); "
"rows=await cur.fetchall(); print('dingtalk_configs cols:',[r[0] for r in rows]); "
"await cur.execute(\\\"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=%s AND TABLE_NAME='scheduled_tasks' AND COLUMN_NAME='dingtalk_notify'\\\",(db,)); "
"print('scheduled_tasks.dingtalk_notify:', await cur.fetchall()); conn.close() "
"asyncio.run(main())\""
)
out, err = ssh_exec(client, sql, timeout=30)
print(f"{out or err}")
else:
sql = (
"docker exec plat-auto-test-app python -c \""
"import sqlite3; c=sqlite3.connect('/app/data/test_platform.db'); "
"print([r[1] for r in c.execute('PRAGMA table_info(dingtalk_configs)')])\""
)
out, err = ssh_exec(client, sql, timeout=30)
print(f"dingtalk_configs 列: {out or err}")
# 4. 复现 API
for api in ["/api/system/sut-config", "/api/system/report-notify-config"]:
out, _ = ssh_exec(client, f"curl -s -o /dev/null -w '%{{http_code}}' http://localhost:8081{api}", timeout=20)
print(f"GET {api} -> HTTP {out}")
client.close()
print(f"===== {host} 完成 =====")
if __name__ == "__main__":
for h in HOSTS:
try:
diag(h)
except Exception as e:
print(f"===== {h} 诊断失败: {e} =====")
This source diff could not be displayed because it is too large. You can view the blob instead.
# -*- coding: utf-8 -*-
"""
模块名称: stop_sched_560.py
模块描述: 停止 5.60 上的定时任务 scheduled_0f375af822ae4d9eaae6fe2d1f1b6236(名称: 测试),
并查询当前执行锁状态,为后续单条用例验证释放执行资源。
最后修改日期: 2026-09-08
"""
import pymysql
TASK_ID = 'scheduled_0f375af822ae4d9eaae6fe2d1f1b6236'
def main():
conn = pymysql.connect(
host='mysql', user='platapp', password='PlatApp2026',
database='plat_auto_test', charset='utf8mb4'
)
cur = conn.cursor()
# 1) 先看当前状态
cur.execute('SELECT id, name, enabled, last_run_at, next_run_at, run_count FROM scheduled_tasks WHERE id=%s', (TASK_ID,))
row = cur.fetchone()
print('更新前:', row)
# 2) 停用
cur.execute('UPDATE scheduled_tasks SET enabled=0 WHERE id=%s', (TASK_ID,))
conn.commit()
print('UPDATE rows:', cur.rowcount)
# 3) 验证
cur.execute('SELECT id, name, enabled, last_run_at, next_run_at, run_count FROM scheduled_tasks WHERE id=%s', (TASK_ID,))
print('更新后:', cur.fetchone())
# 4) 看还有没有其他 enabled 的定时任务
cur.execute('SELECT id, name, enabled FROM scheduled_tasks WHERE enabled=1')
print('仍启用的定时任务:', cur.fetchall())
cur.close()
conn.close()
if __name__ == '__main__':
main()
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论