mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-23 21:18:09 +00:00
feat: 重构前端项目结构并优化代码
refactor: 迁移前端资源文件至web目录 feat: 新增多种图标资源 style: 统一代码风格和格式化配置 docs: 更新README和文档说明 chore: 更新依赖和配置文件 fix: 修复部分类型定义和枚举 perf: 优化路由和组件加载逻辑
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
# -*- 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 ''
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import pytest
|
||||
from playwright.sync_api import Page
|
||||
from case.ui.pages.base_page import BasePage
|
||||
from typing import Dict, Any
|
||||
|
||||
|
||||
@pytest.mark.usefixtures('init_session')
|
||||
class TestUI:
|
||||
|
||||
# 定义测试数据
|
||||
TEST_DATA = [{参数化1},{参数化2}]
|
||||
|
||||
@pytest.mark.ui
|
||||
@pytest.mark.parametrize("test_data", TEST_DATA)
|
||||
def test_login_scenarios(self, init_session: Page, test_data: Dict[str, Any]):
|
||||
"""参数化测试不同的登录场景"""
|
||||
emposat_page = BasePage(init_session)
|
||||
|
||||
login_result = emposat_page.login(test_data=test_data)
|
||||
assert login_result == True, "登录失败"
|
||||
|
||||
logout_result = emposat_page.logout()
|
||||
assert logout_result == True, "退出登录失败"
|
||||
Reference in New Issue
Block a user