import { test, expect, Page } from '@playwright/test'; import * as path from 'path'; import * as fs from 'fs'; // Test data const testEmail = `user+e2e${Date.now()}@lab.veza`; const testPassword = `V3za!e2e-${Date.now()}`; const sessionFile = path.join(__dirname, '../../.session-e2e.json'); // Helper functions async function saveSession(page: Page) { const localStorage = await page.evaluate(() => { return { access_token: window.localStorage.getItem('access_token'), refresh_token: window.localStorage.getItem('refresh_token'), user: window.localStorage.getItem('user'), }; }); fs.writeFileSync(sessionFile, JSON.stringify(localStorage, null, 2)); } async function loadSession(page: Page) { if (fs.existsSync(sessionFile)) { const session = JSON.parse(fs.readFileSync(sessionFile, 'utf-8')); await page.evaluate((session) => { if (session.access_token) window.localStorage.setItem('access_token', session.access_token); if (session.refresh_token) window.localStorage.setItem('refresh_token', session.refresh_token); if (session.user) window.localStorage.setItem('user', session.user); }, session); } } test.describe('Authentication Flow', () => { test.beforeEach(async ({ page }) => { // Clear storage await page.context().clearCookies(); await page.evaluate(() => window.localStorage.clear()); }); test('should show login page for unauthenticated users', async ({ page }) => { await page.goto('/'); // Should redirect to login await expect(page).toHaveURL(/\/(login|auth)/); // Check for login form elements await expect(page.locator('input[type="email"], input[name="email"]')).toBeVisible(); await expect(page.locator('input[type="password"], input[name="password"]')).toBeVisible(); await expect(page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Sign in")')).toBeVisible(); }); test('should show validation errors for invalid inputs', async ({ page }) => { await page.goto('/login'); // Try to submit empty form await page.locator('button[type="submit"], button:has-text("Login")').click(); // Should show validation errors await expect(page.locator('text=/email.*required/i')).toBeVisible({ timeout: 5000 }); // Try invalid email await page.fill('input[type="email"]', 'invalid-email'); await page.fill('input[type="password"]', 'short'); await page.locator('button[type="submit"]').click(); // Should show email validation error await expect(page.locator('text=/invalid.*email/i')).toBeVisible({ timeout: 5000 }); }); test('should register new user successfully', async ({ page }) => { await page.goto('/register'); // Fill registration form await page.fill('input[type="email"]', testEmail); await page.fill('input[type="password"]', testPassword); // Check for password confirmation field const confirmField = page.locator('input[name="confirmPassword"], input[name="password_confirmation"]'); if (await confirmField.isVisible()) { await confirmField.fill(testPassword); } // Submit form await page.locator('button[type="submit"], button:has-text("Register"), button:has-text("Sign up")').click(); // Should redirect to dashboard or show success await expect(page).toHaveURL(/\/(dashboard|home|app)/, { timeout: 10000 }); // Save session await saveSession(page); // Verify user is logged in await expect(page.locator('text=/logout|sign out/i')).toBeVisible({ timeout: 5000 }); }); test('should prevent duplicate registration', async ({ page }) => { await page.goto('/register'); // Try to register with same email await page.fill('input[type="email"]', testEmail); await page.fill('input[type="password"]', testPassword); const confirmField = page.locator('input[name="confirmPassword"], input[name="password_confirmation"]'); if (await confirmField.isVisible()) { await confirmField.fill(testPassword); } await page.locator('button[type="submit"]').click(); // Should show error await expect(page.locator('text=/already.*exist|already.*registered|duplicate/i')).toBeVisible({ timeout: 5000 }); }); test('should login with valid credentials', async ({ page }) => { await page.goto('/login'); // Fill login form await page.fill('input[type="email"]', testEmail); await page.fill('input[type="password"]', testPassword); // Submit await page.locator('button[type="submit"], button:has-text("Login")').click(); // Should redirect to dashboard await expect(page).toHaveURL(/\/(dashboard|home|app)/, { timeout: 10000 }); // Save session await saveSession(page); // Verify user is logged in await expect(page.locator('text=/logout|sign out/i')).toBeVisible({ timeout: 5000 }); }); test('should show error for invalid credentials', async ({ page }) => { await page.goto('/login'); // Try wrong password await page.fill('input[type="email"]', testEmail); await page.fill('input[type="password"]', 'WrongPassword123!'); await page.locator('button[type="submit"]').click(); // Should show error await expect(page.locator('text=/invalid.*credentials|wrong.*password|authentication.*failed/i')).toBeVisible({ timeout: 5000 }); }); test('should handle token refresh', async ({ page }) => { // Load previous session await loadSession(page); // Go to protected page await page.goto('/dashboard'); // Should still be authenticated await expect(page).toHaveURL(/\/(dashboard|home|app)/); // Simulate token expiry by modifying localStorage await page.evaluate(() => { const token = window.localStorage.getItem('access_token'); if (token) { // Modify token to make it invalid window.localStorage.setItem('access_token', token + '_expired'); } }); // Trigger an API call that should refresh the token await page.reload(); // Should either still work (refresh successful) or redirect to login const url = page.url(); expect(url).toMatch(/\/(dashboard|home|app|login)/); }); test('should logout successfully', async ({ page }) => { // Load session and login await loadSession(page); await page.goto('/dashboard'); // Find and click logout const logoutButton = page.locator('button:has-text("Logout"), a:has-text("Logout"), button:has-text("Sign out")'); await logoutButton.click(); // Should redirect to login await expect(page).toHaveURL(/\/(login|auth)/, { timeout: 5000 }); // Should clear session const localStorage = await page.evaluate(() => { return { access_token: window.localStorage.getItem('access_token'), refresh_token: window.localStorage.getItem('refresh_token'), }; }); expect(localStorage.access_token).toBeNull(); expect(localStorage.refresh_token).toBeNull(); }); test('should handle remember me functionality', async ({ page }) => { await page.goto('/login'); // Check if remember me exists const rememberMe = page.locator('input[type="checkbox"][name="remember"], label:has-text("Remember me")'); if (await rememberMe.isVisible()) { await rememberMe.check(); } // Login await page.fill('input[type="email"]', testEmail); await page.fill('input[type="password"]', testPassword); await page.locator('button[type="submit"]').click(); // Wait for redirect await expect(page).toHaveURL(/\/(dashboard|home|app)/, { timeout: 10000 }); // Check if cookies are set with longer expiry const cookies = await page.context().cookies(); const authCookie = cookies.find(c => c.name.includes('auth') || c.name.includes('session')); if (authCookie) { // Cookie should have expiry set (not session cookie) expect(authCookie.expires).toBeGreaterThan(Date.now() / 1000); } }); test('should handle social login buttons', async ({ page }) => { await page.goto('/login'); // Check for social login buttons const googleButton = page.locator('button:has-text("Google"), a:has-text("Google")'); const githubButton = page.locator('button:has-text("GitHub"), a:has-text("GitHub")'); // Test visibility (they might not be implemented) const hasGoogle = await googleButton.isVisible().catch(() => false); const hasGithub = await githubButton.isVisible().catch(() => false); if (hasGoogle) { // Click should navigate to OAuth provider const [popup] = await Promise.all([ page.waitForEvent('popup'), googleButton.click() ]).catch(() => [null]); if (popup) { expect(popup.url()).toContain('google.com'); await popup.close(); } } if (hasGithub) { const [popup] = await Promise.all([ page.waitForEvent('popup'), githubButton.click() ]).catch(() => [null]); if (popup) { expect(popup.url()).toContain('github.com'); await popup.close(); } } }); }); // Cleanup test.afterAll(() => { if (fs.existsSync(sessionFile)) { fs.unlinkSync(sessionFile); } });