skip to content

Playwright loads a saved storageState, yet the admin console still renders signed out — how do you diagnose it?

level: seniorimportance: should knowfreq 52%

answer

  1. Print the file before debugging the test
  2. Origins match scheme, host and port
  3. Cookies match domain and path instead
  4. Ask where the app keeps its token
  5. Snapshot taken before the write lands

basics

~20 s

Open the JSON and compare it with the address under test. Usual causes are an origins entry recorded for a different origin, a session kept in sessionStorage which the file never captures, or state saved before the application had written it.

solid answer

~40 s

Read the file first — it is plain JSON, and it answers most of the question on its own. Compare `origins[].origin` with the exact origin the tests hit: origins match on scheme, host **and** port, so a file captured against `http://localhost:3000` restores no `localStorage` to `https://admin.example.com`. Then compare `cookies[].domain` and the `secure` flag with the same address, since a `secure` cookie will not be sent to an `http://` origin. If both arrays look right for the origin, ask *what* the application relies on: a console that keeps its token in `sessionStorage` gains nothing from the file, because `storageState` never captures it. Finally, check the moment of capture — a `storageState()` call made before the app finished writing its session records the values that existed then, not the ones that arrive a moment later.

code

typescript · 15 lines
typescript
import { readFileSync } from 'node:fs';

type State = {
  cookies: { name: string; domain: string; path: string; secure: boolean }[];
  origins: { origin: string; localStorage: { name: string }[] }[];
};

const state: State = JSON.parse(readFileSync('admin-state.json', 'utf8'));

for (const o of state.origins) {
  console.log(o.origin, o.localStorage.map((e) => e.name));
}
for (const c of state.cookies) {
  console.log(`${c.name} -> ${c.domain}${c.path} secure=${c.secure}`);
}

go deeper

for a junior

Learn the first move: open the JSON and compare its origins and cookie domains with the address the tests use. Most of these failures are visible in the file without running anything.

for a middle

Explain why cookies and localStorage fail differently — domain and path versus exact origin — and why a token kept in sessionStorage can never be restored from the file.

for a senior

Work the causes in order and prove the fix against the file itself, so that the outcome is a specific, evidenced explanation rather than a re-save that happens to work today.

for a principal

Own the practice that keeps this rare: state captured against the same origin the suite targets, a signed-in assertion guarding the save, and a shared understanding of what the format can and cannot carry.

## Start with the file, not the test A saved state file is readable JSON, so the first move is to print what it actually contains and put it beside the address the suite is exercising. Three fields decide almost every case: each entry of `origins[].origin`, each `cookies[].domain`, and each cookie's `secure` flag. ```ts const state = JSON.parse(readFileSync('admin-state.json', 'utf8')); console.log(state.origins.map((o) => o.origin), state.cookies.map((c) => `${c.domain}${c.path}`)); ``` If that output does not mention the origin the tests navigate to, the diagnosis is finished before any browser is launched. ## Cause 1 — the origin does not match `origins` entries are keyed by **exact origin**: scheme, host and port together. These are four different origins, and a file recording one restores `localStorage` to none of the others: - `http://localhost:3000` - `http://localhost:4000` - `https://localhost:3000` - `https://admin.example.com` Capturing state against a locally served copy of the internal admin console and then running the suite against a deployed one is the classic version of this. Cookies are more forgiving, because they match on domain and path rather than on a whole origin, which produces the confusing symptom where the network requests look authenticated but everything the front end kept in `localStorage` is missing. ## Cause 2 — the session is not in a place the file captures `storageState` records cookies and per-origin `localStorage`, plus IndexedDB when `context.storageState({ indexedDB: true })` asks for it in current releases such as 1.63. It records `sessionStorage` never, and in-memory JavaScript state never. An admin console that holds its access token in `sessionStorage` therefore restores a file that is technically perfect and still lands on the sign-in screen. The tell is that the JSON's `origins` entry is present and correct but does not contain the key the application reads. The fix is to restore that value deliberately — for example with a `page.addInitScript()` that writes the key before the app's own script runs — rather than to keep enlarging the file. ## Cause 3 — the snapshot was taken too early `storageState()` writes what exists at the instant it is called. A save that happens immediately after clicking **Sign in**, without waiting for the signed-in view, can beat the application to the write: 1. The click posts the credentials. 2. `storageState()` runs and serialises a context that has the cookie but not yet the profile the app stores locally. 3. Every later run starts half-signed-in and behaves unpredictably. Waiting on a signed-in assertion before the save — an expectation on a dashboard heading, say — makes the snapshot deterministic. ## Cause 4 — cookie attributes rule the cookie out The file preserves each cookie's attributes, and the browser still honours them after restoring: | Attribute in the file | Symptom when it disagrees with the address under test | |---|---| | `secure: true` | Cookie is present but never sent to an `http://` origin | | `domain` | Cookie is present but out of scope for the host being tested | | `path` | Cookie is present but never sent to the paths the tests visit | | `sameSite: "Strict"` | Cookie withheld on cross-site navigations into the console | Each of these shows the same outward symptom — a signed-out page — and each is visible by reading the cookie record next to the URL. ## Ruling causes out in order - Print `origins` and cookie domains, and compare them with the address the tests use. - Look for the key the application actually reads inside the matching origin's `localStorage`. - Check whether that key is one the application keeps in `sessionStorage` instead. - Re-save with an assertion on a signed-in element immediately before the `storageState()` call. - Check the cookie's `secure`, `path` and `sameSite` against the scheme and paths under test. ## Confirming the fix rather than assuming it Once a cause is identified, prove it at the level the file operates on: reopen the JSON and confirm the origin, the key and the cookie attributes now match the address under test. That check is cheaper than a full run, and it distinguishes "the state is right" from "the application happened to behave" on a given day. Everything in this diagnosis is a property of the state file's shape, so the file is both the symptom and the evidence.

  • The cookies clearly arrive but the front end still thinks nobody is signed in. What does that narrow it to?
    It points at the storage half rather than the cookie half. Either the `origins` entry names a different origin than the one under test, or the value the front end reads lives in `sessionStorage`, which the file never captures. Both are visible by reading the JSON next to the URL.
  • How do you stop a saved state file being captured half-written?
    Assert on something only a signed-in session shows — a dashboard heading, an account menu — immediately before calling `context.storageState({ path })`. The assertion waits for the application to finish writing its session, so the snapshot records a complete state rather than whatever existed mid-flight.
  • Why can the same file work locally and fail against a deployed console?
    Because `origins` is matched on the whole origin. A file captured on `http://localhost:3000` carries no `localStorage` for `https://admin.example.com`, and any `secure` cookie in it is withheld from an `http://` target. The file is unchanged; the address it is being replayed against is not.

saying these in an interview costs you the question

  • Debugging the test before reading the state file
  • Assuming origins match on hostname alone
  • Expecting sessionStorage values to come back from the file
  • Saving state immediately after the click, without waiting
  • Ignoring the secure flag when the target uses http