aade9e39d0
- tests/config: 环境配置 (.yaml) + 配置加载器 - tests/utils: 日志工具 + 测试数据生成器 - tests/web: Playwright conftest + Page Object 基类 + 登录页 + 导航栏组件 + 登录用例(6条) - tests/app: Appium conftest + Page Object 基类 + 司机端首页 + 登录用例(3条) - pytest.ini: markers(p0/p1/p2/smoke/web/app) + 运行配置 - 所有用例按最佳实践采用 Page Object 模式 + data-testid 优先定位 + 失败自动截图
110 lines
3.6 KiB
Python
110 lines
3.6 KiB
Python
"""
|
|
Web 端根级 fixture:浏览器实例 + 失败自动截图。
|
|
|
|
提供:
|
|
browser_name — 参数化 fixture,每个浏览器执行一轮
|
|
page — Playwright Page 对象(每个用例独立)
|
|
login — 可复用的登录 helper
|
|
|
|
Usage:
|
|
def test_something(page, browser_name):
|
|
page.goto("/admin")
|
|
...
|
|
|
|
def test_admin_flow(page, login):
|
|
login("super_admin") # 以管理员身份登录
|
|
...
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
# 截图根目录
|
|
SCREENSHOTS_DIR = Path("output/screenshots")
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def web_config(config):
|
|
"""session 级:Web 配置段。"""
|
|
return config["web"]
|
|
|
|
|
|
@pytest.fixture(params=["chromium", "firefox"])
|
|
def browser_name(request):
|
|
"""每个浏览器执行一次同一套用例。"""
|
|
return request.param
|
|
|
|
|
|
@pytest.fixture
|
|
def page(web_config, browser_name, request):
|
|
"""每个用例独立的 Playwright Page,失败自动截图。"""
|
|
viewport = web_config.get("viewport", {"width": 1920, "height": 1080})
|
|
timeout = web_config.get("timeout_seconds", 120) * 1000
|
|
screenshot_on_failure = web_config.get("screenshot_on_failure", True)
|
|
|
|
with sync_playwright() as p:
|
|
browser_launcher = getattr(p, browser_name)
|
|
browser = browser_launcher.launch(headless=True)
|
|
context = browser.new_context(
|
|
viewport={"width": viewport["width"], "height": viewport["height"]},
|
|
locale="zh-CN",
|
|
)
|
|
page = context.new_page()
|
|
page.set_default_timeout(timeout)
|
|
|
|
yield page
|
|
|
|
# ── 失败自动截图 ──
|
|
if screenshot_on_failure and 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_{browser_name}_{ts}.png")
|
|
page.screenshot(path=path, full_page=True)
|
|
|
|
context.close()
|
|
browser.close()
|
|
|
|
|
|
@pytest.fixture
|
|
def login(page, web_config, config):
|
|
"""返回一个登录 helper 函数,按角色名自动填入账号并等待登录完成。
|
|
|
|
Usage:
|
|
def test_admin(page, login):
|
|
login("super_admin")
|
|
page.getByTestId("nav-dashboard").click()
|
|
"""
|
|
|
|
def _login(role: str = "super_admin") -> None:
|
|
account = config["accounts"].get(role)
|
|
if not account:
|
|
raise ValueError(f"未知角色 '{role}',可用: {list(config['accounts'].keys())}")
|
|
|
|
base_url = web_config["base_url"]
|
|
# 如果已经打开某个页面且未过期,直接返回
|
|
if page.url and not page.url.startswith("about:") and "/login" not in page.url:
|
|
return
|
|
|
|
page.goto(f"{base_url}/login")
|
|
page.getByPlaceholder("请输入用户名").fill(account["phone"])
|
|
page.getByPlaceholder("请输入密码").fill(account["password"])
|
|
page.getByRole("button", name="登录").click()
|
|
|
|
# 等待登录成功跳转(10 秒超时)
|
|
page.wait_for_url(f"{base_url}/**", timeout=10_000)
|
|
|
|
return _login
|
|
|
|
|
|
# ── pytest 报告 hook:记录每个用例的失败状态 ──
|
|
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
|
def pytest_runtest_makereport(item, call):
|
|
"""在每个用例执行后记录结果,供 fixture 中的失败截图逻辑使用。"""
|
|
outcome = yield
|
|
rep = outcome.get_result()
|
|
setattr(item, f"rep_{rep.when}", rep)
|