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>
111 lines
5 KiB
TypeScript
111 lines
5 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
||
import { loginViaAPI, navigateTo, navigateToPageWithTracks, playFirstTrack } from '../helpers';
|
||
import { detectOverlaps } from '../helpers/visual-helpers';
|
||
import { TEST_USERS, ROUTES, ALL_PROTECTED_ROUTES } from '../design-tokens';
|
||
|
||
test.describe('CHEVAUCHEMENTS — Aucun élément interactif ne passe par-dessus un autre', () => {
|
||
// --- Pages publiques (pas de login) ---
|
||
for (const route of ROUTES.public) {
|
||
test(`[PUBLIC] ${route.name} (${route.path}) — zéro chevauchement critique`, async ({ page }) => {
|
||
await navigateTo(page, route.path);
|
||
|
||
const overlaps = await detectOverlaps(page);
|
||
const critical = overlaps.filter(o => o.severity === 'critical');
|
||
|
||
for (const o of overlaps) {
|
||
console.log(`[${o.severity.toUpperCase()}] "${o.elementA.text}" ↔ "${o.elementB.text}" : ${o.overlapX}px × ${o.overlapY}px`);
|
||
console.log(` FIX: ${o.fix}`);
|
||
}
|
||
|
||
expect(critical.length,
|
||
`${critical.length} chevauchement(s) critique(s) sur ${route.path}:\n` +
|
||
critical.map(o => `• "${o.elementA.text}" (${o.elementA.rect.x},${o.elementA.rect.y} ${o.elementA.rect.width}×${o.elementA.rect.height}) ↔ "${o.elementB.text}" (${o.elementB.rect.x},${o.elementB.rect.y} ${o.elementB.rect.width}×${o.elementB.rect.height}) overlap: ${o.overlapX}×${o.overlapY}px → ${o.fix}`).join('\n')
|
||
).toBe(0);
|
||
});
|
||
}
|
||
|
||
// --- Pages protégées (listener) ---
|
||
for (const route of ROUTES.listener) {
|
||
test(`[LISTENER] ${route.name} (${route.path}) — zéro chevauchement critique`, async ({ page }) => {
|
||
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
||
await navigateTo(page, route.path);
|
||
|
||
const overlaps = await detectOverlaps(page);
|
||
const critical = overlaps.filter(o => o.severity === 'critical');
|
||
|
||
for (const o of overlaps) {
|
||
console.log(`[${o.severity.toUpperCase()}] "${o.elementA.text}" ↔ "${o.elementB.text}" : ${o.overlapX}px × ${o.overlapY}px`);
|
||
console.log(` FIX: ${o.fix}`);
|
||
}
|
||
|
||
expect(critical.length,
|
||
`${critical.length} chevauchement(s) critique(s) sur ${route.path}:\n` +
|
||
critical.map(o => `• "${o.elementA.text}" ↔ "${o.elementB.text}" ${o.overlapX}×${o.overlapY}px → ${o.fix}`).join('\n')
|
||
).toBe(0);
|
||
});
|
||
}
|
||
|
||
// --- Player bar ne recouvre pas le contenu ---
|
||
test('Le player bar ne recouvre aucun contenu interactif de la page', async ({ page }) => {
|
||
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
||
|
||
const hasTracks = await navigateToPageWithTracks(page);
|
||
if (!hasTracks) {
|
||
console.log('Pas de tracks disponibles — test player bar non applicable');
|
||
return;
|
||
}
|
||
|
||
await playFirstTrack(page);
|
||
await page.waitForTimeout(2_000);
|
||
|
||
const playerIssues = await page.evaluate(() => {
|
||
const player = document.querySelector('[data-testid="global-player"]');
|
||
if (!player) return ['Player bar non trouvé'];
|
||
|
||
const playerRect = player.getBoundingClientRect();
|
||
const issues: string[] = [];
|
||
|
||
// Vérifier que le main content a assez de padding en bas
|
||
const main = document.querySelector('main, [role="main"]');
|
||
if (main) {
|
||
const mainRect = main.getBoundingClientRect();
|
||
const mainPaddingBottom = parseFloat(getComputedStyle(main).paddingBottom);
|
||
if (mainPaddingBottom < playerRect.height) {
|
||
issues.push(
|
||
`Le main content a padding-bottom=${mainPaddingBottom}px mais le player fait ${Math.round(playerRect.height)}px. ` +
|
||
`Les derniers éléments du contenu seront cachés sous le player. ` +
|
||
`FIX: Ajouter pb-[${Math.ceil(playerRect.height + 16)}px] au conteneur principal.`
|
||
);
|
||
}
|
||
}
|
||
|
||
// Vérifier qu'aucun bouton du contenu n'est sous le player
|
||
document.querySelectorAll('main button, main a, main input').forEach(el => {
|
||
const rect = el.getBoundingClientRect();
|
||
if (rect.width === 0 || rect.height === 0) return;
|
||
if (getComputedStyle(el).display === 'none') return;
|
||
|
||
const overlapY = Math.max(0, Math.min(rect.bottom, playerRect.bottom) - Math.max(rect.top, playerRect.top));
|
||
const overlapX = Math.max(0, Math.min(rect.right, playerRect.right) - Math.max(rect.left, playerRect.left));
|
||
|
||
if (overlapX > 0 && overlapY > 10) {
|
||
const text = el.textContent?.trim().slice(0, 30) || el.getAttribute('aria-label') || '';
|
||
issues.push(
|
||
`"${text}" (${el.tagName.toLowerCase()}) est recouvert par le player bar de ${Math.round(overlapY)}px. ` +
|
||
`Position: y=${Math.round(rect.top)} vs player.top=${Math.round(playerRect.top)}`
|
||
);
|
||
}
|
||
});
|
||
|
||
return issues;
|
||
});
|
||
|
||
for (const issue of playerIssues) {
|
||
console.log(`[PLAYER OVERLAP] ${issue}`);
|
||
}
|
||
|
||
expect(playerIssues.length,
|
||
`Le player bar recouvre du contenu:\n${playerIssues.join('\n')}`
|
||
).toBe(0);
|
||
});
|
||
});
|