
PlaywrightのstorageStateファイルは、揃って見えるのに、アプリが本当に頼っている状態を復元できないことがあります。cookiesもlocalStorageもあり、JSONも問題なく見えても、sessionStorageは既定では欠けます。アプリがセッションの一部をそこに置いているなら、新しいcontextにファイルを読み込んでも、その状態は戻りません。
この区別が効くのは、storageStateがスナップショットであり、完全なブラウザプロファイルではないからです。Playwrightはそのスナップショットを書き出し、読み込み、アカウントごとに分離し、検証しやすくします。ただし作業がsessionStorage、拡張機能、既存のログイン済みプロファイル、人がMFAを完了することなど、ファイルにきれいに収まらないブラウザ状態に依存するなら、ego (lite)は別の経路を取ります。JSONから作り直すのではなく、実Chromium環境をそのまま置きます。
このガイドが扱うのはスナップショットそのものです。storageStateに何が入るか、どう生成して読み込むか、アカウントと環境をどう分けるか、復元が本当に効いたかをどう証明するかです。ログインループと自動再認証は別の問題です。ここでの問いはもっと単純です。ファイルは何を本当に保存し、何を残したのか。
PlaywrightのstorageStateとは
PlaywrightのstorageStateは、1つのブラウザcontextのcookieとlocalStorageを保存したスナップショットです。欲しい状態がすでに入ったcontextから書き出し、後のcontextへ流し込み、空の状態ではなくそのスナップショットから始めます。
公式のPlaywright認証ドキュメントは、このファイルの上にテスト準備を組みます。それはスナップショットの利用者であり、定義ではありません。このページが扱うのはファイルです。
XやLinkedInのログイン壁は次の記事で扱います: ログイン壁の向こうのAIスクレイピング。
JavaScriptスクレイピングの経路分けは次の記事で扱います: JavaScriptによるWebスクレイピング。
storageStateファイルに実際入っているもの
ファイルはcookiesとoriginsを持つJSONです。cookiesはcookieオブジェクトの配列です。originsはoriginレコードの配列で、それぞれlocalStorageのname/valueペアを持ちます。Playwrightの storageState APIはパスを渡すとその形を書き、渡さないと同じオブジェクトを返します。
| ストア | 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から確認しました。quotes.toscrape.comのheaded Chromiumは、トップレベルキーcookiesとoriginsのstorageStateを書き出しました。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 authentication docsはsetupプロジェクトに包み、テストがログインUIを飛ばせるようにします。包みは任意です。2つの呼び出しは任意ではありません。
アプリがセッショントークンをsessionStorageにしか置かないなら、このファイルは運びません。page.evaluateでsessionStorageをコピーするか、プロセスを生かし続けるか、実プロファイルを使います。
復元も同じOpenCodeセッションで確認しました。そのファイルから読み込んだ新しいcontextは、local = local-only、session = nullと出力しました。

復元が効いたことを証明する方法
復元の証明は、書いたキーを読むか、保存した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ごとにファイルを1つ。user.jsonにステージングと本番のcookiesを混ぜると、間違ったテナントをテストします。
JSONのoriginsはoriginスコープです。https://quotes.toscrape.comに保存したlocalStorageキーは、http://quotes.toscrape.comには出ません。スキーム、ホスト、ポートのすべてが効きます。
playwright/.auth/staging-admin.json
playwright/.auth/staging-viewer.json
playwright/.auth/prod-readonly.json並行ワーカーはそれぞれ自分のファイルか自分のcontextが必要です。書き戻す2つのcontextで1つのJSONを共有すると競合します。準備のあとで書き出し、実行中は読み取り専用で読み込み、新しいファイルを書くのは専用の更新ジョブだけにします。
保存した状態の期限切れを見抜く方法
ファイルは有効に見えても、サイトがすでにセッションを取り消していることがあります。JSONのcookie expiresを見てから、そのcookieが必要なライブURLを確認します。
expires: -1または0のcookieは、スナップショット内のセッションcookieです。同じ実行では動いても、ブラウザの扱いで後から消えることがあります。過去のタイムスタンプはすでに死んでいます。未来のタイムスタンプでも、サーバー側で取り消されることがあります。
効くのはライブ確認です。読み込み後に認証済みルートを開きます。ログインページ、401、匿名シェルが返ったら、スナップショットは使い果たされています。ファイルを更新してください。このページで一度きりのログイン機械を組まないでください。
storageStateを安全に置く方法
JSONはパスワードとして扱います。Playwright自身の認証ガイドも、本人になりすますcookiesとヘッダーが入ると書いています。git、ログ、CI成果物、モデルのコンテキストに入れないでください。
# .gitignore
playwright/.auth/CIはジョブ開始時にシークレットストアからファイルを注入し、終了時に削除できます。印刷しないでください。失敗テストのzipに付けないでください。「セッションをデバッグする」ためにエージェントのプロンプトへ貼らないでください。
実ブラウザプロファイルを再利用すべきなのはどんなときか
アプリがcookiesとlocalStorage以上を必要とするなら、実Chromiumプロファイルを再利用します。拡張機能、sessionStorage、端末シグナル、人がMFAをやり遂げることです。JSONファイルはそれを運べません。
そこで合うのが ego (lite) 0.5.0.32です。エージェントは、マシン上の日常ブラウザプロファイルに対して、分離したSpaceで動きます。タブを見守れ、プロンプトを引き継げ、タスクを止められます。このバージョンのChangelog日付は2026-09-12です。詳細はego (lite)の更新履歴にあります。storageStateの書き出しツールではありません。ファイルの形が合わないときに、ファイルを飛ばす経路です。
その分離は、OpenCodeからego (lite)で確認しました。Spacesの概要はquotesの作業を単独の実行中Spaceに置き、他の作業は別Spaceへ分けていました。セッションをJSONから作り直してはいません。

同じ公開URLを、そうしたSpaceの1つで確認しました。Space 6はquotes.toscrape.comでエージェント制御のままで、Take overとStopが見えました。ブラウザ環境はそのままで、JSONスナップショットから組み立て直してはいません。

localStorageが戻り、sessionStorageがnullなら、ファイルは役目を果たしています。消えたログインフォームを証拠にしないでください。2026-09-18、復元したcontextはダミーキーからlocal = local-only、session = nullと出力し、パスワードフォームはありませんでした。
課題と制限
ファイルは揃って見えても、アプリが本当に使うストアを欠きます。それが既定の失敗です。
2つ目はoriginの不一致です。localhost:3000に保存して127.0.0.1:3000へ読み込むと、localStorageは空で、cookiesはドメイン次第で付くことがあります。
3つ目は、このページにログインの振り付けを載せすぎることです。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を書き出しても、sessionStorageキーのないJSONができます。復元したcontextはそのストアを空として読みます。2026-09-18のheaded実行はlocal = local-only、session = nullを復元しました。
storageStateファイルの作り方
欲しいcookiesとlocalStorageがすでに入ったcontextのあとで、await context.storageState({ path: 'playwright/.auth/user.json' })を呼びます。
新しいcontextへstorageStateを読み込む方法
browser.newContextにstorageState: 'playwright/.auth/user.json'を渡します。スナップショットが必要なページへ遷移する前に読み込みます。
復元が効いたかの見分け方
設定したlocalStorageキーを読むか、保存したcookiesだけが到達できるURLを開きます。ログイン画面がないことは証拠ではありません。
storageStateをgitにコミットしてよいか
いいえ。playwright/.auth/を.gitignoreに追加します。ファイルは取り込んだアカウントになりすませます。
2つのテストでstorageStateファイルを1つ共有できるか
同じスナップショットを読み取り専用で読み込めます。並行ワーカーに同じファイルを書かせないでください。アカウントが違うならロールごとにファイルを分けます。
実ブラウザプロファイルを使うべきなのはどんなとき
アプリがsessionStorage、拡張機能、MFAのための人を必要とするときです。ego (lite)はその作業を、日常のChromiumプロファイルに対するSpaceで実行します。
storageStateはuser data directoryと同じか
違います。storageStateはJSONスナップショットです。user data directoryはディスク上のプロファイルです。
次の作業がJSONスナップショットではなく、実際のログイン済みブラウザを必要とするなら、ego (lite) は無料でダウンロードできます。
