Files
AppAutoTest/conftest.py
CNWei 6ad6b7ff84 fix(conftest,config_loader): 修复 get_caps 的 Capabilities 加载逻辑
- 新增 pytest_addoption 增加 "--caps_name" 获取配置文件中的设备/平台名称
- 修复 get_caps 的 Capabilities 加载逻辑
- 优化 其他优化 enums.py
- 删除 移除部分文档,代码,第三方包(loguru)
2026-02-28 16:08:14 +08:00

201 lines
7.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# coding=utf-8
"""
@author: CNWei,ChenWei
@Software: PyCharm
@contact: t6g888@163.com
@file: conftest
@date: 2026/1/16 10:52
@desc: Pytest 核心配置与 Fixture 管理
"""
import logging
import secrets
from pathlib import Path
from typing import Generator, Any
import pytest
import allure
from dotenv import load_dotenv
from core.run_appium import start_appium_service, stop_appium_service
from core.driver import CoreDriver
from core.settings import APPIUM_HOST, APPIUM_PORT
from core.enums import AppPlatform
from core.config_loader import get_caps
load_dotenv()
# 注册命令行参数
def pytest_addoption(parser: Any) -> None:
"""
注册自定义命令行参数
:param parser: Pytest 参数解析器对象
"""
parser.addoption("--platform", action="store", default="android1", help="目标平台: Android or IOS")
parser.addoption("--caps_name", action="store", default=None, help="配置文件中的设备/平台名称")
parser.addoption("--udid", action="store", default=None, help="设备唯一标识")
parser.addoption("--host", action="store", default=APPIUM_HOST, help="Appium Server Host")
parser.addoption("--port", action="store", default=str(APPIUM_PORT), help="Appium Server Port")
@pytest.fixture(scope="session")
def appium_server(request: pytest.FixtureRequest) -> Generator[Any, None, None]:
"""
第一层:管理 Appium Server 进程。
:param request: Pytest 请求对象
:return: Appium 服务进程实例
"""
# 获取命令行参数
host = request.config.getoption("--host")
port = int(request.config.getoption("--port"))
service = start_appium_service(host, port)
yield service
stop_appium_service(service)
@pytest.fixture(scope="session")
def driver_session(request: pytest.FixtureRequest, appium_server: Any) -> Generator[CoreDriver, None, None]:
"""
第二层:全局单例 Driver 管理 (Session Scope)。
负责创建和销毁 Driver整个测试过程只启动一次 App。
:param request: Pytest 请求对象
:param appium_server: Appium 服务 fixture 依赖
:return: CoreDriver 实例
"""
platform: AppPlatform = request.config.getoption("--platform")
# 配置名称(caps_name)(决定去 YAML 哪个节点拿数据,默认等于 platform
caps_name = request.config.getoption("--caps_name") or platform
ud_id = request.config.getoption("--udid")
host = request.config.getoption("--host")
port = int(request.config.getoption("--port"))
# 1. 获取基础 Caps
caps = get_caps(caps_name)
# 2. 动态注入参数
if ud_id: caps["udid"] = ud_id
# 将最终生效的 caps 存入 pytest 配置,方便报告读取
request.config._final_caps = caps
request.config._caps_name = caps_name
# 3. 初始化 Driver
driver_helper = CoreDriver()
driver_helper.server_config(host=host, port=port)
try:
driver_helper.connect(platform=platform, caps=caps)
except Exception as e:
pytest.exit(f"无法初始化 Driver: {e}")
yield driver_helper
# 4. 清理
driver_helper.quit()
@pytest.fixture(scope="function")
def driver(driver_session: CoreDriver) -> Generator[Any, None, None]:
"""
第三层:用例级 Driver 注入。
每个用例直接获取已存在的 Driver 实例。
可以在这里添加 reset_app() 逻辑,确保用例间独立性。
:param driver_session: CoreDriver 会话实例
:return: 原始 Appium Driver 对象 (webdriver.Remote)
"""
# 可选:如果需要在每个用例前重置 App 状态
# driver_session.driver.reset()
yield driver_session.driver
def pytest_exception_interact(node: Any, call: Any, report: Any) -> None:
"""
当测试用例抛出异常断言失败或代码报错Pytest 会调用这个钩子。
我们在这里手动把错误信息喂给 logging。
:param node: 测试节点
:param call: 调用信息
:param report: 测试报告
"""
logger = logging.getLogger("pytest")
if report.failed:
# 获取详细的错误堆栈(包含 assert 的对比信息)
# long,short,no-locals
exc_info = call.excinfo.getrepr(style='short')
screenshot_name = f"异常截图_{secrets.token_hex(4)}"
logger.error(f"\n{'=' * 40} TEST FAILED {'=' * 40}\n"
f"Node ID: {node.nodeid}\n"
f"截图名称: {screenshot_name}\n"
f"详细错误信息:\n{exc_info}"
)
# 尝试获取 driver_session (CoreDriver 实例)
if "driver_session" in node.funcargs:
helper = node.funcargs["driver_session"]
try:
# 截图并附加到 Allure
screenshot_bytes = helper.driver.get_screenshot_as_png()
allure.attach(
screenshot_bytes,
name=screenshot_name,
attachment_type=allure.attachment_type.PNG
)
except Exception as e:
logger.error(f"执行异常截图失败: {e}")
logger.error("=" * 93 + "\n")
def pytest_sessionfinish(session: Any, exitstatus: int) -> None:
"""
测试会话结束时,收集环境信息到 Allure 报告
:param session: Pytest 会话对象
:param exitstatus: 退出状态码
"""
match exitstatus:
case pytest.ExitCode.OK:
logging.info("测试全部通过!")
case pytest.ExitCode.TESTS_FAILED:
logging.warning("部分测试用例执行失败,请检查报告。")
case pytest.ExitCode.INTERRUPTED:
logging.error("测试被用户手动中断Ctrl+C")
case pytest.ExitCode.INTERNAL_ERROR:
logging.critical("Pytest 发生内部错误!")
case pytest.ExitCode.USAGE_ERROR:
logging.error("Pytest 命令行参数错误或用法不当。")
case pytest.ExitCode.NO_TESTS_COLLECTED:
logging.warning("未发现任何测试用例。")
case _:
logging.error(f"未知错误状态码: {exitstatus}")
report_dir = session.config.getoption("--alluredir")
final_caps = getattr(session.config, "_final_caps", {})
caps_name = getattr(session.config, "_caps_name", '')
if not report_dir:
return
report_path = Path(report_dir)
# 收集环境信息
env_info = {
"Platform": session.config.getoption("--platform"),
"UDID": final_caps.get("udid") or session.config.getoption("--udid") or "Not Specified",
"CapsName": caps_name,
"Host": session.config.getoption("--host"),
"Python": "3.11+"
}
try:
if not report_path.exists():
report_path.mkdir(parents=True, exist_ok=True)
# 生成 environment.properties 文件
env_file = report_path / "environment.properties"
with env_file.open("w", encoding="utf-8") as f:
for k, v in env_info.items():
f.write(f"{k}={v}\n")
logging.info("Allure 环境信息已生成。")
except Exception as e:
logging.error(f"无法写入环境属性: {e}")