Authenticate once, test everywhere
Learn how to implement a setup step and reuse browser state.
When your test suite grows, it's likely that your tests do the same things over and over again. Logging in is a very common example. Wouldn't it be great if we could log in once and then reuse the browser state (cookies, localStorage) in other test cases?
You bet! This is where project configuration and the storageState method come in handy!
Write the current browser state to disk
Whenever you perform actions in your Playwright scripts, you can write the current state to disk via page.context().storageState({ path }) in a setup file.
A setup file is just a regular spec file that performs some actions and writes the resulting state to disk. The example below logs into the store and then persists the browser state.
import { test as setup, expect } from "@playwright/test";const authFile = "./auth.json";setup("authenticate", async ({ page }) => { await page.goto("/"); await page.getByRole("link", { name: "Login" }).click(); await page.getByLabel("Your name").fill("workshop"); await page.getByLabel("Your password").fill("12345678"); await page.getByRole("button", { name: "Login" }).click(); await expect(page.getByTestId("login-name")).toHaveText("workshop"); await page.context().storageState({ path: authFile });});
The state file should look like this:
{
"cookies": [
{
"name": "name",
"value": "workshop",
"domain": "www.playwright-workshop.online",
"path": "/",
"expires": -1,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
}
],
"origins": []
}
That's pretty sweet! But now the question is: how and when should you run this spec and read the resulting JSON?
Storage state files contain real session cookies. Add them to .gitignore
(e.g. auth.json or a dedicated playwright/.auth/ directory) so you never
commit a logged-in session to your repo.
Read the browser state in your test files
You can read the storage state with a quick one-liner in your spec files.
test.use({ storageState: "./auth.json" });test.describe(/* ... */);
But adding this line to every test file is no fun. The recommended approach is to set up project dependencies and storage state in one go!
export default defineConfig({
projects: [
{
name: "setup", // 1.
testMatch: "*.setup.ts",
},
{
// run the `setup` tests before these
name: "cart",
use: { storageState: "auth.json" }, // 4.
testMatch: "*.spec.ts",
dependencies: ["setup"], // 3.
},
],
});
This playwright.config does multiple things at once:
- it defines a
setupproject matching*.setup.tsfiles - the
setupproject will run tests that create the storage state files (auth.json,admin.json,account.json, etc.) - the
cartproject depends onsetupso thatsetupalways runs first - the
cartproject readsauth.jsonand applies it to all its tests
Beautiful!
Storage state goes stale. Cookies expire, sessions get revoked, and fixture
data resets. All of these can lead to a wall of red tests that fail on the
first assertion after the login. When that happens, delete the auth.json,
re-run the setup project, and you're back in business.
Reuse a logged-in browser state
Move login into a setup project
- Create a
*.setup.tsfile that logs the user in and writes the browser state to disk viapage.context().storageState({ path: authFile }). - Add a
setupproject toplaywright.config.tsthat matches*.setup.ts. - Add
dependenciesonsetup, pointuse.storageStateat the file you wrote, and authenticate all your tests. - Make all your shop tests run in a logged-in state from now on. 🎉