import { test, expect } from '@playwright/test'; import { loginViaAPI, CONFIG, navigateTo } from './helpers'; // ============================================================================ // CHAT — Messagerie temps réel (/chat) // ============================================================================ test.describe('CHAT — Messagerie', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('01. Page /chat se charge @critical', async ({ page }) => { await navigateTo(page, '/chat'); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/500|error|crash/i); // Chat page should show either conversation list (Channels header) or auth prompt const hasContent = body.length > 100; expect(hasContent).toBeTruthy(); console.log(' Chat page loaded at /chat'); }); test('02. Sidebar avec liste des conversations (Channels)', async ({ page }) => { await navigateTo(page, '/chat'); // ChatPage renders a sidebar card with heading "Channels" const channelsHeading = page.getByText('Channels', { exact: true }); const visible = await channelsHeading.isVisible().catch(() => false); console.log(` Channels sidebar heading: ${visible ? '✓' : '✗'}`); // Also check for ChatSidebar component presence const sidebar = page.locator('[class*="w-80"]'); const sidebarVisible = await sidebar.first().isVisible().catch(() => false); console.log(` Chat sidebar panel: ${sidebarVisible ? '✓' : '✗'}`); }); test('03. Champ de saisie de message visible', async ({ page }) => { await navigateTo(page, '/chat'); // ChatInput has aria-label="Type a message" and placeholder containing "Broadcast message" const msgInput = page.getByLabel('Type a message') .or(page.getByPlaceholder(/broadcast message|écrire dans/i)) .or(page.locator('input[type="text"][aria-label="Type a message"]')); const visible = await msgInput.first().isVisible().catch(() => false); console.log(` Input message: ${visible ? '✓' : '✗'}`); }); test('04. Boutons attach/emoji/send présents', async ({ page }) => { await navigateTo(page, '/chat'); // Attach file button const attachBtn = page.getByLabel('Attach file'); const hasAttach = await attachBtn.isVisible().catch(() => false); console.log(` Bouton attach: ${hasAttach ? '✓' : '✗'}`); // Emoji button const emojiBtn = page.getByLabel(/add emoji|close emoji/i); const hasEmoji = await emojiBtn.isVisible().catch(() => false); console.log(` Bouton emoji: ${hasEmoji ? '✓' : '✗'}`); // Send button const sendBtn = page.getByLabel('Send message'); const hasSend = await sendBtn.isVisible().catch(() => false); console.log(` Bouton send: ${hasSend ? '✓' : '✗'}`); }); test('05. WebSocket status indicator visible', async ({ page }) => { await navigateTo(page, '/chat'); // The ChatPage renders a small dot indicating WS connection status // green (bg-success) when connected, red (bg-destructive) when disconnected const statusDot = page.locator('[class*="rounded-full"][class*="bg-success"], [class*="rounded-full"][class*="bg-destructive"]'); const visible = await statusDot.first().isVisible().catch(() => false); console.log(` WS status indicator: ${visible ? '✓' : '✗'}`); }); }); // ============================================================================ // NOTIFICATIONS — Centre de notifications // ============================================================================ test.describe('NOTIFICATIONS — Centre de notifications', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('06. Bouton notifications (bell) visible dans le header @critical', async ({ page }) => { await navigateTo(page, '/dashboard'); // NotificationMenuTrigger has aria-label="Notifications" with a Bell icon const notifBtn = page.getByRole('button', { name: 'Notifications' }); const visible = await notifBtn.isVisible().catch(() => false); expect(visible).toBeTruthy(); console.log(` Bell notifications button: ${visible ? '✓' : '✗'}`); }); test('07. Page /notifications se charge', async ({ page }) => { await navigateTo(page, '/notifications'); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/500|error|crash/i); // NotificationsPageHeader renders an h1 with text "Notifications" const heading = page.getByRole('heading', { name: /notifications/i }); const hasHeading = await heading.first().isVisible().catch(() => false); console.log(` Notifications heading: ${hasHeading ? '✓' : '✗'}`); }); test('08. Bouton "Mark All as Read" présent si notifications non lues', async ({ page }) => { await navigateTo(page, '/notifications'); // NotificationsPageHeader renders "Mark All as Read" button when hasUnread is true const markAllBtn = page.getByRole('button', { name: /mark all as read|marking/i }); const visible = await markAllBtn.isVisible().catch(() => false); console.log(` Bouton "Mark All as Read": ${visible ? '✓ visible' : '✗ absent (no unread notifications)'}`); }); test('09. Préférences de notifications accessibles via settings', async ({ page }) => { await navigateTo(page, '/settings'); // SettingsTabs has a tab trigger "Notifications" — try exact and partial match const notifTab = page.getByRole('tab', { name: /notification/i }); const visible = await notifTab.first().isVisible().catch(() => false); // Fallback: look for any tab or link containing "notification" const altNotifTab = visible ? notifTab.first() : page.locator('[role="tab"]').filter({ hasText: /notif/i }).first(); const altVisible = visible || await altNotifTab.isVisible().catch(() => false); console.log(` Notifications tab in settings: ${altVisible ? '✓' : '✗'}`); // Soft assertion — tab may not exist if settings layout differs if (!altVisible) { console.log(' ⚠ Notifications tab not found — settings may use a different layout'); // Do not fail — settings tabs may have different names or structure return; } // Click the tab to reveal notification preferences const tabToClick = visible ? notifTab.first() : altNotifTab; await tabToClick.click().catch(() => { console.log(' ⚠ Could not click Notifications tab'); return; }); await page.waitForTimeout(500); // NotificationSettings renders checkboxes for email/push preferences const emailNotifCheckbox = page.locator('#email_notifications'); const hasEmailPref = await emailNotifCheckbox.isVisible().catch(() => false); console.log(` Email notifications checkbox: ${hasEmailPref ? '✓' : '✗'}`); const pushNotifCheckbox = page.locator('#push_notifications'); const hasPushPref = await pushNotifCheckbox.isVisible().catch(() => false); console.log(` Push notifications checkbox: ${hasPushPref ? '✓' : '✗'}`); }); }); // ============================================================================ // SETTINGS — Paramètres utilisateur (/settings) // ============================================================================ test.describe('SETTINGS — Paramètres', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('10. Page /settings se charge avec les tabs @critical', async ({ page }) => { await navigateTo(page, '/settings'); const body = await page.textContent('body') || ''; // Only fail on actual server errors, not UI elements that contain "error" in their text expect(body).not.toMatch(/500|Internal Server Error|crash|TypeError/i); // SettingsPage renders heading "System Config" const heading = page.getByRole('heading', { name: /system config/i }); const hasHeading = await heading.isVisible().catch(() => false); console.log(` Settings heading: ${hasHeading ? '✓' : '✗'}`); // SettingsTabs renders tab triggers — names may be in French or English const tabPatterns: [string, RegExp][] = [ ['Account', /account|compte/i], ['Preferences', /pr[ée]f[ée]rences|preferences/i], ['Notifications', /notification/i], ['Privacy', /confidentialit[ée]|privacy/i], ['Playback', /playback|lecture/i], ]; for (const [label, pattern] of tabPatterns) { const tab = page.getByRole('tab', { name: pattern }).first(); const vis = await tab.isVisible().catch(() => false); console.log(` Tab "${label}": ${vis ? '✓' : '✗'}`); } }); test('11. Tab Account — password change form present', async ({ page }) => { await navigateTo(page, '/settings'); // Account tab is defaultValue, so it should be active by default // AccountSettingsPasswordCard renders "Change Password" title and fields const changePasswordTitle = page.getByText('Change Password', { exact: true }); const visible = await changePasswordTitle.first().isVisible().catch(() => false); console.log(` "Change Password" section: ${visible ? '✓' : '✗'}`); // Check for password fields by their HTML ids const currentPwd = page.locator('#current-password'); const newPwd = page.locator('#new-password'); const confirmPwd = page.locator('#confirm-password'); const hasCurrent = await currentPwd.isVisible().catch(() => false); const hasNew = await newPwd.isVisible().catch(() => false); const hasConfirm = await confirmPwd.isVisible().catch(() => false); console.log(` Current Password field: ${hasCurrent ? '✓' : '✗'}`); console.log(` New Password field: ${hasNew ? '✓' : '✗'}`); console.log(` Confirm Password field: ${hasConfirm ? '✓' : '✗'}`); }); test('12. Tab Account — 2FA section present', async ({ page }) => { await navigateTo(page, '/settings'); // TwoFactorSettings renders "Two-Factor Authentication (2FA)" title const twoFactorTitle = page.getByText('Two-Factor Authentication (2FA)'); const visible = await twoFactorTitle.isVisible().catch(() => false); console.log(` 2FA section: ${visible ? '✓' : '✗'}`); // Should show either "2FA is enabled" or "2FA is not enabled" const statusText = page.getByText(/2FA is (enabled|not enabled)/); const hasStatus = await statusText.first().isVisible().catch(() => false); console.log(` 2FA status displayed: ${hasStatus ? '✓' : '✗'}`); }); test('13. Tab Account — data export button (GDPR)', async ({ page }) => { await navigateTo(page, '/settings'); // AccountSettingsExportCard renders "Data Export" title and "Export My Data" button const exportTitle = page.getByText('Data Export', { exact: true }); const hasTitleVisible = await exportTitle.first().isVisible().catch(() => false); console.log(` "Data Export" section: ${hasTitleVisible ? '✓' : '✗'}`); const exportBtn = page.getByRole('button', { name: /export my data/i }); const hasBtn = await exportBtn.isVisible().catch(() => false); console.log(` "Export My Data" button: ${hasBtn ? '✓' : '✗'}`); }); test('14. Tab Account — delete account button with warning', async ({ page }) => { await navigateTo(page, '/settings'); // AccountSettingsDeleteCard renders "Delete Account" title const deleteTitle = page.getByText('Delete Account').first(); const hasTitle = await deleteTitle.isVisible().catch(() => false); console.log(` "Delete Account" section: ${hasTitle ? '✓' : '✗'}`); // Warning text: "This action cannot be undone" const warningText = page.getByText(/this action cannot be undone/i); const hasWarning = await warningText.first().isVisible().catch(() => false); console.log(` Warning text present: ${hasWarning ? '✓' : '✗'}`); // Delete button (we do NOT click it) const deleteBtn = page.getByRole('button', { name: /delete account/i }); const hasBtnVisible = await deleteBtn.isVisible().catch(() => false); console.log(` "Delete Account" button: ${hasBtnVisible ? '✓' : '✗'}`); }); test('15. Tab Preferences — theme radio group', async ({ page }) => { await navigateTo(page, '/settings'); // Click the Preferences tab — may be "Préférences" or "Preferences" const prefsTab = page.getByRole('tab', { name: /pr[ée]f[ée]rences|preferences/i }).first(); if (!(await prefsTab.isVisible({ timeout: 3000 }).catch(() => false))) { console.log(' ⚠ Preferences tab not found — skipping'); return; } await prefsTab.click({ timeout: 3000 }).catch(() => { console.log(' ⚠ Could not click Preferences tab — skipping'); }); await page.waitForTimeout(500); // PreferenceSettings has a RadioGroup for theme with items: light, dark, auto const themeLight = page.locator('#theme-light'); const themeDark = page.locator('#theme-dark'); const themeAuto = page.locator('#theme-auto'); const hasLight = await themeLight.isVisible().catch(() => false); const hasDark = await themeDark.isVisible().catch(() => false); const hasAuto = await themeAuto.isVisible().catch(() => false); console.log(` Theme light radio: ${hasLight ? '✓' : '✗'}`); console.log(` Theme dark radio: ${hasDark ? '✓' : '✗'}`); console.log(` Theme auto radio: ${hasAuto ? '✓' : '✗'}`); }); test('16. Tab Preferences — language selector', async ({ page }) => { await navigateTo(page, '/settings'); const prefsTab = page.getByRole('tab', { name: /pr[ée]f[ée]rences|preferences/i }).first(); if (!(await prefsTab.isVisible({ timeout: 3000 }).catch(() => false))) { console.log(' ⚠ Preferences tab not found — skipping'); return; } await prefsTab.click({ timeout: 3000 }).catch(() => { console.log(' ⚠ Could not click Preferences tab — skipping'); }); await page.waitForTimeout(500); // PreferenceSettings has a Select with name="language" const langSelect = page.locator('[name="language"]') .or(page.locator('select[name="language"]')); const visible = await langSelect.first().isVisible().catch(() => false); console.log(` Language selector: ${visible ? '✓' : '✗'}`); }); test('17. Tab Privacy — confidentiality settings', async ({ page }) => { await navigateTo(page, '/settings'); // Click the Confidentialite/Privacy tab const privacyTab = page.getByRole('tab', { name: /confidentialit[ée]|privacy/i }).first(); if (!(await privacyTab.isVisible({ timeout: 3000 }).catch(() => false))) { console.log(' ⚠ Privacy tab not found — skipping'); return; } await privacyTab.click({ timeout: 3000 }).catch(() => { console.log(' ⚠ Could not click Privacy tab — skipping'); }); await page.waitForTimeout(500); // PrivacySettings and ProfileVisibilityCard should render const body = await page.textContent('body') || ''; const hasPrivacyContent = /profil|privacy|visibility|visibilit/i.test(body); console.log(` Privacy content loaded: ${hasPrivacyContent ? '✓' : '✗'}`); }); test('18. Tab Playback — audio quality and crossfade', async ({ page }) => { await navigateTo(page, '/settings'); // Click the Playback tab const playbackTab = page.getByRole('tab', { name: /playback|lecture/i }).first(); if (!(await playbackTab.isVisible({ timeout: 3000 }).catch(() => false))) { console.log(' ⚠ Playback tab not found — skipping'); return; } await playbackTab.click({ timeout: 3000 }).catch(() => { console.log(' ⚠ Could not click Playback tab — skipping'); }); await page.waitForTimeout(500); const body = await page.textContent('body') || ''; const hasPlaybackContent = /quality|crossfade|autoplay|volume/i.test(body); console.log(` Playback settings loaded: ${hasPlaybackContent ? '✓' : '✗'}`); }); test('19. Save Config button visible', async ({ page }) => { await navigateTo(page, '/settings'); // SettingsPage renders a "Save Config" button const saveBtn = page.getByRole('button', { name: /save config/i }); const visible = await saveBtn.isVisible().catch(() => false); console.log(` "Save Config" button: ${visible ? '✓' : '✗'}`); }); });