Skip to content

第 9 章 · 认证状态复用 storage_state

本章目标:理解 Playwright 的认证复用模型,学会保存与加载 storage_state,按角色管理多份登录态,并在 pytest 中通过 fixture 让全部测试"开机即已登录"。

9.1 认证为什么拖慢 E2E

绝大多数 Web 应用的测试都绕不开登录。如果每个测试都从头走一遍登录表单:每个用例多花 2–5 秒、登录页改版会打挂所有用例、验证码/2FA 更是自动化噩梦。

Playwright 的解法建立在它的隔离模型上:认证状态(cookies、localStorage、IndexedDB)都挂在 BrowserContext 上,而 context 可以把这份状态导出成 JSON 文件、再在任意新 context 中注入还原——这就是 storage_state

python
# ① 登录一次,把状态存盘
page.goto("https://github.com/login")
page.get_by_label("Username or email address").fill("username")
page.get_by_label("Password").fill("password")
page.get_by_role("button", name="Sign in").click()
page.wait_for_load_state()

storage = context.storage_state(path="state.json")   # 导出为文件

# ② 之后任何 context 都可以"出生即已登录"
authed = browser.new_context(storage_state="state.json")
page2 = authed.new_page()
page2.goto("https://github.com")      # 无需再走登录流程

安全提醒

官方文档强烈建议:状态文件里包含可冒充你账号的敏感 Cookie 与令牌,务必把存放目录(如 playwright/.auth/)加入 .gitignore,绝不能提交进仓库。

9.2 按角色管理多份认证

权限体系复杂的系统需要"管理员视角""普通用户视角"分别测试。做法就是给每个角色各存一份状态文件:

python
import os
os.makedirs("playwright/.auth", exist_ok=True)

def login_and_save(browser, creds, out_path):
    """通用登录例程:登录成功后把 storage_state 写到指定文件"""
    context = browser.new_context()
    page = context.new_page()
    page.goto("https://myapp.example/login")
    page.get_by_label("用户名").fill(creds["user"])
    page.get_by_label("密码").fill(creds["pass"])
    page.get_by_role("button", name="登录").click()
    expect(page.get_by_text("工作台")).to_be_visible()   # 确认真的登录成功
    context.storage_state(path=out_path)                # 按角色落盘
    context.close()

login_and_save(browser, ADMIN_CREDS, "playwright/.auth/admin.json")
login_and_save(browser, USER_CREDS, "playwright/.auth/user.json")

# 各自的测试加载对应的角色
admin_ctx = browser.new_context(storage_state="playwright/.auth/admin.json")
user_ctx = browser.new_context(storage_state="playwright/.auth/user.json")

9.3 在 pytest 中用 fixture 加载登录态

结合 pytest-playwright 提供的 browser fixture,写一个 auth_page fixture,让依赖它的测试自动获得已登录的页面:

python
# conftest.py
import pytest
from playwright.sync_api import expect, Page

@pytest.fixture(scope="session")          # 会话级:整场测试只登录一次
def auth_context(browser, base_url):
    context = browser.new_context(
        storage_state="playwright/.auth/user.json",
        base_url=base_url,
    )
    yield context
    context.close()

@pytest.fixture
def auth_page(auth_context) -> Page:
    page = auth_context.new_page()
    yield page

@pytest.fixture(scope="session", autouse=False)
def ensure_auth_state(browser, base_url):
    """状态文件不存在时现场生成一份(首次运行 / CI 冷启动兜底)"""
    import os
    path = "playwright/.auth/user.json"
    if not os.path.exists(path):
        ctx = browser.new_context(base_url=base_url)
        page = ctx.new_page()
        page.goto("/login")
        page.get_by_label("用户名").fill(os.environ["E2E_USER"])
        page.get_by_label("密码").fill(os.environ["E2E_PASS"])
        page.get_by_role("button", name="登录").click()
        expect(page.get_by_text("工作台")).to_be_visible()
        ctx.storage_state(path=path)
        ctx.close()

注意作用域选择:auth_contextscope="session" 复用同一个 context;如果某些测试会破坏会话状态(登出、改资料),就把它降级为函数级或单独提供干净的 fixture 变体。

9.4 Token 过期的处理策略

storage_state 只是快照,token 过期后所有用例会在同一时刻集体变红。常用策略:

  • 短周期内有效 + CI 前置刷新:CI 每次构建先跑一个"登录并导出 state"的 setup 步骤,保证整轮测试期间 token 新鲜;
  • 检测失效自动重建:fixture 里访问首页后检查是否被踢回登录页,是则重新执行登录例程并覆盖 state 文件:
python
def is_logged_in(page) -> bool:
    """访问受保护页面,判断是否被重定向回登录页"""
    page.goto("/dashboard")
    page.wait_for_load_state()
    return "login" not in page.url

# 在加载状态的 fixture 里做一次体检,失效则重建
if not is_logged_in(auth_page):
    login_and_save(browser, USER_CREDS, "playwright/.auth/user.json")
  • 延长测试环境 token 有效期:专供 E2E 的环境把过期时间调长到小时级,是最简单可靠的工程折衷;
  • 官方推荐的完整形态是 setup 项目:用一个专门的项目跑登录脚本产出状态文件,其余项目声明依赖它(Node 版 Playwright 的 project dependencies 特性;Python 侧等价做法即上面的 session 级 fixture 兜底)。

本章小结

  • 认证状态属于 BrowserContext,storage_state(path=...) 导出、new_context(storage_state=...) 注入;
  • 状态文件含敏感凭据,必须 gitignore;
  • 多角色 = 多份状态文件,命名按角色组织;
  • pytest 中用 session 级 fixture 加载登录态,配合"不存在则现场登录"的兜底逻辑应对 CI 冷启动与 token 过期。

🧪 随堂测验

点击你认为正确的选项。答错时会展示正确答案与原因解析。

1. context.storage_state(path="state.json") 导出的内容包含哪些认证数据?

2. 关于认证状态文件的仓库安全,官方的建议是?

3. pytest 中让"已登录页面"供多个测试复用的最佳作用域选择是?

4. storage_state 文件里的 token 过期后,下列哪种处理方式不可取?

🛠️ 动手实践

  1. 为任一支持本地注册的演示站点实现"注册→登录→导出 storage_state→新 context 直接进入个人中心"的完整脚本。
  2. 把本章的 ensure_auth_state 兜底 fixture 补全到你的 conftest.py,删除 state 文件后验证测试仍能整体跑通。
  3. 为 admin/user 两个角色各建一份登录态,编写两个测试分别断言"管理员能看到后台入口""普通用户看不到"。

下一章:表单与复杂组件交互