Use AI to generate Playwright authentication tests using the storageState feature to capture authenticated sessions once, then reuse them across tests. AI assistants generate code that records login state to JSON, loads it in subsequent tests, and eliminates repeated login overhead—reducing test execution time significantly while maintaining proper session management and eliminating inter-test dependencies.
Playwright’s storage state feature solves this problem by capturing authenticated session data once and reusing it across tests. When you combine this capability with AI-generated test code, you get fast, maintainable authentication tests that don’t require repeated login overhead.
Understanding Playwright Storage State
Playwright provides a mechanism to save and load authentication state through the storageState option. Instead of executing login steps in every test, you record the authenticated state once and reuse it. This approach reduces test execution time and eliminates dependencies between tests.
The storage state contains cookies, local storage, and session storage—the complete authentication context needed to restore a logged-in session. Playwright can export this state to a JSON file and load it when creating new browser contexts.
// Recording authentication state
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
// Perform login
await page.goto('https://your-app.com/login');
await page.fill('[data-testid="email"]', 'test@example.com');
await page.fill('[data-testid="password"]', 'securepassword');
await page.click('[data-testid="login-button"]');
await page.waitForURL('https://your-app.com/dashboard');
// Save authenticated state
await context.storageState({ path: 'auth-state.json' });
await browser.close();
})();
Once you have the auth-state.json file, you can launch new contexts with the authenticated state pre-loaded.
Prompting AI for Authentication Flow Tests
When working with AI coding assistants to generate authentication tests, specificity matters. A vague prompt produces generic code that won’t match your application’s actual flow. Provide the AI with clear context about your authentication mechanism, the page structure, and how you want to use stored state.
A well-structured prompt includes your login URL, the selectors for login form elements, the expected post-login URL, and instructions to use storage state. Here’s a template that works well:
Create a Playwright test file for authentication flows. Use stored state (auth-state.json)
for tests that require a logged-in user. Include these test cases:
1. Verify that a user with valid credentials can log in successfully
2. Verify that logged-in users can access protected routes
3. Verify that the logout action clears the session properly
Login URL: https://your-app.com/login
Protected URL: https://your-app.com/dashboard
Use these selectors: [data-testid="email"], [data-testid="password"], [data-testid="login-button"], [data-testid="logout-button"]
The AI will generate tests that incorporate the storage state option in the context creation, skipping the login steps for authenticated tests.
Generated Test Structure
AI-generated authentication tests with stored state typically follow a consistent pattern. The tests separate concerns between unauthenticated and authenticated scenarios, using the storage state only where appropriate.
const { test, expect } = require('@playwright/test');
test.describe('Authentication Flow', () => {
// Test without authentication
test('unauthenticated user redirected to login', async ({ page }) => {
await page.goto('https://your-app.com/dashboard');
await expect(page).toHaveURL(/.*login/);
});
// Test using stored authentication state
test('authenticated user can access dashboard', async ({ page }) => {
await page.goto('https://your-app.com/dashboard');
await expect(page.locator('[data-testid="user-welcome"]')).toBeVisible();
});
// Test logout functionality
test('user can logout successfully', async ({ page, context }) => {
// Use storage state for authenticated context
await context.addInitScript(() => {
window.localStorage.setItem('authToken', 'test-token');
});
await page.goto('https://your-app.com/dashboard');
await page.click('[data-testid="logout-button"]');
await expect(page).toHaveURL(/.*login/);
});
});
The key is ensuring the AI understands when to use storage state versus when to test unauthenticated behavior.
Managing Multiple User Types
Real applications often have different user roles with varying permissions. AI can help generate tests for multiple authenticated states, but you need to specify each role in your prompts. Request separate storage state files for each user type.
Generate tests for a multi-role application:
- Regular user: can view dashboard but cannot access settings
- Admin user: can view dashboard and access settings
Create separate storage state files: regular-user-state.json and admin-user-state.json
The AI will configure Playwright to load the appropriate state file based on the test’s user role.
Best Practices for AI-Generated Authentication Tests
Review the generated code carefully. AI can make mistakes with selectors, timing, or assertions. Verify that the test actually checks what you expect.
Use test fixtures to manage authentication state cleanly. This approach makes it easy to switch between authenticated and unauthenticated states without duplicating code.
// playwright.config.js
module.exports = {
use: {
storageState: ({ params }) => {
if (params.auth === 'admin') return 'admin-state.json';
if (params.auth === 'user') return 'user-state.json';
return undefined;
},
},
};
Keep your storage state files up to date. If your authentication mechanism changes, regenerate the state files and verify that existing tests still pass.
Automating State Generation
You can also use AI to generate scripts that create storage state files automatically. This is useful in CI/CD pipelines where you need fresh authentication state before running tests.
Write a Node.js script using Playwright that:
1. Logs in with admin credentials
2. Handles any 2FA steps if present
3. Saves the authenticated state to admin-state.json
4. Logs out and repeats for regular user credentials
This automation ensures your test state remains current without manual intervention.
Advanced Storage State Management
For complex applications with multiple environments and user roles:
// playwright.config.js - Advanced configuration
module.exports = {
use: {
baseURL: process.env.BASE_URL || 'https://localhost:3000',
storageState: ({ browserName }) => {
if (browserName === 'chromium') {
return 'auth-state/chrome-state.json';
}
return 'auth-state/firefox-state.json';
},
contextOptions: {
permissions: ['notifications'],
},
},
projects: [
{
name: 'auth-setup',
testMatch: '**/auth.setup.ts',
},
{
name: 'authenticated-tests',
dependencies: ['auth-setup'],
use: { storageState: 'auth-state/admin-state.json' },
},
{
name: 'user-tests',
dependencies: ['auth-setup'],
use: { storageState: 'auth-state/user-state.json' },
},
],
};
Authentication Setup Script
AI can generate setup scripts that create multiple authentication states:
// tests/auth.setup.ts
import { test as setup } from '@playwright/test';
setup.describe.configure({ mode: 'parallel' });
setup('authenticate as admin', async ({ page, context }) => {
// Admin login
await page.goto('https://your-app.com/login');
await page.fill('[data-testid="email"]', 'admin@example.com');
await page.fill('[data-testid="password"]', 'admin-password');
await page.click('[data-testid="login-button"]');
// Wait for dashboard and verify admin features
await page.waitForURL('**/dashboard');
await page.waitForSelector('[data-testid="admin-panel"]');
// Save authenticated state
await context.storageState({ path: 'auth-state/admin-state.json' });
});
setup('authenticate as regular user', async ({ page, context }) => {
// Regular user login
await page.goto('https://your-app.com/login');
await page.fill('[data-testid="email"]', 'user@example.com');
await page.fill('[data-testid="password"]', 'user-password');
await page.click('[data-testid="login-button"]');
// Wait for dashboard (without admin features)
await page.waitForURL('**/dashboard');
// Save authenticated state
await context.storageState({ path: 'auth-state/user-state.json' });
});
setup('authenticate as guest', async ({ page, context }) => {
// Guest login (temporary access)
await page.goto('https://your-app.com/login');
await page.click('[data-testid="guest-login"]');
// Handle any guest-specific flows
await page.waitForURL('**/guest-dashboard');
// Save authenticated state
await context.storageState({ path: 'auth-state/guest-state.json' });
});
Handling OAuth and Social Login
For applications using OAuth providers, AI can generate appropriate test patterns:
test('OAuth authentication workflow', async ({ browser }) => {
// Create a new context without existing storage state
const context = await browser.newContext();
const page = await context.newPage();
// Intercept OAuth redirect to mock provider
await page.goto('https://your-app.com/login');
await page.click('[data-testid="github-login"]');
// Handle OAuth popup
const [popup] = await Promise.all([
page.waitForEvent('popup'),
page.click('[data-testid="github-login"]'),
]);
// Mock GitHub login (would use real credentials in integration tests)
await popup.goto('https://github.com/login?client_id=...');
await popup.fill('[name="login"]', 'github-user');
await popup.fill('[name="password"]', process.env.GITHUB_TEST_PASSWORD);
await popup.click('[name="commit"]');
// Return to main app
await page.waitForURL('**/dashboard');
// Verify OAuth token is stored
const cookies = await context.cookies();
const hasAuthToken = cookies.some(c => c.name === 'auth_token');
expect(hasAuthToken).toBe(true);
// Save state for future tests
await context.storageState({ path: 'auth-state/oauth-state.json' });
await context.close();
});
Testing Session Expiration and Renewal
AI assists in generating tests for auth state lifecycle:
test('session expiration handling', async ({ page, context }) => {
// Use saved auth state
const savedState = require('../auth-state/user-state.json');
// Manually expire tokens by modifying storage
await page.goto('https://your-app.com');
await page.addInitScript((stateJSON) => {
const state = JSON.parse(stateJSON);
// Expire all tokens
state.cookies.forEach(c => {
if (c.name.includes('auth') || c.name.includes('session')) {
c.expires = Math.floor(Date.now() / 1000) - 3600; // 1 hour ago
}
});
// Apply to storage
Object.entries(state.origins[0].localStorage || {}).forEach(([key, value]) => {
localStorage.setItem(key, value);
});
}, JSON.stringify(savedState));
// Navigate to protected route
await page.goto('https://your-app.com/protected');
// Should redirect to login
await expect(page).toHaveURL(/.*login/);
});
test('token refresh flow', async ({ page }) => {
await page.goto('https://your-app.com/api-test');
// Intercept API calls to capture refresh token logic
const responses = [];
page.on('response', response => {
if (response.url().includes('/api/auth/refresh')) {
responses.push(response);
}
});
// Make request that should trigger refresh
const response = await page.request.get(
'https://your-app.com/api/protected',
{ headers: { 'Authorization': 'Bearer expired_token' }}
);
// Verify refresh was called
expect(responses.length).toBeGreaterThan(0);
expect(response.status()).toBe(200);
});
Parallel Test Execution with Different Auth States
test.describe('Parallel tests with different auth', () => {
test('admin operations', async ({ page, storageState }) => {
// Uses admin-state.json from config
await page.goto('https://your-app.com/settings');
await expect(page.locator('[data-testid="user-management"]')).toBeVisible();
});
test('regular user operations', async ({ page, storageState }) => {
// Uses user-state.json from config
await page.goto('https://your-app.com/dashboard');
await expect(page.locator('[data-testid="user-management"]')).not.toBeVisible();
});
test('guest operations', async ({ page, storageState }) => {
// Uses guest-state.json from config
await page.goto('https://your-app.com');
await expect(page.locator('[data-testid="upgrade-prompt"]')).toBeVisible();
});
});
Debugging Storage State Issues
When storage state tests fail, use AI-assisted debugging:
test('debug storage state', async ({ page, context }) => {
// Log all cookies and storage
const cookies = await context.cookies();
const storageData = await page.evaluate(() => ({
localStorage: { ...localStorage },
sessionStorage: { ...sessionStorage },
}));
console.log('Cookies:', JSON.stringify(cookies, null, 2));
console.log('Storage:', JSON.stringify(storageData, null, 2));
// Verify expected auth markers exist
expect(cookies.some(c => c.name === 'session_id')).toBe(true);
expect(storageData.localStorage['user_id']).toBeDefined();
});
Related Articles
- How to Use AI to Generate Playwright Keyboard Navigation Tes
- How to Use AI to Generate Playwright Network Interception
- How to Use AI to Generate Playwright Tests for Iframe and
- Best AI for Learning OAuth2 and OIDC Authentication Flows
- Cursor vs Copilot for Implementing Oauth2 Authentication Flo
Built by theluckystrike — More at zovo.one