
Playwright 的 storageState 文件看起来可以很完整,却仍还原不出应用真正依赖的状态。cookies 可能在,localStorage 可能在,JSON 也可能完全合法,但 sessionStorage 默认不在文件里。应用若把部分会话放在那里,把文件加载到新 context 也带不回来。
这个差别很重要,因为 storageState 是快照,不是完整浏览器配置文件。Playwright 让这份快照容易导出、加载、按账号隔离,也容易校验。但任务若依赖装不进这个文件的浏览器状态,例如 sessionStorage、扩展、已有的登录配置文件,或要人完成 MFA,ego (lite) 走另一条路:把真实 Chromium 环境留在原地,而不是试图用 JSON 重建。
本指南只谈快照本身:storageState 里有什么、怎么生成与加载、账号和环境怎么分开,以及如何证明还原真的生效。登录循环与自动重新认证是另一个问题。这里问的更单纯:这个文件真正存了什么,又留下了什么?
Playwright storageState 是什么?
Playwright storageState 是单个浏览器 context 存下来的 cookies 与 localStorage 快照。你在 context 已有要的状态后导出,再拿它去播种之后的 context,让它从这份快照起步,而不是空罐。
官方 Playwright 认证文档用这个文件做测试 setup。那是快照的消费者,不是快照的定义。这页只谈这个文件。
X 和 LinkedIn 的登录墙见 登录墙后方的 AI 抓取。
JavaScript 抓取路线见 用 JavaScript 做网页抓取。
storageState 文件实际存了什么?
这个文件是带 cookies 和 origins 的 JSON。cookies 是 cookie 对象数组。origins 是 origin 记录数组,每条带 localStorage 的 name/value。Playwright 的 storageState API 在你传 path 时写出这个形状;不传 path 时返回同一个对象。
| 存储区 | 默认在 storageState 里? | 代表什么 |
|---|---|---|
| cookies | 是 | 每条可带 name、value、domain、path、expires、httpOnly、secure、sameSite。 |
| localStorage | 是,在 origins 下面 | 按 origin 当键。存在 https://quotes.toscrape.com 的键,不会出现在另一个 origin。 |
| sessionStorage | 否 | JSON 默认没有 sessionStorage 键。还原后的页面读 sessionStorage 会得到 null。 |
我们在 2026-09-18 从 OpenCode 测过这个文件形状。headed Chromium 在 quotes.toscrape.com 导出的 storageState,顶层键是 cookies 和 origins。d05-demo 在 origins 的 localStorage 里。JSON 字符串不含 sessionStorage 或 d05-session。

Playwright 的认证指南也提到,部分设置的复用状态会含 IndexedDB 和 passkeys。别因为某篇博客列过,就假设那些键一定在。打开你生成的文件,读顶层键。
没有 Expires 或 Max-Age 的会话 cookie,是配置文件目录里另一个陷阱。磁盘行为见 持久浏览器会话。在 storageState JSON 里,cookie 过期是每条 cookie 的明确字段。0 或过去的时间戳,就是你看到这颗 cookie 撑不过下一个日历日的方式。
怎么生成 storageState 并加载?
从已经有你要的状态的 context 生成。在打开需要它的页面之前,加载到新 context。别从一个 origin 导出,却期待另一个 origin 的 localStorage 出现。
import { chromium } from "playwright";
const browser = await chromium.launch();
const setup = await browser.newContext();
const page = await setup.newPage();
await page.goto("https://quotes.toscrape.com/");
await page.evaluate(() => {
localStorage.setItem("d05-demo", "local-only");
sessionStorage.setItem("d05-session", "session-only");
});
await setup.storageState({ path: "playwright/.auth/user.json" });
await setup.close();
const reused = await browser.newContext({
storageState: "playwright/.auth/user.json",
});
const next = await reused.newPage();
await next.goto("https://quotes.toscrape.com/");
const restored = await next.evaluate(() => ({
local: localStorage.getItem("d05-demo"),
session: sessionStorage.getItem("d05-session"),
}));
console.log(restored);
await browser.close();那段代码就是整个机制。官方 Playwright 认证文档 会把它包进 setup project,让测试跳过登录界面。包装可选。那两个调用不行。
应用若只把会话 token 放在 sessionStorage,这个文件带不走。用 page.evaluate 复制 sessionStorage、让进程保持活着,或改用真实配置文件。
同一段 OpenCode 会话里,我们测过还原。从该文件加载的新 context 印出 local = local-only、session = null。

如何校验 storageState 是否生效?
用你写过的键证明还原,或用只有已存 cookies 才能打开的已认证 URL。别把「登录表单不见了」当证据。重定向错误也能把表单藏起来。
await page.goto("https://quotes.toscrape.com/");
const local = await page.evaluate(() => localStorage.getItem("d05-demo"));
if (local !== "local-only") {
throw new Error("storageState did not restore localStorage");
}真实账号就打一个只有 cookie 有效才回 200 的 URL,再断言看得见的已登录标签。落到 /login,快照就过期了。重新认证不在这页。这里只要失败信号:这份 JSON 没把会话还原回来。
| 检查 | 通过 | 失败 |
|---|---|---|
| localStorage 键 | 读到你存下的值 | 加载后是 null |
| sessionStorage 键 | null,除非你自己复制过 | 因为 localStorage 还在,就假设它也还在 |
| Cookie 过期 | 你需要的 cookies,expires 在未来 | expires 是 0,或已经过去 |
账号和环境要怎么隔离?
每个账号、每个环境、每个你打算复用的浏览器 context,各一份文件。把 staging 和 production 的 cookies 混进 user.json,就是在测错租户。
JSON 里的 origins 以 origin 为范围。存在 https://quotes.toscrape.com 的 localStorage 键,不会出现在 http://quotes.toscrape.com。scheme、host、port 都算。
playwright/.auth/staging-admin.json
playwright/.auth/staging-viewer.json
playwright/.auth/prod-readonly.json并行 worker 各自需要自己的文件或自己的 context。两份 context 共用一份 JSON 再写回去,就是竞态。setup 后导出,运行期间只读加载,新文件只由专门的刷新任务写出。
怎么判断存下来的状态已过期?
文件看起来可以仍有效,站点却已撤销会话。先查 JSON 里的 cookie expires,再打一个需要那颗 cookie 的实时 URL。
expires: -1 或 0 的 cookie,在快照里是会话 cookie。同一次运行可能还能用,之后会不会消失取决于浏览器怎么对待它。过去的时间戳已经死了。未来的时间戳仍可能被服务端撤销。
真正要紧的是实时检查。加载后打开需要认证的路由。若拿到登录页、401 或匿名空壳,快照就用完了。刷新这个文件。这页不写「登录一次就永远能跑」的机器。
storageState 该怎么安全存放?
把这份 JSON 当密码。Playwright 自己的认证指南说,它可能含有能冒充你的 cookies 和请求头。别放进 git、日志、CI 产物,也别放进模型上下文。
# .gitignore
playwright/.auth/CI 可以在任务开始时从密钥存储注入这个文件,结束时删掉。别打印出来。别附加到失败测试的 zip。别贴进 Agent 提示词去「调试会话」。
什么时候该改用真实浏览器配置文件?
应用需要的不只 cookies 和 localStorage 时,复用真实 Chromium 配置文件:扩展、sessionStorage、设备信号,或要人坐着过 MFA。JSON 文件带不走这些。
这就是 ego (lite) 0.5.0.32 适合的地方。Agent 在隔离的 Space 里,对准机器上已有的日常浏览器配置文件。标签页可以监视,提示可以接管,任务可以停。该版本的更新日志日期是 2026-09-12,见 ego (lite) 更新日志。它不是 storageState 导出器。文件形状不对时,它是跳过这个文件的那条路。
我们从 OpenCode 在 ego (lite) 测过这层隔离。Spaces 总览把名言任务放在自己正在跑的 Space,其他工作在另一个 Space,而不是用 JSON 重建会话。

我们在其中一个 Space 里测过同一个公开 URL。Space 6 在 quotes.toscrape.com 维持 agent control,Take over 和 Stop 看得见。浏览器环境留在原地,而不是用 JSON 快照重建。

localStorage 回来、sessionStorage 是 null,这个文件就做完它该做的。别把登录表单消失当证据。2026-09-18 还原后的 context 用假键印出 local = local-only、session = null,没有密码表单。
挑战和限制有哪些?
文件看起来完整,却仍漏掉应用真正在用的存储区。这就是默认失败。
第二是 origin 对不上。存在 localhost:3000,对 127.0.0.1:3000 加载,localStorage 是空的,cookies 则可能按 domain 仍会附上。
第三是把登录编舞塞进这页。检测 401、跳去 /login、刷新快照,都是真工作。那不是这个文件的工作。
常见问题
Playwright storageState 含 cookies 吗?
含。cookies 是 JSON 的顶层数组。每颗 cookie 可含 name、value、domain、path、expires、httpOnly、secure、sameSite。
storageState 含 localStorage 吗?
含,在 origins 下面。每条 origin 记录只放该 origin 的 localStorage name/value。
storageState 含 sessionStorage 吗?
默认不含。页面可以写 sessionStorage、导出 storageState,产出的 JSON 仍没有 sessionStorage 键。还原后的 context 读那个存储区是空的。2026-09-18 的 headed 运行还原出 local = local-only、session = null。
怎么生成 storageState 文件?
等 context 已有你要的 cookies 和 localStorage 后,调用 await context.storageState({ path: 'playwright/.auth/user.json' })。
怎么在新 context 加载 storageState?
把 storageState: 'playwright/.auth/user.json' 传进 browser.newContext。导航到需要这份快照的页面之前先加载。
如何校验 storageState 是否生效?
读你设过的 localStorage 键,或打开只有已存 cookies 才能到的 URL。登录界面不见了,不是证据。
storageState 该不该 commit 进 git?
不该。把 playwright/.auth/ 加进 .gitignore。这个文件可以冒充它捕捉到的账号。
两个测试能共用一份 storageState 文件吗?
可以只读加载同一份快照。别让并行 worker 写同一个文件。账号不同就每个角色一份文件。
什么时候该改用真实浏览器配置文件?
应用需要 sessionStorage、扩展,或 MFA 要人时。ego (lite) 在 Space 里对准你日常的 Chromium 配置文件跑这些工作。
storageState 等于 user data directory 吗?
不是。storageState 是 JSON 快照。user data directory 是磁盘上的配置文件。
若下一份任务需要真实已登录浏览器,而不是 JSON 快照,ego (lite) 可以免费下载。
