pytest+playwright实现UI自动化(4)-上夹具fixture

接上篇,首先做了几个优化点:

  1. config.py中将敏感信息(数据库地址、测试账号等)改为从.env中读取,并将.env加入.gitignore(不提交至git)
  2. Page Object 里不建议改 sys.path,把项目根目录交给 pytest 管理,在pytest.ini 里加pythonpath = .

后续的页面都要基于登录态进行,那Playwright 里最适合的是 保存登录态,后续用例直接复用登录后的浏览器状态

介绍一下fixture

Fixture 是 Playwright 中用于管理测试准备和清理工作的核心机制,它能确保每个测试都运行在一个干净、隔离的环境中,并支持代码的高效复用。其核心理念是依赖注入,即在测试执行前自动注入所需资源,执行后再统一清理,从而实现解耦与复用

开始执行

  1. 创建conftest.py,封装公共 fixture,用于管理登录态和业务页面访问
python 复制代码
import time
from pathlib import Path
from urllib.parse import urlparse

import pytest
from playwright.sync_api import TimeoutError as PlaywrightTimeoutError

import config
from pages.login_page import LoginPage


AUTH_DIR = Path(".auth")
AUTH_STATE_PATH = AUTH_DIR / "state.json"
# 登录态有效期按 23 小时处理,避开后端 24 小时过期边界。
AUTH_EXPIRE_SECONDS = 23 * 60 * 60


def _get_required_test_value(key):
    """从 TEST_CONFIG 中读取必填配置,缺失时直接让用例失败并提示配置项。"""
    value = config.TEST_CONFIG.get(key)
    if not value:
        raise ValueError(f"请通过环境变量配置测试数据: {key}")
    return value


def _get_login_success_path():
    """读取登录成功后的目标路径,用于判断登录是否完成。"""
    return config.TEST_CONFIG.get("login_success_path") or "/approvalManage"


def _auth_state_is_valid():
    """检查本地登录态文件是否存在,并且没有超过约定的有效期。"""
    if not AUTH_STATE_PATH.exists():
        return False
    return time.time() - AUTH_STATE_PATH.stat().st_mtime < AUTH_EXPIRE_SECONDS


def _auth_state_passes_smoke_check(browser, browser_context_args):
    """使用本地登录态打开登录后页面,确认 token 仍可被前端识别。"""
    context = browser.new_context(
        **browser_context_args,
        storage_state=str(AUTH_STATE_PATH),
    )
    page = context.new_page()
    try:
        page.goto(_get_login_success_path())
        page.wait_for_load_state("networkidle", timeout=15000)
        page.wait_for_timeout(1500)
        token = page.evaluate("localStorage.getItem('geoAdminToken')")
        return bool(token) and urlparse(page.url).path != "/login"
    except Exception:
        return False
    finally:
        context.close()


def _wait_for_path(page, target_path, timeout=15000):
    """等待当前页面 path 变成目标值,只校验路径,不绑定域名。"""
    try:
        page.wait_for_function(
            "([targetPath]) => window.location.pathname === targetPath",
            arg=[target_path],
            timeout=timeout,
        )
    except PlaywrightTimeoutError:
        current_path = urlparse(page.url).path
        raise AssertionError(f"当前路径为 {current_path},预期路径为 {target_path}")


def _create_auth_state(browser, browser_context_args):
    """执行一次真实登录,并把登录后的浏览器状态保存到 .auth/state.json。"""
    AUTH_DIR.mkdir(exist_ok=True)

    context = browser.new_context(**browser_context_args)
    page = context.new_page()
    login_page = LoginPage(page)

    login_page.goto_page()
    login_page.login(
        _get_required_test_value("test_login_phone_number"),
        _get_required_test_value("test_login_pwd"),
    )

    login_success_path = _get_login_success_path()
    _wait_for_path(page, login_success_path)
    context.storage_state(path=str(AUTH_STATE_PATH))
    context.close()


@pytest.fixture(scope="session")
def auth_state(browser, browser_context_args):
    """
    生成或复用已登录 storage_state。

    当本地登录态不存在、超过 23 小时,或冒烟校验失败时,会重新登录生成。
    """
    if not _auth_state_is_valid() or not _auth_state_passes_smoke_check(
        browser,
        browser_context_args,
    ):
        _create_auth_state(browser, browser_context_args)
    return str(AUTH_STATE_PATH)


@pytest.fixture()
def auth_page(browser, browser_context_args, auth_state):
    """
    创建已登录页面。

    后续业务用例优先使用 auth_page,登录页自身测试继续使用默认 page。
    """
    context = browser.new_context(
        **browser_context_args,
        storage_state=auth_state,
    )
    page = context.new_page()
    yield page
    context.close()
  1. 业务页面(需要登录后访问的页面)使用auth_page
    默认"page"是 pytest-playwright 提供的默认页面对象,适合测试未登录场景,例如登录页、忘记密码页。
    "auth_page" 是已登录页面 fixture,后续需要登录后访问的业务页面优先使用它,例如以下页面:
python 复制代码
from pages.my_todo_list_page import MyTodoListPage


def test_my_todo_list_data_display(auth_page):
    """
    验证我的待办列表数据正常显示。
    """
    my_todo_list_page = MyTodoListPage(auth_page)

    response = my_todo_list_page.goto_page()
    assert response.ok, "我的待办列表接口请求失败"

    response_data = my_todo_list_page.get_response_data(response)
    total = response_data.get("total")
    data_list = response_data.get("list") or []

    assert total is None or total > 0, "我的待办列表接口未返回数据"
    assert len(data_list) > 0, "我的待办列表接口 list 为空"

    my_todo_list_page.check_pagination_total_visible()
    my_todo_list_page.check_list_data_visible()
  • Pytest 发现 test_my_todo_list_data_display需要参数 auth_page。

  • 自动寻找并执行同名的 fixture 函数,将其返回值传递给测试。

  • 测试结束后,若 fixture 中使用了 yield,则执行清理代码

至此,我们的项目结构又进一步完善,增加了以下几个文件

相关推荐
为何创造硅基生物3 小时前
LVGL 妙用 LV_OBJ_FLAG_FLOATING
ui
天空属于哈夫克34 小时前
企微 RPA 接口开放:无需官方权限,外部群自由操作
自动化·企业微信·api
ANnianStriver5 小时前
PetLumina 04 — 管理后台 UI 全面升级
java·ui·ai编程
施努卡机器视觉5 小时前
电子水泵自动化生产线如何选型?SNK施努卡一站式集成方案解析
自动化
IT阿瑞6 小时前
制造业 AI Agent 实施服务商横评:2026 年企业级自动化选型全景分析
大数据·人工智能·自动化
2501_941982056 小时前
基于自动化控制架构的企业微信群消息管理系统设计
架构·自动化·企业微信
ANnianStriver6 小时前
PetLumina 05 — App 端 UI 效果应用
java·ui·ai编程
弹简特6 小时前
【接口自动化】02-Pytest固件fixture核心机制与Allure企业级报告实战
自动化·pytest·测试
小小龙学IT6 小时前
Midscene.js:AI驱动的跨平台UI自动化革命
javascript·人工智能·ui