Skip to content
  • Testing
  • Playwright

Test authenticated features

Learn how to efficiently test authenticated features in applications using Kinde authentication with Playwright. This guide covers reusing authentication state to test protected routes and features after authentication. Learn more about testing authenticated features with Kinde.

Playwright supports saving and reusing browser authentication state using storage state. This is the most efficient approach for testing authenticated features. By saving the authentication state once and reusing it across tests, you can significantly speed up your test suite while maintaining test isolation.

Setup Playwright session

Link to this section
  1. Update the playwright.config.ts file to add the following projects configuration:

    playwright.config.ts
    //... previous code ...
    projects: [
    // Setup project - authenticates and saves state
    {
    name: 'setup',
    testMatch: /.*\.setup\.ts/,
    },
    // Main tests - use saved auth state
    {
    name: 'auth-tests',
    use: {
    ...devices['Desktop Chrome'],
    storageState: 'playwright/.auth/user.json',
    },
    dependencies: ['setup'],
    },
    ],
  2. Create an auth.setup.ts file:

    Terminal window
    touch tests/auth.setup.ts
  3. Add the following to the file:

    tests/auth.setup.ts
    import { test as setup, expect } from '@playwright/test';
    const authFile = 'playwright/.auth/user.json';
    setup('authenticate', async ({ page, baseURL }) => {
    // Navigate to your app's login page
    await page.goto(baseURL!);
    // Click sign in (this redirects to Kinde)
    await page.click('[data-testid="sign-in-button"]');
    // Wait for Kinde login page
    await page.waitForURL(/.*kinde\.com.*/);
    // Enter email
    await page.fill('input[name="p_email"]', process.env.TEST_USER_EMAIL!);
    await page.click('button[type="submit"]');
    // Enter password
    await page.fill('input[name="p_password"]', process.env.TEST_USER_PASSWORD!);
    await page.click('button[type="submit"]');
    // Wait for redirect back to your app
    await page.waitForURL(new RegExp(`^${baseURL}/.*`));
    // Verify authentication succeeded
    await expect(page.locator('[data-testid="user-profile"]')).toBeVisible();
    // Save authentication state
    await page.context().storageState({ path: authFile });
    });

Create and run sample tests

Link to this section

If you are starting from scratch, you can run sample tests using the Kinde starter test application.

  1. Create a new dashboard.spec.ts file:

    Terminal window
    touch tests/dashboard.spec.ts
  2. Add the following sample tests to the file:

    tests/dashboard.spec.ts
    import { test, expect } from '@playwright/test';
    // This test runs with pre-authenticated state
    test('user can view dashboard', async ({ page }) => {
    await page.goto('/dashboard');
    // No login required - already authenticated
    await expect(page.locator('[data-testid="start-hero-intro"]')).toContainText('Woohoo!');
    });
    test('user can update profile', async ({ page }) => {
    await page.goto('/profile');
    await page.fill('[data-testid="display-name-input"]', 'Test User');
    await page.click('[data-testid="display-name-submit"]');
    await expect(page.locator('.toast-success')).toBeVisible();
    });
  3. Use the following command:

    Terminal window
    npx playwright test --ui
  4. Select the test spec to run.

    You should now see the Playwright tests passing in the browser.

    Playwright saved user tests passed

Complete project structure

Link to this section
your-project/
├── playwright.config.ts
├── .env # Environment variables (gitignored)
├── playwright/
│ └── .auth/
│ └── user.json # Saved auth state (gitignored)
├── tests/
│ ├── auth.setup.ts # Authentication setup
│ └── dashboard.spec.ts # Tests using saved auth
└── package.json

CI/CD integration (GitHub Actions)

Link to this section
.github/workflows/e2e-tests.yml
name: E2E Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
env:
TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
TEST_APP_URL: ${{ secrets.TEST_APP_URL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30

Troubleshooting

Link to this section

Tests fail with “Navigation timeout”

Link to this section

Kinde redirects can take time. Increase timeout:

await page.waitForURL('https://your-app.com/**', { timeout: 30000 });

Session not persisting

Link to this section

Ensure playwright/.auth/ directory exists and is writable. Add to .gitignore. Check Playwright configuration for storage state settings.

Kinde form selectors changed

Link to this section

Kinde may update the UI. Use more resilient selectors:

// Instead of specific class names
await page.locator('input[type="email"]').fill(email);
await page.locator('input[type="password"]').fill(password);