"""根级 conftest — 全局 fixture:配置加载、日志、中文 HTML 报告生成。 Usage: def test_xxx(admin_config): # 管理端配置 def test_xxx(driver_android_config): # 司机安卓配置 """ from pathlib import Path import sys import pytest sys.path.insert(0, str(Path(__file__).parent.parent)) @pytest.fixture(scope="session") def config(): """全局配置(session 级,只加载一次)。""" from tests.config.test_config import load_config return load_config() @pytest.fixture(scope="function") def logger(request): """每个测试函数的 logger。""" from tests.utils.logger import get_logger module_name = request.module.__name__.replace("tests.", "") return get_logger(module_name) # ═══════════════════════════════════════════════════════════════════ # 各端配置 fixture(从 config 中摘取对应 section) # ═══════════════════════════════════════════════════════════════════ @pytest.fixture(scope="session") def admin_config(config): """管理端 (Playwright) 配置。""" return config["admin"] @pytest.fixture(scope="session") def mini_program_config(config): """司机端小程序 (Playwright) 配置。""" return config["mini_program"] @pytest.fixture(scope="session") def driver_android_config(config): """司机安卓端 (Appium) 配置。""" return config["driver_android"] @pytest.fixture(scope="session") def shipper_android_config(config): """货主安卓端 (Appium) 配置。""" return config["shipper_android"] @pytest.fixture(scope="session") def driver_ios_config(config): """司机苹果端 (Appium) 配置。""" return config["driver_ios"] @pytest.fixture(scope="session") def shipper_ios_config(config): """货主苹果端 (Appium) 配置。""" return config["shipper_ios"] # ═══════════════════════════════════════════════════════════════════ # 中文 HTML 报告生成 (pytest_sessionfinish hook) # ═══════════════════════════════════════════════════════════════════ def pytest_sessionfinish(session): """测试会话结束后,生成中文 HTML 报告。""" from datetime import datetime from xml.etree import ElementTree as ET report_dir = Path("output/reports") report_dir.mkdir(parents=True, exist_ok=True) junit_path = report_dir / "junit.xml" if not junit_path.exists(): return try: tree = ET.parse(junit_path) root = tree.getroot() except Exception: return total = 0 passed = 0 failed = 0 skipped = 0 cases = [] for suite in root: for case in suite: total += 1 func_name = case.attrib.get("name", "") cn_name = _resolve_cn_name(session, func_name) time_elapsed = case.attrib.get("time", "0") failure = case.find("failure") if failure is not None: failed += 1 msg = failure.attrib.get("message", "")[:300] cases.append((cn_name, "失败", time_elapsed, msg)) elif case.find("skipped") is not None: skipped += 1 cases.append((cn_name, "跳过", time_elapsed, "")) else: passed += 1 cases.append((cn_name, "通过", time_elapsed, "")) pass_rate = (passed / total * 100) if total > 0 else 0 now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") html = _build_cn_report_html(total, passed, failed, skipped, pass_rate, cases, now) (report_dir / "report_cn.html").write_text(html, encoding="utf-8-sig") def _resolve_cn_name(session, func_name: str) -> str: """从用例元信息中提取中文名称。 优先级: 1) 用例 docstring 中显式声明的 "用例名称: xxx" 2) display_name 属性 3) docstring 第一行去 【P0】 前缀 用例 docstring 格式示例: 用例ID: TC-D2S-001 用例名称: 司机APP接单到管理端核算打款完整流程 优先级: P0 """ for item in session.items: item_func_name = getattr(item, "originalname", None) or item.name.rsplit("::", 1)[-1] if item_func_name == func_name: test_cls = item.cls if hasattr(item, "cls") else None test_func = getattr(test_cls, item_func_name, None) if test_cls else None doc = (test_func.__doc__ or "") if test_func else "" # 策略1: 从 docstring 中提取 "用例名称: xxx" import re m = re.search(r"用例名称[::]\s*(.+)", doc) if m: return m.group(1).strip() # 策略2: display_name 属性 if test_func and hasattr(test_func, "display_name"): return test_func.display_name # 策略3: docstring 第一行去 【P0】 前缀 if doc: first_line = doc.strip().split("\n")[0].strip() if first_line.startswith("【"): idx = first_line.find("】") if idx > 0: first_line = first_line[idx + 1:] if first_line and len(first_line) > 2: return first_line break return func_name pass_rate = (passed / total * 100) if total > 0 else 0 now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") html = _build_cn_report_html(total, passed, failed, skipped, pass_rate, cases, now) (report_dir / "report_cn.html").write_text(html, encoding="utf-8-sig") def _build_cn_report_html(total, passed, failed, skipped, rate, cases, now): """拼装中文 HTML 报告字符串。""" rows = "" for i, (name, status, elapsed, msg) in enumerate(cases, 1): cls = {"通过": "s-pass", "失败": "s-fail", "跳过": "s-skip"}.get(status, "") msg_html = f'{msg}' if msg else "-" rows += f"{i}{name}{status}" rows += f"{float(elapsed):.1f}{msg_html}" return f""" 自动化测试报告 - {now}

自动化测试报告

执行时间: {now} | 运八网络货运平台 QaAutomationHub
{total}
总计
{passed}
通过
{failed}
失败
通过率: {rate:.1f}% ({passed}/{total})
{rows}
#用例名称状态耗时(s)备注
报告自动生成于 {now}
"""