veza/tests/e2e/audit/pixel-perfect/10-responsive-layout.spec.ts

132 lines
5.3 KiB
TypeScript
Raw Normal View History

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);
}
}
});
});