SessionCourier join waitlist

guides

Export your session as Playwright storageState

Turn a logged-in browser session into a storageState.json that Playwright and Puppeteer tests can reuse — no scripted login flows.

Why storageState

Playwright can start a browser context that is already authenticated, by loading a JSON file — its storageState — that holds the cookies (and, in general, localStorage) of a logged-in session. Most teams generate that file by scripting a login flow: type the username, type the password, click submit, save the state. That works until it doesn’t — captchas, SSO redirects, and 2FA prompts all break a scripted login, and maintaining the script is ongoing work.

SessionCourier takes the other route: it exports the session you already have in your browser. Log in manually once — however awkward the login is — export the state, and your tests reuse it headless. No login code to write, nothing to re-script when the login page changes.

Export the file

  1. Log in to the target site in a normal browser tab.
  2. Open SessionCourier on that tab.
  3. Choose the Playwright storageState export.
  4. Save it as auth.json (or any name you like).

The exported file contains the site’s cookies. The origins array — Playwright’s slot for localStorage — is empty in the current version; if your app keeps its auth token only in localStorage rather than a cookie, that part won’t carry over yet.

{
  "cookies": [
    {
      "name": "session",
      "value": "eyJhbGciOiJIUzI1NiIs…",
      "domain": ".staging.example.com",
      "path": "/",
      "expires": 1789430400,
      "httpOnly": true,
      "secure": true,
      "sameSite": "Lax"
    }
  ],
  "origins": []
}

Use it in Playwright

Point the whole project at the file so every test starts authenticated:

// playwright.config.ts — every test starts authenticated
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: { storageState: 'auth.json' },
});

Or scope it to a single test file or project:

// or per test file / project
import { test } from '@playwright/test';

test.use({ storageState: 'auth.json' });

test('dashboard loads for a logged-in user', async ({ page }) => {
  await page.goto('https://staging.example.com/dashboard');
  await page.getByRole('heading', { name: 'Dashboard' }).waitFor();
});

With the raw library API, pass it when you create the context:

// library API
const context = await browser.newContext({ storageState: 'auth.json' });
const page = await context.newPage();

Use it in Puppeteer

Puppeteer has no storageState option, but you don’t need it: the cookies array inside the same file uses exactly the fields Puppeteer’s page.setCookie accepts — name, value, domain, path, expires, httpOnly, secure, sameSite (with expires: -1 marking a session cookie). So feed Puppeteer the cookies array from the storageState export:

import fs from 'node:fs';
import puppeteer from 'puppeteer';

const { cookies } = JSON.parse(fs.readFileSync('auth.json', 'utf8'));
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setCookie(...cookies);
await page.goto('https://staging.example.com/dashboard');

Do not feed Puppeteer the plain JSON export instead — that one uses Chrome’s cookie shape (expirationDate, lowercase sameSite values like lax), which setCookie doesn’t understand. Use the storageState export here.

(Note for newer Puppeteer: page.setCookie is deprecated in favor of browser.setCookie(...cookies) since v23.)

Pitfalls

  • Session cookies expire. Short-lived sessions time out; when tests suddenly start redirecting to the login page, re-export a fresh auth.json.
  • __Host- and __Secure- prefixes keep their constraints. Cookies with these prefixes only work over HTTPS (and __Host- is bound to the exact host with path=/) — your test URL has to be https://, not http://localhost.
  • SameSite maps to Playwright’s set. Values normalize to "Strict" | "Lax" | "None"; None cookies also need secure: true, which the export already carries.
  • Never commit real-account state. auth.json is a live credential. Add it to .gitignore and use dedicated test accounts, not your own login.
# .gitignore
auth.json
auth/

In CI

Today, export the file locally and hand it to CI as a secret or build artifact, then point storageState at it — no browser or login runs in the pipeline. That already covers most needs.

A fully headless path — a CLI that can pull a synced profile from inside the pipeline, no browser at all — is planned for Pro. Until it lands, the export-and-secret workflow above is the recommended approach.