Packs
Un pack apporte à @surea11y/core des règles, des variantes des règles de core, une norme ou une liste de contrôle, avec leurs profils et leurs messages, depuis un paquet qui lui est propre, sans fork ni build de core. Une norme nationale comme le RGAA peut être un pack, tout comme la politique d’accessibilité de votre organisation. Une analyse reçoit les packs dans engineOptions.packs, et les exécute comme elle exécute les règles et les normes propres à core : dans Node et dans un navigateur, depuis la CLI, les bindings et test-matchers, avec des résultats, des agrégations et des entrées de rapport au même format que ceux de core. Les packs nécessitent @surea11y/core 1.11.1 ou une version ultérieure.
Quel type de pack
| Vous avez | Type | Ce qu’il contient |
|---|---|---|
| La politique d’accessibilité d’une organisation : « WCAG 2.2 AA, plus ces règles, moins celle-là, avec ces points dans notre rapport » | liste de contrôle (checklist) | Des profils qui choisissent parmi les règles de core et les vôtres, et des agrégations qui sont les points de la liste de contrôle. |
| Une norme qui a ses propres exigences (une norme nationale comme le RGAA, celle d’un secteur) : des exigences numérotées, chacune vérifiée par certaines règles | norme (standard) | Un tableau d’exigences, les règles qui vérifient chaque exigence, un résultat par exigence, et des profils. |
| Quelques règles à ajouter à chaque analyse, sans profil | règles seules | Des règles, et des variantes ou des surcharges de celles de core. Partez d’une liste de contrôle et supprimez ses profils et ses agrégations. |
Les règles et les profils d’un pack peuvent utiliser n’importe quelle règle de core : une liste de contrôle ou une norme est surtout faite de règles de core, choisies et regroupées, plus les quelques règles que core n’a pas. Les exemples de cette page utilisent acme-policy-pack.js, le petit pack que chaque binding livre dans son dossier examples/ : une règle maison, un profil de liste de contrôle (WCAG 2.2 A et AA, region et la règle maison) et deux points de liste de contrôle. Son code source figure plus bas.
Le pack EN 301 549
@surea11y/pack-en301549 est un pack de norme de la famille SureA11y, prêt à installer : le chapitre 9 (Web) d’EN 301 549, en V3.2.1 et V4.1.1. Il a un profil par version, en301549-v4.1.1 et en301549-v3.2.1, qui rapporte un résultat par clause, et mappings: ['en301549'] pour nommer les clauses dans les résultats de n’importe quelle analyse, WCAG compris. Il demande @surea11y/core 1.13.0 ou ultérieur.
Sa version 1.1.0, construite sur core 1.14.0, juge chaque clause comme core juge le critère WCAG qu’elle reprend. Aucune clause ne réussit donc encore : là où toutes ses règles réussissent, une clause est cantTell, pour qu’une personne vérifie le reste, comme son critère (voir Correspondance de conformité WCAG). Le profil en301549-v3.2.1 conserve le 4.1.1 Analyse syntaxique, si bien qu’un id dupliqué y fait toujours échouer la clause 9.4.1.1, et les clauses 9.1.4.5, 9.3.3.1 et 9.4.1.3 ont leurs premières règles (image-of-text, invalid-field-error-message, live-region-announcements). Avec core 1.13, il se comporte comme ses versions 1.0.
// npm install @surea11y/core @surea11y/pack-en301549const en301549 = require('@surea11y/pack-en301549');runDomRulesInPage(url, null, { packs: [en301549], profile: 'en301549-v4.1.1' });runDomRulesInPage(url, null, { packs: [en301549], profile: 'wcag22-aa', mappings: ['en301549:V4.1.1'] });
C’est aussi un exemple complet de norme à vous. Il n’a pas de règles : le chapitre 9 reprend WCAG, donc chaque clause est vérifiée par les règles de core pour son critère. Ses profils choisissent ces règles (tags, mappedRules: true), nomment la version de WCAG selon laquelle chaque version de la norme est jugée (wcagVersion) et, pour V3.2.1, conservent le 4.1.1 Analyse syntaxique (keepsParsing). Ses clauses reprennent des critères WCAG un pour un (applicability: 'wcag'), et sa table de règles est dérivée du catalogue du core installé : une règle que core ajoute est couverte sans changer le pack. Une norme avec des exigences que WCAG n’a pas suit la même forme et ajoute ses propres règles. Son code est court et mérite d’être lu à côté de Écrire un pack, plus bas.
Core intégrait EN 301 549 jusqu’à la 1.13.0, qui la rend obsolète au profit de ce pack (voir la correspondance WCAG). Comme tout profil, ceux du pack choisissent ce qui s’exécute : une déclaration de conformité demande toujours qu’une personne vérifie chaque clause.
Utiliser un pack
Depuis Node
const { runDomRulesInPage, getChecksCatalog } = require('@surea11y/core');const acmePolicy = require('./acme-policy-pack');const result = runDomRulesInPage(url, null, { packs: [acmePolicy], profile: 'acme-web-2026' }, null);result.engine.packs; // ['@acme/a11y-policy@1.0.0']getChecksCatalog({ packs: [acmePolicy] }); // core's rules and the pack's
Le pack est préparé une fois et conservé pour la prochaine analyse avec le même objet pack : un crawl qui passe les mêmes packs à chaque page ne les prépare donc qu’une fois. definePack fige l’objet pack : traitez-le comme immuable, et réutilisez le même objet d’une analyse à l’autre. Passer un pack l’enregistre pour l’analyse ; cela ne sélectionne pas ses règles. Une règle de pack qui porte le tag de l’espace de noms du pack est optionnelle : sans l’un des profils du pack, elle ne s’exécute que si elle est sélectionnée par identifiant ou par ce tag (voir Noms). Une règle de pack sans ce tag est sélectionnée comme les règles de core, si bien qu’une analyse par défaut avec le pack l’exécute. Ajoutez runOnly: { bestPractices: true } à côté du profil pour exécuter aussi les bonnes pratiques.
Avec un binding navigateur
Playwright, Puppeteer, Selenium et WebdriverIO acceptent l’objet pack dans .withPacks(), et l’enregistrent dans la page, ainsi que dans chaque frame avec .frames(true), avant l’analyse :
const { A11yCoreBuilder, getScanGaps } = require('@surea11y/playwright');const acmePolicy = require('./acme-policy-pack');const results = await new A11yCoreBuilder({ page }).withPacks(acmePolicy).options({ profile: 'acme-web-2026' }).analyze();results.engine.packs; // ['@acme/a11y-policy@1.0.0']// A checklist's items, each one result, beside core's WCAG rollups:results.rulesResults.filter((r) => r.meta && r.meta.standard === 'Acme web policy');
Passez un tableau, ou appelez de nouveau .withPacks(), pour analyser avec plusieurs packs. Les bindings partagent .withPacks(), mais chaque builder prend son propre objet de pilotage ({ page }, { driver } ou { browser }) et demande sa propre mise en place : voir Playwright, Puppeteer, Selenium et WebdriverIO.
Avec Cypress
Un pack se prépare dans Node, et un test Cypress s’exécute dans le navigateur. Dans Cypress, .withPacks() prend donc le nom de module de chaque pack (un nom de paquet, ou un chemin depuis la racine du projet), et le plugin du binding prépare le pack dans Node. Enregistrez le plugin une fois :
// cypress.config.jsconst { defineConfig } = require('cypress');const surea11y = require('@surea11y/cypress/plugin');module.exports = defineConfig({e2e: {setupNodeEvents(on, config) {surea11y(on, config);}}});
new A11yCoreBuilder().withPacks('./examples/acme-policy-pack.js') // or a package name, '@acme/a11y-policy'.options({ profile: 'acme-web-2026' }).analyze().then((results) => {expect(results.engine.packs).to.deep.equal(['@acme/a11y-policy@1.0.0']);});
.analyze() demande au plugin, via cy.task, le script qui enregistre les packs, et l’exécute dans la page, ainsi que dans chaque frame de même origine avec .frames(true). Le plugin conserve un pack pour le reste de l’exécution : une modification du fichier d’un pack apparaît donc au cypress run suivant, ou après un redémarrage de cypress open. Un objet pack, tel que l’acceptent les autres bindings, lève une erreur dans .withPacks() : il ne peut pas être préparé dans le navigateur.
Avec test-matchers
@surea11y/test-matchers analyse dans Node : un pack est donc passé sous forme d’objet :
const acmePolicy = require('./acme-policy-pack');const policy = { packs: [acmePolicy], profile: 'acme-web-2026' };test("a page that breaks the pack's house rule fails the assertion", () => {document.body.innerHTML = '<main><h1>Welcome</h1><video autoplay src="intro.mp4"></video></main>';expect(document).not.toHaveNoA11yViolations(policy);});
Gardez les objets pack en un seul endroit (un fichier de setup ou un module), pour que chaque test passe les mêmes. Un pack qui ne s’est pas exécuté fait échouer l’assertion. Voir test-matchers.
Depuis la CLI
$ surea11y scan ./dist/index.html --pack ./acme-policy-pack.js --profile acme-web-2026$ surea11y scan ./dist/index.html --pack @acme/a11y-policy --profile acme-web-2026
--pack prend un nom de paquet, résolu depuis le projet du répertoire de travail, ou un chemin, et se répète pour plusieurs packs. Le pack charge @surea11y/core/pack, qui est installé à côté de la CLI quand vous installez @surea11y/cli dans le projet. Voir CLI.
Dans votre propre page
Pour une extension de navigateur ou votre propre page.evaluate, injectez packScript([acmePolicy]) de @surea11y/core/pack après le bundle navigateur de core, ou livrez un seul fichier construit avec buildBrowserBundle({ packs: [acmePolicy] }), et nommez le pack sous la forme name@version. Voir Autonome. Pour une page qui charge ses scripts sous forme de fichiers, préférez le fichier unique de buildBrowserBundle : le script d’un pack ne s’exécute qu’avec la version de core qui l’a écrit.
Une page dotée du bundle d’un autre core ignore le pack, et skippedPacks nomme les deux versions : reconstruisez donc ce fichier à chaque version de core ou du pack.
Ce que rapporte une analyse
engine.packsnomme chaque pack qui s’est exécuté, sous la formename@version, etengine.profilele profil qui s’est appliqué.- Les règles d’un pack apparaissent dans
checksResultscomme celles de core, avec des identifiants qui commencent par son espace de noms (acme-no-autoplay). Une règle de pack associée à un critère WCAG compte dans l’agrégation de ce critère, sauf si elle porte le tag optionnel de l’espace de noms du pack : ces règles vérifient ce que WCAG n’exige pas, et ne comptent donc que dans les agrégations propres au pack. LeurwcagScnomme tout de même les critères WCAG qu’elles vérifient, pour les rapports, et aucune autre norme (EN 301 549, qui associe chaque règle par ses critères WCAG) ne les revendique. Une surcharge compte à la place de la règle de core qu’elle remplace, là où sa propre correspondance la place. - Les points d’une liste de contrôle, ou les exigences d’une norme, sont des agrégations dans
rulesResults, avec le nom de la liste de contrôle ou de la norme commemeta.standard. Le rapport HTML les affiche dans une section à part, le rapport SARIF attribue à chaque règle les tags des points auxquels elle appartient (acme-images), et le rapport JUnit les liste comme propriétés. Le champstandardsdu résultat nomme la liste de contrôle, si bien qu’un résultat enregistré s’affiche de la même façon où qu’il soit rendu. - La gravité définie par un profil apparaît sur les résultats qu’elle modifie, celle de la règle étant conservée dans
ruleSeverity.
Quand un pack ne s’exécute pas
Un pack dont la plage core exclut la version de core installée, qui n’est pas valide, ou qui n’est pas compatible avec core (un identifiant de règle hors de son espace de noms, une clé de dictionnaire que core possède déjà, une variante d’une règle qui n’existe pas) est ignoré : l’analyse s’exécute sans lui, affiche un avertissement et le liste dans skippedPacks sous la forme { name, reason }. Avec strictOptions, elle lève une erreur, avec code: 'INVALID_ENGINE_OPTIONS', comme toute erreur dans engineOptions.packs. Deux packs en conflit (une clé de dictionnaire que les deux définissent, deux normes avec une même clé) lèvent une erreur, puisque toute façon de choisir entre eux dépendrait de l’ordre dans lequel ils ont été passés. Dans une page, un pack nommé dans engineOptions.packs qu’aucun appel à packScript n’a enregistré, ou qui a été enregistré avec une autre version de core, est listé de la même façon. Une analyse peut donc se terminer sans les vérifications pour lesquelles vous avez ajouté le pack : un outil qui dépend d’un pack devrait consulter skippedPacks avant de considérer l’analyse comme complète.
Chaque outil transforme un pack ignoré en un échec visible :
- Les bindings navigateur vérifient chaque pack quand vous appelez
.withPacks(), et lèvent une erreur avant l’analyse pour un pack que core ne peut pas exécuter. Un pack que la page n’exécute ensuite pas est une lacune'pack-skipped'dansgetScanGaps(results), et.analyze()l’annonce par un avertissement : un test qui vérifie les lacunes échoue donc. - test-matchers fait échouer l’assertion, en nommant le pack et la raison.
- La CLI se termine avec le code
2, sans écrire de rapport ni de baseline, pour un pack qu’elle ne trouve pas ou ne peut pas charger, ou que le moteur ignore.
Installez le pack à côté de @surea11y/core et du paquet qui l’exécute, pour que les trois partagent une seule copie de core.
Écrire un pack
Partir de l’exemple
Un pack est un objet créé avec definePack de @surea11y/core/pack. Voici acme-policy-pack.js, l’exemple que livrent les bindings :
'use strict';// A small pack: one house rule, a checklist profile and two checklist items.// A real pack is a package of its own; `npx surea11y-pack new <folder>` writes one.const { definePack, wcagTags } = require('@surea11y/core/pack');module.exports = definePack({name: '@acme/a11y-policy',title: 'Acme web policy',version: '1.0.0',namespace: 'acme',core: '^1.11.1',rules: [{id: 'acme-no-autoplay',meta: {title: 'Videos do not play sound on their own',description: 'A video that starts with sound talks over a screen reader.',tags: ['acme'],defaultSeverity: 'serious'},runInPage(ctx) {// defaultMuted reflects the muted attribute where muted isn't set from it yet (jsdom).const loud = [...ctx.document.querySelectorAll('video[autoplay]')].filter((v) => !(v.muted || v.defaultMuted));return loud.length? {outcome: 'fail',occurrences: loud.map((v) =>ctx.helpers.reportOccurrence(v, { summary: 'This video plays with sound as the page opens.', hint: 'Remove autoplay, or add muted.' }))}: { outcome: 'pass', occurrences: [] };}}],profiles: {'acme-web-2026': { tags: wcagTags('2.2', ['A', 'AA']), rules: ['region', 'acme-no-autoplay'] }},rollups: [{ id: 'acme-images', title: 'Images have a text alternative', checksIds: ['img-alt-present'] },{ id: 'acme-media', title: 'Media stays quiet until asked', checksIds: ['acme-no-autoplay'] }]});
Son profil, acme-web-2026, exécute les règles WCAG 2.2 A et AA de core, la règle de bonnes pratiques region, qu’aucun tag WCAG ne sélectionne, et la règle maison. Ses deux agrégations sont les points de la liste de contrôle : chacune regroupe des règles, de core ou du pack, en un seul résultat.
Pour une norme, le pack EN 301 549 est un exemple complet et publié.
En créer un avec surea11y-pack new
Pour votre propre pack, partez d’un paquet généré :
$ npx -p @surea11y/core surea11y-pack new acme-a11y --name @acme/a11y-pack# or, for a standard:$ npx -p @surea11y/core surea11y-pack new city-standard --name @city/a11y-standard --kind standard --title "City Standard"$ cd acme-a11y$ npm install$ npx playwright install chromium # once, for npm run docs$ npm test && npm run lint && npm run docs
Le pack fonctionne tel qu’il est généré : ses tests réussissent, ses règles passent le lint sans erreur, ses exemples concordent avec ses règles. surea11y-pack new vérifie le pack comme le ferait une analyse avant d’écrire quoi que ce soit, et refuse un nom, un espace de noms ou un titre qui le rendrait invalide (un titre WCAG désigne sa norme comme l’une de celles de core), en expliquant pourquoi. Modifiez-le ensuite pas à pas, en lançant npm test au fur et à mesure. Son README présente chaque fichier :
| Fichier | Ce qu’il contient |
|---|---|
index.js | Le pack, écrit avec definePack() de @surea11y/core/pack. |
rules/automatic/<ns>-link-text-specific.js | Une règle qui tranche, à l’aide des helpers de core. |
rules/automatic/<ns>-contrast-enhanced.js | Une variante de contrast-minimum de core à 7:1. |
rules/manual/<ns>-new-window-review.js | Une règle manuelle qui demande une revue humaine, avec ses codes de raison et les étapes qu’une personne suit pour y répondre (meta.reasonCodes, meta.review). |
i18n/en.json | Les messages. |
tests/rules/*.test.js, tests/profiles.test.js | Un test par règle, et ce qu’exécute chaque profil. |
docs/RULE_EXAMPLES.md | Des exemples des résultats de chaque règle. |
eslint.config.js | Les règles de lint de core pour les règles. |
requirements.js, rule-map.js | Norme seulement : les exigences et les règles qui vérifient chacune d’elles. |
package.json | private, avec @surea11y/core en dépendance peer à la version qui l’a écrit (^1.14.1), et eslint, jsdom et Playwright en dépendances de développement. |
README.md, .gitignore | Le guide du pack lui-même sur ces fichiers, et ce que git laisse de côté. |
Noms
- Le nom et la version du paquet sont un nom de paquet npm et une version complète (
1.0.0). Une analyse les rapporte dansengine.packs. - L’espace de noms (
namespace: 'acme') sépare les identifiants du pack de ceux de core et des autres packs : chaque identifiant de règle, d’agrégation et de profil commence par lui suivi de-. Il ne peut pas être un préfixe des identifiants de règle de core (img,ariaoulinkle seraient, comme dansimg-alt-present), ni commencer parwcag, et deux packs d’une même analyse ne peuvent pas le partager. C’est aussi le tag qui rend optionnelles les règles propres au pack : une règle qui le porte ne s’exécute que sous l’un des profils du pack, par son identifiant, ou par le tag (optInRules: ['acme']). Par défaut, une analyse qui vise WCAG ne l’exécute donc pas. Débloquer le tag avecoptInRulesne retire pas le profil ni les filtres de l’analyse : sous un profil WCAG, une règle débloquée ne s’exécute que si ses tags correspondent aussi à ce profil. Omettez le tag sur une règle qui devrait s’exécuter dans chaque analyse avec le pack : elle est alors sélectionnée comme les règles de core, et une analyse qui vise WCAG peut l’exécuter et signaler ses échecs quand ses tags correspondent. coreest la plage des versions de core avec lesquelles le pack fonctionne ('^1.11.1'), lue comme npm lit les plages. Faites partir les nouveaux packs de 1.11.1 : le paquet 1.11.0 omettait un fichier dont les packs ont besoin dans Node. Une analyse avec une version de core hors de cette plage ignore le pack, avec la raison dansskippedPacks.surea11y-pack newécrit la version qui a généré le pack (^1.14.1) ; gardez une plage qui commence à 1.14 si le pack utilise ce qu’a ajouté la 1.14 :meta.review, des lignesreasonCodesetfindings,meta.contributions,applicability,partialoukeepsParsing.titleest le nom que les résultats et les rapports donnent à une liste de contrôle, etdescriptionune phrase sur ce que vérifie le pack, pour les outils qui laissent leurs utilisateurs choisir des packs (describePacksla renvoie).
Profils
Un profil est une sélection nommée, exécutée avec engineOptions.profile. Une règle s’exécute sous un profil quand elle porte l’un de ses tags ou que son identifiant figure dans rules, et qu’elle n’est pas dans exclude ; les règles du pack qui portent le tag de son espace de noms s’exécutent sous chaque profil du pack.
profiles: {'acme-policy': {tags: wcagTags('2.2', ['A', 'AA']), // core's WCAG 2.2 A and AA rulesrules: ['region', 'heading-order'], // and these, by idexclude: { rules: ['contrast-minimum'] }, // but not this oneseverity: { 'img-alt-present': 'critical' } // and this one is critical here},'acme-quick': {tags: [],rules: ['img-alt-present', 'link-name-present', 'page-title-present']}}
| Champ | Ce qu’il fait |
|---|---|
tags | Les règles qui portent l’un de ces tags. wcagTags(version, levels) de @surea11y/core/pack donne les tags WCAG de core (wcagTags('2.1') correspond à 2.0 et 2.1, A et AA). |
rules | Des règles par identifiant, de core ou du pack : une règle de bonnes pratiques qu’aucun tag WCAG ne sélectionne, ou, avec tags: [], les règles de core qu’exécute le profil en plus de celles du pack. |
exclude | { rules: [...] } écarte des règles de l’analyse ; { criteria: ['2.5.8'] } dispense de ces critères : leurs agrégations disparaissent, ainsi que chaque règle dont tous les critères sont dispensés ; une règle qui vérifie aussi un critère que le profil conserve s’exécute toujours. Cela restreint ce que l’analyse vérifie : les exigences de WCAG restent les mêmes. |
severity | { ruleId: level } : une autre gravité sous le profil. Les résultats l’affichent et gardent celle de la règle dans ruleSeverity ; les baselines et les identifiants de constats ne changent pas. |
wcagVersion | '2.0', '2.1' ou '2.2' : la version de WCAG selon laquelle les analyses du profil sont jugées (engine.wcagVersion). Sans elle, les tags WCAG de tags impliquent la version, et un profil qui n’en a aucun est jugé selon 2.2. Le engineOptions.wcagVersion propre à une analyse l’emporte toujours. Core 1.12.0 et versions ultérieures. |
keepsParsing | true quand la norme exige le 4.1.1 Analyse syntaxique tel qu’il est écrit, comme EN 301 549 V3.2.1 : un échec à ce critère reste alors un échec avec une cible 2.0 ou 2.1. Sous un profil sans ce champ, les errata de 2023 à WCAG 2.0 et 2.1 considèrent le 4.1.1 comme satisfait pour le HTML et le XML, et duplicate-id pose la question à la place (en 2.2, qui a retiré le 4.1.1, il la pose quoi que dise le profil). Seul compte le champ du profil lui-même : le profil d’un pack qui prend la place d’un profil de core n’en hérite pas. engineOptions.mappings ne change jamais un résultat : une analyse jugée selon une norme qui conserve le 4.1.1 exécute donc son profil. Core 1.14.0 et ultérieur. |
version, mappedRules | Profil d’une norme seulement : la version de la norme qu’il vise, et mappedRules: true pour exécuter toutes les règles que la norme associe à cette version. |
Un profil qui n’exécute que ce que demande sa norme. Le profil d’une norme avec tags: [<ns>] et mappedRules: true exécute les règles propres au pack et les règles de core que sa table associe à une exigence, et rien d’autre : aucune règle ne vérifie ce que la norme ne demande pas, et les résultats ne contiennent aucun critère WCAG. Sans tag WCAG, en revanche, rien ne dit sur quelle version de WCAG la norme est fondée, et l’exécution est jugée selon 2.2. Pour une norme fondée sur 2.1, c’est faux : l’exécution rapporte des critères de 2.2, comme le 2.5.8, que la norme n’a pas. Donnez la version, et keepsParsing: true si la norme exige toujours le 4.1.1 Analyse syntaxique. Le index.js généré la lit dans requirements.js, où chaque version de la norme nomme la version de WCAG sur laquelle elle est fondée ({ version: '1.0', wcagVersion: '2.2' }) :
'city-1.0': {version: '1.0',tags: ['city'], // the pack's own rulesmappedRules: true, // and the core rules its map links to a requirementwcagVersion: wcagVersionOf('1.0') // judged by the WCAG version requirements.js gives for 1.0}
Les règles de core, avec leurs identifiants et leurs tags, figurent dans le catalogue des règles.
Points de liste de contrôle et exigences
Les points d’une liste de contrôle sont ses rollups : { id, title, checksIds }, chacun regroupant des règles en un seul résultat. Un point échoue quand l’une de ses règles échoue, et réussit quand elles réussissent. Quand aucune n’échoue mais que l’une renvoie cantTell, le point est cantTell lui aussi : il demande une revue humaine. Un pack fournit soit des profiles et des rollups, une liste de contrôle, soit une norme (standard), pas les deux.
Les exigences d’une norme viennent de deux tableaux qu’écrit le générateur, requirements.js (par version, le numéro, la formulation et les critères WCAG de chaque exigence) et rule-map.js (les exigences que vérifie chaque règle, et pourquoi). ruleMappedStandard() de @surea11y/core/pack en fait la norme : une agrégation par exigence, les exigences que nomme le résultat de chaque règle, et des vérifications des deux tableaux quand le pack est préparé.
Envoyer des constats vers des exigences
La ligne d’une règle dans rule-map.js peut envoyer ailleurs ses constats d’un code de raison, avec reasonCodes: { [code]: { requirements, outcome } } : requirements (par défaut, celles de la ligne) sont les exigences auxquelles ces constats comptent, [] pour aucune, et outcome: 'cantTell' fait d’un constat en échec une question à cet endroit. L’agrégation d’une exigence juge alors la règle sur les constats qui comptent pour elle : une norme peut ainsi poser une question sur une cause là où WCAG la fait échouer, ou classer deux causes sous deux tests :
'list-children-valid': {requirements: ['9.3.1'],reasonCodes: { LIST_ITEM_ROLE_OVERRIDDEN: { requirements: ['9.3.1', '9.3.2'], outcome: 'cantTell' } }}
Une ligne peut aussi envoyer des constats selon ce qu’ils consignent, avec findings: [{ when, requirements, outcome }] : when donne des valeurs que des champs du data.details d’un constat doivent avoir, nommés par leur chemin à points. Un constat suit la première entrée à laquelle il correspond, sinon son code de raison, sinon la ligne. Une norme peut ainsi classer les échecs d’une règle sous le test de leur taille de texte, bien qu’ils partagent un code de raison :
'contrast-minimum': {requirements: ['3.1', '3.2'],findings: [{ when: { 'typography.isLargeText': false }, requirements: ['3.1'] },{ when: { 'typography.isLargeText': true }, requirements: ['3.2'] }]}
Ne comparez que des champs qu’une règle garde stables : reasonCode sur toutes les règles, et les quelques autres que nomme la politique de stabilité de l’API de core sur GitHub (dont le rapport, le seuil et la typographie de contrast-minimum). Un champ qui change ou disparaît ne correspond à rien, et le constat suit son code ou la ligne. Les codes de raison de chaque règle sont listés sous « What a finding reports » dans le catalogue des règles de core sur GitHub.
Deux autres champs changent la façon dont une exigence est jugée. Une norme dont les exigences reprennent des critères WCAG un pour un, comme le chapitre 9 d’EN 301 549, passe applicability: 'wcag' à ruleMappedStandard : l’agrégation d’une exigence n’est alors notApplicable ou pass que là où le serait l’agrégation WCAG de ses critères, si bien que les deux concordent dans un même rapport. Une exigence que les règles de core ne vérifient pas entièrement, sans reprendre un critère, porte partial: true dans le tableau des exigences : son agrégation est alors cantTell là où toutes ses règles réussissent ou ne trouvent rien, puisqu’elles n’en ont vérifié qu’une partie. Une norme qui écrit ses propres agrégations indique les deux dans le meta de chacune, et envoie les constats dans meta.contributions ; StandardRollup dans pack.d.ts en donne la forme, et le guide des packs de core sur GitHub le détaille. Tout cela demande core 1.14.0.
Règles
Une règle a la forme des modules de règle de core : { id, meta, runInPage }, plus applicability quand elle ne s’applique que dans certains cas, avec le même ctx. runInPage(ctx) s’exécute dans la page : elle n’utilise donc que ctx, jamais une variable ou un module extérieur, et se contente de lire : les règles d’un pack s’exécutent après celles de core, et les helpers et options qu’elles reçoivent sont en lecture seule. La lecture seule est une règle, pas un bac à sable : rien n’empêche le code d’une règle d’en faire davantage, alors ne chargez que des packs en lesquels vous avez confiance (voir la politique de sécurité de core sur GitHub). Trois raccourcis demandent peu ou pas de code : une variante exécute une règle de core avec d’autres réglages ({ id, from: 'contrast-minimum', config: { normalTextRatio: 7 }, meta } ; un seuil prend un nombre fini supérieur à 0, un choix l’une de ses valeurs listées, et definePack refuse tout le reste), une surcharge remplace une règle de core (listée dans overrides, et dans le overriddenBuiltinIds des résultats), et une règle de core telle quelle ne demande que son identifiant dans un profil ou une agrégation. L’écriture d’une règle est décrite dans le guide d’écriture de règles de core sur GitHub.
La liste des réglages des règles de core sur GitHub donne chaque réglage que les règles de core offrent à une variante, avec sa valeur par défaut, les valeurs qu’il prend et ce qu’il change ; getChecksCatalog() donne la même chose dans le variantSettings de chaque règle. C’est ainsi que la version d’une exigence d’une autre norme, ou un choix que WCAG laisse à la personne qui évalue, s’exécute sur le code de core. Depuis core 1.14, contrast-minimum propose des choix pour le texte qui ressemble à un logo (logoText), une page dotée d’un bouton de passage à un contraste plus élevé (contrastSwitch) et le texte demi-gras (semiBold), et valid-lang propose codes: 'iso639' pour une norme, comme le RGAA, qui accepte tout code de langue ISO 639.
Comment une personne vérifie un cantTell
Une règle qui peut laisser un constat non tranché peut dire, par code de raison, comment une personne y répond, comme le font les règles de core (voir « Comment vérifier » dans le schéma de sortie) : quelques étapes à partir de l’élément que nomme le constat, puis ce qu’elle constate si cela réussit et si cela échoue.
meta.review = {GENERIC_LINK_NAME: {steps: ['Find the link the report names.', 'Read the text around it.'],passWhen: 'The text around the link says where it goes.',failWhen: 'Nothing near the link says where it goes, which fails WCAG 2.4.4.'}};
definePack vérifie la forme : de 1 à 6 étapes et un passWhen et un failWhen non vides par code de raison. Une règle qui déclare ses codes de raison dans meta.reasonCodes (['GENERIC_LINK_NAME']) ne donne des étapes que pour un code qu’elle y déclare ou qu’elle nomme dans son runInPage, si bien qu’un code mal saisi est refusé : les déclarer vaut la peine pour cette vérification. Les étapes sont en anglais, quelle que soit la langue de l’analyse, et restent hors de la page : runDomRulesInPage avec engineOptions.packs les ajoute au résultat, et pour un résultat issu d’une page, c’est addHowToCheck(result, { packs }). Une variante a les étapes de sa règle de core, traduites, et n’en a donc besoin d’aucune ; une surcharge, comme une règle personnalisée, n’en a pas. Core 1.14.0 et ultérieur.
Messages et langues
Chaque texte qu’affiche une règle a une clé, conservée par langue dans i18n/<locale>.json et listée dans les dictionaries du pack. Une analyse avec engineOptions.locale: 'fr' affiche les textes français ; dans une langue pour laquelle le pack n’a pas de dictionnaire, ils s’affichent en anglais. Les clés d’un pack lui sont propres et commencent par son espace de noms : un pack ne peut pas modifier un message de core dans une langue que core fournit.
Le tester, le linter et le documenter
- Les tests utilisent
@surea11y/core/testing, ce qu’utilisent les tests des règles de core :runa11yCoreOnHtml(html, { engineOptions })analyse du HTML dans jsdom, etassertRule(result, ruleId, outcome)vérifie une règle. Avec des packs, l’analyse les exécute à la fois depuis les objets pack et tels qu’une page les reçoit, si bien qu’une règle qui lit une variable extérieure à sa fonction échoue à son test. jsdom doit être installé comme dépendance de développement. - Les vérifications :
definePacklève une erreur qui nomme chaque problème, si bien que les tests du pack lui-même échouent ;checkPack(pack)renvoie la même liste sans lever d’erreur, en vérifiant aussi la version de core installée. Tous deux exécutent les vérifications propres d’une norme sur ses tableaux (validate) contre les règles de core et celles du pack, comme une analyse, si bien qu’une version sans sonwcagVersion, ou un constat envoyé vers une exigence absente du tableau, est refusé là plutôt que de faire ignorer le pack par chaque analyse. Core 1.14.0 et ultérieur. - Le lint (
npm run lint,@surea11y/core/eslint-plugin) soumet les règles du pack aux règles de lint auxquelles sont soumises celles de core. - La documentation (
npm run docs,surea11y-pack docs) écrit ledocs/RULE_CATALOG.mddu pack et exécute chaque exemple de sondocs/RULE_EXAMPLES.mddans Chromium (CHROMIUM_EXECUTABLE_PATHdésigne une autre version de Chromium) ;--checkéchoue quand l’un ou l’autre n’est plus à jour, pour la CI du pack.
Le publier
Le paquet généré est private. Choisissez sa licence, définissez license et retirez private dans package.json, puis lancez npm publish. Gardez @surea11y/core comme dépendance peer, avec la plage indiquée dans core. Le semver couvre ce sur quoi s’appuie un pack : un pack écrit pour core 1.11 continue de fonctionner avec les versions 1.x ultérieures. Pour vos propres utilisateurs, l’identifiant d’une règle et ses codes de raison sont ce que contiennent leurs baselines : ne renommez ou ne supprimez l’un d’eux que dans une version majeure du pack.
Le guide complet, d’un dossier vide à un pack publié, est le PACKS.md de core sur GitHub, et chaque champ figure dans la référence des packs de ENGINE_OPTIONS.md.