import { test, expect } from '@chromatic-com/playwright'; import { loginViaAPI, navigateTo } from '../helpers'; import { checkOverflow } from '../helpers/visual-helpers'; import { TEST_USERS, ROUTES, VIEWPORTS } from '../design-tokens'; test.describe('RESPONSIVE — Chaque page × chaque viewport, zéro overflow', () => { const viewportsToTest = [ VIEWPORTS.mobileSE, VIEWPORTS.tablet, VIEWPORTS.laptop, VIEWPORTS.desktop, ] as const; const viewportNames = ['mobileSE (375×667)', 'tablet (768×1024)', 'laptop (1280×720)', 'desktop (1440×900)'] as const; // --- Pages publiques --- for (const route of ROUTES.public) { for (let v = 0; v < viewportsToTest.length; v++) { const viewport = viewportsToTest[v]; const vpName = viewportNames[v]; test(`[PUBLIC] ${route.name} @ ${vpName} — pas de débordement horizontal`, async ({ page }) => { await page.setViewportSize(viewport); await navigateTo(page, route.path); const overflows = await checkOverflow(page); for (const o of overflows) { console.log(`[OVERFLOW] ${route.path} @ ${vpName}: ${o.selector} dépasse de ${o.overflowX}px`); console.log(` FIX: ${o.fix}`); } expect(overflows.length, `${overflows.length} débordement(s) sur ${route.path} @ ${vpName}:\n` + overflows.map(o => `• ${o.selector}: +${o.overflowX}px → ${o.fix}`).join('\n') ).toBe(0); }); } } // --- Pages protégées (sélection) --- const protectedPages = [ ROUTES.listener[0], // Dashboard ROUTES.listener[1], // Feed ROUTES.listener[2], // Discover ROUTES.listener[3], // Library ROUTES.listener[6], // Profile ROUTES.listener[7], // Settings ROUTES.listener[10], // Playlists ROUTES.listener[13], // Marketplace ]; for (const route of protectedPages) { for (let v = 0; v < viewportsToTest.length; v++) { const viewport = viewportsToTest[v]; const vpName = viewportNames[v]; test(`[PROTECTED] ${route.name} @ ${vpName} — pas de débordement horizontal`, async ({ page }) => { await page.setViewportSize(viewport); await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password); await navigateTo(page, route.path); const overflows = await checkOverflow(page); for (const o of overflows) { console.log(`[OVERFLOW] ${route.path} @ ${vpName}: ${o.selector} dépasse de ${o.overflowX}px`); console.log(` FIX: ${o.fix}`); } expect(overflows.length, `${overflows.length} débordement(s) sur ${route.path} @ ${vpName}:\n` + overflows.map(o => `• ${o.selector}: +${o.overflowX}px → ${o.fix}`).join('\n') ).toBe(0); }); } } // --- Vérifications mobiles spécifiques --- test('Mobile — le sidebar est caché par défaut', async ({ page }) => { await page.setViewportSize(VIEWPORTS.mobileSE); await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password); await navigateTo(page, '/dashboard'); const sidebar = page.locator('[data-testid="app-sidebar"]'); // Sur mobile (<1024px), le sidebar devrait être caché ou collapsé const sidebarVisible = await sidebar.isVisible({ timeout: 3_000 }).catch(() => false); if (sidebarVisible) { const box = await sidebar.boundingBox(); if (box && box.width > 100) { console.log(`[MOBILE] Le sidebar est visible et prend ${box.width}px sur mobile — devrait être caché`); expect(box.width, `Le sidebar est trop large sur mobile (${box.width}px). FIX: Cacher avec lg:block.`).toBeLessThan(100); } } }); test('Mobile — le contenu principal utilise toute la largeur', async ({ page }) => { await page.setViewportSize(VIEWPORTS.mobileSE); await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password); await navigateTo(page, '/dashboard'); const mainWidth = await page.evaluate(() => { const main = document.querySelector('main, [role="main"]'); if (!main) return null; const rect = main.getBoundingClientRect(); return { width: Math.round(rect.width), viewportWidth: window.innerWidth }; }); if (mainWidth) { const usagePercent = (mainWidth.width / mainWidth.viewportWidth) * 100; expect(usagePercent, `Le contenu principal n'utilise que ${usagePercent.toFixed(0)}% de la largeur mobile (${mainWidth.width}px / ${mainWidth.viewportWidth}px). FIX: Retirer les margin-left/right sur mobile.` ).toBeGreaterThan(80); } }); test('Mobile — le player bar est visible et accessible', async ({ page }) => { await page.setViewportSize(VIEWPORTS.mobileSE); await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password); await navigateTo(page, '/dashboard'); // Le player bar apparaît quand un track est en lecture // Vérifier qu'il ne déborde pas sur mobile const playerBar = page.locator('[data-testid="global-player"]'); if (await playerBar.isVisible({ timeout: 3_000 }).catch(() => false)) { const box = await playerBar.boundingBox(); if (box) { expect(box.width, `Player bar dépasse du viewport mobile: ${box.width}px > ${VIEWPORTS.mobileSE.width}px`).toBeLessThanOrEqual(VIEWPORTS.mobileSE.width + 2); } } }); });