mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-23 13:13:09 +00:00
refactor: 迁移前端资源文件至web目录 feat: 新增多种图标资源 style: 统一代码风格和格式化配置 docs: 更新README和文档说明 chore: 更新依赖和配置文件 fix: 修复部分类型定义和枚举 perf: 优化路由和组件加载逻辑
187 lines
7.1 KiB
Python
187 lines
7.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import base64
|
|
import time
|
|
import logging
|
|
import datetime
|
|
import shutil
|
|
import pytest
|
|
from pytest_metadata.plugin import metadata_key
|
|
from _pytest.terminal import TerminalReporter
|
|
|
|
from config.setting import settings
|
|
from tools.email_tool import EmailPack
|
|
from tools.dingding_tool import DingTalkPack
|
|
from tools.qiwei_tool import QiWeiPack
|
|
|
|
# 常量定义
|
|
TEST_TYPE: str = settings.TEST_TYPE
|
|
ENVIRONMENT: str = settings.ENVIRONMENT
|
|
TESTER: str = settings.TESTER
|
|
|
|
# 状态常量
|
|
success = "✓"
|
|
fail = "✗"
|
|
|
|
# 全局page对象,用于在整个测试会话中共享
|
|
GLOBAL_PAGE = None
|
|
|
|
"""
|
|
module级别的测试初始化和结果收集
|
|
class级别的测试初始化和结果收集
|
|
function级别的测试初始化和结果收集
|
|
"""
|
|
@pytest.fixture(scope="session", autouse=True)
|
|
def init_session():
|
|
"""会话级别的测试初始化和结果收集 - 使用Playwright,确保唯一驱动实例"""
|
|
if TEST_TYPE not in ['API', 'UI']:
|
|
raise ValueError("请输入正确的自动化测试类型['API','UI']")
|
|
if settings.DELETE_ON_OFF == 'True':
|
|
if settings.REPORT_PATH.exists():
|
|
shutil.rmtree(settings.REPORT_PATH)
|
|
logging.info("历史报告数据清理完成")
|
|
logging.info(
|
|
f"{settings.BANNER}\n"
|
|
f"开始执行 {TEST_TYPE} 自动化测试\n"
|
|
f"测试环境: {ENVIRONMENT}\n"
|
|
f"测试人员: {TESTER}"
|
|
)
|
|
from playwright.sync_api import sync_playwright
|
|
playwright = sync_playwright().start()
|
|
browser = playwright.chromium.launch(headless=False)
|
|
context = browser.new_context(record_video_dir="./report/videos/", viewport={"width": 1920, "height": 1080}) # 生成适配视频
|
|
page = context.new_page()
|
|
# 更新全局page对象
|
|
global GLOBAL_PAGE
|
|
GLOBAL_PAGE = page
|
|
logging.info(f"{success} ==> Playwright 浏览器实例初始化完成")
|
|
# 返回page对象供测试用例使用
|
|
yield page
|
|
# 清理资源
|
|
try:
|
|
context.close()
|
|
browser.close()
|
|
playwright.stop()
|
|
logging.info(f"{success} ==> Playwright 资源已全部释放")
|
|
except Exception as e:
|
|
logging.error(f"{fail} ==> Playwright 资源释放失败: {str(e)}")
|
|
else:
|
|
logging.info(f"========== {TEST_TYPE} 自动化测试结束 ==========")
|
|
|
|
def pytest_terminal_summary(terminalreporter: TerminalReporter, exitstatus: int, config: pytest.Config):
|
|
"""收集终端测试结果"""
|
|
stats = terminalreporter.stats
|
|
result = {
|
|
'total': terminalreporter._numcollected,
|
|
'passed': len(stats.get('passed', [])),
|
|
'failed': len(stats.get('failed', [])),
|
|
'skipped': len(stats.get('skipped', [])),
|
|
'error': len(stats.get('error', [])),
|
|
'success_rate': f"{((len(stats.get('passed', [])) / terminalreporter._numcollected * 100) if terminalreporter._numcollected else 0):.2f}%",
|
|
'duration': f'{round(time.time() - terminalreporter._sessionstarttime, 2)}秒',
|
|
'reprot_url': settings.REPORT_URL,
|
|
'jenkins_url': settings.JENKINS_URL
|
|
}
|
|
|
|
logging.info(f"总用例数: {result['total']} | 通过: {result['passed']}| 失败: {result['failed']} | 跳过: {result['skipped']} | 错误: {result['error']} | 成功率: {result['success_rate']} | 总耗时: {result['duration']}")
|
|
|
|
# 发送邮件
|
|
if settings.EMAIL_ON_OFF:
|
|
EmailPack(
|
|
fromaddr=settings.EMAIL_SENDER,
|
|
password=settings.EMAIL_PASSWORD,
|
|
toaddrs=[settings.EMAIL_RECEIVER],
|
|
server_host=settings.EMAIL_SMTP_SERVER
|
|
).send_default_email(
|
|
title=TEST_TYPE,
|
|
environment=ENVIRONMENT,
|
|
tester=TESTER,
|
|
**result
|
|
)
|
|
|
|
# 发送钉钉消息
|
|
if settings.DINGDING_ON_OFF:
|
|
DingTalkPack(settings.DINGDING_WEBHOOK, settings.DINGDING_SECRET).send_dingding(
|
|
title=TEST_TYPE,
|
|
environment=ENVIRONMENT,
|
|
tester=TESTER,
|
|
**result
|
|
)
|
|
|
|
# 发送企业微信消息
|
|
if settings.QIWEI_ON_OFF:
|
|
# 调整参数名以匹配企业微信工具的要求
|
|
qiwei_result = {
|
|
'total': result['total'],
|
|
'pass_num': result['passed'],
|
|
'fail_num': result['failed'],
|
|
'error_num': result['error'],
|
|
'skip_num': result['skipped'],
|
|
'rate': result['success_rate'],
|
|
'duration': result['duration'],
|
|
'report_url': result['reprot_url'], # 修正拼写错误
|
|
'jenkins_url': result['jenkins_url']
|
|
}
|
|
QiWeiPack(settings.WEBHOOK_URL).send_test_report(
|
|
title=TEST_TYPE,
|
|
environment=ENVIRONMENT,
|
|
tester=TESTER,
|
|
**qiwei_result
|
|
)
|
|
|
|
def pytest_html_report_title(report):
|
|
"""设置报告标题"""
|
|
report.title = f"{TEST_TYPE}自动化测试报告"
|
|
|
|
def pytest_configure(config: pytest.Config):
|
|
"""测试配置"""
|
|
config.stash[metadata_key].update({
|
|
"项目名称": f"{TEST_TYPE} 自动化测试",
|
|
"测试类型": TEST_TYPE,
|
|
"测试环境": ENVIRONMENT,
|
|
"测试人员": TESTER,
|
|
"开始时间": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
})
|
|
|
|
@pytest.hookimpl(hookwrapper=True)
|
|
def pytest_runtest_makereport(item: pytest.Item, call: pytest.CallInfo):
|
|
pytest_html = item.config.pluginmanager.getplugin('html')
|
|
outcome = yield
|
|
report: pytest.TestReport = outcome.get_result()
|
|
extras = getattr(report, 'extras', [])
|
|
extras.append(pytest_html.extras.url(str(settings.LOG_PATH), name="查看运行日志"))
|
|
if report.when == 'call' or report.when == "setup":
|
|
xfail = hasattr(report, 'wasxfail')
|
|
if (report.skipped and xfail) or (report.failed and not xfail):
|
|
file_name = report.nodeid.replace("::", "_")+".png"
|
|
# 使用全局page对象
|
|
base64_img = screenshot_base64()
|
|
if file_name:
|
|
html = """
|
|
<div>
|
|
<img src="data:image/png;base64,%s" alt="screenshot" style="width:460px;height:230px;" align="right" onclick="window.open(this.src)"/>
|
|
</div>
|
|
""" % base64_img
|
|
extras.append(pytest_html.extras.html(html))
|
|
report.extras = extras
|
|
report.description = str(item.function.__doc__)
|
|
|
|
def screenshot_base64():
|
|
"""获取当前页面的截图base64编码,使用全局page对象"""
|
|
# 生成简洁的截图文件名(年月日时分秒格式)
|
|
settings.UI_IMG_PATH.mkdir(parents=True, exist_ok=True)
|
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
screenshot_path = settings.UI_IMG_PATH / f"{timestamp}.png"
|
|
# 使用全局page对象
|
|
global GLOBAL_PAGE
|
|
page = GLOBAL_PAGE
|
|
# 检查page对象是否存在
|
|
if page and hasattr(page, 'screenshot'):
|
|
try:
|
|
screenshot_bytes = page.screenshot(path=screenshot_path)
|
|
logging.info(f"{success} ==> 保存失败截图: {screenshot_path}")
|
|
return base64.b64encode(screenshot_bytes).decode('utf-8')
|
|
except Exception as e:
|
|
logging.error(f"{fail} ==> 截图失败: {str(e)}")
|
|
return ''
|
|
|