Uso con Cypress
Cypress es un framework de pruebas end-to-end en JavaScript que ejecuta tus pruebas dentro del propio navegador, junto a la aplicación sometida a prueba. surea11y se integra mediante la integración (binding) @surea11y/cypress.
$ npm install @surea11y/core @surea11y/cypress cypress
cypress es una entrada de peerDependencies: necesitas tu propia instalación de Cypress para que los tipos/runtime se resuelvan, igual que al escribir cualquier archivo de prueba de Cypress.
// cypress/e2e/my-page.cy.jsconst { A11yCoreBuilder } = require('@surea11y/cypress');it('has no accessibility violations', () => {cy.visit('https://example.com/');new A11yCoreBuilder().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().then((results) => {const fails = results.checksResults.filter((r) => r.outcome === 'fail');expect(fails).to.have.length(0);});});
.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.
Un pack se prepara en Node, y una prueba de Cypress se ejecuta en el navegador, así que en Cypress .withPacks() recibe el nombre del módulo de cada pack (un nombre de paquete, o una ruta desde la raíz del proyecto), y el plugin del binding, @surea11y/cypress/plugin, lo prepara en Node: regístralo devolviendo surea11y(on, config) desde setupNodeEvents. .options({ profile }) elige el perfil del pack. Un pack que no se ejecuta se informa como una carencia del análisis de tipo 'pack-skipped'.
Sin argumento de constructor { page }/{ browser }/{ driver } , a diferencia de las integraciones basadas en driver: en Cypress no existe ese tipo de referencia. cy está disponible de forma ambiental en cada archivo de prueba y es el driver. analyze() devuelve un objeto encadenable de Cypress, no una Promise: no le hagas await; llama a .then() como se muestra arriba.
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:
// cypress/support/e2e.jsimport { installInternalsHook } from '@surea11y/cypress';installInternalsHook();
Llamado una vez en el archivo de soporte, se ejecuta en cada visita, a través del evento window:before:load de Cypress. Para una sola visita, pásalo a cy.visit() como { onBeforeLoad: installInternalsHook }. Se ejecuta con el eval de la ventana de la aplicación, lo que funciona porque Cypress elimina por defecto la Content Security Policy de la aplicación, y los frames dentro de la aplicación no lo reciben.
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:
// cypress/support/e2e.jsimport { installListenerHook, installLiveRegionHook } from '@surea11y/cypress';installListenerHook();installLiveRegionHook();// in a speccy.get('#save').click();A11yCoreBuilder.announcements().then((said) => {expect(said.map((a) => a.text)).to.include('Saved');});
Como con el hook de internals, el archivo de soporte cubre todas las visitas, y solo los recibe la ventana de la propia aplicación, no los frames que contiene. announcements() lee la ventana de la aplicación y devuelve un chainable, como .analyze().
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('@surea11y/pack-wai-aria').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. Cypress muestra la aplicación bajo prueba en un frame de su ejecutor, así que .analyze() le pasa engineOptions.topLevelDocument: true, y la aplicación se sigue juzgando como la página; los frames que contiene se leen como frames. 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. Un ancho pasa por cy.viewport(); un esquema de color necesita un navegador de la familia Chromium (Chrome, Edge, Electron), y en Firefox .analyze() falla 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. En Cypress los pasos vienen del plugin: setupNodeEvents tiene que devolver lo que devuelve surea11y(on, config). Sin eso, el resultado no tiene pasos, y no falla nada.
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; .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.