import { test, expect } from '@playwright/test'; import { CONFIG, loginViaAPI, navigateTo } from './helpers'; // =========================================================================== // SEARCH PAGE AUDIT // =========================================================================== test.describe('Search Page Audit (/search)', () => { // ------------------------------------------------------------------------- // Chargement & Rendu // ------------------------------------------------------------------------- test.describe('Chargement & Rendu', () => { test('01. la page /search se charge sans crash', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/search'); await expect(page.locator('main')).toBeVisible(); const body = await page.textContent('body'); expect(body).not.toMatch(/500|Internal Server Error/i); }); test('02. le titre du document est mis a jour', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/search'); await expect(page).toHaveTitle(/Search/i); }); test('03. etat decouverte affiche les cards de navigation', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/search'); await expect(page.getByRole('heading', { name: /new releases/i })).toBeVisible(); await expect(page.getByRole('heading', { name: /curated mixes/i })).toBeVisible(); await expect(page.getByRole('heading', { name: /explore artists/i })).toBeVisible(); }); test('04. recherche avec resultats affiche les onglets', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/search?q=luna'); // Wait for results to load await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 }); await expect(page.getByRole('tab', { name: /tracks/i })).toBeVisible(); await expect(page.getByRole('tab', { name: /artists/i })).toBeVisible(); await expect(page.getByRole('tab', { name: /playlists/i })).toBeVisible(); }); test('05. recherche sans resultats affiche empty state', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/search?q=zzzznonexistent999'); await expect(page.getByRole('heading', { name: /no results found/i })).toBeVisible({ timeout: 10_000 }); }); }); // ------------------------------------------------------------------------- // Fonctionnalites // ------------------------------------------------------------------------- test.describe('Fonctionnalites', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('06. la recherche fonctionne avec debounce', async ({ page }) => { await navigateTo(page, '/search'); const input = page.getByRole('combobox', { name: /search/i }); await input.fill('luna'); // Wait for debounce (500ms) + API response await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 }); }); test('07. le bouton clear efface la recherche', async ({ page }) => { await navigateTo(page, '/search?q=luna'); // Wait for results await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 }); // Click clear await page.getByRole('button', { name: /clear search/i }).click(); // Input should be empty const input = page.getByRole('combobox', { name: /search/i }); await expect(input).toHaveValue(''); // Discovery cards should reappear await expect(page.getByRole('heading', { name: /new releases/i })).toBeVisible({ timeout: 5_000 }); }); test('08. clic sur un resultat artiste navigue vers le profil', async ({ page }) => { await navigateTo(page, '/search?q=luna'); // Wait for artist results await expect(page.getByRole('tab', { name: /artists/i })).toBeVisible({ timeout: 10_000 }); // Click first artist link const firstArtistLink = page.getByRole('link', { name: /luna/ }).first(); await firstArtistLink.click(); // Should navigate to profile await page.waitForURL(/\/u\//, { timeout: 10_000 }); expect(page.url()).toContain('/u/'); }); test('09. onglets filtrent les resultats', async ({ page }) => { await navigateTo(page, '/search?q=luna'); await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 }); // Click Artists tab await page.getByRole('tab', { name: /artists/i }).click(); await expect(page.getByRole('tabpanel', { name: /artists/i })).toBeVisible(); // Click Playlists tab await page.getByRole('tab', { name: /playlists/i }).click(); await expect(page.getByRole('tabpanel', { name: /playlists/i })).toBeVisible(); }); test('10. URL params ?q= pre-remplissent la recherche', async ({ page }) => { await navigateTo(page, '/search?q=luna'); const input = page.getByRole('combobox', { name: /search/i }); await expect(input).toHaveValue('luna'); }); test('11. historique de recherche apparait', async ({ page }) => { // First search to create history await navigateTo(page, '/search'); const input = page.getByRole('combobox', { name: /search/i }); await input.fill('testhistory'); await page.waitForTimeout(1000); // wait for debounce + history save // Clear and reload await navigateTo(page, '/search'); // History should show const historyButton = page.getByRole('button', { name: 'testhistory' }); // History might or might not appear depending on timing if (await historyButton.isVisible().catch(() => false)) { await historyButton.click(); await expect(input).toHaveValue('testhistory'); } }); }); // ------------------------------------------------------------------------- // Securite // ------------------------------------------------------------------------- test.describe('Securite', () => { test('12. /search est protege par authentification', async ({ page }) => { await page.goto(`${CONFIG.baseURL}/search`); await page.waitForURL(/\/(login|search)/, { timeout: CONFIG.timeouts.navigation }); const url = page.url(); if (!url.includes('/search')) { expect(url).toContain('/login'); } }); test('13. pas de token ou email dans l\'URL de recherche', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/search?q=luna'); const url = page.url(); expect(url).not.toMatch(/token|access_token|email|password/i); }); }); // ------------------------------------------------------------------------- // Accessibilite // ------------------------------------------------------------------------- test.describe('Accessibilite', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('14. search input est un combobox avec aria-controls', async ({ page }) => { await navigateTo(page, '/search'); const input = page.getByRole('combobox', { name: /search/i }); await expect(input).toBeVisible(); const controls = await input.getAttribute('aria-controls'); expect(controls).toBeTruthy(); }); test('15. discovery cards sont accessibles au clavier', async ({ page }) => { await navigateTo(page, '/search'); const card = page.getByRole('link', { name: /new releases/i }); await expect(card).toBeVisible(); await card.focus(); await expect(card).toBeFocused(); }); test('16. HelpText dit "Help:" et pas "Aide:"', async ({ page }) => { await navigateTo(page, '/search'); const helpSpan = page.locator('[aria-label^="Help:"]'); await expect(helpSpan).toBeVisible(); // Should NOT have French prefix const aidePrefixed = page.locator('[aria-label^="Aide:"]'); await expect(aidePrefixed).toHaveCount(0); }); test('17. resultat cards ont role="link"', async ({ page }) => { await navigateTo(page, '/search?q=luna'); await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 }); const links = page.getByRole('link', { name: /luna/ }); const count = await links.count(); expect(count).toBeGreaterThan(0); }); }); // ------------------------------------------------------------------------- // i18n // ------------------------------------------------------------------------- test.describe('i18n', () => { test('18. pas de cles i18n brutes', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/search'); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/\bsearch\.\w+/); expect(body).not.toMatch(/\bcommon\.\w+/); }); test('19. pas de "Trending" ou "Top Artists" (ORIGIN compliance)', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/search'); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/Trending creators/i); // "Top Artists" should be replaced by "Explore Artists" expect(body).not.toMatch(/\bTop Artists\b/); }); test('20. pas de melange FR/EN', async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); await navigateTo(page, '/search'); const body = await page.textContent('body') || ''; expect(body).not.toMatch(/\bRecherche\b/); expect(body).not.toMatch(/\bEffacer\b/); }); }); // ------------------------------------------------------------------------- // Responsive // ------------------------------------------------------------------------- test.describe('Responsive', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('21. mobile 375px: page se charge correctement', async ({ page }) => { await page.setViewportSize({ width: 375, height: 812 }); await navigateTo(page, '/search'); await expect(page.getByRole('combobox', { name: /search/i })).toBeVisible(); await expect(page.getByRole('heading', { name: /search/i })).toBeVisible(); }); test('22. tablet 768px: resultats affichent correctement', async ({ page }) => { await page.setViewportSize({ width: 768, height: 1024 }); await navigateTo(page, '/search?q=luna'); await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 }); }); }); // ------------------------------------------------------------------------- // Regression // ------------------------------------------------------------------------- test.describe('Regression', () => { test.beforeEach(async ({ page }) => { await loginViaAPI(page, CONFIG.users.listener.email, CONFIG.users.listener.password); }); test('23. BUG#1: recherche "luna" ne crash plus', async ({ page }) => { await navigateTo(page, '/search?q=luna'); // Should NOT show error boundary await expect(page.getByText(/Failed to load/i)).not.toBeVisible({ timeout: 5_000 }); await expect(page.getByText(/Cannot read properties/i)).not.toBeVisible(); // Should show results await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 }); }); test('24. BUG#2: clear search fonctionne correctement', async ({ page }) => { await navigateTo(page, '/search'); const input = page.getByRole('combobox', { name: /search/i }); await input.fill('luna'); // Wait for results to appear await expect(page.getByRole('tab', { name: /all results/i })).toBeVisible({ timeout: 10_000 }); // Click clear await page.getByRole('button', { name: /clear search/i }).click(); // Input should be empty and discovery should return await expect(input).toHaveValue(''); await expect(page.getByRole('heading', { name: /new releases/i })).toBeVisible({ timeout: 5_000 }); }); test('25. BUG#5: pas de contenu "trending" dans discovery', async ({ page }) => { await navigateTo(page, '/search'); // Should say "Explore Artists" not "Top Artists" await expect(page.getByRole('heading', { name: /explore artists/i })).toBeVisible(); // "Trending creators" text should not appear const body = await page.textContent('body') || ''; expect(body).not.toMatch(/Trending creators/i); }); test('26. BUG#9: HelpText prefix is "Help:" not "Aide:"', async ({ page }) => { await navigateTo(page, '/search'); const helpElement = page.locator('[aria-label^="Help:"]'); await expect(helpElement).toBeVisible(); }); }); });