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.
What you need
Link to this section- Completed the Playwright setup
- A test user and a running Kinde application.
Setup Playwright session
Link to this section-
Update the
playwright.config.tsfile 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'],},], -
Create an
auth.setup.tsfile:Terminal window touch tests/auth.setup.ts -
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 pageawait page.goto(baseURL!);// Click sign in (this redirects to Kinde)await page.click('[data-testid="sign-in-button"]');// Wait for Kinde login pageawait page.waitForURL(/.*kinde\.com.*/);// Enter emailawait page.fill('input[name="p_email"]', process.env.TEST_USER_EMAIL!);await page.click('button[type="submit"]');// Enter passwordawait page.fill('input[name="p_password"]', process.env.TEST_USER_PASSWORD!);await page.click('button[type="submit"]');// Wait for redirect back to your appawait page.waitForURL(new RegExp(`^${baseURL}/.*`));// Verify authentication succeededawait expect(page.locator('[data-testid="user-profile"]')).toBeVisible();// Save authentication stateawait page.context().storageState({ path: authFile });});
Create and run sample tests
Link to this sectionIf you are starting from scratch, you can run sample tests using the Kinde starter test application.
-
Create a new
dashboard.spec.tsfile:Terminal window touch tests/dashboard.spec.ts -
Add the following sample tests to the file:
tests/dashboard.spec.ts import { test, expect } from '@playwright/test';// This test runs with pre-authenticated statetest('user can view dashboard', async ({ page }) => {await page.goto('/dashboard');// No login required - already authenticatedawait 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();}); -
Use the following command:
Terminal window npx playwright test --ui -
Select the test spec to run.
You should now see the Playwright tests passing in the browser.
Complete project structure
Link to this sectionyour-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.jsonCI/CD integration (GitHub Actions)
Link to this sectionname: 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: 30Troubleshooting
Link to this sectionTests fail with “Navigation timeout”
Link to this sectionKinde redirects can take time. Increase timeout:
await page.waitForURL('https://your-app.com/**', { timeout: 30000 });Session not persisting
Link to this sectionEnsure playwright/.auth/ directory exists and is writable. Add to .gitignore. Check Playwright configuration for storage state settings.
Kinde form selectors changed
Link to this sectionKinde may update the UI. Use more resilient selectors:
// Instead of specific class namesawait page.locator('input[type="email"]').fill(email);await page.locator('input[type="password"]').fill(password);