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>
121 lines
4.4 KiB
TypeScript
121 lines
4.4 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { loginViaAPI, navigateTo } from '../helpers';
|
|
import { TEST_USERS, ROUTES } from '../design-tokens';
|
|
|
|
test.describe('LISIBILITÉ — Taille de texte minimale et line-height', () => {
|
|
// Public pages
|
|
for (const route of ROUTES.public) {
|
|
test(`[PUBLIC] ${route.name} — aucun texte plus petit que 11px`, async ({ page }) => {
|
|
await navigateTo(page, route.path);
|
|
|
|
const tooSmall = await findTinyText(page);
|
|
|
|
for (const issue of tooSmall) {
|
|
console.log(`[TEXT SIZE] ${issue}`);
|
|
}
|
|
|
|
expect(tooSmall.length,
|
|
`Texte trop petit sur ${route.path}:\n${tooSmall.join('\n')}`
|
|
).toBe(0);
|
|
});
|
|
}
|
|
|
|
// Protected pages
|
|
for (const route of ROUTES.listener.slice(0, 10)) {
|
|
test(`[PROTECTED] ${route.name} — aucun texte plus petit que 11px`, async ({ page }) => {
|
|
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
|
await navigateTo(page, route.path);
|
|
|
|
const tooSmall = await findTinyText(page);
|
|
|
|
for (const issue of tooSmall) {
|
|
console.log(`[TEXT SIZE] ${issue}`);
|
|
}
|
|
|
|
expect(tooSmall.length,
|
|
`Texte trop petit sur ${route.path}:\n${tooSmall.join('\n')}`
|
|
).toBe(0);
|
|
});
|
|
|
|
test(`[PROTECTED] ${route.name} — line-height suffisant sur les blocs de texte`, async ({ page }) => {
|
|
await loginViaAPI(page, TEST_USERS.listener.email, TEST_USERS.listener.password);
|
|
await navigateTo(page, route.path);
|
|
|
|
const tightText = await page.evaluate(() => {
|
|
const issues: string[] = [];
|
|
const seen = new Set<string>();
|
|
|
|
document.querySelectorAll('p, li, td, dd').forEach(el => {
|
|
const text = el.textContent?.trim();
|
|
if (!text || text.length < 20) return;
|
|
const style = getComputedStyle(el);
|
|
if (style.display === 'none') return;
|
|
|
|
const fontSize = parseFloat(style.fontSize);
|
|
const lineHeight = parseFloat(style.lineHeight);
|
|
if (isNaN(lineHeight) || isNaN(fontSize) || fontSize === 0) return;
|
|
const ratio = lineHeight / fontSize;
|
|
|
|
if (ratio < 1.3 && ratio > 0) {
|
|
const className = (typeof el.className === 'string' ? el.className : '').split(' ')[0] || '';
|
|
const key = `${el.tagName}.${className}`;
|
|
if (seen.has(key)) return;
|
|
seen.add(key);
|
|
|
|
const selector = `${el.tagName.toLowerCase()}.${className}`;
|
|
issues.push(
|
|
`ÉLÉMENT: ${selector} | PAGE: ${location.pathname} | MESURÉ: line-height ${ratio.toFixed(2)} (${Math.round(lineHeight)}px / ${Math.round(fontSize)}px) | ATTENDU: >= 1.3 | FIX TAILWIND: Ajouter leading-normal (1.5) ou leading-relaxed (1.625) sur ${selector}`
|
|
);
|
|
}
|
|
});
|
|
|
|
return issues.slice(0, 10);
|
|
});
|
|
|
|
for (const issue of tightText) {
|
|
console.log(`[LINE HEIGHT] ${issue}`);
|
|
}
|
|
|
|
expect(tightText.length,
|
|
`Line-height insuffisant sur ${route.path}:\n${tightText.join('\n')}`
|
|
).toBe(0);
|
|
});
|
|
}
|
|
});
|
|
|
|
async function findTinyText(page: import('@playwright/test').Page): Promise<string[]> {
|
|
return page.evaluate(() => {
|
|
const issues: string[] = [];
|
|
const seen = new Set<string>();
|
|
|
|
document.querySelectorAll('*').forEach(el => {
|
|
const text = el.textContent?.trim();
|
|
if (!text || text.length < 2) return;
|
|
// Only leaf text nodes
|
|
if (el.children.length > 0 && el.children[0].textContent?.trim() === text) return;
|
|
|
|
const style = getComputedStyle(el);
|
|
if (style.display === 'none' || style.visibility === 'hidden') return;
|
|
|
|
const size = parseFloat(style.fontSize);
|
|
if (size >= 11 || size === 0) return;
|
|
|
|
// Skip sr-only elements
|
|
if (el.classList.contains('sr-only')) return;
|
|
const rect = el.getBoundingClientRect();
|
|
if (rect.width === 0 || rect.height === 0) return;
|
|
|
|
const className = (typeof el.className === 'string' ? el.className : '').split(' ')[0] || '';
|
|
const key = `${el.tagName}.${className}`;
|
|
if (seen.has(key)) return;
|
|
seen.add(key);
|
|
|
|
const selector = `${el.tagName.toLowerCase()}.${className}`;
|
|
issues.push(
|
|
`ÉLÉMENT: ${selector} "${text.slice(0, 20)}" | PAGE: ${location.pathname} | MESURÉ: ${size}px | ATTENDU: >= 11px | FIX TAILWIND: Remplacer text-[${size}px] par text-xs (12px) sur ${selector}`
|
|
);
|
|
});
|
|
|
|
return issues.slice(0, 10);
|
|
});
|
|
}
|