skip to content

An end-to-end suite loads its authenticated session from a state file committed to the repository. It passed for weeks, then every test started failing with a redirect to the login page. What went wrong, and how should that session be produced instead?

level: seniorimportance: should knowfreq 41%

answer

  1. a session file is a perishable credential
  2. it looks the same when it stops working
  3. never commit it, always regenerate
  4. fail loudly in setup, not in assertions
  5. long suites can outlive short tokens

basics

~20 s

The saved session expired. A stored session is a time-limited credential, not a fixture, so committing it guarantees it goes stale — and leaks a credential. Regenerate it by authenticating at the start of every run, write it to a gitignored path, and fail loudly when setup cannot authenticate.

solid answer

~50 s

A storage-state file is a snapshot of a credential with a lifetime attached — a session cookie's max-age, or an access token's expiry claim. Committing it freezes a value that the server will eventually stop honouring, so the suite works until the day it does not, and then every test redirects to login at once. It is also a secret in version control. The fix is to treat authentication as a step of the run rather than an artefact of the repo: a setup phase authenticates each identity against the target environment, writes the state to a gitignored directory, and the tests restore from there. Have that setup assert the response is successful and fail with a clear message, so an auth outage reports as "login failed" rather than as two hundred confusing assertion failures. For suites long enough to outlive an access token, make sure the restored state also carries whatever the app refreshes with, or re-authenticate per worker.

go deeper

for a junior

Know that a saved login session expires like any credential and must be regenerated by the test run, and that such a file is a secret that never belongs in the repository.

for a middle

Explain where the lifetime actually lives — a cookie max-age or a token expiry the server enforces — and why the file gives no visible signal before it stops working.

for a senior

Diagnose from the failure shape: everything failing at once means a stale or revoked credential, while failures clustered late mean the session expired inside the run; then choose between capturing the refresh credential, re-authenticating per worker, or extending lifetimes.

for a principal

Own the policy: which credentials live in CI secrets and how they rotate, whether test environments may diverge from production token lifetimes, and how the suite still proves that real expiry and re-authentication behave correctly.

## Reading the symptom "Every test suddenly redirects to login" is a distinctive failure. Total, simultaneous, and unrelated to any code change is the signature of a *credential* problem rather than a code problem. The three usual causes, in order of likelihood: 1. The saved session expired. 2. The session was invalidated — the test account's password was rotated, the account was disabled, or a "sign out everywhere" action revoked it. 3. The environment changed under the file — the state was captured against a different origin, or the environment was reset and the account no longer exists. All three point at the same root design error: the session was treated as a static fixture when it is a perishable credential. ## Why a committed state file is doomed Authentication artefacts carry lifetimes by design. A session cookie has a max-age; an access token has an expiry; a refresh token is often single-use and rotated on redemption. Whatever the mechanism, the server decides when the snapshot stops being honoured, and that decision is invisible from the file. The file looks identical on the day it works and the day it stops. There is a second, worse problem: a state file is a working credential for a real account. In version control it is a leaked secret — visible in history even after deletion, and usable by anyone who can read the repo against whatever environment it was minted for. `.gitignore` the directory, and if such a file has already been committed, rotate the account's credentials rather than only deleting the file. ## Produce the session as part of the run The robust pattern is that nothing authenticated is stored between runs: 1. A setup phase runs before the tests, once per identity. 2. It authenticates against the *target* environment, using credentials from the environment (a CI secret), never from a checked-in file. 3. It writes the resulting state to a gitignored working directory. 4. Tests restore from that directory. The freshness question disappears, because the maximum age of any session is the length of one run. Make the setup step fail loudly: ```ts const res = await context.request.post(`${baseURL}/api/login`, { data: creds }); if (!res.ok()) { throw new Error(`auth setup failed for ${creds.email}: ${res.status()}`); } ``` This is disproportionately valuable. Without it, a broken credential presents as every test failing on its first assertion with a message about a missing dashboard heading, and someone spends an hour reading test code. With it, the run stops in seconds with the actual cause. ## Long runs and mid-suite expiry Regenerating per run solves staleness *between* runs. A large suite can still outlive a short-lived access token inside a single run — an access token measured in minutes against a forty-minute suite means tests that start late begin failing while early ones passed. Symptoms: failures clustered at the end of the run, or in the workers that started last. Options, roughly in order: - **Rely on the app's own refresh.** If the app refreshes silently using a longer-lived refresh cookie, make sure the saved state includes it — capturing only the short-lived access token strips the very thing that would have kept the session alive. This is the best answer because it exercises production behaviour. - **Re-authenticate per worker at worker start**, so each worker's session is only as old as its own slice of the run. - **Extend the token lifetime in the test environment.** Effective and cheap, but it means the suite no longer runs under production-like expiry, so an expiry bug can hide. A related gotcha: significant clock skew between the CI runner and the auth server makes freshly-issued sessions look already-expired. If sessions fail immediately rather than after a while, suspect the clock before the code. ## Expiry as a feature worth testing One inversion is worth mentioning: everything above treats expiry as an obstacle, but "what the app does when the session expires" is real product behaviour — a re-authentication prompt, a redirect that preserves the current page, an unsaved-work warning. That deserves its own test, driven by deliberately invalidating or fast-forwarding the session rather than by waiting. Saying so signals that you understand expiry as a behaviour and not just a nuisance.

  • The failures cluster in the tests that run last rather than hitting the whole suite at once. What does that pattern suggest?
    Expiry inside the run rather than between runs. A session minted at setup outlives the early tests and dies partway through, so later tests inherit a dead credential. Check the access-token lifetime against the suite duration, ensure the saved state carries whatever the app refreshes with, or re-authenticate at the start of each worker.
  • Someone already committed a state file for the staging admin account. What do you do beyond deleting it?
    Treat it as a leaked credential: rotate the account's password and revoke its outstanding sessions, since the file remains in git history and is usable by anyone with repo read access. Then gitignore the output directory, move the credentials to CI secrets, and regenerate the session at run start.
  • Would extending token lifetime in the test environment be a reasonable fix?
    It is pragmatic and often used, but it buys stability by diverging from production: an expiry or refresh bug can no longer surface in the suite. Prefer capturing the refresh credential so the app renews the way it does in production, and if you do extend lifetimes, keep a dedicated test that exercises expiry behaviour explicitly.

saying these in an interview costs you the question

  • Treats a saved session file as a static fixture
  • Commits a session or token file to version control
  • Adds a retry loop instead of regenerating the session
  • Captures only the access token and drops the refresh credential
  • Lets failed authentication surface as ordinary assertion failures

context