veza/tests/e2e/audit/pixel-perfect/10-responsive-layout.spec.ts
senke 463ad5386b test: update e2e test suite and add audit tests
Refine auth, player, tracks, playlists, search, workflows, edge cases,
forms, responsive, network errors, error boundary, performance, visual
regression, cross-browser, profile, smoke, storybook, chat, and session
tests. Add audit test suite (accessibility, ethical, functional, design
tokens). Update test helpers and visual snapshots.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 16:06:26 +01:00

131 lines
5.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test, expect } from '@playwright/test';
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);
}
}
});
});