The Authentication Bottleneck in Automated Testing
When QA teams write automated test suites, authentication is often one of the biggest bottlenecks.
If your application has 100 test cases that check user settings, billing details, or dashboard views, you need to be logged in to run them. If every single test case logs in manually by entering username/password and clicking submit, your test suite runs incredibly slow, and you risk rate-limiting your test accounts.
Fortunately, Playwright offers a native solution to this problem: Storage State.
By logging in once, capturing the session cookies and local storage tokens, and reusing them across all test cases, you can drastically speed up your test execution and keep your test pipeline highly reliable. Let's look at how to set this up step-by-step.
1. Understanding Playwright's Storage State
In modern web applications, login states are maintained using cookies, localStorage, or sessionStorage tokens.
Playwright allows you to save this exact state into a local JSON file using the command:
await context.storageState({ path: 'state.json' });
When you launch a new browser context for a subsequent test, you can pass this state.json file as an configuration parameter. The browser opens pre-authenticated, skipping the login screen entirely.
2. Step 1: Writing the Authentication Setup Script
First, we create a setup script that logs into the application once and saves the state. We place this inside a file (e.g., tests/auth.setup.ts):
import { test as setup, expect } from '@playwright/test';
const authFile = 'playwright/.auth/user.json';
setup('authenticate user', async ({ page }) => {
// Navigate to login page
await page.goto('https://example.com/login');
// Fill in credentials
await page.getByPlaceholder('Username').fill('test_user');
await page.getByPlaceholder('Password').fill('secure_password123');
// Click submit and wait for dashboard navigation
await page.getByRole('button', { name: 'Sign In' }).click();
await expect(page).toHaveURL(/.*dashboard/);
// Save storage state to local JSON file
await page.context().storageState({ path: authFile });
});
3. Step 2: Configuring Playwright to Run Setup First
Next, we update the playwright.config.ts configuration file. We define a dependency project so that the setup script runs before any of our actual tests run:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
// Setup Project
{
name: 'setup',
testMatch: /auth\.setup\.ts/,
},
// Main Testing Project using Chrome
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
// Use the storage state generated by the setup project
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});
By adding dependencies: ['setup'], Playwright guarantees it will run auth.setup.ts once, generate the user.json file, and then feed it to the main testing projects.
4. Step 3: Writing Pre-Authenticated Tests
Now, when you write your regular test scripts, they don't need any login blocks. They open directly on the pages you want to validate, saving valuable seconds per test:
import { test, expect } from '@playwright/test';
test('inspect dashboard statistics', async ({ page }) => {
// Page is already logged in!
await page.goto('https://example.com/dashboard');
// Directly verify authenticated features
await expect(page.getByRole('heading', { name: 'Welcome Back' })).toBeVisible();
await expect(page.locator('.revenue-card')).toContainText('$');
});
test('update user profile preferences', async ({ page }) => {
await page.goto('https://example.com/settings');
await page.getByLabel('Dark Mode').check();
await page.getByRole('button', { name: 'Save Changes' }).click();
await expect(page.getByText('Settings Saved')).toBeVisible();
});
Benefits of Storage State Automation
Implementing Playwright's auth state architecture provides key benefits:
- Massive Speed Increases: Instead of doing 100 login cycles, you do 1. Your test suite runtime can drop by 60-80%.
- Flake Reduction: The login screen is often the most dynamic part of the app (subject to CAPTCHAs, MFA prompts, or database slowdowns). Skipping it makes tests stable.
- Realistic User Simulation: Browser contexts are completely isolated, ensuring that test states do not bleed into one another while sharing authentication credentials.
If you are a QA Engineer ready to build professional automation frameworks, learning setup orchestrations and config patterns is crucial. Dive into full-stack testing with our Playwright TypeScript Masterclass to master API integration, custom fixtures, and CI/CD pipelines.