Uso con Puppeteer

Puppeteer es una librería de Node.js para controlar Chrome/Chromium en modo headless o completo mediante el protocolo DevTools. Se utiliza para automatizar navegadores y ejecutar scripts. A menudo se emplea en pruebas, aunque no es en sí mismo un framework de pruebas. surea11y se integra mediante la integración (binding) @surea11y/puppeteer.

$ npm install @surea11y/core @surea11y/puppeteer puppeteer

Puppeteer descarga su propio Chrome incluido como parte de npm install: a diferencia de Playwright, no hay un paso separado de instalación del navegador.

const puppeteer = require('puppeteer');
const { A11yCoreBuilder } = require('@surea11y/puppeteer');
 
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com/');
 
const results = await new A11yCoreBuilder({ page })
.include('#main') // optional -- call multiple times for multi-region scans
.exclude('.cookie-banner') // optional
.withWcag('2.2', 'AA') // the rules for WCAG 2.2's A and AA criteria
.disableRules(['meta-refresh-no-exceptions'])
.analyze();
 
console.log(results.checksResults.filter(r => r.outcome === 'fail'));
await browser.close();

.withWcag(version, level) selecciona por objetivo de conformidad: las reglas de los criterios de nivel A y AA de WCAG 2.2, según el nivel que cada criterio tiene en esa versión. .withBestPractices() añade las reglas que no comprueban ningún criterio de WCAG, como region; por sí solo ejecuta solo esas.

.withPacks() registra un pack en la página antes del análisis, y .options({ profile }) elige su perfil. Un pack que no se ejecuta se informa como una carencia del análisis de tipo 'pack-skipped', de modo que una prueba que comprueba getScanGaps(results) falla en lugar de pasar sin él.

Componentes web. Un componente que guarda su ElementInternals en un campo privado, y se da un nombre, fija sus estados o se asigna un rol a través de él, no puede leerse desde ningún script de la página. Por defecto, el análisis pregunta por ese componente (cantTell) en lugar de juzgarlo, y un rol asignado solo de esa forma no se comprueba en absoluto. Si construyes o pruebas una biblioteca de componentes, instala el hook de internals opcional de la integración antes de que se ejecuten los scripts de la propia página, y esos componentes se juzgan como cualquier otro control:

const page = await browser.newPage();
await A11yCoreBuilder.installInternalsHook(page); // before goto
await page.goto('https://example.com/');

Llámalo antes de page.goto(). Se ejecuta mediante page.evaluateOnNewDocument(), así que una sola llamada cubre los frames y las navegaciones de la página durante toda su vida, en Chrome y Firefox; una pestaña nueva que abra la propia página no lo recibe.

El hook está pensado para ejecuciones de pruebas y auditorías: guarda lo que registra donde cualquier script de la página puede leerlo, y por eso es opcional. Sin él, el análisis avisa una vez cuando ha preguntado por esos componentes. La sección del README sobre el hook de internals tiene los detalles.

Manejadores de clic y regiones vivas. Desde core 1.14 hay dos hooks opcionales más. La mayoría de las páginas añaden sus manejadores de clic desde un script, donde ningún script de la página puede enumerarlos después, así que el análisis no ve que un <div role="button"> con un listener de clic no hace nada desde el teclado; con el hook de listeners, ese botón falla (CLICK_HANDLER_NO_KEY_LISTENER). El hook de regiones vivas registra lo que reciben las regiones vivas de la página: el análisis pregunta entonces por un mensaje de estado añadido con su texto ya dentro, que los lectores de pantalla no suelen anunciar, y announcements() devuelve lo que la página puso a disposición para anunciar, para que una prueba compruebe que una acción le dijo algo al usuario. Instálalos antes de que se ejecuten los scripts de la propia página, como el hook de internals:

const page = await browser.newPage();
await A11yCoreBuilder.installListenerHook(page); // before goto
await A11yCoreBuilder.installLiveRegionHook(page);
await page.goto('https://example.com/settings');
await page.click('#save');
 
const said = await A11yCoreBuilder.announcements(page);
// [{ text: 'Saved', politeness: 'polite', ..., selector: '#status' }]

Ambos se instalan como el hook de internals, mediante page.evaluateOnNewDocument(), antes de page.goto().

Como el hook de internals, ambos son para ejecuciones de pruebas y auditorías: cualquier script de la página puede leer lo que registran. announcements() es lo que la página puso a disposición para anunciar, no lo que dijo un lector de pantalla, y es null sin el hook de regiones vivas. Sin alguno de los dos hooks, getScanNotes() avisa cuando se juzgaron sin él hallazgos que el hook habría decidido. Las secciones del README sobre el hook de listeners y el hook de regiones vivas tienen los detalles.

Varios perfiles a la vez. Con core 1.15 (binding 1.6.0), .options({ profile }) acepta también una lista, el perfil de un pack junto al de WCAG o al de otro pack: .withPacks(waiAria).options({ profile: ['wcag22-aa', 'wai-aria-1.2'] }) juzga la página con WCAG y con WAI-ARIA en un solo análisis. Una regla que ejecutan ambos es una sola comprobación: su resultado es el peor, y sus verdicts dicen qué hace de ella cada perfil (ver Opciones del motor). Un perfil que no se ejecutó figura en results.engine.profilesNotRun, y getScanGaps() lo informa como 'profile-not-run'.

Frames, leídos como los lee el navegador. Desde core 1.15, un documento en un frame no es una página propia, así que sus reglas de título, idioma, viewport y regiones de referencia son notApplicable; un frame bajo aria-hidden o inert se lee como tal, uno tras un diálogo modal abierto se deja fuera, y uno en un shadow root se lee a través de su host. Y donde solo se incumple WAI-ARIA o el modelo de contenido de HTML, las reglas de estructura ARIA y HTML ahora preguntan en lugar de fallar (qué cambió); los hallazgos conservan sus códigos de motivo, así que los baselines siguen coincidiendo. Un hallazgo de text-spacing-content-loss también se muestra en imagen con el espaciado aplicado, con .screenshots().

Modo oscuro, anchos de móvil y un umbral de gravedad. .conditions([{ colorScheme: 'dark' }, { width: 320 }]) analiza la página tal como está y de nuevo bajo cada condición, y devuelve un único resultado: el resultado de una regla es el peor de todos los análisis, y cada hallazgo indica las condiciones en las que se vio. Cada condición es un análisis completo, y .frames(true) todavía no se puede combinar con ella. En Firefox los anchos funcionan, pero un esquema de color necesita el protocolo DevTools, que Puppeteer solo tiene en Chrome, así que allí .analyze() se rechaza antes de analizar nada. getGatingFailures(results, { failOnSeverity: 'serious' }) devuelve las reglas que fallan con esa gravedad o más, para que una prueba bloquee primero los problemas graves y críticos mientras el resto se sigue informando; formatFailures() admite la misma opción.

Preguntas con tendencia, y sus pasos. Desde core 1.14, un hallazgo cantTell puede tener una tendencia hacia fail o pass, explicando por qué, y lleva howToCheck, los pasos que sigue una persona para responderlo. formatFailures() enumera los probables fallos justo después de los fallos, e imprime los pasos con { howToCheck: true }. Para que una prueba falle también con los probables fallos, añade al umbral leaningFailures(results) de core.

A11yCoreBuilder también tiene .wcagLevel(), que limita los criterios del informe a un nivel de WCAG sin cambiar qué reglas se ejecutan; .includeMode('or'), que ejecuta las reglas de una etiqueta más las reglas que nombres; .plannedChecks() y .plannedCriteria(), que enumeran lo que comprobará un análisis antes de ejecutarlo, para que una selección que no ejecuta nada no pase por un análisis limpio; .logger(), que envía los avisos de carencias del análisis a tu propio log; .fetchStyleSheets(false), que evita que .analyze() descargue las hojas de estilo de otros orígenes que la página no puede leer por sí misma, para las reglas que leen el CSS; y getScanNotes(results), que dice cómo se analizó la página. El README de la integración explica cada uno.

Consulta también un ejemplo ejecutable en el propio repositorio del paquete.

Siguiente: elige qué reglas se ejecutan, integra el análisis en CI y consulta la estructura completa del resultado.