veza/tests/e2e/audit/pixel-perfect/19-text-readability.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

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