/** * QA-AUTH-019: Authentication Tests * * E2E Test Suite for Authentication Flow * - Registration * - Login * - Protected Routes * - Logout */ import { test, expect } from '@playwright/test'; import { navigateTo, waitForLoading } from './utils/test-helpers'; import { generateTestEmail, generateTestUser, loginUserViaUI, registerUserViaUI, logoutUser, isAuthenticated, waitForAuthRedirect, clearAuthToken, } from './utils/auth-helpers'; // ============================================ // TEST SUITE: Registration // ============================================ test.describe('QA-AUTH-019: Registration', () => { test.beforeEach(async ({ page }) => { await page.goto('/register'); await page.waitForLoadState('networkidle'); }); test('should register new user successfully', async ({ page }) => { const testUser = generateTestUser('Registration'); // Fill registration form await page.getByLabel(/full name|name/i).fill(testUser.fullName); await page.getByLabel(/email/i).fill(testUser.email); await page.getByLabel(/^password$/i).fill(testUser.password); await page.getByLabel(/confirm password|repeat password/i).fill(testUser.password); // Submit form await page.getByRole('button', { name: /register|sign up|create account/i }).click(); // Verify redirect to dashboard await page.waitForURL('/', { timeout: 10000 }); await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); // Verify user is authenticated expect(await isAuthenticated(page)).toBe(true); }); test('should show error for duplicate email', async ({ page, request }) => { const testEmail = generateTestEmail('duplicate'); const testUser = generateTestUser(); // Register first user await registerUserViaUI(page, testEmail, testUser.password, testUser.fullName); // Logout and try to register again with same email await logoutUser(page); await page.goto('/register'); await page.waitForLoadState('networkidle'); // Fill form with same email await page.getByLabel(/full name|name/i).fill('Another Name'); await page.getByLabel(/email/i).fill(testEmail); await page.getByLabel(/^password$/i).fill('AnotherPassword123!'); await page.getByLabel(/confirm password|repeat password/i).fill('AnotherPassword123!'); // Submit form await page.getByRole('button', { name: /register|sign up|create account/i }).click(); // Verify error message await expect( page.getByText(/email already exists|already registered|duplicate|account exists/i).first() ).toBeVisible({ timeout: 5000 }); // Should stay on register page await expect(page).toHaveURL(/\/register/); }); test('should show error for password mismatch', async ({ page }) => { const testUser = generateTestUser('Mismatch'); // Fill registration form with mismatched passwords await page.getByLabel(/full name|name/i).fill(testUser.fullName); await page.getByLabel(/email/i).fill(testUser.email); await page.getByLabel(/^password$/i).fill(testUser.password); await page.getByLabel(/confirm password|repeat password/i).fill('DifferentPassword123!'); // Submit form await page.getByRole('button', { name: /register|sign up|create account/i }).click(); // Verify error message about password mismatch await expect( page.getByText(/password.*match|password.*mismatch|passwords.*not.*match/i).first() ).toBeVisible({ timeout: 5000 }); // Should stay on register page await expect(page).toHaveURL(/\/register/); }); test('should show error for invalid email format', async ({ page }) => { // Fill registration form with invalid email await page.getByLabel(/full name|name/i).fill('Test User'); await page.getByLabel(/email/i).fill('invalid-email-format'); await page.getByLabel(/^password$/i).fill('ValidPassword123!'); await page.getByLabel(/confirm password|repeat password/i).fill('ValidPassword123!'); // Submit form await page.getByRole('button', { name: /register|sign up|create account/i }).click(); // Verify error message about invalid email await expect( page.getByText(/valid email|invalid email|email format|email address/i).first() ).toBeVisible({ timeout: 5000 }); // Should stay on register page await expect(page).toHaveURL(/\/register/); }); test('should show error for weak password', async ({ page }) => { // Fill registration form with weak password await page.getByLabel(/full name|name/i).fill('Test User'); await page.getByLabel(/email/i).fill(generateTestEmail()); await page.getByLabel(/^password$/i).fill('123'); await page.getByLabel(/confirm password|repeat password/i).fill('123'); // Submit form await page.getByRole('button', { name: /register|sign up|create account/i }).click(); // Verify error message about weak password await expect( page.getByText(/password.*too short|weak password|password.*at least|password.*minimum/i).first() ).toBeVisible({ timeout: 5000 }); }); test('should validate required fields', async ({ page }) => { // Submit empty form await page.getByRole('button', { name: /register|sign up|create account/i }).click(); // Verify validation errors for required fields await expect( page.getByText(/required|please fill|field is empty/i).first() ).toBeVisible({ timeout: 5000 }); }); test('should navigate to login page from register', async ({ page }) => { // Find and click login link const loginLink = page.getByRole('link', { name: /sign in|login|already have account/i }); await loginLink.click(); // Verify navigation to login page await expect(page).toHaveURL(/\/login/); await expect(page.getByRole('heading', { name: /login|sign in/i })).toBeVisible(); }); }); // ============================================ // TEST SUITE: Login // ============================================ test.describe('QA-AUTH-019: Login', () => { test.beforeEach(async ({ page }) => { await page.goto('/login'); await page.waitForLoadState('networkidle'); }); test('should login with valid credentials', async ({ page, request }) => { // First register a user const testUser = generateTestUser('Login'); const registerResponse = await request.post('http://localhost:8000/api/v1/auth/register', { data: { email: testUser.email, password: testUser.password, full_name: testUser.fullName, }, }); if (!registerResponse.ok()) { test.skip(); } // Clear and navigate to login await page.goto('/login'); await page.waitForLoadState('networkidle'); // Fill login form await page.getByLabel(/email/i).fill(testUser.email); await page.getByLabel(/password/i).fill(testUser.password); // Submit form await page.getByRole('button', { name: /login|sign in/i }).click(); // Verify redirect to dashboard await page.waitForURL('/', { timeout: 10000 }); await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); // Verify user is authenticated expect(await isAuthenticated(page)).toBe(true); }); test('should show error for invalid credentials', async ({ page }) => { // Fill login form with invalid credentials await page.getByLabel(/email/i).fill('invalid@example.com'); await page.getByLabel(/password/i).fill('wrongpassword123!'); // Submit form await page.getByRole('button', { name: /login|sign in/i }).click(); // Verify error message await expect( page.getByText(/invalid.*credential|incorrect.*password|wrong.*email|authentication.*failed/i).first() ).toBeVisible({ timeout: 5000 }); // Should stay on login page await expect(page).toHaveURL(/\/login/); }); test('should show error for non-existent user', async ({ page }) => { // Fill login form with non-existent email await page.getByLabel(/email/i).fill(generateTestEmail('nonexistent')); await page.getByLabel(/password/i).fill('SomePassword123!'); // Submit form await page.getByRole('button', { name: /login|sign in/i }).click(); // Verify error message await expect( page.getByText(/invalid.*credential|user.*not found|account.*not exist/i).first() ).toBeVisible({ timeout: 5000 }); }); test('should validate email format', async ({ page }) => { // Fill login form with invalid email format await page.getByLabel(/email/i).fill('not-an-email'); await page.getByLabel(/password/i).fill('SomePassword123!'); // Submit form await page.getByRole('button', { name: /login|sign in/i }).click(); // Verify validation error await expect( page.getByText(/valid email|invalid email|email format/i).first() ).toBeVisible({ timeout: 5000 }); }); test('should navigate to register page from login', async ({ page }) => { // Find and click register link const registerLink = page.getByRole('link', { name: /sign up|register|create account/i }); await registerLink.click(); // Verify navigation to register page await expect(page).toHaveURL(/\/register/); await expect(page.getByRole('heading', { name: /register|sign up/i })).toBeVisible(); }); test('should navigate to forgot password page', async ({ page }) => { // Find and click forgot password link const forgotLink = page.getByRole('link', { name: /forgot.*password|reset.*password/i }); if (await forgotLink.isVisible().catch(() => false)) { await forgotLink.click(); // Verify navigation to forgot password page await expect(page).toHaveURL(/\/forgot-password|reset-password/); } }); }); // ============================================ // TEST SUITE: Protected Routes // ============================================ test.describe('QA-AUTH-019: Protected Routes', () => { test('should redirect to login when accessing /scenarios without auth', async ({ page }) => { // Clear any existing auth await clearAuthToken(page); // Try to access protected route directly await page.goto('/scenarios'); await page.waitForLoadState('networkidle'); // Should redirect to login await waitForAuthRedirect(page, '/login'); await expect(page.getByRole('heading', { name: /login|sign in/i })).toBeVisible(); }); test('should redirect to login when accessing /profile without auth', async ({ page }) => { await clearAuthToken(page); await page.goto('/profile'); await page.waitForLoadState('networkidle'); await waitForAuthRedirect(page, '/login'); }); test('should redirect to login when accessing /settings without auth', async ({ page }) => { await clearAuthToken(page); await page.goto('/settings'); await page.waitForLoadState('networkidle'); await waitForAuthRedirect(page, '/login'); }); test('should redirect to login when accessing /settings/api-keys without auth', async ({ page }) => { await clearAuthToken(page); await page.goto('/settings/api-keys'); await page.waitForLoadState('networkidle'); await waitForAuthRedirect(page, '/login'); }); test('should allow access to /scenarios with valid auth', async ({ page, request }) => { // Register and login a user const testUser = generateTestUser('Protected'); const registerResponse = await request.post('http://localhost:8000/api/v1/auth/register', { data: { email: testUser.email, password: testUser.password, full_name: testUser.fullName, }, }); if (!registerResponse.ok()) { test.skip(); } // Login via UI await loginUserViaUI(page, testUser.email, testUser.password); // Now try to access protected route await page.goto('/scenarios'); await page.waitForLoadState('networkidle'); // Should stay on scenarios page await expect(page).toHaveURL('/scenarios'); await expect(page.getByRole('heading', { name: 'Scenarios' })).toBeVisible(); }); test('should persist auth state after page refresh', async ({ page, request }) => { // Register and login const testUser = generateTestUser('Persist'); const registerResponse = await request.post('http://localhost:8000/api/v1/auth/register', { data: { email: testUser.email, password: testUser.password, full_name: testUser.fullName, }, }); if (!registerResponse.ok()) { test.skip(); } await loginUserViaUI(page, testUser.email, testUser.password); // Refresh page await page.reload(); await waitForLoading(page); // Should still be authenticated and on dashboard await expect(page).toHaveURL('/'); await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible(); expect(await isAuthenticated(page)).toBe(true); }); }); // ============================================ // TEST SUITE: Logout // ============================================ test.describe('QA-AUTH-019: Logout', () => { test('should logout and redirect to login', async ({ page, request }) => { // Register and login const testUser = generateTestUser('Logout'); const registerResponse = await request.post('http://localhost:8000/api/v1/auth/register', { data: { email: testUser.email, password: testUser.password, full_name: testUser.fullName, }, }); if (!registerResponse.ok()) { test.skip(); } await loginUserViaUI(page, testUser.email, testUser.password); // Verify logged in expect(await isAuthenticated(page)).toBe(true); // Logout await logoutUser(page); // Verify redirect to login await expect(page).toHaveURL('/login'); await expect(page.getByRole('heading', { name: /login|sign in/i })).toBeVisible(); }); test('should clear tokens on logout', async ({ page, request }) => { // Register and login const testUser = generateTestUser('ClearTokens'); const registerResponse = await request.post('http://localhost:8000/api/v1/auth/register', { data: { email: testUser.email, password: testUser.password, full_name: testUser.fullName, }, }); if (!registerResponse.ok()) { test.skip(); } await loginUserViaUI(page, testUser.email, testUser.password); // Logout await logoutUser(page); // Check local storage is cleared const accessToken = await page.evaluate(() => localStorage.getItem('access_token')); const refreshToken = await page.evaluate(() => localStorage.getItem('refresh_token')); expect(accessToken).toBeNull(); expect(refreshToken).toBeNull(); }); test('should not access protected routes after logout', async ({ page, request }) => { // Register and login const testUser = generateTestUser('AfterLogout'); const registerResponse = await request.post('http://localhost:8000/api/v1/auth/register', { data: { email: testUser.email, password: testUser.password, full_name: testUser.fullName, }, }); if (!registerResponse.ok()) { test.skip(); } await loginUserViaUI(page, testUser.email, testUser.password); await logoutUser(page); // Try to access protected route await page.goto('/scenarios'); await page.waitForLoadState('networkidle'); // Should redirect to login await waitForAuthRedirect(page, '/login'); }); }); // ============================================ // TEST SUITE: Token Management // ============================================ test.describe('QA-AUTH-019: Token Management', () => { test('should refresh token when expired', async ({ page, request }) => { // This test verifies the token refresh mechanism // Implementation depends on how the frontend handles token expiration test.skip(true, 'Token refresh testing requires controlled token expiration'); }); test('should store tokens in localStorage', async ({ page, request }) => { const testUser = generateTestUser('TokenStorage'); const registerResponse = await request.post('http://localhost:8000/api/v1/auth/register', { data: { email: testUser.email, password: testUser.password, full_name: testUser.fullName, }, }); if (!registerResponse.ok()) { test.skip(); } await loginUserViaUI(page, testUser.email, testUser.password); // Check tokens are stored const accessToken = await page.evaluate(() => localStorage.getItem('access_token')); const refreshToken = await page.evaluate(() => localStorage.getItem('refresh_token')); expect(accessToken).toBeTruthy(); expect(refreshToken).toBeTruthy(); }); });