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
- Log in to the target site in a normal browser tab.
- Open SessionCourier on that tab.
- Choose the Playwright
storageStateexport. - 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 withpath=/) — your test URL has to behttps://, nothttp://localhost.- SameSite maps to Playwright’s set. Values normalize to
"Strict" | "Lax" | "None";Nonecookies also needsecure: true, which the export already carries. - Never commit real-account state.
auth.jsonis a live credential. Add it to.gitignoreand 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.