Verwendung mit Cypress

Cypress ist ein JavaScript-Framework für End-to-End-Tests, das Ihre Tests direkt im Browser ausführt, neben der zu testenden Anwendung. surea11y wird über das Binding @surea11y/cypress eingebunden.

$ npm install @surea11y/core @surea11y/cypress cypress

cypress ist ein Eintrag in peerDependencies — Sie brauchen eine eigene Cypress-Installation, damit Typen und Laufzeit aufgelöst werden, genau wie beim Schreiben jeder anderen Cypress-Spec.

// cypress/e2e/my-page.cy.js
const { 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) wählt nach Konformitätsziel aus: die Regeln für die Erfolgskriterien der Stufen A und AA von WCAG 2.2, nach der Stufe, die jedes Kriterium in dieser Version hat. .withBestPractices() fügt die Regeln hinzu, die kein WCAG-Erfolgskriterium prüfen, etwa region; allein ausgeführt, laufen nur diese.

Ein Pack wird in Node vorbereitet, und ein Cypress-Test läuft im Browser. Deshalb nimmt .withPacks() in Cypress den Modulnamen jedes Packs (einen Paketnamen oder einen Pfad ab dem Projektstamm), und das Plugin des Bindings, @surea11y/cypress/plugin, bereitet es in Node vor: Registrieren Sie es, indem setupNodeEvents den Rückgabewert von surea11y(on, config) zurückgibt. .options({ profile }) wählt das Profil des Packs. Ein Pack, das nicht läuft, wird als Scan-Lücke 'pack-skipped' gemeldet.

Anders als bei den treiberbasierten Bindings gibt es kein Konstruktorargument { page }/{ browser }/{ driver } — ein solches Handle gibt es in Cypress nicht. cy ist in jeder Spec-Datei verfügbar und ist der Treiber. analyze() gibt ein Cypress-Chainable zurück, kein Promise — verwenden Sie kein await; rufen Sie .then() wie gezeigt auf.

Web Components. Eine Komponente, die ihre ElementInternals in einem privaten Feld hält und sich darüber einen Namen, ihre Zustände oder eine Rolle gibt, lässt sich von keinem Skript der Seite auslesen. Standardmäßig stellt der Scan zu einer solchen Komponente eine Frage (cantTell), statt sie zu beurteilen, und eine Rolle, die nur so gesetzt ist, wird gar nicht geprüft. Wenn Sie eine Komponentenbibliothek bauen oder testen, installieren Sie den optionalen Internals-Hook des Bindings, bevor die eigenen Skripte der Seite laufen, und diese Komponenten werden wie jedes andere Bedienelement beurteilt:

// cypress/support/e2e.js
import { installInternalsHook } from '@surea11y/cypress';
 
installInternalsHook();

Einmal in der Support-Datei aufgerufen, läuft er bei jedem Besuch, über das Cypress-Ereignis window:before:load. Für einen einzelnen Besuch übergeben Sie ihn an cy.visit() als { onBeforeLoad: installInternalsHook }. Er läuft mit dem eval des Anwendungsfensters, was funktioniert, weil Cypress die Content Security Policy der Anwendung standardmäßig entfernt; Frames innerhalb der Anwendung erhalten ihn nicht.

Der Hook ist für Test- und Audit-Läufe gedacht: Er legt das, was er aufzeichnet, dort ab, wo jedes Skript der Seite es lesen kann, deshalb ist er optional. Ohne ihn meldet der Scan einmal, wenn er zu solchen Komponenten Fragen gestellt hat. Der Abschnitt der README zum Internals-Hook enthält die Details.

Klick-Handler und Live-Regionen. Seit core 1.14 gibt es zwei weitere optionale Hooks. Die meisten Seiten fügen ihre Klick-Handler per Skript hinzu, wo kein Skript der Seite sie danach auflisten kann. Der Scan sieht daher nicht, dass ein <div role="button"> mit einem Klick-Listener per Tastatur nichts tut; mit dem Listener-Hook ergibt ein solcher Button fail (CLICK_HANDLER_NO_KEY_LISTENER). Der Live-Region-Hook zeichnet auf, was die Live-Regionen der Seite erhalten: Der Scan stellt dann eine Frage zu einer Statusmeldung, die schon mit ihrem Text eingefügt wurde und die Screenreader oft nicht ansagen, und announcements() gibt zurück, was die Seite zur Ansage bereitgestellt hat, damit ein Test prüfen kann, ob eine Aktion den Nutzenden etwas mitgeteilt hat. Installieren Sie sie, bevor die eigenen Skripte der Seite laufen, wie den Internals-Hook:

// cypress/support/e2e.js
import { installListenerHook, installLiveRegionHook } from '@surea11y/cypress';
 
installListenerHook();
installLiveRegionHook();
 
// in a spec
cy.get('#save').click();
A11yCoreBuilder.announcements().then((said) => {
expect(said.map((a) => a.text)).to.include('Saved');
});

Wie beim Internals-Hook deckt die Support-Datei jeden Besuch ab, und nur das eigene Fenster der Anwendung erhält sie, nicht die Frames darin. announcements() liest das Fenster der Anwendung und gibt ein Chainable zurück, wie .analyze().

Wie der Internals-Hook sind beide für Test- und Audit-Läufe gedacht: Jedes Skript der Seite kann lesen, was sie aufzeichnen. announcements() ist das, was die Seite zur Ansage bereitgestellt hat, nicht das, was ein Screenreader gesagt hat, und ohne den Live-Region-Hook null. Fehlt einer der beiden Hooks, meldet getScanNotes(), wenn Befunde, die er entschieden hätte, ohne ihn beurteilt wurden. Die Abschnitte der README zum Listener-Hook und zum Live-Region-Hook enthalten die Details.

Dunkler Modus, Smartphone-Breiten und eine Schwelle nach Schweregrad. .conditions([{ colorScheme: 'dark' }, { width: 320 }]) scannt die Seite so, wie sie ist, und dann erneut unter jeder Bedingung, und gibt ein einziges Ergebnis zurück: Das Ergebnis einer Regel ist ihr schlechtestes über alle Scans, und jeder Befund nennt die Bedingungen, unter denen er auftrat. Jede Bedingung ist ein vollständiger Scan, und .frames(true) lässt sich noch nicht damit kombinieren. Eine Breite läuft über cy.viewport(); ein Farbschema braucht einen Browser der Chromium-Familie (Chrome, Edge, Electron), und in Firefox schlägt .analyze() vor jedem Scan fehl. getGatingFailures(results, { failOnSeverity: 'serious' }) gibt die fehlschlagenden Regeln ab diesem Schweregrad zurück, sodass ein Test zuerst bei schwerwiegenden und kritischen Problemen blockiert, während der Rest weiter gemeldet wird; formatFailures() nimmt dieselbe Option.

Fragen mit Tendenz und Prüfschritten. Seit core 1.14 kann ein cantTell-Befund zu fail oder pass tendieren, mit Begründung, und er enthält howToCheck: die Schritte, mit denen eine Person ihn beantwortet. formatFailures() listet die wahrscheinlich nicht erfüllten Fragen direkt nach den Fehlschlägen und gibt mit { howToCheck: true } die Schritte aus. Soll ein Test auch bei wahrscheinlichen Fehlschlägen scheitern, nehmen Sie leaningFailures(results) aus core in die Schwelle auf. In Cypress kommen die Schritte vom Plugin: setupNodeEvents muss zurückgeben, was surea11y(on, config) zurückgibt. Sonst hat das Ergebnis keine Schritte, und nichts schlägt fehl.

A11yCoreBuilder hat außerdem .wcagLevel(), das die gemeldeten Kriterien auf eine WCAG-Stufe begrenzt, ohne zu ändern, welche Regeln laufen; .includeMode('or'), das die Regeln eines Tags plus die Regeln ausführt, die Sie nennen; .plannedChecks() und .plannedCriteria(), die auflisten, was ein Scan prüfen wird, bevor er läuft, damit eine Auswahl, die nichts ausführt, nicht als Scan ohne Befunde durchgeht; .fetchStyleSheets(false), das .analyze() davon abhält, Stylesheets anderer Origins, die die Seite selbst nicht lesen kann, für die Regeln abzurufen, die das CSS lesen; und getScanNotes(results), das angibt, wie die Seite gescannt wurde. Die README des Bindings beschreibt jede davon.

Siehe auch ein lauffähiges Beispiel im Repository des Pakets.

Als Nächstes: festlegen, welche Regeln laufen, den Scan in die CI einbinden und das vollständige Ergebnisschema ansehen.