What is inside a Playwright storageState JSON file, and which session state does it leave out?
answer
- Two arrays, cookies and origins
- Origins are keyed by exact origin
- Cookie records carry every attribute
- sessionStorage never appears
- IndexedDB only when opted in
basics
~10 sA Playwright state file has two arrays: cookies, with each cookie's attributes, and origins, pairing an origin with its localStorage name/value entries. sessionStorage, in-memory JavaScript state and IndexedDB (unless opted in) are absent.
solid answer
~50 sThe file is a JSON object with exactly two top-level arrays. `cookies` holds one entry per cookie, each carrying `name`, `value`, `domain`, `path`, `expires` (a Unix timestamp in seconds, or `-1` for a session cookie), `httpOnly`, `secure` and `sameSite`. `origins` holds one entry per origin the context visited, each an `origin` string plus a `localStorage` array of name/value pairs. That is the whole vocabulary. What is *not* there matters just as much: `sessionStorage` is never captured, so an app that keeps its token there gains nothing from the file; no in-page JavaScript state survives; and IndexedDB is included only when you ask for it with `context.storageState({ path, indexedDB: true })` in current Playwright releases, 1.63 among them. Because the file is readable JSON, the fastest way to settle any argument about what a suite restores is to open it.
code
json · 23 lines{
"cookies": [
{
"name": "console_session",
"value": "s%3A9f2c8a41d0",
"domain": "admin.example.com",
"path": "/",
"expires": 1789200000,
"httpOnly": true,
"secure": true,
"sameSite": "Lax"
}
],
"origins": [
{
"origin": "https://admin.example.com",
"localStorage": [
{ "name": "console.theme", "value": "dark" },
{ "name": "console.lastRole", "value": "support" }
]
}
]
}go deeper
Memorise the two arrays: cookies, and origins with their localStorage entries. Knowing that sessionStorage is not one of them already prevents a whole class of confusion.
Explain why cookies and localStorage obey different scoping rules — domain and path for one, exact origin for the other — and what the cookie attributes in the file actually control.
Show that you open the JSON as a first diagnostic step and can say, from its contents alone, whether a given application's session could survive the round trip at all.
Own the judgment about applications whose session does not fit the format — sessionStorage or IndexedDB holders — and decide whether to add explicit restoration or to change what the application persists.
## Two arrays, and that is all A saved Playwright storage state file is a JSON object with two top-level keys, `cookies` and `origins`. Everything a restored context knows about a session comes from those two arrays, so knowing their shape is what lets you reason about a suite that starts signed in. ```json { "cookies": [ { "name": "console_session", "value": "s%3A9f2c...", "domain": "admin.example.com", "path": "/", "expires": 1789200000, "httpOnly": true, "secure": true, "sameSite": "Lax" } ], "origins": [ { "origin": "https://admin.example.com", "localStorage": [ { "name": "console.theme", "value": "dark" } ] } ] } ``` ## The cookies array Each entry is a full cookie record rather than a bare name and value: - `name` and `value` — the pair the browser sends back. - `domain` and `path` — the scope that decides which requests carry it. - `expires` — a Unix timestamp in seconds, or `-1` for a session cookie that would normally die with the browser. - `httpOnly` — whether page JavaScript can read it. - `secure` — whether the browser will only send it over HTTPS. - `sameSite` — `Strict`, `Lax` or `None`. Those attributes are not decoration. A restored `secure` cookie will not be sent to an `http://` origin, and a cookie whose `domain` does not cover the host under test will sit in the file doing nothing. ## The origins array `origins` is keyed by **origin** — scheme, host and port together — not by hostname. Each entry carries a `localStorage` list of `{ name, value }` pairs for that one origin. An internal admin console served from `https://admin.example.com` and a copy of it on `http://localhost:3000` are two distinct origins, and a file that only mentions one of them restores storage only for that one. ## What the file does not carry | Browser state | In a saved state file? | |---|---| | Cookies | Yes, with all attributes | | `localStorage` per origin | Yes | | IndexedDB | Only with `indexedDB: true` | | `sessionStorage` | No | | In-memory JavaScript state | No | | Open pages, navigation history | No | The `sessionStorage` gap is the one that bites. `sessionStorage` is per tab and dies with it, and Playwright never records it, so an application that stashes its access token there gets nothing back from a restored file even though the file looks healthy. Applications like that need the value put back deliberately, for instance with an `page.addInitScript()` that writes the key before the app's own code reads it. IndexedDB is opt-in. In current Playwright releases, including 1.63, `context.storageState({ path: 'admin-state.json', indexedDB: true })` adds IndexedDB contents to the saved file; omit the flag and databases are skipped, which keeps the common file small. ## Why the shape is worth memorising Three everyday activities depend on it: 1. **Diagnosis.** When a suite that should start signed in renders the sign-in page, opening the JSON and comparing `origins[].origin` and `cookies[].domain` with the address the tests actually use is the fastest first move. 2. **Reasoning about scope.** Cookies are matched by domain and path; `localStorage` is matched by exact origin. Those are different rules, and they live in different arrays for that reason. 3. **Constructing state by hand.** Because the format is documented and small, an empty session is simply `{ cookies: [], origins: [] }` — a literal you can pass wherever a path is accepted. ## A note on reading the file The file is a snapshot of one moment. Values that the application writes after the `storageState()` call are absent, and values it wrote before are present regardless of whether they still matter. It carries no schema version, no marker of which browser produced it, and no encryption — it is the plainest possible JSON, and that transparency is exactly what makes it debuggable.
- The admin console keeps its token in sessionStorage. What do you do about it?Put the value back yourself, because `storageState` never captures `sessionStorage`. A common approach is `page.addInitScript()` writing the key before the application's own script reads it, so each restored context reaches the same state the file gives cookie-based apps for free.
- How would you express a completely empty session in that format?`{ cookies: [], origins: [] }` — the two arrays present but empty. Because the config and `test.use` accept the object form as well as a path, that literal is the way to declare a context with no saved session at all.
saying these in an interview costs you the question
- Claiming sessionStorage is saved alongside localStorage
- Thinking origins are matched by hostname, ignoring scheme and port
- Expecting IndexedDB in the file without passing indexedDB true
- Assuming cookies are stored as bare name and value pairs
- Believing the file records the pages the context had open