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>
110 lines
4.8 KiB
TypeScript
110 lines
4.8 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
||
import { loginViaAPI, navigateTo } from '../helpers';
|
||
import { TEST_USERS, ROUTES } from '../design-tokens';
|
||
|
||
test.describe('IMAGES — Ratio correct, fallback, pas de distorsion', () => {
|
||
// Distortion check on protected pages
|
||
for (const route of ROUTES.listener.slice(0, 10)) {
|
||
test(`[PROTECTED] ${route.name} — aucune image déformée`, async ({ page }) => {
|
||
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
||
await navigateTo(page, route.path);
|
||
|
||
const distorted = await page.evaluate(() => {
|
||
return Array.from(document.querySelectorAll('img[src]'))
|
||
.filter(img => {
|
||
if (!img.complete || img.naturalWidth === 0) return false;
|
||
if (getComputedStyle(img).objectFit === 'cover' || getComputedStyle(img).objectFit === 'contain') return false;
|
||
const displayRatio = img.clientWidth / img.clientHeight;
|
||
const naturalRatio = img.naturalWidth / img.naturalHeight;
|
||
const distortion = Math.abs(displayRatio - naturalRatio) / naturalRatio;
|
||
return distortion > 0.1;
|
||
})
|
||
.map(img => {
|
||
const src = img.src.split('/').pop() || img.src.slice(-40);
|
||
const testid = img.getAttribute('data-testid');
|
||
const selector = testid ? `[data-testid="${testid}"]` : `img[src*="${src.slice(0, 20)}"]`;
|
||
return {
|
||
src: src.slice(0, 40),
|
||
natural: `${img.naturalWidth}×${img.naturalHeight}`,
|
||
display: `${img.clientWidth}×${img.clientHeight}`,
|
||
fix: `ÉLÉMENT: ${selector} | PAGE: ${location.pathname} | MESURÉ: ratio naturel ${(img.naturalWidth / img.naturalHeight).toFixed(2)} vs affiché ${(img.clientWidth / img.clientHeight).toFixed(2)} | FIX TAILWIND: Ajouter object-cover ou object-contain sur ${selector}`,
|
||
};
|
||
});
|
||
});
|
||
|
||
for (const d of distorted) {
|
||
console.log(`[IMAGE DISTORTED] ${d.fix}`);
|
||
}
|
||
|
||
expect(distorted.length,
|
||
`Images déformées sur ${route.path}:\n${distorted.map(d => `• ${d.fix}`).join('\n')}`
|
||
).toBe(0);
|
||
});
|
||
|
||
test(`[PROTECTED] ${route.name} — toutes les images ont un alt ou aria-label`, async ({ page }) => {
|
||
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
||
await navigateTo(page, route.path);
|
||
|
||
const missingAlt = await page.evaluate(() => {
|
||
return Array.from(document.querySelectorAll('img'))
|
||
.filter(img => {
|
||
if (getComputedStyle(img).display === 'none') return false;
|
||
const alt = img.getAttribute('alt');
|
||
const ariaLabel = img.getAttribute('aria-label');
|
||
const ariaHidden = img.getAttribute('aria-hidden');
|
||
const role = img.getAttribute('role');
|
||
const isDecorative = alt === '' || role === 'presentation' || ariaHidden === 'true';
|
||
return alt === null && !ariaLabel && !isDecorative;
|
||
})
|
||
.map(img => {
|
||
const src = img.src?.split('/').pop()?.slice(0, 30) || '';
|
||
return `ÉLÉMENT: img[src*="${src}"] | PAGE: ${location.pathname} | FIX TAILWIND: Ajouter alt="description" ou alt="" si décorative, ou aria-hidden="true"`;
|
||
})
|
||
.slice(0, 10);
|
||
});
|
||
|
||
for (const issue of missingAlt) {
|
||
console.log(`[IMAGE ALT] ${issue}`);
|
||
}
|
||
|
||
expect(missingAlt.length,
|
||
`Images sans alt sur ${route.path}:\n${missingAlt.join('\n')}`
|
||
).toBe(0);
|
||
});
|
||
}
|
||
|
||
// Public pages
|
||
for (const route of ROUTES.public) {
|
||
test(`[PUBLIC] ${route.name} — aucune image déformée ni sans alt`, async ({ page }) => {
|
||
await navigateTo(page, route.path);
|
||
|
||
const issues = await page.evaluate(() => {
|
||
const problems: string[] = [];
|
||
|
||
document.querySelectorAll('img').forEach(img => {
|
||
if (getComputedStyle(img).display === 'none') return;
|
||
|
||
// Check missing alt
|
||
const alt = img.getAttribute('alt');
|
||
const isDecorative = alt === '' || img.getAttribute('role') === 'presentation' || img.getAttribute('aria-hidden') === 'true';
|
||
if (alt === null && !img.getAttribute('aria-label') && !isDecorative) {
|
||
const src = img.src?.split('/').pop()?.slice(0, 25) || '';
|
||
problems.push(`img[src*="${src}"] sans alt. FIX: Ajouter alt="" si décorative`);
|
||
}
|
||
|
||
// Check broken
|
||
if (img.src && (!img.complete || img.naturalWidth === 0)) {
|
||
const src = img.src?.split('/').pop()?.slice(0, 25) || '';
|
||
problems.push(`img[src*="${src}"] cassée (ne charge pas). FIX: Vérifier le src ou ajouter un fallback`);
|
||
}
|
||
});
|
||
|
||
return problems.slice(0, 10);
|
||
});
|
||
|
||
expect(issues.length,
|
||
`Problèmes d'images sur ${route.path}:\n${issues.join('\n')}`
|
||
).toBe(0);
|
||
});
|
||
}
|
||
});
|