首页 / Playwright 入门教程 / 认证状态复用 storageState

Playwright 入门教程

认证状态复用 storageState

本教程共 59 篇 · 第 33 篇 · 更新于 2026-08-04 · 约 11 分钟阅读

Playwright认证复用storageState登录cookielocalStorage

本节目标:学完能把登录后的状态存下来,让之后的测试直接带着登录态跑,不用每条都重新登录。

每条测试一开头就登录,一遍遍走完整流程,又慢又啰嗦。Playwright 想了个办法:登录一次,把状态存成文件,后面谁要登录态,直接拿来用。

核心概念:存储状态

浏览器靠什么记住你登录了?主要是 cookie 和 localStorage。Playwright 把这两样打包成一个 JSON,叫 storageState(存储状态)。

复用思路很简单:先正常登录一次,把 storageState 落盘;之后新开一个 context,传进这个文件,页面一打开就已经是登录态了。

Note

默认存下来的是 cookie + localStorage。另外两样要显式打开:

  • indexedDB: true 才会把 IndexedDB 一起存进去。用 Firebase 这类把令牌塞进 IndexedDB 的方案,必须开。
  • credentials: true 才会带上虚拟 WebAuthn 凭据,也就是 passkey(通行密钥)。
await page.context().storageState({ path: authFile, indexedDB: true });

不开就默默存不到,之后测试跑起来还是未登录状态——这个坑很隐蔽。

推荐做法:setup 项目

官方最推荐的方式是用一个「setup 项目」专门负责登录,其他项目都依赖它。先写一个登录脚本:

// tests/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
import path from 'path';

const authFile = path.join(__dirname, '../playwright/.auth/user.json');

setup('登录并保存状态', async ({ page }) => {
  await page.goto('https://github.com/login');
  await page.getByLabel('Username or email address').fill('username');
  await page.getByLabel('Password').fill('password');
  await page.getByRole('button', { name: 'Sign in' }).click();

  // 等关键 URL 出现,确认 cookie 真的写进去了
  await page.waitForURL('https://github.com/');
  await page.context().storageState({ path: authFile });
});

再在配置里把 setup 声明为依赖,让真正跑测试的项目都复用这份状态:

// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    { name: 'setup', testMatch: /.*\.setup\.ts/ },
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome'],
        storageState: 'playwright/.auth/user.json',  // 用登录态
      },
      dependencies: ['setup'],   // 先跑 setup
    },
  ],
});

之后每个测试打开就是已登录,再也不用重复填账号密码。

Warning

这个 JSON 里可能含敏感 cookie,相当于能冒用你的账号。千万别提交进代码仓库,记得把它加进 .gitignore

通过 API 登录更快

如果后端支持接口登录,用 request 夹具直接发请求拿状态,比走界面快得多:

setup('用接口登录', async ({ request }) => {
  await request.post('https://github.com/login', {
    form: { 'user': 'user', 'password': 'password' },
  });
  await request.storageState({ path: 'playwright/.auth/user.json' });
});

不同角色分开存

一套系统往往有管理员和普通用户。在 setup 里登录两次,存两份文件即可:

setup('以管理员登录', async ({ page }) => {
  // 登录管理员账号...
  await page.context().storageState({ path: 'playwright/.auth/admin.json' });
});

setup('以普通用户登录', async ({ page }) => {
  // 登录普通账号...
  await page.context().storageState({ path: 'playwright/.auth/user.json' });
});

然后按文件或按组指定用哪个:

test.use({ storageState: 'playwright/.auth/admin.json' });

test('管理员视图', async ({ page }) => {
  // 已以管理员身份登录
});

一个测试里两个角色

想让管理员和用户在同一测试里互动?开两个 context 各带各的状态:

test('管理员与用户互动', async ({ browser }) => {
  const adminContext = await browser.newContext({ storageState: 'playwright/.auth/admin.json' });
  const userContext = await browser.newContext({ storageState: 'playwright/.auth/user.json' });
  const adminPage = await adminContext.newPage();
  const userPage = await userContext.newPage();
  // 分别操作两个页面...
  await adminContext.close();
  await userContext.close();
});

登录态过期怎么办

storageState 文件不是永久有效的。cookie 有自己的过期时间,过了期测试就会以未登录状态跑,莫名其妙全红。两点建议:

  1. 如果不需要跨运行保留状态,把文件写到 testProject.outputDir,每次运行前自动清空,永远用最新的。
  2. 在 CI 上定期重跑 setup,别让它缓存太久。
Tip

调试时若怀疑是登录态问题,先手动删掉 playwright/.auth 目录,让 setup 重新登录一次,能快速排除这类干扰。

什么时候该复用

只要你的测试不改动服务端状态,全用一个共享账号就够了。

如果测试会改服务端数据(比如一个在改设置、一个在读设置),并行跑就会互相打架。这时得给每个并行 worker(工作进程)配独立账号。更细的玩法我们放到第 39 章夹具组合里讲。

小结

  • storageState(存储状态)就是把 cookie 和 localStorage 打包成 JSON,登录一次,处处复用。
  • IndexedDB 和 passkey 不默认存,要传 indexedDB: true / credentials: true
  • 官方推荐用 setup 项目专门登录,其他项目用 dependencies: ['setup'] 依赖它。
  • 后端支持接口登录时,用 request 夹具发请求更快,省掉走界面的时间。
  • 多角色就存多份 JSON,用 test.use({ storageState }) 按文件或按组指定。
  • 同一个测试里要两个角色同时在场,就开两个 context,各带各的状态。
  • 这个 JSON 等于账号钥匙,务必加进 .gitignore,别提交进仓库。
  • 状态会过期。不需要跨运行保留就写进 outputDir,每次跑之前自动清空。

下一章更进一步:连浏览器都不开,直接对着后端发请求做接口测试。