import { test, expect } from '@chromatic-com/playwright'; import { loginViaAPI, CONFIG, navigateTo, assertNotBroken } from './helpers'; // ============================================================================= // FORMS — Validation des formulaires @feature-forms // // Ce fichier teste la validation cote client de TOUS les formulaires // de l'application : soumission vide, champs invalides, messages d'erreur. // ============================================================================= // ============================================================================= // LOGIN FORM VALIDATION // ============================================================================= test.describe('FORMS — Login form validation @feature-forms', () => { test.beforeEach(async ({ page }) => { await navigateTo(page, '/login'); await page.getByTestId('login-form').waitFor({ state: 'visible', timeout: 10_000 }); }); test('01. Soumettre login vide affiche erreurs de validation @critical', async ({ page }) => { const submitBtn = page.getByTestId('login-submit'); await submitBtn.click(); // Should stay on login page await expect(page).toHaveURL(/login/); // Check for validation errors (custom or HTML5 native) const body = await page.textContent('body') || ''; const hasCustomError = /required|obligatoire|email|invalid|invalide/i.test(body); const emailInput = page.locator('input[type="email"]').first(); const emailValidation = await emailInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const passwordInput = page.locator('input[type="password"]').first(); const passwordValidation = await passwordInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const hasValidation = hasCustomError || emailValidation.length > 0 || passwordValidation.length > 0; expect(hasValidation).toBeTruthy(); console.log(` Empty login: validation shown (email: "${emailValidation}", password: "${passwordValidation}")`); }); test('02. Soumettre login avec email seul affiche erreur mot de passe', async ({ page }) => { const emailInput = page.locator('input[type="email"]'); await emailInput.fill('test@example.com'); const submitBtn = page.getByTestId('login-submit'); await submitBtn.click(); // Should stay on login await expect(page).toHaveURL(/login/); // Password should show validation const passwordInput = page.locator('input[type="password"]').first(); const validationMessage = await passwordInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const body = await page.textContent('body') || ''; const hasError = validationMessage.length > 0 || /required|obligatoire|password|mot de passe/i.test(body); expect(hasError).toBeTruthy(); console.log(` Email only: password validation shown ("${validationMessage}")`); }); test('03. Soumettre login avec password seul affiche erreur email', async ({ page }) => { const passwordInput = page.locator('input[type="password"]').first(); await passwordInput.fill('Password123!'); const submitBtn = page.getByTestId('login-submit'); await submitBtn.click(); // Should stay on login await expect(page).toHaveURL(/login/); // Email should show validation const emailInput = page.locator('input[type="email"]').first(); const validationMessage = await emailInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const body = await page.textContent('body') || ''; const hasError = validationMessage.length > 0 || /required|obligatoire|email/i.test(body); expect(hasError).toBeTruthy(); console.log(` Password only: email validation shown ("${validationMessage}")`); }); test('04. Email invalide format affiche erreur validation', async ({ page }) => { const emailInput = page.locator('input[type="email"]'); await emailInput.fill('not-an-email'); const passwordInput = page.locator('input[type="password"]').first(); await passwordInput.fill('Password123!'); const submitBtn = page.getByTestId('login-submit'); await submitBtn.click(); // Should stay on login await expect(page).toHaveURL(/login/); // Check for email validation error const emailEl = page.locator('input[type="email"]').first(); const validationMessage = await emailEl.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const body = await page.textContent('body') || ''; const hasError = validationMessage.length > 0 || /invalid|invalide|email.*format|format.*email/i.test(body); expect(hasError).toBeTruthy(); console.log(` Invalid email format: validation shown ("${validationMessage}")`); }); test('05. Identifiants incorrects affiche erreur serveur sans crash', async ({ page }) => { const emailInput = page.locator('input[type="email"]'); await emailInput.clear(); await emailInput.fill('nonexistent@example.com'); const passwordInput = page.locator('input[type="password"]').first(); await passwordInput.clear(); await passwordInput.fill('WrongPassword999!'); const submitBtn = page.getByTestId('login-submit'); await submitBtn.click(); await page.waitForTimeout(3_000); // Should stay on login await expect(page).toHaveURL(/login/); // Should show an error alert const body = await page.textContent('body') || ''; expect(body).not.toMatch(/crash|TypeError|Cannot read/i); const errorAlert = page.getByRole('alert'); const hasAlert = await errorAlert.isVisible().catch(() => false); const hasErrorText = /incorrect|invalid|erreur|error|unauthorized|identifiants/i.test(body); console.log(` Wrong credentials: ${hasAlert ? 'alert shown' : hasErrorText ? 'error text shown' : 'handled'}`); }); }); // ============================================================================= // REGISTER FORM VALIDATION // ============================================================================= test.describe('FORMS — Register form validation @feature-forms', () => { test.beforeEach(async ({ page }) => { await navigateTo(page, '/register'); await page.getByTestId('register-form').waitFor({ state: 'visible', timeout: 10_000 }); }); test('06. Soumettre register vide affiche erreurs multiples @critical', async ({ page }) => { const submitBtn = page.getByTestId('register-submit'); await submitBtn.click(); // Should stay on register page await expect(page).toHaveURL(/register/); // Check for validation errors const body = await page.textContent('body') || ''; const hasCustomErrors = /required|obligatoire|invalid|invalide|trop court|too short/i.test(body); const usernameInput = page.locator('#register-username'); const usernameValidation = await usernameInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const hasValidation = hasCustomErrors || usernameValidation.length > 0; expect(hasValidation).toBeTruthy(); console.log(` Empty register: validation shown (${hasCustomErrors ? 'custom errors' : 'native validation'})`); }); test('07. Username trop court (< 3 chars) affiche erreur', async ({ page }) => { const usernameInput = page.locator('#register-username'); await usernameInput.fill('ab'); await usernameInput.blur(); await page.waitForTimeout(500); const body = await page.textContent('body') || ''; const hasError = /trop court|too short|minimum|au moins|at least|3.*caract|3.*char/i.test(body); const validationMessage = await usernameInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const validated = hasError || validationMessage.length > 0; console.log(` Short username: ${validated ? 'error shown' : 'no explicit error (may validate on submit)'}`); }); test('08. Mot de passe trop court (< 12 chars) affiche erreur', async ({ page }) => { const passwordInput = page.locator('#register-password'); await passwordInput.fill('Short1!'); await passwordInput.blur(); await page.waitForTimeout(500); const body = await page.textContent('body') || ''; const hasError = /trop court|too short|minimum|au moins|at least|caract|char|password/i.test(body); const validationMessage = await passwordInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const validated = hasError || validationMessage.length > 0; console.log(` Short password: ${validated ? 'error shown' : 'no explicit error (may validate on submit)'}`); }); test('09. Mots de passe ne correspondent pas affiche erreur', async ({ page }) => { const passwordInput = page.locator('#register-password'); await passwordInput.fill('SecurePassword123!@#'); const confirmInput = page.locator('#register-password_confirm'); await confirmInput.fill('DifferentPassword456!@#'); await confirmInput.blur(); await page.waitForTimeout(500); const body = await page.textContent('body') || ''; const hasError = /ne correspondent pas|do not match|don't match|mismatch|identiques|match/i.test(body); // Also try submitting to trigger validation if (!hasError) { // Fill other required fields first await page.locator('#register-username').fill('testuser'); await page.locator('#register-email').fill('test@example.com'); const termsCheckbox = page.locator('#register-terms'); if (await termsCheckbox.isVisible().catch(() => false)) { await termsCheckbox.check(); } const submitBtn = page.getByTestId('register-submit'); await submitBtn.click(); await page.waitForTimeout(1_000); const bodyAfterSubmit = await page.textContent('body') || ''; const hasErrorAfterSubmit = /ne correspondent pas|do not match|don't match|mismatch|identiques|match/i.test(bodyAfterSubmit); console.log(` Mismatched passwords: ${hasErrorAfterSubmit ? 'error shown on submit' : 'check behavior'}`); } else { console.log(' Mismatched passwords: error shown on blur'); } // Should stay on register regardless await expect(page).toHaveURL(/register/); }); test('10. Terms non cochees affiche erreur', async ({ page }) => { // Fill all fields except terms await page.locator('#register-username').fill('testuser123'); await page.locator('#register-email').fill(`terms-test-${Date.now()}@example.com`); await page.locator('#register-password').fill('SecurePassword123!@#'); await page.locator('#register-password_confirm').fill('SecurePassword123!@#'); // Make sure terms is NOT checked const termsCheckbox = page.locator('#register-terms'); if (await termsCheckbox.isVisible().catch(() => false)) { if (await termsCheckbox.isChecked()) { await termsCheckbox.uncheck(); } } const submitBtn = page.getByTestId('register-submit'); await submitBtn.click(); await page.waitForTimeout(1_000); // Should stay on register page await expect(page).toHaveURL(/register/); const body = await page.textContent('body') || ''; const hasTermsError = /terms|conditions|accepter|accept|cgu|tos/i.test(body); const termsValidation = await termsCheckbox.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); console.log(` Terms unchecked: ${hasTermsError || termsValidation.length > 0 ? 'error shown' : 'form blocked (native or custom)'}`); }); test('11. Email invalide dans le formulaire d\'inscription affiche erreur', async ({ page }) => { await page.locator('#register-email').fill('invalid-email-format'); await page.locator('#register-email').blur(); await page.waitForTimeout(500); const body = await page.textContent('body') || ''; const hasError = /email.*invalide|invalid.*email|format/i.test(body); const emailInput = page.locator('#register-email'); const validationMessage = await emailInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const validated = hasError || validationMessage.length > 0; expect(validated).toBeTruthy(); console.log(` Invalid register email: error shown ("${validationMessage}")`); }); }); // ============================================================================= // FORGOT PASSWORD FORM VALIDATION // ============================================================================= test.describe('FORMS — Forgot password form validation @feature-forms', () => { test.beforeEach(async ({ page }) => { await navigateTo(page, '/forgot-password'); }); test('12. Soumettre sans email affiche erreur', async ({ page }) => { const submitBtn = page.getByRole('button', { name: /reset|réinitialiser|send|envoyer|submit/i }); if (!(await submitBtn.isVisible().catch(() => false))) { console.log(' Forgot password form not found (skipping)'); return; } await submitBtn.click(); const body = await page.textContent('body') || ''; const hasError = /required|obligatoire|email|invalid|invalide/i.test(body); const emailInput = page.locator('input[type="email"]').first(); const validationMessage = await emailInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const validated = hasError || validationMessage.length > 0; expect(validated).toBeTruthy(); console.log(` Empty forgot password: validation shown ("${validationMessage}")`); }); test('13. Email invalide affiche erreur', async ({ page }) => { const emailInput = page.locator('input[type="email"]').first() .or(page.getByLabel(/email/i).first()); if (!(await emailInput.isVisible().catch(() => false))) { console.log(' Forgot password email input not found (skipping)'); return; } await emailInput.fill('not-an-email'); const submitBtn = page.getByRole('button', { name: /reset|réinitialiser|send|envoyer|submit/i }); await submitBtn.click(); const validationMessage = await emailInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const body = await page.textContent('body') || ''; const hasError = validationMessage.length > 0 || /invalid|invalide|format/i.test(body); expect(hasError).toBeTruthy(); console.log(` Invalid email in forgot password: error shown ("${validationMessage}")`); }); test('14. Email valide affiche message de succes', async ({ page }) => { const emailInput = page.locator('input[type="email"]').first() .or(page.getByLabel(/email/i).first()); if (!(await emailInput.isVisible().catch(() => false))) { console.log(' Forgot password email input not found (skipping)'); return; } await emailInput.fill('test@example.com'); const submitBtn = page.getByRole('button', { name: /reset|réinitialiser|send|envoyer|submit/i }); await submitBtn.click(); await page.waitForTimeout(3_000); const body = await page.textContent('body') || ''; // Should show a success message (email sent) or an error (email not found) // Either way, should not crash expect(body).not.toMatch(/crash|TypeError|Cannot read/i); const hasSuccess = /envoyé|sent|check.*email|vérif.*email|lien.*envoyé|link.*sent|succès|success/i.test(body); const hasError = /not found|introuvable|error|erreur/i.test(body); console.log(` Valid email forgot password: ${hasSuccess ? 'success message' : hasError ? 'error (expected if email not in DB)' : 'response received'}`); }); }); // ============================================================================= // PLAYLIST CREATE FORM VALIDATION // ============================================================================= test.describe('FORMS — Playlist create form validation @feature-forms', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('15. Creer playlist sans titre affiche erreur', async ({ page }) => { await navigateTo(page, '/playlists'); const createBtn = page.getByRole('button', { name: /créer|create|nouvelle|new/i }).first() .or(page.getByRole('link', { name: /créer|create|nouvelle|new/i }).first()); if (!(await createBtn.isVisible().catch(() => false))) { console.log(' Create playlist button not found (skipping)'); return; } await createBtn.click(); await page.waitForTimeout(1_000); // Try to submit without filling the title — scope to dialog to avoid strict mode violation const dialog = page.locator('[role="dialog"]').first(); const dialogVisible = await dialog.isVisible().catch(() => false); const saveBtn = dialogVisible ? dialog.getByRole('button', { name: /créer|create|sauvegarder|save|ok|ajouter|add/i }).first() : page.getByRole('button', { name: /créer|create|sauvegarder|save|ok|ajouter|add/i }).first(); if (!(await saveBtn.isVisible().catch(() => false))) { console.log(' Save button not found after clicking create (skipping)'); return; } await saveBtn.click(); await page.waitForTimeout(1_000); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/crash|TypeError|Cannot read/i); const hasError = /required|obligatoire|titre|title|nom|name|vide|empty/i.test(body); const nameInput = page.getByLabel(/nom|name|titre|title/i).first() .or(page.getByPlaceholder(/nom|name|titre/i).first()); const validationMessage = await nameInput.evaluate( (el: HTMLInputElement) => el.validationMessage, ).catch(() => ''); const validated = hasError || validationMessage.length > 0; console.log(` Empty playlist title: ${validated ? 'error shown' : 'form blocked or handled'}`); }); test('16. Creer playlist avec titre valide fonctionne', async ({ page }) => { await navigateTo(page, '/playlists'); const createBtn = page.getByRole('button', { name: /créer|create|nouvelle|new/i }).first() .or(page.getByRole('link', { name: /créer|create|nouvelle|new/i }).first()); if (!(await createBtn.isVisible().catch(() => false))) { console.log(' Create playlist button not found (skipping)'); return; } await createBtn.click(); await page.waitForTimeout(1_000); const nameInput = page.getByLabel(/nom|name|titre|title/i).first() .or(page.getByPlaceholder(/nom|name|titre/i).first()); if (!(await nameInput.isVisible().catch(() => false))) { console.log(' Playlist name input not found (skipping)'); return; } const playlistName = `E2E Validation Test ${Date.now()}`; await nameInput.fill(playlistName); // Scope to dialog to avoid strict mode violation (sidebar "Create" + dialog "Create") const dialog = page.locator('[role="dialog"]').first(); const dialogVisible = await dialog.isVisible().catch(() => false); let saveBtn: import('@playwright/test').Locator; if (dialogVisible) { saveBtn = dialog.getByRole('button', { name: /créer|create|sauvegarder|save|ok|ajouter|add/i }).first(); } else { // Fallback: also try [data-state="open"] overlays const overlay = page.locator('[data-state="open"]').first(); const overlayVisible = await overlay.isVisible().catch(() => false); if (overlayVisible) { saveBtn = overlay.getByRole('button', { name: /créer|create|sauvegarder|save|ok|ajouter|add/i }).first(); } else { saveBtn = page.getByRole('button', { name: /créer|create|sauvegarder|save|ok|ajouter|add/i }).first(); } } await saveBtn.click(); await page.waitForTimeout(3_000); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/crash|TypeError|Cannot read/i); // Should either show the new playlist or redirect to it const success = body.includes(playlistName) || page.url().includes('/playlists/'); console.log(` Create playlist with title: ${success ? 'success' : 'check behavior'}`); }); }); // ============================================================================= // SETTINGS FORMS VALIDATION // ============================================================================= test.describe('FORMS — Settings forms validation @feature-forms', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/settings'); }); test('17. Changer mot de passe — champs vides affiche erreur', async ({ page }) => { // Find the password change section const passwordSection = page.getByText(/changer.*mot de passe|change.*password|modifier.*mot de passe/i); if (!(await passwordSection.isVisible().catch(() => false))) { console.log(' Password change section not found (skipping)'); return; } // Look for a submit button in the password section const changeBtn = page.getByRole('button', { name: /changer|change|modifier|update|save|sauvegarder/i }); const allButtons = await changeBtn.all(); // Try clicking the button closest to password section for (const btn of allButtons) { const btnText = await btn.textContent().catch(() => ''); if (/password|mot de passe|changer|change|modifier/i.test(btnText || '')) { await btn.click(); break; } } // Fallback: click first matching button if (allButtons.length > 0) { await allButtons[0].click(); } await page.waitForTimeout(1_000); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/crash|TypeError|Cannot read/i); const hasError = /required|obligatoire|vide|empty|remplir|fill/i.test(body); console.log(` Empty password change: ${hasError ? 'error shown' : 'handled'}`); }); test('18. Changer mot de passe — nouveau != confirmation affiche erreur', async ({ page }) => { // Find password fields const currentPassword = page.getByLabel(/actuel|current/i).first() .or(page.locator('input[name*="current_password"]').first()); const newPassword = page.getByLabel(/nouveau|new/i).first() .or(page.locator('input[name*="new_password"]').first()); const confirmPassword = page.getByLabel(/confirm/i).first() .or(page.locator('input[name*="confirm"]').first()); if (!(await currentPassword.isVisible().catch(() => false))) { console.log(' Password change fields not found (skipping)'); return; } await currentPassword.fill('OldPassword123!'); await newPassword.fill('NewPassword123!@#'); await confirmPassword.fill('DifferentPassword456!@#'); const changeBtn = page.getByRole('button', { name: /changer|change|modifier|update|save|sauvegarder/i }).first(); await changeBtn.click(); await page.waitForTimeout(1_000); // Should stay on settings and show error const body = await page.textContent('body') || ''; expect(body).not.toMatch(/crash|TypeError|Cannot read/i); const hasError = /ne correspondent pas|do not match|don't match|mismatch|identiques/i.test(body); console.log(` Mismatched new passwords: ${hasError ? 'error shown' : 'handled'}`); }); }); // ============================================================================= // SEARCH FORM VALIDATION // ============================================================================= test.describe('FORMS — Search form validation @feature-forms', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('19. Recherche vide ne crash pas, affiche etat initial', async ({ page }) => { await navigateTo(page, '/search'); const searchInput = page.locator('input[role="combobox"][aria-label="Search"]') .or(page.getByPlaceholder(/search for tracks/i)) .or(page.locator('[role="search"] input')); if (!(await searchInput.first().isVisible().catch(() => false))) { console.log(' Search input not found (skipping)'); return; } // Clear and press Enter await searchInput.first().fill(''); await searchInput.first().press('Enter'); await page.waitForTimeout(1_000); await assertNotBroken(page); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/crash|TypeError|Cannot read/i); console.log(' Empty search: no crash, page stable'); }); test('20. Recherche avec caracteres speciaux ne crash pas', async ({ page }) => { await navigateTo(page, '/search'); const searchInput = page.locator('input[role="combobox"][aria-label="Search"]') .or(page.getByPlaceholder(/search for tracks/i)) .or(page.locator('[role="search"] input')); if (!(await searchInput.first().isVisible().catch(() => false))) { console.log(' Search input not found (skipping)'); return; } const specialInputs = [ '', "'; DROP TABLE tracks; --", '../../etc/passwd', '%00%0d%0a', String.raw`\x00\x1f`, ]; for (const input of specialInputs) { await searchInput.first().fill(input); await page.waitForTimeout(500); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/crash|TypeError|Cannot read|Unexpected token/i); } console.log(' Special characters in search: no crash'); }); test('21. Recherche avec espaces seuls ne crash pas', async ({ page }) => { await navigateTo(page, '/search'); const searchInput = page.locator('input[role="combobox"][aria-label="Search"]') .or(page.getByPlaceholder(/search for tracks/i)) .or(page.locator('[role="search"] input')); if (!(await searchInput.first().isVisible().catch(() => false))) { console.log(' Search input not found (skipping)'); return; } await searchInput.first().fill(' '); await searchInput.first().press('Enter'); await page.waitForTimeout(1_000); await assertNotBroken(page); console.log(' Whitespace-only search: no crash'); }); }); // ============================================================================= // COMMENT FORM VALIDATION // ============================================================================= test.describe('FORMS — Comment form validation @feature-forms', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('22. Soumettre commentaire vide ne l\'envoie pas', async ({ page }) => { // Navigate to a track page or discover page where comments might be await navigateTo(page, '/discover'); // Try to find a track link and navigate to its detail page const trackLink = page.locator('a[href*="/tracks/"]').first(); if (await trackLink.isVisible().catch(() => false)) { await trackLink.click(); await page.waitForLoadState('networkidle').catch(() => {}); } // Look for comment input const commentInput = page.getByPlaceholder(/comment|ajouter.*commentaire|écrire/i).first() .or(page.locator('textarea[name*="comment"]').first()) .or(page.getByLabel(/comment/i).first()); if (!(await commentInput.isVisible().catch(() => false))) { console.log(' Comment form not found on page (skipping)'); return; } // Leave comment empty and try to submit await commentInput.fill(''); const submitBtn = page.getByRole('button', { name: /publier|post|envoyer|send|comment/i }).first(); if (!(await submitBtn.isVisible().catch(() => false))) { console.log(' Comment submit button not found (skipping)'); return; } // Track if a request was sent let commentRequestSent = false; page.on('request', (req) => { if (req.url().includes('/comment') && req.method() === 'POST') { commentRequestSent = true; } }); await submitBtn.click(); await page.waitForTimeout(1_500); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/crash|TypeError|Cannot read/i); // The button might be disabled or validation might prevent sending console.log(` Empty comment: ${commentRequestSent ? 'request sent (check server validation)' : 'not sent (client validation)'}`); }); }); // ============================================================================= // CONTACT / SUPPORT FORM VALIDATION // ============================================================================= test.describe('FORMS — Support/Contact form validation @feature-forms', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('23. Soumettre formulaire support vide affiche erreur', async ({ page }) => { await navigateTo(page, '/support'); // Give the page a moment to settle (redirects, lazy loading) await page.waitForTimeout(1_000); const url = page.url(); const body = await page.textContent('body') || ''; // /support may not exist — if we landed on 404, a redirect, or unrelated page, skip gracefully if (url.includes('/404') || url.includes('/login') || url.includes('/dashboard') || !/support|aide|help|ticket|contact/i.test(body)) { console.log(` Support page not found (ended at ${url}) — skipping`); return; } // Look for a submit button — if the support page has no form, skip const submitBtn = page.getByRole('button', { name: /envoyer|send|soumettre|submit|créer|create|send message/i }).first(); const submitVisible = await submitBtn.isVisible({ timeout: 5_000 }).catch(() => false); if (!submitVisible) { console.log(' Support submit button not found — support form may not exist (skipping)'); return; } // The support form button is disabled when the form is empty (client validation). // Check if button is disabled — that IS the expected validation behavior. const isDisabled = await submitBtn.isDisabled().catch(() => false); if (isDisabled) { console.log(' Empty support form: submit button disabled (client validation works)'); return; } // If not disabled, try clicking (force: true to bypass actionability) await submitBtn.click({ force: true, timeout: 5_000 }); await page.waitForTimeout(1_000); const bodyAfter = await page.textContent('body') || ''; expect(bodyAfter).not.toMatch(/crash|TypeError|Cannot read/i); const hasError = /required|obligatoire|vide|empty|remplir|fill|erreur|error/i.test(bodyAfter); console.log(` Empty support form: ${hasError ? 'error shown' : 'handled'}`); }); }); // ============================================================================= // PROFILE EDIT FORM VALIDATION // ============================================================================= test.describe('FORMS — Profile edit form validation @feature-forms', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('24. Vider le champ username dans le profil affiche erreur', async ({ page }) => { await navigateTo(page, '/settings'); const usernameInput = page.getByLabel(/username|nom d'utilisateur|pseudo/i).first() .or(page.locator('input[name*="username"]').first()); if (!(await usernameInput.isVisible().catch(() => false))) { // Try navigating to /profile/edit await navigateTo(page, '/profile/edit'); const usernameInput2 = page.getByLabel(/username|nom d'utilisateur|pseudo/i).first() .or(page.locator('input[name*="username"]').first()); if (!(await usernameInput2.isVisible().catch(() => false))) { console.log(' Username field not found in settings or profile (skipping)'); return; } } // Clear the username field const input = page.getByLabel(/username|nom d'utilisateur|pseudo/i).first() .or(page.locator('input[name*="username"]').first()); await input.fill(''); const saveBtn = page.getByRole('button', { name: /save|sauvegarder|mettre à jour|update/i }).first(); if (await saveBtn.isVisible().catch(() => false)) { await saveBtn.click(); await page.waitForTimeout(1_000); } const body = await page.textContent('body') || ''; expect(body).not.toMatch(/crash|TypeError|Cannot read/i); const hasError = /required|obligatoire|vide|empty|username/i.test(body); console.log(` Empty username: ${hasError ? 'error shown' : 'handled'}`); }); });