/** * Authentication Flow E2E Tests * * Critical flows tested: * 1. Login page loads correctly * 2. Email validation * 3. OTP screen navigation * 4. OTP input and verification * 5. Wrong OTP handling * 6. Resend OTP cooldown * 7. Back navigation from OTP * 8. Full login flow with bypass OTP */ import { test, expect } from '@playwright/test'; import { LoginPage, OtpPage, EnterNamePage, BeneficiariesPage, } from '../helpers/page-objects'; import { enableConsoleLogging, waitForAppLoad, TEST_CREDENTIALS, } from '../helpers/test-helpers'; test.describe.configure({ mode: 'serial' }); test.describe('Authentication Flow', () => { test.beforeEach(async ({ page }) => { enableConsoleLogging(page); }); test('1. Login page loads with all elements', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); // Verify all required elements await expect(loginPage.emailInput).toBeVisible(); await expect(loginPage.continueButton).toBeVisible(); await expect(loginPage.inviteCodeLink).toBeVisible(); console.log('✅ Login page loaded correctly'); }); test('2. Empty email shows validation', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); // Try to submit with empty email await loginPage.submit(); // Should stay on login page await page.waitForTimeout(1000); await expect(loginPage.welcomeText).toBeVisible(); console.log('✅ Empty email validation works'); }); test('3. Invalid email format shows validation', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); // Try invalid email await loginPage.enterEmail(TEST_CREDENTIALS.invalid.email); await loginPage.submit(); // Should stay on login page or show error await page.waitForTimeout(1000); await expect(loginPage.welcomeText).toBeVisible(); console.log('✅ Invalid email format handled'); }); test('4. Valid email navigates to OTP screen', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); // Enter valid email await loginPage.loginWithEmail(TEST_CREDENTIALS.existingUser.email); // Should navigate to OTP screen const otpPage = new OtpPage(page); await otpPage.expectLoaded(); // Verify email is shown on OTP screen await expect(page.getByText(TEST_CREDENTIALS.existingUser.email)).toBeVisible(); console.log('✅ OTP screen appears for valid email'); }); test('5. OTP screen has all required elements', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); await loginPage.loginWithEmail(TEST_CREDENTIALS.existingUser.email); const otpPage = new OtpPage(page); await otpPage.expectLoaded(); // Verify all OTP screen elements await expect(otpPage.verifyButton).toBeVisible(); await expect(otpPage.resendButton).toBeVisible(); await expect(otpPage.useDifferentEmailLink).toBeVisible(); console.log('✅ OTP screen has all elements'); }); test('6. OTP input accepts keyboard input', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); await loginPage.loginWithEmail('keyboard.test@example.com'); const otpPage = new OtpPage(page); await otpPage.expectLoaded(); // Type OTP code await otpPage.enterCode('123456'); // Wait for auto-submit or verify button should be clickable await page.waitForTimeout(1000); console.log('✅ OTP input accepts keyboard input'); }); test('7. Wrong OTP shows error and stays on page', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); await loginPage.loginWithEmail('wrong.otp@example.com'); const otpPage = new OtpPage(page); await otpPage.expectLoaded(); // Enter wrong OTP await otpPage.enterCode(TEST_CREDENTIALS.invalid.wrongOtp); // Wait for auto-submit await page.waitForTimeout(3000); // Should show error or stay on OTP page const stillOnOtp = await otpPage.headerText.isVisible({ timeout: 2000 }).catch(() => false); expect(stillOnOtp).toBe(true); console.log('✅ Wrong OTP handled correctly'); }); test('8. Resend shows cooldown timer', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); await loginPage.loginWithEmail('resend.test@example.com'); const otpPage = new OtpPage(page); await otpPage.expectLoaded(); // Click resend await otpPage.resend(); // Should show cooldown timer await otpPage.expectResendCooldown(); console.log('✅ Resend cooldown works'); }); test('9. Back to login clears state', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); await loginPage.loginWithEmail('back.test@example.com'); const otpPage = new OtpPage(page); await otpPage.expectLoaded(); // Go back to login await otpPage.goBackToLogin(); // Should be on login page again await loginPage.expectLoaded(); console.log('✅ Back navigation works'); }); test('10. Full login flow with bypass OTP', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); // Enter email await loginPage.loginWithEmail(TEST_CREDENTIALS.existingUser.email); // Wait for OTP screen const otpPage = new OtpPage(page); await otpPage.expectLoaded(); // Enter bypass OTP await otpPage.enterCode(TEST_CREDENTIALS.existingUser.bypassOtp); // Wait for navigation after OTP verification await page.waitForTimeout(3000); // Should be on one of: dashboard, enter-name, add-loved-one, or beneficiaries const isLoggedIn = await Promise.race([ page.getByText('My Loved Ones').isVisible({ timeout: 5000 }).catch(() => false), page.getByText('Dashboard').isVisible({ timeout: 5000 }).catch(() => false), page.getByText('What should we call you?').isVisible({ timeout: 5000 }).catch(() => false), page.getByText(/Add.*Loved One/i).isVisible({ timeout: 5000 }).catch(() => false), ]); // At minimum, should not be on login or OTP page const stillOnAuth = await loginPage.welcomeText.isVisible({ timeout: 1000 }).catch(() => false) || await otpPage.headerText.isVisible({ timeout: 1000 }).catch(() => false); expect(stillOnAuth).toBe(false); console.log('✅ Full login flow completed'); }); test('11. Rate limiting protection', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); // Make multiple rapid requests for (let i = 0; i < 3; i++) { await loginPage.enterEmail(`rate.limit.${i}@test.com`); await loginPage.submit(); await page.waitForTimeout(500); await page.goto(loginPage.page.url()); // Reset await page.waitForLoadState('networkidle'); } // App should still be responsive await expect(loginPage.welcomeText).toBeVisible({ timeout: 10000 }); console.log('✅ Rate limiting handled gracefully'); }); }); test.describe('New User Onboarding Flow', () => { test.beforeEach(async ({ page }) => { enableConsoleLogging(page); }); test('New user is prompted for name after first login', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); // Use a unique email that likely doesn't exist const newEmail = `new.user.${Date.now()}@test.com`; await loginPage.loginWithEmail(newEmail); // Wait for OTP screen const otpPage = new OtpPage(page); await otpPage.expectLoaded(); // Enter bypass OTP await otpPage.enterCode(TEST_CREDENTIALS.newUser.bypassOtp); // Wait for navigation await page.waitForTimeout(3000); // New user should see either enter-name or add-loved-one screen const isNewUserFlow = await Promise.race([ page.getByText('What should we call you?').isVisible({ timeout: 5000 }).catch(() => false), page.getByText(/Add.*Loved One/i).isVisible({ timeout: 5000 }).catch(() => false), ]); console.log(`New user flow detected: ${isNewUserFlow}`); console.log('✅ New user onboarding flow works'); }); test('Enter name form validates input', async ({ page }) => { const loginPage = new LoginPage(page); await loginPage.goto(); await loginPage.expectLoaded(); // This test assumes we can get to enter-name screen // In real scenario, would use a known new user or mock const newEmail = `name.test.${Date.now()}@test.com`; await loginPage.loginWithEmail(newEmail); const otpPage = new OtpPage(page); await otpPage.expectLoaded(); await otpPage.enterCode(TEST_CREDENTIALS.newUser.bypassOtp); await page.waitForTimeout(3000); // If on enter-name screen, test validation const enterNamePage = new EnterNamePage(page); const isOnEnterName = await enterNamePage.headerText.isVisible({ timeout: 2000 }).catch(() => false); if (isOnEnterName) { // Try to submit without name await enterNamePage.submit(); await page.waitForTimeout(500); // Should still be on enter-name screen await expect(enterNamePage.headerText).toBeVisible(); console.log('✅ Enter name validation works'); } else { console.log('⚠️ Not on enter-name screen, skipping validation test'); } }); });