/** * E2E Tests for Authentication Flow * Tests user registration, login, and OAuth */ import { test, expect } from '@playwright/test'; test.describe('Authentication', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); }); test.describe('User Registration', () => { test('should display registration page', async ({ page }) => { await page.goto('/register'); await expect(page.locator('h1')).toContainText('Basil'); await expect(page.locator('input[type="email"]')).toBeVisible(); await expect(page.locator('input[type="password"]')).toBeVisible(); }); test('should register new user successfully', async ({ page }) => { await page.goto('/register'); const timestamp = Date.now(); const email = `test-${timestamp}@example.com`; const password = 'TestPassword123'; await page.fill('input[type="email"]', email); await page.fill('input[name="name"]', 'Test User'); await page.fill('input[type="password"]', password); await page.click('button[type="submit"]'); // Should show success message or redirect await expect(page).toHaveURL(/\/(login|verify-email)/); }); test('should show error for weak password', async ({ page }) => { await page.goto('/register'); await page.fill('input[type="email"]', 'test@example.com'); await page.fill('input[type="password"]', 'weak'); await page.click('button[type="submit"]'); // Should display error message await expect(page.locator('.error, .auth-error')).toBeVisible(); }); test('should show error for duplicate email', async ({ page }) => { await page.goto('/register'); await page.fill('input[type="email"]', 'existing@example.com'); await page.fill('input[type="password"]', 'TestPassword123'); await page.click('button[type="submit"]'); // Should show error if email already exists // Or allow registration (depends on implementation) }); test('should validate email format', async ({ page }) => { await page.goto('/register'); await page.fill('input[type="email"]', 'invalid-email'); await page.fill('input[type="password"]', 'TestPassword123'); await page.click('button[type="submit"]'); // Should show validation error or prevent submission const emailInput = page.locator('input[type="email"]'); await expect(emailInput).toHaveAttribute('type', 'email'); }); }); test.describe('User Login', () => { test('should display login page', async ({ page }) => { await page.goto('/login'); await expect(page.locator('h1, h2')).toContainText(/Welcome|Login|Sign/i); await expect(page.locator('input[type="email"]')).toBeVisible(); await expect(page.locator('input[type="password"]')).toBeVisible(); }); test('should show Google OAuth button', async ({ page }) => { await page.goto('/login'); const googleButton = page.locator('button:has-text("Google"), button:has-text("Continue with Google")'); await expect(googleButton).toBeVisible(); }); test('should login with valid credentials', async ({ page, context }) => { // Create test user first (or use existing) await page.goto('/login'); await page.fill('input[type="email"]', 'test@example.com'); await page.fill('input[type="password"]', 'TestPassword123'); await page.click('button[type="submit"]'); // Should redirect to home or dashboard after login // Check for authentication token in localStorage or cookies await page.waitForURL('/', { timeout: 5000 }).catch(() => {}); const cookies = await context.cookies(); const hasAuthCookie = cookies.some(cookie => cookie.name.includes('token') || cookie.name.includes('auth') ); // Should have auth token in storage const hasToken = await page.evaluate(() => { return localStorage.getItem('basil_access_token') !== null; }); expect(hasToken || hasAuthCookie).toBeTruthy(); }); test('should show error for invalid credentials', async ({ page }) => { await page.goto('/login'); await page.fill('input[type="email"]', 'wrong@example.com'); await page.fill('input[type="password"]', 'WrongPassword'); await page.click('button[type="submit"]'); // Should display error message await expect(page.locator('.error, .auth-error')).toBeVisible({ timeout: 5000 }); }); test('should show error for unverified email', async ({ page }) => { // This test depends on having an unverified user // Skip or implement based on your setup }); test('should have forgot password link', async ({ page }) => { await page.goto('/login'); const forgotLink = page.locator('a:has-text("Forgot password")'); await expect(forgotLink).toBeVisible(); await forgotLink.click(); await expect(page).toHaveURL(/forgot-password/); }); test('should have link to registration page', async ({ page }) => { await page.goto('/login'); const signupLink = page.locator('a:has-text("Sign up")'); await expect(signupLink).toBeVisible(); await signupLink.click(); await expect(page).toHaveURL(/register/); }); }); test.describe('Google OAuth', () => { test('should redirect to Google OAuth', async ({ page }) => { await page.goto('/login'); const googleButton = page.locator('button:has-text("Google"), button:has-text("Continue with Google")'); await googleButton.click(); // Should redirect to /api/auth/google which then redirects to Google // We can't test the actual Google OAuth flow, but we can test the redirect await page.waitForTimeout(1000); // URL should change (either to Google or to API endpoint) const currentUrl = page.url(); expect(currentUrl).not.toBe('http://localhost:5173/login'); }); test('should handle OAuth callback', async ({ page }) => { // Simulate OAuth callback with tokens await page.goto('/auth/callback?accessToken=test_token&refreshToken=test_refresh'); // Should store tokens and redirect const hasToken = await page.evaluate(() => { return localStorage.getItem('basil_access_token') !== null; }); // Should redirect to home after callback await expect(page).toHaveURL('/', { timeout: 5000 }).catch(() => {}); }); test('should handle OAuth error', async ({ page }) => { await page.goto('/login?error=oauth_callback_failed'); // Should display error message const errorMessage = page.locator('.error, .auth-error'); await expect(errorMessage).toBeVisible(); }); }); test.describe('Logout', () => { test('should logout and clear session', async ({ page }) => { // First login await page.goto('/login'); // ... login logic ... // Then logout const logoutButton = page.locator('button:has-text("Logout"), button:has-text("Sign out")'); if (await logoutButton.isVisible()) { await logoutButton.click(); // Should clear tokens const hasToken = await page.evaluate(() => { return localStorage.getItem('basil_access_token') === null; }); expect(hasToken).toBeTruthy(); // Should redirect to login await expect(page).toHaveURL(/login/); } }); }); test.describe('Protected Routes', () => { test('should redirect to login when accessing protected route', async ({ page }) => { // Clear any existing auth await page.context().clearCookies(); await page.evaluate(() => localStorage.clear()); // Try to access protected route await page.goto('/recipes/new'); // Should redirect to login await expect(page).toHaveURL(/login/, { timeout: 5000 }); }); test('should allow access to protected route when authenticated', async ({ page }) => { // Set auth token in localStorage await page.evaluate(() => { localStorage.setItem('basil_access_token', 'test_token'); localStorage.setItem('basil_user', JSON.stringify({ id: 'test-user', email: 'test@example.com', })); }); await page.goto('/recipes'); // Should NOT redirect to login await expect(page).toHaveURL(/recipes/); }); }); });