Informe JUnit
@surea11y/core/junit genera un resultado de análisis como JUnit XML, el formato de informes de pruebas que los paneles de CI leen de forma nativa: el widget de pruebas de las merge requests de GitLab, la pestaña Tests de Azure DevOps, Jenkins y CircleCI.
const { renderJunitReport } = require('@surea11y/core/junit');const { runDomRulesInPage } = require('@surea11y/core');const result = runDomRulesInPage(url, null, { profile: 'wcag22-aa' }, null);require('fs').writeFileSync('surea11y.junit.xml', renderJunitReport(result));
renderJunitReport(result, options) es una función pura: devuelve el XML como cadena y nunca toca el sistema de archivos. Funciona igual de bien con un resultado guardado antes como JSON, como la salida de surea11y scan <target> --json de @surea11y/cli. Desde la línea de comandos no hace falta llamarla: surea11y scan <target> --junit a11y.junit.xml (CLI 1.1.0 o posterior) escribe el informe por sí mismo, con las opciones predeterminadas más las baselineEntries de su archivo --baseline. Ver Integración con CI para recetas de GitLab y Azure DevOps.
Estructura
Un <testsuite> por criterio de éxito de WCAG, un <testcase> por regla asociada a él. Al analizar esta página con la llamada anterior:
<!doctype html><html lang="en"><head><title>Home</title></head><body><main><h1>Welcome</h1><img src="a.png"></main></body></html>
da:
<?xml version="1.0" encoding="UTF-8"?><testsuites name="surea11y" tests="14" failures="1" errors="0" skipped="4" time="0"><testsuite name="WCAG 1.1.1" tests="1" failures="1" errors="0" skipped="0" time="0"><properties><property name="wcagCriterion" value="1.1.1"/><property name="title" value="Non-text content: text alternatives"/><property name="wcagLevel" value="A"/><property name="criterionOutcome" value="fail"/><property name="engine" value="a11ycore"/><property name="schemaVersion" value="1.0.0"/><property name="wcagVersion" value="2.2"/><property name="profile" value="wcag22-aa"/><property name="layout" value="false"/><property name="locale" value="en"/><property name="url" value="https://example.test/"/></properties><testcase classname="wcag-1.1.1" name="img-alt-present" time="0"><failure type="fail" message="1 failing occurrence: This <img> has no alt attribute.">- This <img> has no alt attribute. Add an alt attribute that says what the image shows, or alt="" if the image is purely decorative.selector: html > body > main > imghtml: <img src="a.png">help: https://www.w3.org/WAI/WCAG22/Understanding/non-text-content.html</failure></testcase></testsuite><!-- 10 more suites trimmed: 13 tests, 4 of them skipped (manual review) --></testsuites>
- Una suite en verde con el nombre de un criterio significa que las reglas que se ejecutaron para él no informaron ningún fallo. No significa que el criterio se comprobara por completo, ni que se cumpla: la mayoría de los criterios tienen partes que ninguna regla cubre (ver Mapeo de conformidad con WCAG). Su propiedad
criterionOutcomelo dice:cantTellcuando las reglas pasaron pero solo comprueban parte del criterio, lo que hoy ocurre con todos los criterios. - Las suites siguen al criterio, porque es lo que la gente sigue, y los testcases siguen a la regla y no a la ocurrencia, así que un defecto repetido cuarenta veces en una página es una sola prueba fallida cuyo cuerpo enumera las cuarenta, y los recuentos de pruebas se mantienen estables entre ejecuciones.
- Los nombres son los mismos en todos los idiomas. Una suite se llama
WCAG <criterion>(WCAG 1.4.3), la suite de las reglas sin criterio asociadoOther checks, un testcase por su id de regla, y suclassnameeswcag-<criterion>uother, sea cual sea elengineOptions.localecon el que se ejecutó el análisis. El historial de pruebas de la CI (GitLab, Jenkins, los generadores de informes de pruebas de GitHub) se basa en estos nombres, así que un análisis leído en francés da las mismas pruebas que uno leído en inglés. El título del criterio, que sí se traduce, es la propiedadtitlede la suite (Contrast: minimum,Contraste (minimum)). El texto propio de los hallazgos (el mensaje y la pista de cada ocurrencia) está en el idioma del análisis; las palabras que el informe añade alrededor están en inglés en todos los idiomas: el recuento con el que empieza unmessage(1 failing occurrence: Cet élément <img> n’a pas d’attribut alt.), las etiquetasselector:,html:yhelp:, y los mensajes de<skipped>. Antes de core 1.12, el nombre de una suite llevaba el título del criterio (WCAG 1.1.1 Non-text content: text alternatives), así que el primer informe después de actualizar muestra cada prueba una vez como nueva en un panel que seguía los nombres antiguos, y su historial vuelve a empezar desde ahí. - Las suites salen de las asociaciones de WCAG de cada regla, no de los compuestos, así que cada regla que se ejecutó se informa aunque se hayan excluido los compuestos. El compuesto, cuando se ejecutó, aporta la propiedad
titlede la suite y la propiedadcriterionOutcome, que cuenta las reglas personalizadas asociadas al criterio igual que las incorporadas. Un criterio comprobado por varios compuestos (4.1.2: su nombre accesible y su validez ARIA) toma el peor de sus resultados, ya que si falla una parte falla el criterio, y el título que comparten sus títulos (Name, role, value). Una regla asociada a dos criterios aparece en ambas suites. Una regla sin criterio asociado va a una suite finalOther checksconclassname="other". - Las suites siguen el nivel y la versión de WCAG del análisis. Un criterio que el análisis dejó fuera de
rulesResultspor ellos no tiene suite: un criterio AAA conengineOptions.wcagLevel: 'AA'(engine.wcagLevel), o 4.1.1 Parsing cuandoincludeRuleIds: ['duplicate-id']se ejecutó con WCAG 2.2 (engine.wcagVersion), que lo eliminó. Un criterio que el análisis sí informa se conserva, como un resultado agregado nombrado por id por encima del nivel, uno comprobado en parte, o 4.1.1 en un análisis 2.2 que lo seleccionó. Una regla asociada solo a criterios que quedan fuera va aOther checks, y sus fallos son<skipped message="Not counted: WCAG 1.4.6 is outside the scan's target, WCAG 2.2 AA; 1 failing occurrence">, con las ocurrencias en<system-out>: la regla se ejecutó y falló, lo que el informe muestra, pero una compilación AA no falla por un criterio AAA. Una regla que no terminó sigue siendo un<error>. Un nivel que da la selección (runOnly.wcag.level, o etiquetas de nivel) también se registra comoengine.wcagLevel, y filtra las suites de la misma forma. Un resultado que no registra niengine.wcagLevelniengine.wcagVersion, como el de un motor anterior, conserva todos los criterios. engine,schemaVersion,wcagVersion,profile,optInRules, las condiciones de renderizado deengine.environment(layouty, en un navegador,viewportcomo1280x720,devicePixelRatio,colorScheme,forcedColorsyfonts),localeyurlde la ejecución se repiten como propiedades en cada suite, cada una solo cuando el resultado la tiene. Un fallo que aparece y desaparece con el ancho del viewport se puede atribuir al ancho que usó la ejecución. El ejemplo anterior es un análisis en jsdom, que no tiene diseño.- Una página analizada con varios anchos de viewport da los mismos nombres de suite y de test con cada ancho, y una vista de tests de CI puede fusionarlos. Escribe un archivo por ancho, y nombra o agrupa los archivos por ancho en la configuración de informes de la CI; la propiedad
viewportregistra qué ancho describe un archivo. - Las suites se ordenan por criterio, numéricamente (1.4.3 antes que 1.4.10), y los testcases por id de regla.
- Las propiedades
en301549nombran la cláusula de EN 301 549 que reformula el criterio, cuando la hay y el análisis pidió cláusulas de EN 301 549 (ver EN 301 549). Cada otro estándar que figura en elstandardsdel resultado añade propiedades con el nombre de su clave, una por cada requisito suyo al que se asocian las reglas de la suite: el elemento de lista de comprobación de un pack que agrupa la regla se indica por su id,<property name="acme" value="acme-images"/>. Un estándar que solo nombra una regla personalizada no añade ninguna propiedad.
Un resultado compacto (engineOptions.output.detail: 'findings') se lee como el resultado completo que representa, así que se informa igual.
Resultados entre frames
Un resultado entre frames (ver Esquema de salida) da primero las suites de la página y después las de cada frame, nombradas por la ruta del frame (Frame #checkout: WCAG 1.1.1, igual en todos los idiomas) y con una propiedad frame que la contiene, y el classname de sus testcases empieza por ella (frame #checkout wcag-1.1.1), para que un fallo en un frame no se fusione con los de la página en una CI que identifica las pruebas por el nombre de la suite o por classname y name. Un frame que no respondió es una suite Frame <path> con un testcase omitido, Not scanned: <error>: el frame no se comprobó, lo que no es un aprobado. Los totales de <testsuites> cuentan todos los frames.
Resultados
| Resultado de la regla | JUnit | Por qué |
|---|---|---|
fail | <failure type="fail">, una línea por ocurrencia fallida (mensaje, pista, selector, marcado), y después help: <url> cuando la regla tiene un enlace de ayuda http(s) (meta.helpUrl), o si no, el documento Understanding de su primer criterio de WCAG | El caso determinista que condiciona la compilación. |
cantTell | <skipped> indicando cuántas ocurrencias necesitan revisión manual | JUnit no tiene "no se pudo determinar". Omitido lo muestra sin poner la compilación en rojo, el mismo criterio que sigue SARIF con warning. |
pass | un <testcase> sin más | |
notApplicable | se omite | Una página tiene cientos y ninguno dice nada. includeNotApplicable: true los añade como <skipped message="Not applicable">. |
una regla que no terminó (cantTell sin ocurrencias y con un error: lanzó una excepción) | <error type="ruleError"> con el error, contado en errors | El elemento de JUnit para una prueba que no se pudo ejecutar. La página no se comprobó con la regla, lo que no es ni un aprobado ni algo que revisar. |
Una regla fail que también tiene ocurrencias cantTell informa los fallos en <failure> y las no decididas en <system-out>, donde los paneles muestran la salida de las pruebas. Una regla cantTell omitida hace lo mismo.
La línea selector: de un elemento dentro de un árbol shadow nombra primero los hosts shadow y después el elemento, unidos con >>> : html > body > main > x-card >>> button. Es la misma ubicación que dan SARIF y el informe HTML.
Opciones
| Opción | Por defecto | Efecto |
|---|---|---|
cantTellAs | 'skipped' | 'failure' informa las reglas cantTell como <failure type="cantTell">, para un pipeline que no debe pasar mientras quede algo sin decidir. Se lee sin distinguir mayúsculas y minúsculas; cualquier otro valor lanza un TypeError, ya que uno mal escrito dejaría de condicionar la compilación sin avisar. |
leanFailAs | 'skipped' | 'failure' informa como <failure type="leanFail"> una regla entre cuyos hallazgos sin decidir hay uno que tiende a fallo (su lean), enumerando esos hallazgos, cada uno con por qué probablemente falla (likely fails:), la advertencia de la tendencia cuando la tiene, y cómo lo confirma una persona (how to check:, los pasos de howToCheck del hallazgo, y luego fails when:), que el informe añade en Node a un resultado que llega sin ellos; sus demás hallazgos sin decidir quedan como diga cantTellAs. Para un pipeline que se condiciona a los fallos probables sin condicionarse a cada pregunta. También el de una regla de buenas prácticas: una regla así, sin criterio de WCAG asociado, nunca falla, y los hechos que constata tienden a fallo sin condiciones, así que un nivel de encabezado saltado (heading-order, un <h1> y luego un <h3>) es un <failure type="leanFail"> en Other checks. Una regla cuyos criterios quedan todos fuera del objetivo del análisis no condiciona la compilación, igual que con un fallo. Se lee sin distinguir mayúsculas y minúsculas; cualquier otro valor lanza un TypeError. leaningFailures(result, { wcagOnly }), de @surea11y/core, enumera los hallazgos que condiciona, antes de la línea base. |
leanFailWcagOnly | false | Con leanFailAs: 'failure', true se condiciona solo a los hallazgos de reglas asociadas a un criterio de WCAG dentro del objetivo del análisis, y deja fuera las tendencias de las reglas de buenas prácticas. Un valor distinto de true o false lanza un TypeError. |
includeNotApplicable | false | Incluye las reglas notApplicable como pruebas omitidas. |
baselineEntries | ninguno | Entradas de buildBaselineEntries() de la línea base. Las ocurrencias fallidas registradas ahí se descartan, exactamente como en SARIF. Acepta el archivo de línea base tal como se guardó (JSON.parse del mismo) o su lista entries. Una regla cuyos fallos ya son todos conocidos es <skipped message="N known failures recorded in the baseline">, no una prueba superada: no pasó. Una entrada escrita por la 1.13 con un código de motivo que cambió en la 1.14 sigue coincidiendo con el hallazgo con su código nuevo. Con leanFailAs: 'failure', un hallazgo que tiende a fallo y tiene una entrada marcada con lean: 'fail' (buildBaselineEntries(result, { leanFail: true })) tampoco condiciona la compilación, y el mensaje de <skipped> lo cuenta entre los known likely failures recorded in the baseline. |
packs | ninguno | Los packs que ejecutó el análisis (engineOptions.packs): con leanFailAs: 'failure', los fallos probables de una regla de un pack dan los pasos de su propio meta.review, como en el informe HTML. |
name | 'surea11y' | El name del <testsuites> raíz, para distinguir los informes de varias páginas en un mismo panel. |
Determinismo
El motor no tiene reloj, y este informe no se inventa uno: cada time es "0", y un atributo timestamp aparece en cada suite solo cuando el resultado lleva uno (engineOptions.timestamp). El mismo análisis siempre genera un XML idéntico byte a byte, así que un informe se puede versionar o comparar.
El texto se escapa para XML, y los caracteres que XML 1.0 prohíbe incluso escapados (la mayoría de los caracteres de control, los sustitutos sueltos) se eliminan, ya que el marcado capturado de una página puede contenerlos.