import { test, expect, Page } from '@playwright/test'; // Test configuration const BASE_URL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:5175'; const PERFORMANCE_THRESHOLDS = { LOGIN_MAX_TIME: 3000, // 3 seconds max for login LOGOUT_MAX_TIME: 1000, // 1 second max for logout NAVIGATION_MAX_TIME: 2000, // 2 seconds max for route navigation AUTH_CHECK_MAX_TIME: 1000, // 1 second max for auth check }; // Test users with different roles const TEST_USERS = { admin: { email: 'admin@example.com', role: 'orgAdmin' }, superadmin: { email: 'superadmin@example.com', role: 'superadmin' }, staff: { email: 'staff@example.com', role: 'staff' }, territory: { email: 'territory@example.com', role: 'territoryManager' }, }; // Helper function to login with performance tracking async function performLogin(page: Page, email: string, password: string = 'password123') { const startTime = Date.now(); await page.goto(`${BASE_URL}/login`); await page.fill('input[type="email"]', email); await page.fill('input[type="password"]', password); const loginPromise = page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME }); await page.click('[data-testid="loginBtn"]'); await loginPromise; const loginTime = Date.now() - startTime; console.log(`Login completed in ${loginTime}ms`); expect(loginTime).toBeLessThan(PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME); return loginTime; } // Helper function to logout with performance tracking async function performLogout(page: Page) { const startTime = Date.now(); // Look for logout button in header or sidebar await page.click('button:has-text("Sign out"), button:has-text("Logout")'); await page.waitForURL('**/login', { timeout: PERFORMANCE_THRESHOLDS.LOGOUT_MAX_TIME }); const logoutTime = Date.now() - startTime; console.log(`Logout completed in ${logoutTime}ms`); expect(logoutTime).toBeLessThan(PERFORMANCE_THRESHOLDS.LOGOUT_MAX_TIME); return logoutTime; } test.describe('Bulletproof Authentication System', () => { test.beforeEach(async ({ page }) => { // Clear any existing auth state await page.goto(BASE_URL); await page.evaluate(() => { localStorage.clear(); sessionStorage.clear(); }); }); test.describe('Core Authentication Flow', () => { test('should login successfully without infinite loops', async ({ page }) => { await page.goto(`${BASE_URL}/login`); // Verify login page loads await expect(page.locator('h1')).toContainText('Sign in to Black Canyon Tickets'); await expect(page.locator('[data-testid="loginBtn"]')).toBeVisible(); // Test admin login await performLogin(page, TEST_USERS.admin.email); // Verify successful login await expect(page.locator('body')).not.toContainText('Loading...'); expect(page.url()).toMatch(/\/(dashboard|events)/); // Take screenshot for documentation await page.screenshot({ path: 'tests/screenshots/auth-login-success.png' }); }); test('should handle login errors gracefully', async ({ page }) => { await page.goto(`${BASE_URL}/login`); // Test invalid email await page.fill('input[type="email"]', 'invalid@example.com'); await page.fill('input[type="password"]', 'password123'); await page.click('[data-testid="loginBtn"]'); // Should show error message await expect(page.locator('.bg-red-500')).toContainText('Invalid email address'); // Test short password await page.fill('input[type="email"]', TEST_USERS.admin.email); await page.fill('input[type="password"]', 'ab'); await page.click('[data-testid="loginBtn"]'); await expect(page.locator('.bg-red-500')).toContainText('Password must be at least 3 characters'); await page.screenshot({ path: 'tests/screenshots/auth-login-errors.png' }); }); test('should complete full login/logout cycle', async ({ page }) => { // Login await performLogin(page, TEST_USERS.admin.email); // Verify authenticated state expect(page.url()).not.toContain('/login'); // Logout await performLogout(page); // Verify logged out state await expect(page.locator('h1')).toContainText('Sign in to Black Canyon Tickets'); expect(page.url()).toContain('/login'); }); test('should redirect with returnTo parameter', async ({ page }) => { // Try to access protected route await page.goto(`${BASE_URL}/settings`); // Should redirect to login with returnTo await page.waitForURL('**/login*returnTo*', { timeout: 5000 }); expect(page.url()).toContain('returnTo=%2Fsettings'); // Login and verify redirect back to settings await performLogin(page, TEST_USERS.admin.email); expect(page.url()).toContain('/settings'); }); }); test.describe('Quick Login Functionality', () => { test('should provide quick login buttons for all roles', async ({ page }) => { await page.goto(`${BASE_URL}/login`); // Verify all quick login buttons are present await expect(page.locator('button:has-text("Admin")')).toBeVisible(); await expect(page.locator('button:has-text("Super Admin")')).toBeVisible(); await expect(page.locator('button:has-text("Staff")')).toBeVisible(); await expect(page.locator('button:has-text("Territory")')).toBeVisible(); // Test quick login for admin await page.click('button:has-text("Admin")'); // Verify form is filled await expect(page.locator('input[type="email"]')).toHaveValue('admin@example.com'); await expect(page.locator('input[type="password"]')).toHaveValue('password123'); // Complete login await page.click('[data-testid="loginBtn"]'); await page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME }); await page.screenshot({ path: 'tests/screenshots/auth-quick-login.png' }); }); test('should test all user roles via quick login', async ({ page }) => { for (const [roleKey] of Object.entries(TEST_USERS)) { await page.goto(`${BASE_URL}/login`); // Click appropriate quick login button const buttonText = roleKey === 'territory' ? 'Territory' : roleKey === 'superadmin' ? 'Super Admin' : roleKey.charAt(0).toUpperCase() + roleKey.slice(1); await page.click(`button:has-text("${buttonText}")`); await page.click('[data-testid="loginBtn"]'); // Verify login success await page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME }); await page.screenshot({ path: `tests/screenshots/auth-role-${roleKey}.png` }); // Logout for next test await performLogout(page); } }); }); test.describe('Session Persistence', () => { test('should persist session across page reloads', async ({ page }) => { // Login await performLogin(page, TEST_USERS.admin.email); // Reload page const startTime = Date.now(); await page.reload(); await page.waitForLoadState('networkidle'); const reloadTime = Date.now() - startTime; // Should still be authenticated expect(page.url()).not.toContain('/login'); expect(reloadTime).toBeLessThan(PERFORMANCE_THRESHOLDS.AUTH_CHECK_MAX_TIME); console.log(`Session restore completed in ${reloadTime}ms`); }); test('should persist session across browser navigation', async ({ page }) => { // Login await performLogin(page, TEST_USERS.admin.email); // Navigate away and back await page.goto('https://example.com'); await page.goto(`${BASE_URL}/dashboard`); // Should still be authenticated expect(page.url()).not.toContain('/login'); await expect(page.locator('body')).not.toContainText('Loading...'); }); test('should handle corrupted localStorage gracefully', async ({ page }) => { await page.goto(BASE_URL); // Inject corrupted auth data await page.evaluate(() => { localStorage.setItem('bct_auth_user', 'invalid-json'); }); // Refresh and should redirect to login await page.reload(); await page.waitForURL('**/login', { timeout: 5000 }); // Should not show any errors await expect(page.locator('.bg-red-500')).toHaveCount(0); }); }); test.describe('Role-Based Access Control', () => { const roleRouteTests = [ { role: 'staff', allowedRoutes: ['/dashboard', '/events', '/tickets'], deniedRoutes: ['/admin'] }, { role: 'territory', allowedRoutes: ['/dashboard', '/events', '/scan'], deniedRoutes: ['/admin'] }, { role: 'admin', allowedRoutes: ['/dashboard', '/events', '/tickets', '/analytics'], deniedRoutes: ['/admin'] }, { role: 'superadmin', allowedRoutes: ['/dashboard', '/events', '/admin'], deniedRoutes: [] } ]; roleRouteTests.forEach(({ role, allowedRoutes, deniedRoutes }) => { test(`should enforce ${role} role permissions correctly`, async ({ page }) => { const userData = TEST_USERS[role as keyof typeof TEST_USERS]; await performLogin(page, userData.email); // Test allowed routes for (const route of allowedRoutes) { await page.goto(`${BASE_URL}${route}`); await page.waitForLoadState('networkidle'); // Should not redirect to login or show access denied expect(page.url()).not.toContain('/login'); await expect(page.locator('body')).not.toContainText('Access Denied'); } // Test denied routes for (const route of deniedRoutes) { await page.goto(`${BASE_URL}${route}`); await page.waitForLoadState('networkidle'); // Should show access denied or redirect const hasAccessDenied = await page.locator('body').textContent(); expect(hasAccessDenied).toMatch(/(Access Denied|Sign in to)/); } await page.screenshot({ path: `tests/screenshots/auth-rbac-${role}.png` }); }); }); }); test.describe('Performance Benchmarks', () => { test('should meet all performance thresholds', async ({ page }) => { const metrics = { loginTime: 0, logoutTime: 0, navigationTime: 0, authCheckTime: 0 }; // Measure login performance metrics.loginTime = await performLogin(page, TEST_USERS.admin.email); // Measure navigation performance const navStart = Date.now(); await page.goto(`${BASE_URL}/settings`); await page.waitForLoadState('networkidle'); metrics.navigationTime = Date.now() - navStart; // Measure auth check performance const authStart = Date.now(); await page.reload(); await page.waitForLoadState('networkidle'); metrics.authCheckTime = Date.now() - authStart; // Measure logout performance metrics.logoutTime = await performLogout(page); // Log all metrics console.log('Performance Metrics:', metrics); // Validate thresholds expect(metrics.loginTime).toBeLessThan(PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME); expect(metrics.logoutTime).toBeLessThan(PERFORMANCE_THRESHOLDS.LOGOUT_MAX_TIME); expect(metrics.navigationTime).toBeLessThan(PERFORMANCE_THRESHOLDS.NAVIGATION_MAX_TIME); expect(metrics.authCheckTime).toBeLessThan(PERFORMANCE_THRESHOLDS.AUTH_CHECK_MAX_TIME); }); test('should handle rapid login attempts', async ({ page }) => { await page.goto(`${BASE_URL}/login`); // Rapid clicks should not cause issues await page.fill('input[type="email"]', TEST_USERS.admin.email); await page.fill('input[type="password"]', 'password123'); // Click login button multiple times quickly await Promise.all([ page.click('[data-testid="loginBtn"]'), page.click('[data-testid="loginBtn"]'), page.click('[data-testid="loginBtn"]') ]); // Should still login successfully once await page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME }); expect(page.url()).not.toContain('/login'); }); }); test.describe('Timeout and Failsafe Testing', () => { test('should prevent infinite loading with timeout failsafe', async ({ page }) => { await page.goto(BASE_URL); // Simulate slow auth check by delaying localStorage await page.addInitScript(() => { const originalGetItem = localStorage.getItem; localStorage.getItem = (key: string): string | null => { if (key === 'bct_auth_user') { // Delay to test timeout - return null for timeout simulation setTimeout(() => originalGetItem.call(localStorage, key), 3000); return null; } return originalGetItem.call(localStorage, key); }; }); // Should not hang indefinitely - timeout should kick in await page.waitForTimeout(3000); // Should redirect to login after timeout expect(page.url()).toContain('/login'); await expect(page.locator('h1')).toContainText('Sign in to Black Canyon Tickets'); }); test('should handle network errors gracefully', async ({ page }) => { // Simulate offline mode await page.context().setOffline(true); await page.goto(`${BASE_URL}/login`); // Fill form and attempt login await page.fill('input[type="email"]', TEST_USERS.admin.email); await page.fill('input[type="password"]', 'password123'); await page.click('[data-testid="loginBtn"]'); // Since we're using mock auth, this should still work await page.context().setOffline(false); await page.waitForURL(/\/(dashboard|events)/, { timeout: PERFORMANCE_THRESHOLDS.LOGIN_MAX_TIME }); }); }); test.describe('Edge Cases and Security', () => { test('should prevent authentication bypass attempts', async ({ page }) => { await page.goto(BASE_URL); // Try to inject fake user data await page.evaluate(() => { localStorage.setItem('bct_auth_user', JSON.stringify({ uid: 'hacker', email: 'hacker@evil.com', role: 'superadmin', orgId: 'fake_org' })); }); // Navigate to protected route await page.goto(`${BASE_URL}/admin`); // Should redirect to login (fake user not in MOCK_USERS) await page.waitForURL('**/login', { timeout: 5000 }); }); test('should handle malformed session data', async ({ page }) => { await page.goto(BASE_URL); // Set various malformed data const malformedData = ['invalid', '{}', '{"incomplete":true}', 'null', 'undefined']; for (const data of malformedData) { await page.evaluate((data) => { localStorage.setItem('bct_auth_user', data); }, data); await page.reload(); // Should gracefully handle and redirect to login await page.waitForURL('**/login', { timeout: 5000 }); await expect(page.locator('h1')).toContainText('Sign in to Black Canyon Tickets'); } }); test('should maintain security during concurrent operations', async ({ page }) => { // Login normally await performLogin(page, TEST_USERS.admin.email); // Try to manipulate session during navigation await Promise.all([ page.goto(`${BASE_URL}/settings`), page.evaluate(() => { localStorage.setItem('bct_auth_user', JSON.stringify({ uid: 'different-user', email: 'different@example.com', role: 'staff', orgId: 'different_org' })); }) ]); // Should handle gracefully await page.waitForLoadState('networkidle'); expect(page.url()).not.toContain('different'); }); }); });