Saltar al contenido
Alea
Pro
  1. Alea
  2. Desarrollo
  3. Auditoría de calidad

Empezar

Auditoría de calidad

One config-driven audit for Alea and the apps built on it: axe, overflow, touch targets, focus, reduced motion, a screen-reader simulation and CSS guardrails, across viewports, themes and text sizes.

@alea-design/audit ejecuta tu app en un navegador real una vez por cada combinación de ancho, tema, tamaño de texto, idioma, puntero y ajuste de movimiento, y revisa cada ruta en todas. Es el núcleo común de los scripts de auditoría que Alea, Heliodore, Spinelle y Grenat escribieron por separado.

Instalar

El paquete no incluye un navegador. Maneja el Google Chrome del equipo (o el que indique CHROME_PATH) mediante playwright-core, y lee axe-core desde tu proyecto. Ambos son dependencias opcionales: la auditoría dice qué instalar cuando falta una.

npm i -D @alea-design/audit playwright-core axe-core
npx alea-audit init # writes alea-audit.config.mjs and a GitHub Actions workflow (one job, weekly)
npx alea-audit # npx alea-audit list shows the checks

Con la CLI de Alea instalada, alea audit hace lo mismo y dice qué instalar cuando falta el paquete.

Configuración

Un solo archivo declara la app, las rutas, la matriz y las comprobaciones. La lógica queda en el paquete; el archivo son datos y unos pocos ganchos.

// alea-audit.config.mjs
import { defineConfig } from '@alea-design/audit';
export default defineConfig({
server: { command: 'npm run build && npx vite preview --port {port} --strictPort', url: 'http://127.0.0.1:{port}' },
routes: ['/', '/pricing', { path: '/settings', setup: async ({ page }) => { await page.getByRole('button', { name: 'Load sample data' }).click(); } }],
viewports: [320, 375, 768, 1024, 1280, 1920],
themes: ['light', 'dark'],
theme: { attribute: 'data-theme', storageKey: 'my-app-theme' },
textSizes: [100, 150, 200],
locales: ['en', 'es'],
checks: {
overflow: { scrollRegions: ['.alea-table-wrap'] },
'layout-parity': true,
guardrails: true,
},
guardrails: { files: ['src/**'], ignoreFiles: ['**/tokens.css'], rules: { 'raw-px': true, 'raw-hex': true, 'long-dash': true, 'alea-internals': true } },
ignore: [
{ check: 'axe', rule: 'color-contrast', route: '/legal', reason: 'The third-party widget draws this text; reported upstream.' },
],
});
Clave
Qué hace
baseUrl / serverUna app que ya está corriendo, o cómo arrancarla: un comando con {port} (en cada ejecución se elige un puerto libre, así dos auditorías nunca chocan), o una carpeta de archivos estáticos.
routesUna lista, una función que devuelve una, o sitemap: true. Una ruta puede ser un objeto con su propio setup, navigate o skipChecks. Las rutas con hash, como #/plan, funcionan.
setupSe ejecuta una vez por sesión de navegador antes de las rutas: iniciar sesión, cargar datos de ejemplo, sembrar el almacenamiento.
themeCómo se entera la app de su tema: un atributo en el elemento html, una clave de almacenamiento (para un ThemeProvider que la lee), o ambos. El esquema de color del navegador siempre se aplica.
ignoreHallazgos aceptados. Cada entrada necesita un motivo de al menos una frase, o se rechaza la configuración.
flowsGuiones de acciones con sus propias verificaciones, para lo que una lectura estática no ve: enviar un formulario vacío, quitar una fila.
failOnLa severidad mínima que hace fallar la ejecución: error (por defecto), warning, info o none.

La matriz

La estrategia por defecto, cover, usa cada valor de cada eje al menos una vez y siempre cruza el ancho más estrecho con el texto más grande, lo que son unas pocas decenas de sesiones. Usa matrix: { strategy: "cross" } para el producto completo (los punteros táctiles solo se combinan con anchos hasta 1024).

Eje
Por defecto
Cómo se aplica
viewports320, 375, 768, 1024, 1280, 1920El tamaño de la ventana del navegador.
themeslight, darkprefers-color-scheme, más el atributo o la clave de almacenamiento de la configuración del tema.
textSizes100, 150, 200La preferencia real de tamaño de fuente del navegador, así rem, em y las media queries reaccionan como para una persona lectora. No es zoom de página ni una hoja de estilos.
localesenEl idioma del navegador, más el almacenamiento que la app use para su propio ajuste de idioma.
pointersfine, coarseEl puntero táctil activa la emulación táctil; el mínimo de tamaño de objetivo pasa a 44 px.
motionfull, reducedLa característica de medios prefers-reduced-motion.

Antes de que cualquier comprobación mida, la página tiene que estar quieta: sin indicador de carga, con las fuentes listas y dos pasadas seguidas sin ninguna animación finita en curso. Una pasada tranquila no basta, porque los datos que llegan tarde inician animaciones nuevas y una fila que sale sigue en el documento hasta que termina su animación. Medir antes da contrastes de 1,01 con la mitad de opacidad y botones de 43 px con una escala de 0,98.

Comprobaciones

Check
Qué revisa
axeaxe-core con WCAG 2.0 a 2.2 A y AA más buenas prácticas. Las reglas de página completa se relajan mientras hay un modal abierto. El impacto se convierte en severidad.
overflowLa página que se desplaza de lado, y las áreas con desplazamiento (scrollRegions) que se desplazan pero no son una región con nombre y foco, o se anuncian sin nada visible que desplazar.
touch-targets44 px con puntero táctil, 24 px con puntero fino. Un control más pequeño pasa si una pulsación a la distancia requerida aún cae sobre él (áreas de toque ampliadas).
focusUn recorrido real con Tab: anillo de foco visible, sin tabindex positivo, orden, y todo lo interactivo que Tab nunca alcanza.
consoleErrores y avisos de consola, excepciones no capturadas, peticiones fallidas y errores HTTP.
reduced-motionCon el movimiento reducido activado, no empieza ninguna animación ni transición de más de unos milisegundos.
layout-parityEl diseño ya asentado es el mismo con y sin movimiento. Está apagada hasta que la actives.
unique-namesLos puntos de referencia, los avisos y las regiones con desplazamiento del mismo tipo tienen nombres accesibles distintos.
structureUn h1 visible, un título y un lang que coincide con el idioma que se audita.
clipped-textTexto cortado por una caja que oculta el desborde, sin puntos suspensivos.
screen-readerEl árbol de accesibilidad leído como un lector de pantalla: controles sin nombre, nombres repetidos en una región, regiones vacías, tablas que faltan en la lectura, campos anunciados como no válidos al llegar.
guardrailsUn escaneo estático del código, sin navegador: valores crudos de px, hex y ms, rayas largas en los textos, px en estilos en línea, selectores que se meten en las clases .alea-*, o tus propias reglas.

Las comprobaciones propias van en customChecks: un objeto con un nombre, una descripción y una función run que recibe la página y devuelve hallazgos.

Simulación de lector de pantalla

La simulación lee el árbol de accesibilidad de Chrome y calcula lo que anunciaría un lector: nombre, rol, estado, posición, filas de tabla con sus encabezados, las regiones que se entran y se salen, y las regiones vivas capturadas mientras actúas. Está disponible por separado para pruebas de extremo a extremo.

import { ScreenReader, verifyErrorAnnounced } from '@alea-design/audit/reader';
const reader = await new ScreenReader(page).attach();
await reader.readScreen(); // what the whole screen says, in order
await reader.press('Tab'); // { text: 'Save, button', live: [...] }
const problems = await verifyErrorAnnounced(page, reader, {
field: '#email',
trigger: () => page.keyboard.press('Enter'), // focus is already in the field
});
console.log(reader.toText(), reader.issues, problems);

Límites conocidos, no hallazgos: los segmentos de los campos de fecha siguen el idioma del navegador; no hay comportamiento de puntuación ni de siglas por lector; los guiones no separables y los saldos negativos escritos con signo menos necesitan un lector real. No sustituye a VoiceOver ni a NVDA.

Integración continua

Los resultados se guardan por ruta, combinación, compilación y ajustes de las comprobaciones, así que una ejecución sin cambios termina en segundos. Un navegador que se cierra bajo carga se reinicia para esa combinación y nunca es un hallazgo. AUDIT_JOBS o --jobs fija cuántas combinaciones corren a la vez; por defecto sigue la carga de la CPU.

name: Quality audit
on:
workflow_dispatch:
schedule:
- cron: '0 5 * * 1'
# pull_request:
# paths: ['src/**', 'public/**', 'package-lock.json']
concurrency:
group: audit-${{ github.ref }}
cancel-in-progress: true
jobs:
audit:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 22, cache: npm }
- run: npm ci --no-audit --no-fund
- uses: actions/cache@v4
with:
path: .alea-audit/cache
key: alea-audit-${{ hashFiles('package-lock.json', 'src/**') }}
restore-keys: alea-audit-
- run: npx alea-audit run --jobs 2 --json audit.json
- uses: actions/upload-artifact@v4
if: failure()
with: { name: audit, path: '.alea-audit/artifacts' }

Los repositorios privados pagan cada minuto de cada trabajo, y cada trabajo vuelve a instalar el proyecto. Por eso el flujo que escribe init es un solo trabajo que corre a pedido y una vez por semana, no en cada push; el disparador pull_request está como opción comentada para las rutas que cambian la interfaz. Una ejecución nueva cancela la anterior, una ejecución se detiene a los 30 minutos y se guardan en caché la instalación de npm y los resultados. El encabezado del archivo estima los minutos facturados.

Ejecuta npx alea-audit run --plan para contar la matriz y estimar el tiempo antes de añadir un disparador. init --shards 3 divide la matriz en tres trabajos: termina antes pero factura unos dos minutos más por cada fragmento. La auditoría usa el Chrome que ya traen los ejecutores Ubuntu de GitHub, y ejecutarla en tu equipo no cuesta nada.

El código de salida es 0 cuando no se encontró nada en failOn o por encima, 1 cuando sí, y 2 cuando la auditoría misma no pudo ejecutarse. --json escribe el informe completo y las capturas de las pantallas con fallos van a .alea-audit/artifacts.