Authenticate once, test everywhere
Learn how to implement a setup step and reuse browser state.
If you test suite grows it's likely that your tests do the same things over and over again. Login is a very common example. Wouldn't it be great if we could log in the user 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 }). A setup file is just a regular spec file with the purpose to drive some actions and dump the resulting state to disk.
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("Name").fill("workshop");
await page.getByRole("button", { name: "Sign in" }).click();
await expect(page.getByText("Welcome, workshop")).toBeVisible();
await page.context().storageState({ path: authFile });
});
If you write the state to disk, it should look like this.
{
"cookies": [
{
"name": "name",
"value": "stefan",
"domain": "www.playwright-workshop.online",
"path": "/",
"expires": -1,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
}
],
"origins": []
}
That's pretty sweet! But now there's the questions, how and when should your run this spec or 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
In a single spec file, you can read the storage state with a quick one-liner.
test.use({ storageState: "./.auth.json" });test.describe(/* ... */);
However, adding this line to every test is no fun. The recommended approach is to set up project dependencies and storage state in one go!
export default defineConfig({ // projects that leverage setup and storage state // npx playwright test --project=storageState projects: [ { name: "setup", testMatch: "*.setup.ts", }, { // run the `setup` tests before these dependencies: ["setup"], name: "cart", use: { storageState: "auth.json" }, testMatch: "*.with-state.spec.ts", }, ],});
This playwright.config does multiple things at once:
- it defines a
setupproject matching*.setup.tsfiles - the
setupproject will then create the storage state file (auth.json) - the
cartproject depends onsetupso thatsetupis always run first - the
cartproject readsauth.jsonand applies it to all running tests.
Beautiful!
Storage state goes stale. Cookies expire, sessions get revoked, and fixture
data resets. All these scenarios can lead to a wall of red tests that all fail
on the first assertion past the login wall. 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. 🎉