feat: 重构测试框架 — 覆盖 6 端 (管理端 + 小程序 + 4 个移动端)

目录结构:
  tests/web/admin/       管理端 (Playwright)
  tests/web/mini_program/ 司机端小程序 (Playwright/微信 WebView)
  tests/app/driver_android/  司机安卓 (Appium)
  tests/app/shipper_android/ 货主安卓 (Appium)
  tests/app/driver_ios/      司机苹果 (Appium)
  tests/app/shipper_ios/     货主苹果 (Appium)

公共模块:
  tests/app/_shared.py       driver+ensure_logged_in 工厂
  tests/app/_base_page.py    App Page Object 基类

配置:
  test_config.yaml 拆分为 6 个独立 section + 货主账号
This commit is contained in:
xst
2026-07-15 14:02:08 +08:00
parent 4b4b83a2c0
commit 03e152baab
37 changed files with 823 additions and 727 deletions
+52
View File
@@ -0,0 +1,52 @@
"""司机端小程序 fixture — Playwright(模拟微信 WebView)。
核心机制:
模拟微信内置浏览器 UA,微信 WebView 仍需登录态。
首次使用 python tests/web/admin/save_auth.py 生成后用在此端也可以。
"""
from datetime import datetime
from pathlib import Path
import pytest
from playwright.sync_api import sync_playwright, Page
SCREENSHOTS_DIR = Path("output/screenshots")
@pytest.fixture
def page(mini_program_config, request) -> Page:
"""微信 WebView 模拟 Page。"""
viewport = mini_program_config.get("viewport", {"width": 375, "height": 812})
timeout = mini_program_config.get("timeout_seconds", 120) * 1000
user_agent = mini_program_config.get("user_agent", "Mozilla/5.0 MicroMessenger/8.0.0")
base_url = mini_program_config["base_url"]
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
context = browser.new_context(
viewport={"width": viewport["width"], "height": viewport["height"]},
user_agent=user_agent,
locale="zh-CN",
)
page = context.new_page()
page.set_default_timeout(timeout)
yield page
if hasattr(request.node, "rep_call") and request.node.rep_call.failed:
scenario_dir = SCREENSHOTS_DIR / request.node.name
scenario_dir.mkdir(parents=True, exist_ok=True)
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
path = str(scenario_dir / f"FAIL_mini_{ts}.png")
page.screenshot(path=path)
context.close()
browser.close()
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
setattr(item, f"rep_{rep.when}", rep)
+31
View File
@@ -0,0 +1,31 @@
"""小程序 Page Object 基类。"""
from datetime import datetime
from pathlib import Path
from playwright.sync_api import Page, Locator, expect
SCREENSHOTS_DIR = Path("output/screenshots")
class MiniProgramBasePage:
"""小程序 Page Object 基类。"""
def __init__(self, page: Page, timeout_ms: int = 30_000):
self.page = page
self.timeout = timeout_ms
def wait_visible(self, locator: Locator, timeout_ms: int | None = None) -> None:
expect(locator).to_be_visible(timeout=timeout_ms or self.timeout)
def click_when_ready(self, locator: Locator) -> None:
self.wait_visible(locator)
locator.click()
def screenshot(self, name: str) -> str:
scenario_dir = SCREENSHOTS_DIR / self.__class__.__name__
scenario_dir.mkdir(parents=True, exist_ok=True)
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
path = str(scenario_dir / f"{name}_{ts}.png")
self.page.screenshot(path=path)
return path
@@ -0,0 +1,24 @@
"""小程序首页 — Page Object。
Usage:
from tests.web.mini_program.pages import MiniProgramBasePage
"""
from playwright.sync_api import Page
from tests.web.mini_program.pages import MiniProgramBasePage
class MiniHomePage(MiniProgramBasePage):
"""司机端小程序首页。"""
def __init__(self, page: Page):
super().__init__(page)
# ⚠️ 定位器需根据实际小程序 UI 确认
self.order_list_tab = page.get_by_text("运单")
self.mine_tab = page.get_by_text("我的")
def go_to_order_list(self) -> None:
self.click_when_ready(self.order_list_tab)
def go_to_mine(self) -> None:
self.click_when_ready(self.mine_tab)
@@ -0,0 +1,16 @@
"""小程序冒烟测试。"""
import pytest
pytestmark = [pytest.mark.mini_program]
class TestMiniProgramSmoke:
"""小程序基础验证。"""
@pytest.mark.p0
@pytest.mark.smoke
def test_mini_program_accessible(self, page, mini_program_config):
"""验证小程序首页可正常加载。"""
page.goto(mini_program_config["base_url"])
assert page.title(), "小程序首页应显示页面标题"