认证状态复用 storageState
本教程共 59 篇 · 第 33 篇 · 更新于 2026-08-04 · 约 11 分钟阅读
本节目标:学完能把登录后的状态存下来,让之后的测试直接带着登录态跑,不用每条都重新登录。
每条测试一开头就登录,一遍遍走完整流程,又慢又啰嗦。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 有自己的过期时间,过了期测试就会以未登录状态跑,莫名其妙全红。两点建议:
- 如果不需要跨运行保留状态,把文件写到
testProject.outputDir,每次运行前自动清空,永远用最新的。 - 在 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,每次跑之前自动清空。
下一章更进一步:连浏览器都不开,直接对着后端发请求做接口测试。