Refactor Playwright tests to use cached authentication state for improved performance

- Removed redundant `loginViaUI` calls across E2E tests, leveraging cached storage state for faster test execution.
- Enhanced Playwright configuration to include a `setup` project for pre-caching admin and regular user authentication states.
- Added new `auth.setup.ts` to handle initial authentication and save storage states to `.auth` directory.
- Increased local worker count to 16 (CI unchanged) to optimize parallel execution.
- Updated `.gitignore` to exclude authentication state files.
This commit is contained in:
2025-11-08 20:46:59 +01:00
parent bf95aab7ec
commit a6a10855fa
11 changed files with 184 additions and 70 deletions

View File

@@ -14,7 +14,7 @@ test.describe('Admin Access Control', () => {
test('regular user should not see admin link in header', async ({ page }) => {
// Set up mocks for regular user (not superuser)
await setupAuthenticatedMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
// Navigate to authenticated page to test authenticated header (not homepage)
await page.goto('/settings');
@@ -31,7 +31,7 @@ test.describe('Admin Access Control', () => {
}) => {
// Set up mocks for regular user
await setupAuthenticatedMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
// Try to access admin page directly
await page.goto('/admin');
@@ -42,9 +42,10 @@ test.describe('Admin Access Control', () => {
});
test('superuser should see admin link in header', async ({ page }) => {
// Set up mocks for superuser
// Auth state already loaded from setup (admin.json storage state)
// Set up API route mocks for superuser
await setupSuperuserMocks(page);
await loginViaUI(page);
// Note: loginViaUI removed - auth already cached in storage state!
// Navigate to settings page to ensure user state is loaded
// (AuthGuard fetches user on protected pages)
@@ -65,7 +66,7 @@ test.describe('Admin Access Control', () => {
}) => {
// Set up mocks for superuser
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
// Navigate to admin page
await page.goto('/admin');
@@ -79,7 +80,7 @@ test.describe('Admin Access Control', () => {
test.describe('Admin Dashboard', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin');
});
@@ -134,7 +135,7 @@ test.describe('Admin Dashboard', () => {
test.describe('Admin Navigation', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin');
});
@@ -240,7 +241,7 @@ test.describe('Admin Navigation', () => {
test.describe('Admin Breadcrumbs', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
});
test('should show single breadcrumb on dashboard', async ({ page }) => {

View File

@@ -9,7 +9,7 @@ import { setupSuperuserMocks, loginViaUI } from './helpers/auth';
test.describe('Admin Dashboard - Page Load', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin');
});
@@ -28,7 +28,7 @@ test.describe('Admin Dashboard - Page Load', () => {
test.describe('Admin Dashboard - Statistics Cards', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin');
});
@@ -61,7 +61,7 @@ test.describe('Admin Dashboard - Statistics Cards', () => {
test.describe('Admin Dashboard - Quick Actions', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin');
});
@@ -107,7 +107,7 @@ test.describe('Admin Dashboard - Quick Actions', () => {
test.describe('Admin Dashboard - Analytics Charts', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin');
});
@@ -152,7 +152,7 @@ test.describe('Admin Dashboard - Analytics Charts', () => {
test.describe('Admin Dashboard - Accessibility', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin');
});

View File

@@ -10,7 +10,7 @@ import { setupSuperuserMocks, loginViaUI } from './helpers/auth';
test.describe('Admin Organization Members - Navigation from Organizations List', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
await page.waitForSelector('table tbody tr', { timeout: 10000 });
});
@@ -49,7 +49,7 @@ test.describe('Admin Organization Members - Navigation from Organizations List',
test.describe('Admin Organization Members - Page Structure', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
await page.waitForSelector('table tbody tr', { timeout: 10000 });
@@ -119,7 +119,7 @@ test.describe('Admin Organization Members - Page Structure', () => {
test.describe('Admin Organization Members - AddMemberDialog E2E Tests', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
await page.waitForSelector('table tbody tr', { timeout: 10000 });

View File

@@ -9,7 +9,7 @@ import { setupSuperuserMocks, loginViaUI } from './helpers/auth';
test.describe('Admin Organization Management - Page Load', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
});
@@ -40,7 +40,7 @@ test.describe('Admin Organization Management - Page Load', () => {
test.describe('Admin Organization Management - Organization List Table', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
});
@@ -106,7 +106,7 @@ test.describe('Admin Organization Management - Organization List Table', () => {
test.describe('Admin Organization Management - Pagination', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
});
@@ -126,7 +126,7 @@ test.describe('Admin Organization Management - Pagination', () => {
test.describe('Admin Organization Management - Create Organization Button', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
});
@@ -139,7 +139,7 @@ test.describe('Admin Organization Management - Create Organization Button', () =
test.describe('Admin Organization Management - Action Menu', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
await page.waitForSelector('table tbody tr', { timeout: 10000 });
});
@@ -245,7 +245,7 @@ test.describe('Admin Organization Management - Action Menu', () => {
test.describe('Admin Organization Management - Edit Organization Dialog', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
await page.waitForSelector('table tbody tr', { timeout: 10000 });
});
@@ -294,7 +294,7 @@ test.describe('Admin Organization Management - Edit Organization Dialog', () =>
test.describe('Admin Organization Management - Member Count Interaction', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
await page.waitForSelector('table tbody tr', { timeout: 10000 });
});
@@ -318,7 +318,7 @@ test.describe('Admin Organization Management - Member Count Interaction', () =>
test.describe('Admin Organization Management - Accessibility', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/organizations');
});

View File

@@ -9,7 +9,7 @@ import { setupSuperuserMocks, loginViaUI } from './helpers/auth';
test.describe('Admin User Management - Page Load', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
});
@@ -37,7 +37,7 @@ test.describe('Admin User Management - Page Load', () => {
test.describe('Admin User Management - User List Table', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
});
@@ -100,7 +100,7 @@ test.describe('Admin User Management - User List Table', () => {
test.describe('Admin User Management - Search and Filters', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
});
@@ -222,7 +222,7 @@ test.describe('Admin User Management - Search and Filters', () => {
test.describe('Admin User Management - Pagination', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
});
@@ -240,7 +240,7 @@ test.describe('Admin User Management - Pagination', () => {
test.describe('Admin User Management - Row Selection', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
await page.waitForSelector('table tbody tr', { timeout: 10000 });
});
@@ -303,7 +303,7 @@ test.describe('Admin User Management - Row Selection', () => {
test.describe('Admin User Management - Create User Dialog', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
});
@@ -427,7 +427,7 @@ test.describe('Admin User Management - Create User Dialog', () => {
test.describe('Admin User Management - Action Menu', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
await page.waitForSelector('table tbody tr', { timeout: 10000 });
});
@@ -480,7 +480,7 @@ test.describe('Admin User Management - Action Menu', () => {
test.describe('Admin User Management - Edit User Dialog', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
await page.waitForSelector('table tbody tr', { timeout: 10000 });
});
@@ -541,7 +541,7 @@ test.describe('Admin User Management - Edit User Dialog', () => {
test.describe('Admin User Management - Bulk Actions', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
await page.waitForSelector('table tbody tr', { timeout: 10000 });
});
@@ -611,7 +611,7 @@ test.describe('Admin User Management - Bulk Actions', () => {
test.describe('Admin User Management - Accessibility', () => {
test.beforeEach(async ({ page }) => {
await setupSuperuserMocks(page);
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
await page.goto('/admin/users');
});

View File

@@ -0,0 +1,70 @@
/**
* Authentication Setup for Playwright Tests
*
* This file sets up authenticated browser states that can be reused across tests.
* Instead of logging in via UI for every test (5-7s overhead), we login once per
* worker and save the storage state (cookies, localStorage) to disk.
*
* Performance Impact:
* - Before: 133 tests × 5-7s login = ~700s overhead
* - After: 2 logins (once per role) × 5s = ~10s overhead
* - Savings: ~690s (~11 minutes) per test run
*/
import { test as setup, expect } from '@playwright/test';
import path from 'path';
import { setupAuthenticatedMocks, setupSuperuserMocks, loginViaUI } from './helpers/auth';
// Use absolute paths to ensure correct file location
const ADMIN_STORAGE_STATE = path.join(__dirname, '.auth', 'admin.json');
const USER_STORAGE_STATE = path.join(__dirname, '.auth', 'user.json');
/**
* Setup: Authenticate as admin/superuser
* This runs ONCE before all admin tests
*/
setup('authenticate as admin', async ({ page }) => {
// Set up API mocks for superuser
await setupSuperuserMocks(page);
// Login via UI (one time only)
await loginViaUI(page);
// Verify we're actually logged in
await page.goto('/settings');
await page.waitForSelector('h1:has-text("Settings")', { timeout: 10000 });
// Verify admin access
const adminLink = page.locator('header nav').getByRole('link', { name: 'Admin', exact: true });
await expect(adminLink).toBeVisible();
// Save authenticated state to file
await page.context().storageState({ path: ADMIN_STORAGE_STATE });
console.log('✅ Admin authentication state saved to:', ADMIN_STORAGE_STATE);
});
/**
* Setup: Authenticate as regular user
* This runs ONCE before all user tests
*/
setup('authenticate as regular user', async ({ page }) => {
// Set up API mocks for regular user
await setupAuthenticatedMocks(page);
// Login via UI (one time only)
await loginViaUI(page);
// Verify we're actually logged in
await page.goto('/settings');
await page.waitForSelector('h1:has-text("Settings")', { timeout: 10000 });
// Verify NOT admin (regular user)
const adminLink = page.locator('header nav').getByRole('link', { name: 'Admin', exact: true });
await expect(adminLink).not.toBeVisible();
// Save authenticated state to file
await page.context().storageState({ path: USER_STORAGE_STATE });
console.log('✅ Regular user authentication state saved to:', USER_STORAGE_STATE);
});

View File

@@ -12,11 +12,12 @@ test.describe('Settings Navigation', () => {
await setupAuthenticatedMocks(page);
// Login via UI to establish authenticated session
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
});
test('should navigate from home to settings profile', async ({ page }) => {
// From home page
// Start at home page (auth already cached in storage state)
await page.goto('/');
await expect(page).toHaveURL('/');
// Navigate to settings/profile
@@ -30,7 +31,8 @@ test.describe('Settings Navigation', () => {
});
test('should navigate from home to settings password', async ({ page }) => {
// From home page
// Start at home page (auth already cached in storage state)
await page.goto('/');
await expect(page).toHaveURL('/');
// Navigate to settings/password

View File

@@ -12,7 +12,7 @@ test.describe('Password Change', () => {
await setupAuthenticatedMocks(page);
// Login via UI to establish authenticated session
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
// Navigate to password page
await page.goto('/settings/password', { waitUntil: 'networkidle' });

View File

@@ -12,7 +12,7 @@ test.describe('Profile Settings', () => {
await setupAuthenticatedMocks(page);
// Login via UI to establish authenticated session
await loginViaUI(page);
// Auth already cached in storage state (loginViaUI removed for performance)
// Navigate to profile page
await page.goto('/settings/profile');