veza/tests/e2e/audit/pixel-perfect/09-icons-images.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

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