Last updated: March 16, 2026

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.

Table of Contents

Prerequisites

Before you begin, make sure you have the following ready:

Step 1: Understand 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.

Step 2: 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.

Step 3: 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.

Step 4: Manage 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.

Step 5: Automate 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' },
    },
  ],
};

Step 6: 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' });
});

Step 7: 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();
});

Step 8: Test 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);
});

Step 9: 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();
  });
});

Step 10: 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();
});

Troubleshooting

Configuration changes not taking effect

Restart the relevant service or application after making changes. Some settings require a full system reboot. Verify the configuration file path is correct and the syntax is valid.

Permission denied errors

Run the command with sudo for system-level operations, or check that your user account has the necessary permissions. On macOS, you may need to grant terminal access in System Settings > Privacy & Security.

Connection or network-related failures

Check your internet connection and firewall settings. If using a VPN, try disconnecting temporarily to isolate the issue. Verify that the target server or service is accessible from your network.

Frequently Asked Questions

How long does it take to use ai to generate playwright authentication flow?

For a straightforward setup, expect 30 minutes to 2 hours depending on your familiarity with the tools involved. Complex configurations with custom requirements may take longer. Having your credentials and environment ready before starting saves significant time.

What are the most common mistakes to avoid?

The most frequent issues are skipping prerequisite steps, using outdated package versions, and not reading error messages carefully. Follow the steps in order, verify each one works before moving on, and check the official documentation if something behaves unexpectedly.

Do I need prior experience to follow this guide?

Basic familiarity with the relevant tools and command line is helpful but not strictly required. Each step is explained with context. If you get stuck, the official documentation for each tool covers fundamentals that may fill in knowledge gaps.

Is this approach secure enough for production?

The patterns shown here follow standard practices, but production deployments need additional hardening. Add rate limiting, input validation, proper secret management, and monitoring before going live. Consider a security review if your application handles sensitive user data.

Where can I get help if I run into issues?

Start with the official documentation for each tool mentioned. Stack Overflow and GitHub Issues are good next steps for specific error messages. Community forums and Discord servers for the relevant tools often have active members who can help with setup problems.