import { test, expect } from '@chromatic-com/playwright'; import { CONFIG, loginViaUI, navigateTo, assertNoDebugText } from './helpers'; test.describe('AUTH — Inscription', () => { test('01. La page /register se charge correctement', async ({ page }) => { await navigateTo(page, '/register'); await expect(page.getByTestId('register-form')).toBeVisible({ timeout: 10_000 }); await expect(page.locator('#register-username')).toBeVisible({ timeout: 5_000 }); await expect(page.locator('#register-email')).toBeVisible({ timeout: 5_000 }); await expect(page.locator('#register-password')).toBeVisible({ timeout: 5_000 }); await assertNoDebugText(page); }); test('02. Inscription avec email + mot de passe valides', async ({ page }) => { test.setTimeout(60_000); await navigateTo(page, '/register'); await expect(page.getByTestId('register-form')).toBeVisible({ timeout: 10_000 }); const uniqueSuffix = Date.now(); const uniqueEmail = `e2e-${uniqueSuffix}@veza.test`; await page.locator('#register-username').fill(`e2e-user-${uniqueSuffix}`); await page.locator('#register-email').fill(uniqueEmail); await page.locator('#register-password').fill('SecurePass123!@#'); await page.locator('#register-password_confirm').fill('SecurePass123!@#'); // Accept terms const termsCheckbox = page.locator('#register-terms'); await expect(termsCheckbox).toBeAttached({ timeout: 5_000 }); await termsCheckbox.click({ force: true }); await page.waitForTimeout(300); const submitBtn = page.getByTestId('register-submit'); await expect(submitBtn).toBeVisible({ timeout: 5_000 }); await submitBtn.click(); // Must get a success indication: redirect OR verification notice const successIndicator = page.getByText(/vérification|verification|email envoyé|check your email|inscription réussie/i) .or(page.locator('[role="status"]').first()); await expect( successIndicator.first(), ).toBeVisible({ timeout: 20_000 }); }); test('03. Inscription avec email deja existant -> erreur claire', async ({ page }) => { await navigateTo(page, '/register'); await expect(page.getByTestId('register-form')).toBeVisible({ timeout: 10_000 }); await page.locator('#register-username').fill('duplicate-user'); await page.locator('#register-email').fill(CONFIG.users.listener.email); await page.locator('#register-password').fill('SecurePass123!@#'); await page.locator('#register-password_confirm').fill('SecurePass123!@#'); const termsCheckbox = page.locator('#register-terms'); await expect(termsCheckbox).toBeAttached({ timeout: 5_000 }); await termsCheckbox.click({ force: true }); await page.waitForTimeout(300); await page.getByTestId('register-submit').click(); // Must show an error — not silently succeed const errorIndicator = page.getByRole('alert') .or(page.getByText(/existe déjà|already exists|email.*taken/i)); await expect(errorIndicator.first()).toBeVisible({ timeout: 10_000 }); }); test('04. Validation cote client — mot de passe trop court', async ({ page }) => { await navigateTo(page, '/register'); await page.locator('#register-password').fill('123'); await page.locator('#register-password').press('Tab'); await page.waitForTimeout(500); // Also submit to trigger validation await page.locator('#register-email').fill('valid@test.com'); await page.locator('#register-username').fill('testuser'); await page.locator('#register-password_confirm').fill('123'); await page.getByTestId('register-submit').click(); await page.waitForTimeout(500); // Must display a validation error const errorMsg = page.locator('#register-password-error') .or(page.getByRole('alert')) .or(page.getByText(/trop court|too short|minimum|au moins|at least|caractères|doit contenir/i)); await expect(errorMsg.first()).toBeVisible({ timeout: 5_000 }); }); test('05. Validation cote client — email invalide', async ({ page }) => { await navigateTo(page, '/register'); await page.locator('#register-email').fill('not-an-email'); await page.locator('#register-email').blur(); await expect( page.getByText(/email.*invalide|invalid.*email|format/i), ).toBeVisible({ timeout: 3_000 }); }); }); test.describe('AUTH — Connexion', () => { test('06. La page /login se charge correctement', async ({ page }) => { await navigateTo(page, '/login'); await expect(page.getByTestId('login-form')).toBeVisible({ timeout: 10_000 }); await expect(page.getByTestId('login-submit')).toBeVisible({ timeout: 5_000 }); await assertNoDebugText(page); }); test('07. Connexion avec identifiants valides @critical', async ({ page }) => { await loginViaUI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); // Must not be on /login anymore await expect(page).not.toHaveURL(/login/); // Authenticated layout must be visible await expect(page.getByTestId('app-sidebar')).toBeVisible({ timeout: 5_000 }); }); test('08. Connexion avec mauvais mot de passe -> erreur claire', async ({ page }) => { test.setTimeout(60_000); await navigateTo(page, '/login'); await expect(page.getByTestId('login-form')).toBeVisible({ timeout: 10_000 }); const emailInput = page.locator('input[type="email"]'); await emailInput.clear(); await emailInput.fill(CONFIG.users.listener.email); const passwordInput = page.locator('input[type="password"]').first(); await passwordInput.clear(); await passwordInput.fill('WrongPassword123!'); await page.getByTestId('login-submit').click(); // Must show error AND stay on /login const errorIndicator = page.getByRole('alert') .or(page.getByText(/incorrect|invalid|erreur|error|identifiants/i)); await expect(errorIndicator.first()).toBeVisible({ timeout: 15_000 }); await expect(page).toHaveURL(/login/); }); test('09. Lien mot de passe oublie fonctionne', async ({ page }) => { await navigateTo(page, '/login'); await expect(page.getByTestId('login-form')).toBeVisible({ timeout: 10_000 }); const forgotLink = page.getByRole('link', { name: /forgot password/i }) .or(page.locator('a[href="/forgot-password"]')); await expect(forgotLink.first()).toBeVisible({ timeout: 8_000 }); await forgotLink.first().click(); await expect(page).toHaveURL(/forgot-password/, { timeout: 10_000 }); }); test('10. Lien vers inscription depuis la page login', async ({ page }) => { await navigateTo(page, '/login'); await expect(page.getByTestId('login-form')).toBeVisible({ timeout: 10_000 }); const registerLink = page.getByRole('link', { name: /sign up/i }) .or(page.locator('a[href="/register"]')); await expect(registerLink.first()).toBeVisible({ timeout: 8_000 }); await registerLink.first().click(); await expect(page).toHaveURL(/register/, { timeout: 10_000 }); }); }); test.describe('AUTH — Sessions et securite', () => { test('11. Redirection vers /login si non authentifie @critical', async ({ page }) => { test.setTimeout(60_000); await page.goto('/dashboard', { waitUntil: 'domcontentloaded' }); // Must redirect to login await expect(page).toHaveURL(/login/, { timeout: 20_000 }); }); test('12. L\'utilisateur est authentifie apres connexion (auth-storage)', async ({ page }) => { test.setTimeout(60_000); await loginViaUI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); // Verify isAuthenticated is true in the Zustand auth-storage const isAuthenticated = await page.evaluate(() => { const raw = localStorage.getItem('auth-storage'); if (!raw) return false; try { const parsed = JSON.parse(raw); return parsed?.state?.isAuthenticated === true; } catch { return false; } }); expect(isAuthenticated, 'auth-storage should have isAuthenticated=true after login').toBeTruthy(); }); test('13. Deconnexion fonctionne correctement', async ({ page }) => { test.setTimeout(60_000); await loginViaUI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); // Find and click sign out — try header menu first, then sidebar const userMenu = page.getByTestId('user-menu'); await expect(userMenu).toBeVisible({ timeout: 5_000 }); await userMenu.click(); await page.waitForTimeout(800); const signOutBtn = page.locator('button.text-destructive').first() .or(page.locator('button').filter({ hasText: /sign out|déconnexion|logout/i }).first()); await expect(signOutBtn).toBeVisible({ timeout: 3_000 }); await signOutBtn.click(); // Must redirect to /login await expect(page).toHaveURL(/login/, { timeout: 20_000 }); // Auth state must be cleared const isAuthenticated = await page.evaluate(() => { const raw = localStorage.getItem('auth-storage'); if (!raw) return false; try { return JSON.parse(raw)?.state?.isAuthenticated === true; } catch { return false; } }); expect(isAuthenticated, 'auth-storage should be cleared after logout').toBeFalsy(); }); test('14. Protection CSRF — la page login charge sans erreur CSRF', async ({ page }) => { await navigateTo(page, '/login'); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/csrf.*error|forbidden/i); }); }); test.describe('AUTH — OAuth', () => { test('15. Boutons OAuth visibles sur la page login', async ({ page }) => { await navigateTo(page, '/login'); // At least one OAuth provider button must be visible const oauthBtn = page.getByRole('button', { name: /google|github|discord|spotify/i }).first() .or(page.locator('[data-provider]').first()) .or(page.locator('a[href*="oauth"]').first()); await expect(oauthBtn).toBeVisible({ timeout: 5_000 }); }); });