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,则执行清理代码

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

相关推荐
laowangpython12 天前
Photoshop 2025 下载安装全攻略
其他·ui·photoshop
shushangyun_12 天前
2026年快消品B2B系统推荐:支持终端门店订货、促销政策自动化的工具?
java·运维·网络·数据库·人工智能·spring·自动化
施努卡机器视觉12 天前
SNK施努卡侧滑门锁上滑轮总成自动化装配线,从零件到组件,全流程精密制造方案
运维·自动化·制造
dayuOK630712 天前
写作卡壳怎么办?我的“5分钟启动法”
人工智能·职场和发展·自动化·新媒体运营·媒体
风华圆舞12 天前
Flutter + 鸿蒙 Intents Kit:页面直达能力的完整接入方案
flutter·ui·华为·harmonyos
鲲穹AI超级员工12 天前
多款实用配色工具汇总,适配设计、UI 创作等多元场景
ui·色彩设计
志栋智能12 天前
超自动化巡检:如何选择适合你的起点?
运维·自动化
HackTwoHub12 天前
Sqli-Scanner SQL注入SKILL自动化挖掘SQL注入,零依赖自动化SQL注入挖掘,赏金猎人
数据库·人工智能·sql·web安全·网络安全·自动化·系统安全
csdndeyeye12 天前
拆解AI投简历插件:塔塔网申的技术逻辑和实测数据
人工智能·自动化·秋招·ai投简历插件·ai找工作·求职助手·应届生就业
小白学大数据12 天前
Python + 大模型行业资讯自动化摘要流水线完整工程实现方案
开发语言·python·自动化