You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现PyTest加载测试文件前执行单次Playwright登录认证,生成/更新有效存储状态文件?

如何实现PyTest加载测试文件前执行单次Playwright登录认证,生成/更新有效存储状态文件?

我完全懂你遇到的这个痛点——用Playwright+PyTest做自动化时,要么本地的认证状态过期,要么新拉代码的同事跑测试直接卡在登录页,之前写的session级fixture还导致每个测试都弹登录窗口,太闹心了!

核心问题其实是:你需要在所有测试启动的最开始,只执行一次认证检查和登录(不管是状态文件不存在还是过期),而且这个过程要在PyTest初始化其他fixture、扫描测试用例之前完成。我结合你的代码调整了方案,绝对能解决你的问题:


第一步:整理基础配置与登录逻辑

先把常量和登录函数抽出来,确保登录时生成的状态文件路径正确,并且只在需要时弹出手动登录窗口(支持OTP):

import time
from pathlib import Path
from playwright.sync_api import sync_playwright, Browser, BrowserContext, Page
import pytest

# 全局配置
AUTH_FILE = Path(".auth/storage_state.json")
# 认证状态过期时间:这里设为24小时,可根据需求调整
AUTH_EXPIRY_SECONDS = 60 * 60 * 24

def authentication_login():
    """手动完成登录(非无头模式),并保存认证状态到文件"""
    # 确保存储目录存在,避免创建文件时报错
    AUTH_FILE.parent.mkdir(exist_ok=True)
    
    with sync_playwright() as p:
        # 非无头启动浏览器,方便你手动输入账号、OTP
        browser = p.chromium.launch(headless=False, slow_mo=500)
        context = browser.new_context()
        page = context.new_page()
        
        try:
            page.goto("http://localhost/")
            print("\n👉 请在弹出的浏览器窗口完成登录(包括OTP验证),完成后按Playwright Inspector的继续按钮或回车!")
            # 暂停等待手动登录操作
            page.pause()
            
            # 登录成功后,保存当前会话的认证状态
            context.storage_state(path=AUTH_FILE)
            print(f"✅ 新的认证状态已保存到 {AUTH_FILE}")
        finally:
            # 确保资源被正确释放
            context.close()
            browser.close()

第二步:核心会话级Autouse Fixture

这是关键!用session级+autouse=True的fixture,确保它在所有测试、所有其他fixture之前只执行一次:

@pytest.fixture(scope="session", autouse=True)
def ensure_valid_authentication():
    """会话启动前自动执行:检查认证状态,无效则触发手动登录"""
    should_trigger_login = False
    
    # 检查状态文件是否存在
    if not AUTH_FILE.exists():
        print("⚠️ 未找到认证状态文件,将启动手动登录流程...")
        should_trigger_login = True
    else:
        # 检查状态文件是否过期
        time_since_last_update = time.time() - AUTH_FILE.stat().st_mtime
        if time_since_last_update > AUTH_EXPIRY_SECONDS:
            print(f"⚠️ 认证状态已过期(超过{AUTH_EXPIRY_SECONDS//3600}小时),将启动手动登录流程...")
            should_trigger_login = True
    
    if should_trigger_login:
        authentication_login()
    
    # yield 之后的代码会在所有测试结束后执行(这里不需要额外操作,直接yield即可)
    yield

第三步:调整其他测试Fixtures

把playwright、浏览器上下文等fixture设为session级,避免重复创建资源,同时确保它们使用已生成的有效认证状态:

@pytest.fixture(scope="session")
def playwright():
    with sync_playwright() as p:
        yield p

@pytest.fixture(scope="session")
def browser(playwright):
    # 这里可以设为headless=True,因为不需要手动操作了
    browser = playwright.chromium.launch(headless=True)
    yield browser
    browser.close()

@pytest.fixture(scope="session")
def browser_context(browser):
    # 从已有的认证状态文件创建上下文
    context = browser.new_context(storage_state=AUTH_FILE)
    yield context
    context.close()

@pytest.fixture(scope="function")
def page(browser_context):
    page = browser_context.new_page()
    page.goto("http://localhost/")
    # 可以保留你自己的加载等待逻辑
    # wait_for_loaders_to_complete(page)
    yield page
    page.close()

为什么这个方案能解决你的问题?

  1. 执行时机绝对优先:ensure_valid_authentication是session级+autouse=True,PyTest会在整个测试会话的最开始就执行它,比所有测试用例、其他fixture都早,而且只执行一次。
  2. 避免重复登录:只有在状态文件不存在/过期时才会弹出手动登录窗口,后续所有测试都会直接复用有效状态,不会再弹登录页。
  3. 资源高效:浏览器、上下文都是session级,整个测试会话只会启动一次浏览器,不会像之前那样每个测试都开新窗口。

额外优化建议

  • 加文件锁避免并行测试冲突:如果用了pytest-xdist并行跑测试,可以加个文件锁确保只有一个进程触发登录:
    from filelock import FileLock
    
    def authentication_login():
        lock_file = AUTH_FILE.parent / "login.lock"
        with FileLock(lock_file):
            # 原来的登录逻辑...
    
  • 支持命令行参数调整配置:可以把过期时间、状态文件路径做成pytest命令行参数,方便灵活修改:
    def pytest_addoption(parser):
        parser.addoption("--auth-expiry", type=int, default=60*60*24, help="认证过期时间(秒)")
        parser.addoption("--auth-file", type=Path, default=AUTH_FILE, help="认证状态文件路径")
    

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 08:07:58