JUnit-Bericht

@surea11y/core/junit gibt ein Scan-Ergebnis als JUnit-XML aus, das Testberichtsformat, das CI-Dashboards nativ lesen: das Test-Widget in Merge Requests von GitLab, den Tests-Tab von Azure DevOps, Jenkins und 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) ist eine reine Funktion: Sie gibt das XML als String zurück und greift nie auf das Dateisystem zu. Sie funktioniert genauso mit einem zuvor als JSON gespeicherten Ergebnis, etwa der Ausgabe von surea11y scan <target> --json aus @surea11y/cli. Auf der Kommandozeile müssen Sie sie nicht aufrufen: surea11y scan <target> --junit a11y.junit.xml (CLI 1.1.0 oder neuer) schreibt den Bericht selbst, mit den Standardoptionen und den baselineEntries seiner --baseline-Datei. Rezepte für GitLab und Azure DevOps finden Sie unter CI-Integration.

Aufbau

Ein <testsuite> pro WCAG-Erfolgskriterium, ein <testcase> pro zugeordneter Regel. Ein Scan dieser Seite mit dem Aufruf oben:

<!doctype html>
<html lang="en">
<head><title>Home</title></head>
<body><main><h1>Welcome</h1><img src="a.png"></main></body>
</html>

ergibt:

<?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 &lt;img&gt; has no alt attribute.">- This &lt;img&gt; has no alt attribute. Add an alt attribute that says what the image shows, or alt=&quot;&quot; if the image is purely decorative.
selector: html &gt; body &gt; main &gt; img
html: &lt;img src=&quot;a.png&quot;&gt;
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>
  • Eine grüne Suite, die nach einem Kriterium benannt ist, bedeutet, dass die Regeln, die dafür gelaufen sind, keinen Fehlschlag gemeldet haben. Sie bedeutet nicht, dass das Kriterium vollständig geprüft oder erfüllt ist: Die meisten Kriterien haben Teile, die keine Regel abdeckt (siehe WCAG-Konformitätszuordnung). Das sagt auch ihre Eigenschaft criterionOutcome: cantTell, wo die Regeln bestanden haben, aber nur einen Teil des Kriteriums prüfen, was derzeit für jedes Kriterium gilt.
  • Suites folgen dem Kriterium, weil Menschen danach verfolgen, und Testcases folgen der Regel statt dem Vorkommen, sodass ein Fehler, der vierzigmal auf einer Seite auftritt, ein einziger fehlgeschlagener Test ist, dessen Inhalt alle vierzig auflistet, und die Testzahlen zwischen Läufen stabil bleiben.
  • Die Namen sind in jeder Locale gleich. Eine Suite heißt WCAG <criterion> (WCAG 1.4.3), die Suite der Regeln ohne Kriterium Other checks, ein Testcase wie seine Regel-ID, und sein classname ist wcag-<criterion> oder other, gleich in welcher engineOptions.locale der Scan lief. Die Testhistorie der CI (GitLab, Jenkins, Test-Reporter für GitHub) ist an diese Namen gebunden, daher ergibt ein auf Französisch gelesener Scan dieselben Tests wie ein auf Englisch gelesener. Der Titel des Kriteriums, der übersetzt wird, ist die Eigenschaft title der Suite (Contrast: minimum, Contraste (minimum)). Der Text der Befunde selbst (Meldung und Hinweis jedes Vorkommens) steht in der Locale des Scans; die Wörter, die der Bericht darum herum hinzufügt, sind in jeder Locale Englisch: die Zählung am Anfang einer message (1 failing occurrence: Cet élément <img> n’a pas d’attribut alt.), die Labels selector:, html: und help: und die Meldungen in <skipped>. Vor core 1.12 enthielt der Name einer Suite den Titel des Kriteriums (WCAG 1.1.1 Non-text content: text alternatives). Ein Dashboard, das die alten Namen verfolgt hat, zeigt daher im ersten Bericht nach dem Update jeden Test einmalig als neu, und ihre Historie beginnt dort von vorn.
  • Suites entstehen aus den WCAG-Zuordnungen jeder Regel, nicht aus den zusammengesetzten Regeln, daher wird jede gelaufene Regel gemeldet, auch wenn zusammengesetzte Regeln ausgeschlossen wurden. Die zusammengesetzte Regel liefert, falls sie gelaufen ist, die Eigenschaft title der Suite und die Eigenschaft criterionOutcome, die die eigenen Regeln, die dem Kriterium zugeordnet sind, genauso zählt wie die eingebauten. Ein Kriterium, das von mehreren zusammengesetzten Regeln geprüft wird (4.1.2: sein zugänglicher Name und seine ARIA-Gültigkeit), übernimmt das schlechteste ihrer Ergebnisse, da ein fehlgeschlagener Teil das Kriterium scheitern lässt, und den Titel, den ihre Titel gemeinsam haben (Name, role, value). Eine Regel mit zwei Kriterien erscheint in beiden Suites. Eine Regel ohne Kriterium kommt in eine abschließende Suite Other checks mit classname="other".
  • Suites folgen der WCAG-Stufe und -Version des Scans. Ein Kriterium, das der Scan ihretwegen aus rulesResults ausgelassen hat, erhält keine Suite: ein AAA-Kriterium unter engineOptions.wcagLevel: 'AA' (engine.wcagLevel) oder 4.1.1 „Syntaxanalyse“, wenn includeRuleIds: ['duplicate-id'] unter WCAG 2.2 lief (engine.wcagVersion), da WCAG 2.2 es gestrichen hat. Ein Kriterium, das der Scan meldet, bleibt erhalten, etwa eine per ID genannte Zusammenfassung oberhalb der Stufe, ein teilweise geprüftes Kriterium oder 4.1.1 in einem 2.2-Scan, der es ausgewählt hat. Eine Regel, die nur ausgelassenen Kriterien zugeordnet ist, kommt in Other checks, und ihre Fehlschläge sind <skipped message="Not counted: WCAG 1.4.6 is outside the scan's target, WCAG 2.2 AA; 1 failing occurrence">, mit den Vorkommen in <system-out>: Die Regel ist gelaufen und hat fail ergeben, was der Bericht zeigt, aber ein AA-Build scheitert nicht an einem AAA-Kriterium. Eine Regel, die nicht abgeschlossen wurde, ist weiterhin ein <error>. Eine Stufe, die sich aus der Auswahl ergibt (runOnly.wcag.level oder Stufen-Tags), wird ebenfalls als engine.wcagLevel festgehalten und begrenzt die Suites genauso. Ein Ergebnis, das weder engine.wcagLevel noch engine.wcagVersion festhält, wie das einer älteren Engine, behält jedes Kriterium.
  • engine, schemaVersion, wcagVersion, profile, optInRules, die Darstellungsbedingungen aus engine.environment (layout und in einem Browser viewport als 1280x720, devicePixelRatio, colorScheme, forcedColors und fonts), locale und url des Laufs werden als Eigenschaften in jeder Suite wiederholt, jeweils nur, wenn das Ergebnis sie enthält. Ein Fehlschlag, der je nach Viewport-Breite auftaucht oder verschwindet, lässt sich so auf die Breite zurückführen, die der Lauf verwendet hat. Das Beispiel oben ist ein jsdom-Scan, der kein Layout hat.
  • Eine bei mehreren Viewport-Breiten gescannte Seite ergibt bei jeder Breite dieselben Namen für Suites und Tests, und eine Testansicht in der CI führt sie womöglich zusammen. Schreiben Sie eine Datei pro Breite, und benennen oder gruppieren Sie die Dateien in den Berichtseinstellungen der CI nach Breite; die Eigenschaft viewport hält fest, welche Breite eine Datei beschreibt.
  • Suites sind nach Kriterium numerisch sortiert (1.4.3 vor 1.4.10), Testcases nach Regel-id.
  • en301549-Eigenschaften nennen die EN-301-549-Klausel, die das Kriterium wiedergibt, sofern es eine gibt und der Scan EN-301-549-Klauseln angefordert hat (siehe EN 301 549). Jede andere Norm in standards des Ergebnisses fügt Eigenschaften hinzu, die nach ihrem Schlüssel benannt sind, eine pro Anforderung dieser Norm, auf die die Regeln der Suite abgebildet sind: Ein Checklistenpunkt eines Packs, der die Regel gruppiert, wird mit seiner id aufgeführt, <property name="acme" value="acme-images"/>. Eine Norm, die nur eine eigene Regel nennt, fügt keine Eigenschaft hinzu.

Ein kompaktes Ergebnis (engineOptions.output.detail: 'findings') wird als das vollständige Ergebnis gelesen, für das es steht, und daher genauso gemeldet.

Frame-übergreifende Ergebnisse

Ein Frame-übergreifendes Ergebnis (siehe Ausgabeschema) liefert zuerst die Suites der Seite, dann die jedes Frames, benannt nach dem Pfad des Frames (Frame #checkout: WCAG 1.1.1, in jeder Locale gleich) und mit einer Eigenschaft frame, die ihn enthält, und der classname ihrer Testcases beginnt mit ihm (frame #checkout wcag-1.1.1), sodass eine CI, die Tests nach Suite-Namen oder nach classname und name zuordnet, einen Fehlschlag in einem Frame nicht mit denen der Seite zusammenführt. Ein Frame, der nicht geantwortet hat, ist eine Suite Frame <path> mit einem übersprungenen Testcase, Not scanned: <error>: Der Frame wurde nicht geprüft, und das ist kein Bestehen. Die Summen auf <testsuites> zählen jeden Frame.

Ergebnisse

Ergebnis der RegelJUnitWarum
fail<failure type="fail">, eine Zeile pro fehlgeschlagenem Vorkommen (Meldung, Hinweis, Selektor, Markup), danach help: <url>, wenn die Regel einen http(s)-Hilfelink hat (meta.helpUrl), sonst das Understanding-Dokument ihres ersten WCAG-KriteriumsDer deterministische Fall, an dem das Gate scheitert.
cantTell<skipped> mit der Anzahl der Vorkommen, die manuell geprüft werden müssenJUnit kennt kein „nicht entscheidbar“. Übersprungen macht es sichtbar, ohne den Build rot zu färben — dieselbe Linie, die SARIF mit warning zieht.
passein einfaches <testcase>
notApplicableweggelassenEine Seite hat Hunderte davon, und keines sagt etwas aus. includeNotApplicable: true fügt sie als <skipped message="Not applicable"> hinzu.
eine Regel, die nicht abgeschlossen wurde (cantTell ohne Vorkommen und mit einem error: Sie hat eine Ausnahme geworfen)<error type="ruleError"> mit dem Fehler, gezählt in errorsDas JUnit-Element für einen Test, der nicht laufen konnte. Die Seite wurde nicht gegen die Regel geprüft, was weder ein Bestehen ist noch etwas, das zu prüfen wäre.

Eine fail-Regel, die auch cantTell-Vorkommen hat, meldet die Fehlschläge in <failure> und die unentschiedenen in <system-out>, wo Dashboards die Testausgabe zeigen. Eine übersprungene cantTell-Regel macht es genauso.

Die selector:-Zeile eines Elements in einem Shadow Tree nennt zuerst die Shadow Hosts, dann das Element, verbunden mit >>> : html > body > main > x-card >>> button. Es ist derselbe Fundort, den SARIF und der HTML-Bericht angeben.

Optionen

OptionStandardWirkung
cantTellAs'skipped''failure' meldet cantTell-Regeln als <failure type="cantTell">, für eine Pipeline, die nicht bestehen darf, solange etwas unentschieden ist. Groß- und Kleinschreibung spielt keine Rolle; ein anderer Wert wirft einen TypeError, da ein falsch geschriebener stillschweigend nicht als Gate wirken würde.
leanFailAs'skipped''failure' meldet eine Regel, unter deren offenen Befunden einer zu fail tendiert (sein lean), als <failure type="leanFail"> und listet diese Befunde auf, jeweils mit dem Grund, warum er wahrscheinlich nicht erfüllt ist (likely fails:), dem Hinweis der Tendenz, falls vorhanden, und wie eine Person ihn bestätigt (how to check:, die Schritte aus howToCheck des Befunds, dann fails when:), die der Bericht in Node einem Ergebnis hinzufügt, das ohne sie kommt; die übrigen offenen Befunde der Regel folgen cantTellAs. Für eine Pipeline, die bei wahrscheinlichen Fehlern scheitern soll, ohne bei jeder offenen Frage zu scheitern. Das gilt auch für eine Best-Practice-Regel: Eine solche Regel, keinem WCAG-Kriterium zugeordnet, ergibt nie fail, und die Fakten, die sie feststellt, tendieren ohne Bedingung zu fail; eine übersprungene Überschriftenebene (heading-order, ein <h1>, dann ein <h3>) ist also ein <failure type="leanFail"> in Other checks. Eine Regel, deren Kriterien alle außerhalb des Ziels des Scans liegen, lässt den Build nicht scheitern, wie bei einem Fehlschlag. Groß- und Kleinschreibung spielt keine Rolle; ein anderer Wert wirft einen TypeError. leaningFailures(result, { wcagOnly }) aus @surea11y/core listet die Befunde, nach denen die Option entscheidet, vor Anwendung der Baseline.
leanFailWcagOnlyfalseMit leanFailAs: 'failure' lässt true den Build nur bei Befunden von Regeln scheitern, die einem WCAG-Kriterium im Ziel des Scans zugeordnet sind, und lässt die Tendenzen der Best-Practice-Regeln außen vor. Ein anderer Wert als true oder false wirft einen TypeError.
includeNotApplicablefalseNimmt notApplicable-Regeln als übersprungene Tests auf.
baselineEntrieskeineEinträge aus buildBaselineEntries() der Baseline. Dort erfasste fehlgeschlagene Vorkommen werden verworfen, genau wie in SARIF. Die Option nimmt die Baseline-Datei so, wie sie gespeichert ist (JSON.parse davon), oder ihre entries-Liste. Eine Regel, deren Fehlschläge alle bereits bekannt sind, wird zu <skipped message="N known failures recorded in the baseline">, nicht zu einem bestandenen Test: Sie hat nicht bestanden. Ein Eintrag, den 1.13 unter einem Reason-Code geschrieben hat, der sich in 1.14 geändert hat, passt weiterhin zum Befund unter seinem neuen Code. Unter leanFailAs: 'failure' lässt auch ein Befund, der zu fail tendiert und einen mit lean: 'fail' gekennzeichneten Eintrag hat (buildBaselineEntries(result, { leanFail: true })), den Build nicht scheitern, und die <skipped>-Meldung zählt ihn zu den known likely failures recorded in the baseline.
packskeineDie Packs, die der Scan ausgeführt hat (engineOptions.packs): Mit leanFailAs: 'failure' geben die wahrscheinlichen Fehler einer Pack-Regel die Schritte ihres eigenen meta.review an, wie im HTML-Bericht.
name'surea11y'Der name des obersten <testsuites>, um die Berichte mehrerer Seiten in einem Dashboard auseinanderzuhalten.

Determinismus

Die Engine hat keine Uhr, und dieser Bericht erfindet keine: Jedes time ist "0", und ein timestamp-Attribut erscheint an jeder Suite nur, wenn das Ergebnis eines trägt (engineOptions.timestamp). Derselbe Scan erzeugt immer byte-identisches XML, sodass sich ein Bericht einchecken oder vergleichen lässt.

Text wird für XML maskiert, und Zeichen, die XML 1.0 auch maskiert verbietet (die meisten Steuerzeichen, einzelne Surrogates), werden entfernt, da von einer Seite erfasstes Markup sie enthalten kann.