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>
112 lines
4.4 KiB
TypeScript
112 lines
4.4 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { loginViaAPI, navigateTo } from '../helpers';
|
|
import { checkImages } from '../helpers/visual-helpers';
|
|
import { TEST_USERS, ROUTES } from '../design-tokens';
|
|
|
|
test.describe('ICÔNES & IMAGES — Toutes chargées, tailles cohérentes', () => {
|
|
// --- Pages publiques ---
|
|
for (const route of ROUTES.public) {
|
|
test(`[PUBLIC] ${route.name} — aucune image cassée`, async ({ page }) => {
|
|
await navigateTo(page, route.path);
|
|
|
|
const issues = await checkImages(page);
|
|
const broken = issues.filter(i => i.issue.includes('cassée'));
|
|
|
|
for (const issue of broken) {
|
|
console.log(`[IMAGE] ${route.path}: ${issue.issue}`);
|
|
}
|
|
|
|
expect(broken.length,
|
|
`${broken.length} image(s) cassée(s) sur ${route.path}:\n${broken.map(i => i.issue).join('\n')}`
|
|
).toBe(0);
|
|
});
|
|
}
|
|
|
|
// --- Pages protégées ---
|
|
for (const route of ROUTES.listener.slice(0, 10)) {
|
|
test(`[PROTECTED] ${route.name} — aucune image cassée`, async ({ page }) => {
|
|
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
|
await navigateTo(page, route.path);
|
|
|
|
const issues = await checkImages(page);
|
|
const broken = issues.filter(i => i.issue.includes('cassée'));
|
|
|
|
for (const issue of broken) {
|
|
console.log(`[IMAGE] ${route.path}: ${issue.issue}`);
|
|
}
|
|
|
|
expect(broken.length,
|
|
`${broken.length} image(s) cassée(s) sur ${route.path}:\n${broken.map(i => i.issue).join('\n')}`
|
|
).toBe(0);
|
|
});
|
|
}
|
|
|
|
test('Dashboard — les icônes SVG sont de taille cohérente', async ({ page }) => {
|
|
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
const iconSizes = await page.evaluate(() => {
|
|
const svgs = document.querySelectorAll('svg');
|
|
const sizes: Array<{ width: number; height: number; parent: string; context: string }> = [];
|
|
|
|
svgs.forEach(svg => {
|
|
const rect = svg.getBoundingClientRect();
|
|
if (rect.width === 0 || rect.height === 0) return;
|
|
if (getComputedStyle(svg).display === 'none') return;
|
|
|
|
const parentTag = svg.parentElement?.tagName.toLowerCase() || '';
|
|
const parentText = svg.parentElement?.textContent?.trim().slice(0, 20) || '';
|
|
|
|
sizes.push({
|
|
width: Math.round(rect.width),
|
|
height: Math.round(rect.height),
|
|
parent: parentTag,
|
|
context: parentText,
|
|
});
|
|
});
|
|
|
|
return sizes;
|
|
});
|
|
|
|
// Les icônes dans les boutons devraient toutes avoir la même taille (16px, 20px, ou 24px)
|
|
const buttonIcons = iconSizes.filter(i => i.parent === 'button');
|
|
if (buttonIcons.length >= 2) {
|
|
const iconWidths = buttonIcons.map(i => i.width);
|
|
const uniqueWidths = [...new Set(iconWidths)];
|
|
|
|
if (uniqueWidths.length > 3) {
|
|
console.log(`[ICONS] ${uniqueWidths.length} tailles d'icônes différentes dans les boutons: ${uniqueWidths.join(', ')}px`);
|
|
console.log(` FIX: Standardiser les icônes à 16px (w-4), 20px (w-5), ou 24px (w-6)`);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Avatars — ont un fallback quand l\'image ne charge pas', async ({ page }) => {
|
|
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
|
await navigateTo(page, '/dashboard');
|
|
|
|
const avatarStatus = await page.evaluate(() => {
|
|
const avatars = document.querySelectorAll('[class*="avatar"], img[class*="rounded-full"]');
|
|
const results: Array<{ hasImage: boolean; hasFallback: boolean; selector: string }> = [];
|
|
|
|
avatars.forEach(el => {
|
|
const img = el.querySelector('img') || (el.tagName === 'IMG' ? el : null);
|
|
const hasImage = img ? (img as HTMLImageElement).complete && (img as HTMLImageElement).naturalWidth > 0 : false;
|
|
const hasFallback = !!el.querySelector('span, div:not(:empty)') || el.textContent?.trim().length! > 0;
|
|
|
|
results.push({
|
|
hasImage,
|
|
hasFallback: hasImage || hasFallback,
|
|
selector: el.getAttribute('data-testid') || (typeof el.className === 'string' ? el.className : '').slice(0, 30),
|
|
});
|
|
});
|
|
|
|
return results;
|
|
});
|
|
|
|
const missingFallback = avatarStatus.filter(a => !a.hasFallback);
|
|
if (missingFallback.length > 0) {
|
|
console.log(`[AVATAR] ${missingFallback.length} avatars sans fallback (ni image ni initiales)`);
|
|
}
|
|
});
|
|
});
|