"""跨端 E2E fixtures — 同时持有 Playwright (管理端) 和 Appium (司机安卓) 两个 driver。 核心挑战: pytest 的单端 conftest 各自拥有 page / driver fixture, 跨端测试需要同时注入两者而不冲突。 解决方案: 使用显式命名的 fixture (admin_page / app_driver), 各自独立创建/销毁生命周期,内部统一复用 core 层工厂。 前置条件: - 管理端: python tests/web/admin/save_auth.py --role super_admin --browser chromium - 司机安卓: 设备上手动登录一次 (noReset=true 自动复用) - Appium server: appium (默认 http://localhost:4723) 运行: pytest tests/cross_end/ -m cross_end -v """ import pytest from tests.core.appium_support import ( create_driver, make_ensure_logged_in, save_failure_screenshot, ) from tests.core.playwright_support import ( admin_browser_page, save_web_failure_screenshot, ) # ═══════════════════════════════════════════════════════════════════ # Admin Page (Playwright) # ═══════════════════════════════════════════════════════════════════ @pytest.fixture def admin_page(admin_config, request): """管理端 Playwright Page — 仅 chromium,加载 super_admin 登录态。 跨端测试已经够慢,不参数化浏览器矩阵。 """ with admin_browser_page(admin_config, browser_name="chromium", role="super_admin") as (page, _): yield page save_web_failure_screenshot(page, request, tag="admin_chromium") # ═══════════════════════════════════════════════════════════════════ # App Driver (Appium) # ═══════════════════════════════════════════════════════════════════ @pytest.fixture def app_driver(driver_android_config, request): """司机安卓端 Appium driver。""" cfg = driver_android_config d = None try: d = create_driver(cfg, platform="android") yield d save_failure_screenshot(d, cfg, "android", request) finally: if d is not None: try: d.quit() except Exception: pass # ═══════════════════════════════════════════════════════════════════ # Login helpers # ═══════════════════════════════════════════════════════════════════ @pytest.fixture def ensure_admin_login(admin_config, admin_page, config): """确保管理端已登录为 super_admin(跨端流程始终用超管)。""" def _ensure_login(role: str = "super_admin") -> None: account = config["accounts"].get(role) if not account: raise ValueError(f"未知角色 '{role}',可用: {list(config['accounts'].keys())}") base_url = admin_config["base_url"] skip_login = admin_config.get("skip_login", True) if skip_login: admin_page.goto(base_url) admin_page.wait_for_timeout(2000) if "#/login" not in admin_page.url and "/login" not in admin_page.url: return raise RuntimeError( f"登录态已过期!请运行: python tests/web/admin/save_auth.py --role {role}" ) # skip_login: false — 实时 UI 登录 from tests.pages.admin.login_page import LoginPage admin_page.goto(f"{base_url}/#/login") try: LoginPage(admin_page).login(account["phone"], account["password"]) except AssertionError as e: raise RuntimeError( "登录失败!可能存在图形验证码。请改为手动登录: python tests/web/admin/save_auth.py" ) from e return _ensure_login @pytest.fixture def ensure_driver_login(driver_android_config, config): """确保司机安卓端已登录。""" fn = make_ensure_logged_in(driver_android_config, config, account_role="driver") return lambda d: fn(d) # ═══════════════════════════════════════════════════════════════════ # Shared context # ═══════════════════════════════════════════════════════════════════ @pytest.fixture def cross_end_context() -> dict: """跨端流程共享上下文 — 各步骤通过此 dict 传递数据。 写入: context["order_no"] = "TEST20260721..." context["load_confirm"] = True 读取: order_no = context["order_no"] """ return { "order_no": "", "waybill_no": "", "load_confirm": False, "unload_confirm": False, "accounting_done": False, "review_done": False, }