Utilisation avec Selenium

Selenium WebDriver est la norme historique pour piloter de vrais navigateurs depuis n’importe quel langage : l’outil d’automatisation multinavigateur le plus établi, couramment utilisé pour les tests de bout en bout. surea11y s’y intègre via le binding @surea11y/selenium.

$ npm install @surea11y/core @surea11y/selenium selenium-webdriver

selenium-webdriver inclut Selenium Manager, qui télécharge et gère automatiquement le chromedriver correspondant la première fois que vous créez un pilote : pas de serveur Selenium séparé, pas d’installation manuelle du pilote (Chrome lui-même doit déjà être présent sur la machine).

const { Builder, Browser } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const { A11yCoreBuilder } = require('@surea11y/selenium');
 
const options = new chrome.Options().addArguments('--headless=new');
const driver = await new Builder().forBrowser(Browser.CHROME).setChromeOptions(options).build();
await driver.get('https://example.com/');
 
const results = await new A11yCoreBuilder({ driver })
.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 driver.quit();

.withWcag(version, level) sélectionne par cible de conformité : les règles des critères de niveau A et AA de WCAG 2.2, selon le niveau de chaque critère dans cette version. .withBestPractices() ajoute les règles qui ne vérifient aucun critère WCAG, comme region ; seule, elle n’exécute que celles-ci.

.withPacks() enregistre un pack dans la page avant l’analyse, et .options({ profile }) choisit son profil. Un pack qui ne s’exécute pas est signalé comme une lacune d’analyse 'pack-skipped', si bien qu’un test qui vérifie getScanGaps(results) échoue au lieu de passer sans lui.

Le builder prend { driver } (un WebDriver Selenium), là où Puppeteer/Playwright prennent { page } : c’est la seule différence de construction. Tout le reste est identique.

Composants web. Un composant qui garde ses ElementInternals dans un champ privé, et se donne un nom, ses états ou un rôle par ce biais, ne peut être relu par aucun script de la page. Par défaut, l’analyse pose donc une question sur ce composant (cantTell) au lieu de le juger, et un rôle donné uniquement ainsi n’est pas vérifié du tout. Si vous construisez ou testez une bibliothèque de composants, installez le hook d’internals facultatif du binding avant que les scripts de la page elle-même ne s’exécutent, et ces composants sont jugés comme n’importe quel autre contrôle :

const options = new chrome.Options().enableBidi(); // the webSocketUrl: true capability
const driver = await new Builder().forBrowser(Browser.CHROME).setChromeOptions(options).build();
 
await A11yCoreBuilder.installInternalsHook(driver); // 'bidi' (or 'cdp' without BiDi on Chrome)
await driver.get('https://example.com/');

Appelez-le avant driver.get(). Le WebDriver classique ne peut pas exécuter un script avant ceux de la page, donc le hook utilise WebDriver BiDi quand la session le prend en charge (Chrome, Edge et Firefox, avec BiDi activé à la construction du driver, comme ci-dessus), et sinon le protocole DevTools dans Chrome et Edge ; il se résout en 'bidi' ou 'cdp'. Dans une session classique sous Safari, ou sous Firefox sans BiDi, l’appel est rejeté.

Le hook est destiné aux exécutions de test et d’audit : il garde ce qu’il enregistre là où n’importe quel script de la page peut le lire, c’est pourquoi il est facultatif. Sans lui, l’analyse le signale une fois lorsqu’elle a posé une question sur de tels composants. La section du README sur le hook d’internals donne les détails.

Gestionnaires de clic et régions live. Depuis core 1.14, il existe deux autres hooks facultatifs. La plupart des pages ajoutent leurs gestionnaires de clic depuis un script, où aucun script de la page ne peut les lister ensuite, si bien que l’analyse ne voit pas qu’un <div role="button"> doté d’un écouteur de clic ne fait rien au clavier ; avec le hook d’écouteurs, ce bouton échoue (CLICK_HANDLER_NO_KEY_LISTENER). Le hook de régions live enregistre ce que reçoivent les régions live de la page : l’analyse pose alors une question sur un message d’état ajouté avec son texte déjà dedans, que les lecteurs d’écran n’annoncent souvent pas, et announcements() renvoie ce que la page a mis à disposition pour être annoncé, afin qu’un test vérifie qu’une action a bien informé l’utilisateur. Installez-les avant que les scripts de la page elle-même ne s’exécutent, comme le hook d’internals :

await A11yCoreBuilder.installListenerHook(driver); // before driver.get()
await A11yCoreBuilder.installLiveRegionHook(driver);
await driver.get('https://example.com/settings');
await driver.findElement(By.css('#save')).click();
 
const said = await A11yCoreBuilder.announcements(driver);
// [{ text: 'Saved', politeness: 'polite', ..., selector: '#status' }]

Tous deux s’installent comme le hook d’internals, avant driver.get() : via WebDriver BiDi quand la session l’a, sinon via le protocole DevTools dans Chrome et Edge. Chacun se résout en 'bidi' ou 'cdp'. By vient de selenium-webdriver.

Comme le hook d’internals, tous deux sont destinés aux exécutions de test et d’audit : n’importe quel script de la page peut lire ce qu’ils enregistrent. announcements() est ce que la page a mis à disposition pour être annoncé, pas ce qu’un lecteur d’écran a dit, et vaut null sans le hook de régions live. Sans l’un ou l’autre hook, getScanNotes() signale quand des constats que ce hook aurait tranchés ont été jugés sans lui. Les sections du README sur le hook d’écouteurs et le hook de régions live donnent les détails.

Mode sombre, largeurs de téléphone et seuil de gravité. .conditions([{ colorScheme: 'dark' }, { width: 320 }]) analyse la page telle qu’elle est, puis à nouveau sous chaque condition, et renvoie un seul résultat : le résultat d’une règle est le pire de toutes les analyses, et chaque constat indique les conditions sous lesquelles il a été vu. Chaque condition est une analyse complète, et .frames(true) ne peut pas encore s’y combiner. Dans Chrome et Edge, les deux passent par le protocole DevTools ; dans les autres navigateurs, une largeur redimensionne la fenêtre, et un schéma de couleurs fait rejeter .analyze() avant toute analyse. getGatingFailures(results, { failOnSeverity: 'serious' }) renvoie les règles en échec à cette gravité ou au-dessus, pour qu’un test bloque d’abord les problèmes graves et critiques pendant que les autres restent signalés ; formatFailures() accepte la même option.

Des questions qui penchent, avec des étapes. Depuis core 1.14, un constat cantTell peut pencher vers fail ou pass, en disant pourquoi, et porte howToCheck, les étapes qu’une personne suit pour y répondre. formatFailures() liste les échecs probables juste après les échecs, et affiche les étapes avec { howToCheck: true }. Pour qu’un test échoue aussi sur les échecs probables, ajoutez leaningFailures(results) de core au seuil.

A11yCoreBuilder a aussi .wcagLevel(), qui limite les critères rapportés à un niveau WCAG sans changer les règles exécutées ; .includeMode('or'), qui exécute les règles d’une étiquette plus les règles que vous nommez ; .plannedChecks() et .plannedCriteria(), qui listent ce qu’une analyse vérifiera avant qu’elle ne s’exécute, pour qu’une sélection qui n’exécute rien ne passe pas pour une analyse sans échec ; .logger(), qui envoie les avertissements de lacunes d’analyse à votre propre journal ; .fetchStyleSheets(false), qui empêche .analyze() de récupérer les feuilles de style d’autres origines que la page ne peut pas lire elle-même, pour les règles qui lisent le CSS ; et getScanNotes(results), qui indique comment la page a été analysée. Le README du binding les décrit chacun.

Voir aussi un exemple exécutable dans le dépôt du paquet.

Ensuite : choisir les règles à exécuter, intégrer l’analyse à la CI et consulter le schéma complet du résultat.