Correspondance de conformité WCAG
Comment les résultats de chaque règle se rapportent à un critère de succès (CS) WCAG, et ce que surea11y peut et ne peut pas vous dire sur la conformité globale.
Les trois couches
- Règles atomiques (
checksResults[]) : chacune porte une seule décision normative, par exemple « cet<img>a-t-il un attributalt». Voir le catalogue des règles pour toutes les règles. - Facettes : un CS WCAG est généralement plus vaste que ce qu’une seule règle peut trancher de façon déterministe. En interne, chaque CS est découpé en « facettes » nommées (par exemple, 1.1.1 Contenu non textuel a des facettes comme
img-alt-attr-present,text-alternative-quality,decorative-null) suivies danssrc/coverage/wcag-facets.js, chacune marquéefull(une règle la tranche avec un haut niveau de confiance),partial(une règle en tranche une partie ; voir les notes de périmètre de chaque règle) oumanual(aucune heuristique automatisée sûre n’existe). Lanceznpm run coveragepour régénérercoverage/coverage-report.md, le détail des facettes par CS. - Règles composites (agrégation par CS WCAG) (
rulesResults[]) : un agrégat généré de toutes les règles atomiques associées à un CS, qui vous donne un verdict pass/fail/cantTell/notApplicable par CS au lieu de devoir agréger vous-même des dizaines de résultats atomiques. Voir la liste complète (par exemple,wcag-1.1.1-non-text-contentagrège 21 règles atomiques).
Comment le résultat d’une règle composite est calculé
Une priorité déterministe, évaluée sur les contributeurs atomiques de la règle composite :
| Condition | Résultat composite |
|---|---|
Au moins un contributeur fail | fail |
Aucun fail, mais au moins un contributeur cantTell, ou un contributeur listé qui ne s’est pas exécuté du tout | cantTell |
Tous les contributeurs notApplicable, pour un critère dont les règles trouvent tous les cas auxquels il s’applique | notApplicable |
Tous les contributeurs notApplicable, pour tout autre critère | cantTell (composite.rollup.cantTell.partialCoverage) |
| Sinon (tous exécutés, aucun fail/cantTell, pas tous N/A), pour un critère dont les règles vérifient tout ce qu’il demande | pass |
| Sinon, pour tout autre critère | cantTell (composite.rollup.cantTell.partialCheck) |
Quand un critère est satisfait, et quand il est non applicable
Depuis la 1.14, un critère n’est satisfait que si ses règles vérifient tout ce qu’il demande, comme les WCAG ne le tiennent pour satisfait que s’il l’est entièrement. Des règles qui renvoient toutes pass disent seulement que les parties qu’elles vérifient sont respectées : html-lang-attr-present constate que <html lang="fr"> désigne une langue, pas que la page est en français (3.1.1), et page-title-present que la page a un titre, pas qu’il décrit la page (2.4.2). Un tel critère est cantTell, avec le code de raison composite.rollup.cantTell.partialCheck, et le rapport HTML indique qu’il a été vérifié en partie (« Vérifié en partie ») : une personne vérifie le reste. Les critères que leurs règles vérifient entièrement sont listés dans src/coverage/complete-applicability.js (COMPLETE_CHECK), chacun avec sa raison, et il n’y en a encore aucun : chacun demande quelque chose que les règles ne voient pas, qu’il s’agisse de savoir si une alternative textuelle, un libellé, un titre ou le texte d’un lien dit ce qu’il faut, ou comment un contenu qu’un script dessine ou modifie y répond. Aucune règle composite WCAG ne renvoie donc pass en 1.14.
De même, un critère n’est notApplicable que si ses règles trouvent tous les cas auxquels il s’applique : les liens pour 2.4.4 et 2.4.9, les éléments focalisables pour 2.4.3 et 2.4.7, les composants munis d’un libellé pour 2.5.3, le balisage pour 4.1.1. Les champs de formulaire (1.3.5, 3.3.2) et les cibles de pointeur (2.5.8) ne figurent pas dans cette liste : un champ construit avec un rôle ARIA, un champ sans autocomplete ou un élément qui n’est une cible que par un script peuvent se trouver sur une page où ces règles n’ont rien trouvé. Pour tout autre critère, des règles qui n’ont rien trouvé disent seulement que les cas qu’elles cherchent sont absents : un script qui redirige après un délai est une limite de temps (2.2.1) sans <meta http-equiv="refresh">, et un passage dans une autre langue (3.1.2) n’a pas forcément de lang. Dans les termes de WCAG-EM, « absent » suppose d’avoir regardé partout où le contenu pourrait se trouver ; un tel critère est donc cantTell, avec le code de raison composite.rollup.cantTell.partialCoverage.
Autrement dit : un fail composite est une violation réelle et déterministe, et un cantTell composite dont tous les contributeurs ont renvoyé pass (partialCheck) dit que toutes les vérifications automatisées de ce critère sont revenues sans problème, et que le reste revient à une personne. Ce n’est pas à lui seul une déclaration de conformité. Le tableau des facettes (coverage-report.md) montre quelles parties de chaque critère ont une couverture automatisée, et lesquelles une personne vérifie. Présentez-les comme « vérifications réussies, revue nécessaire » et « rien trouvé à vérifier », jamais comme réussis.
Le tableau data.details.contributors d’une règle composite (voir le schéma de sortie) liste chaque règle atomique et son résultat individuel : servez-vous-en pour voir précisément quelle(s) facette(s) ont conduit à un fail/cantTell, plutôt que de traiter la règle composite comme une boîte noire.
Une règle personnalisée (engineOptions.customRules) ou une règle d’un pack qui s’est exécutée compte dans la règle composite de chaque critère auquel elle est associée, par meta.wcagSc ou par une entrée WCAG dans meta.normativeMappings, comme une règle intégrée : si elle échoue, la règle composite échoue. La règle composite marque une règle personnalisée custom: true parmi ses contributeurs et la liste dans data.details.customChecksIds, puisque les règles personnalisées ne sont pas vérifiées par rapport aux spécifications comme le sont les règles intégrées. Une surcharge d’une règle intégrée compte là où sa propre correspondance l’indique : à la place de la règle intégrée quand elle est associée à ce critère, nulle part quand elle n’est associée à aucun (avec un avertissement).
Une analyse qui nomme des règles plutôt que des critères (par identifiant, comme includeRuleIds: ['link-name-present'], par un tag qui n’est pas un tag WCAG, par optInRules, ou en tant que bonnes pratiques) obtient aussi les règles composites des critères que ces règles vérifient ; un tag de critère comme wcag244 rapporte ce seul critère (voir Options du moteur). Avant la 1.12, une telle analyse n’en rapportait aucune. Le tableau ci-dessus les tranche : une règle qui échoue fait échouer le critère, et les règles que l’analyse n’a pas sélectionnées sont des contributeurs qui ne se sont pas exécutés, si bien que le critère est au mieux cantTell, vérifié en partie (reasonCode composite.rollup.cantTell.notSelectedChild, chacun de ces contributeurs marqué notSelected: true ; « Vérifié en partie » dans le rapport HTML).
Cibler un niveau de conformité (A / AA / AAA)
Nommez la version et le niveau WCAG dans runOnly.wcag :
// WCAG 2.2 Level A and AA:runDomRulesInPage(url, null, {}, { wcag: { version: '2.2', level: 'AA' } });// The same, and the best-practice rules, which test no WCAG criterion:runDomRulesInPage(url, null, {}, { wcag: { version: '2.2', level: 'AA' }, bestPractices: true });
Une cible exécute les règles de chaque critère en vigueur dans cette version à ce niveau et en dessous, selon le niveau de chaque critère dans cette version (WCAG_CRITERIA dans @surea11y/core/wcag) : 4.1.1 Analyse syntaxique fait donc partie des cibles 2.0 et 2.1, et non des cibles 2.2. Elle fixe aussi la version WCAG de l’exécution et le niveau des agrégations rapportées. Voir Sélectionner par cible WCAG.
Chaque entrée de WCAG_CRITERIA a aussi l’id que le W3C donne au critère dans WCAG 2.2 (non-text-content). Chaque correspondance WCAG que porte une règle renvoie à la section de son critère dans la Recommandation de cette version (url) et à son document Understanding (understandingUrl), pour 2.1 et 2.2 : les reporters utilisent le document Understanding comme lien d’aide d’une règle quand la règle n’en a pas de propre.
Avec des tags
Les mêmes cibles peuvent s’écrire avec des tags de niveau, dans runOnly.tags (ou engineOptions.tags.include) :
// WCAG 2.0 A and AA. Both tags are required: they are not cumulative.runDomRulesInPage(url, null, {}, { tags: ['wcag2a', 'wcag2aa'] });
Les tags de niveau ne s’emboîtent pas, et c’est l’erreur la plus facile à faire ici. Une règle porte un tag de niveau par critère de succès auquel elle correspond, et rien de plus : une règle associée uniquement à un critère AA porte le tag wcag2aa et pas wcag2a. Demander { tags: ['wcag2aa'] } seul exécute donc les 11 règles associées à un critère AA de la version 2.0, et non les 105 qui constituent une cible A + AA en 2.0. Listez chaque niveau que vous visez, ou utilisez runOnly.wcag, qui n’a pas ce problème. engineOptions.profile (wcag22-aa, en301549-v4.1.1, en301549-v3.2.1, section508) le fait aussi pour vous ; voir Profils de conformité.
Il en va de même d’une version des WCAG à l’autre (un critère introduit en 2.1 ou 2.2 ne porte que le tag de sa version d’origine), donc une cible de conformité complète est une union d’ensembles de tags. Voir le guide des options du moteur pour les ensembles prêts à l’emploi par version, y compris le seul critère que les WCAG 2.2 ont retiré plutôt qu’ajouté.
Le critère retiré est géré pour vous. Chaque exécution détermine une version WCAG cible (engineOptions.wcagVersion, sinon la wcagVersion du profil de pack que l’exécution applique, sinon la version de runOnly.wcag, sinon celle qu’impliquent vos tags de version, sinon 2.2) et la renvoie dans engine.wcagVersion. Avec une cible 2.2, une règle associée uniquement au CS 4.1.1 Analyse syntaxique ne peut pas renvoyer fail : elle s’exécute, rapporte ses occurrences et renvoie cantTell avec un champ wcagVersionScope qui explique cette conversion. Depuis la 1.14, avec une cible 2.0 ou 2.1, elle fait de même, avec wcagVersionScope.by: "errata" : les errata de 2023 du W3C à ces versions considèrent le 4.1.1 comme toujours satisfait pour un contenu en HTML ou en XML. Aucune analyse WCAG ne bloque donc sur le 4.1.1 ; seul un profil dont la norme en garde le texte, un profil qui déclare keepsParsing: true comme le fait en301549-v3.2.1, peut encore le faire échouer.
Les règles composites, contrairement aux règles atomiques, sont filtrées de façon cumulative. Le runner lit engineOptions.wcagLevel, sinon le niveau de runOnly.wcag, ou le niveau le plus élevé indiqué dans tags, et écarte toutes les règles composites au-dessus : demander ['wcag2a', 'wcag2aa'] ne renvoie donc aucune entrée rulesResults pour un CS de niveau AAA uniquement. wcagLevel ('A', 'AA' ou 'AAA') est l’option à définir pour une analyse qui nomme des vérifications plutôt qu’une cible : toutes les règles sélectionnées s’exécutent toujours, et seuls les critères rapportés sont limités à ce niveau, si bien qu’une analyse de contrast-computable au niveau 'AA' rapporte le 1.4.3 et non le 1.4.6, de niveau AAA. L’exécution l’enregistre dans engine.wcagLevel. Voir targetWcagLevelOf/isAllowedByTargetLevel dans src/core/dom-runner.js si vous avez besoin de l’ordre de priorité exact.
Omettez complètement tags (le comportement par défaut) et toutes les règles de tous les niveaux s’exécutent, sans suppression de règles composites.
EN 301 549
EN 301 549 passe dans son propre pack, @surea11y/pack-en301549, et quitte core en 2.0.0, avec ses profils, sa clé mappings et @surea11y/core/en301549. Tout fonctionne comme avant, avec un console.warn par processus. Pour changer, passez le pack dans engineOptions.packs : l’analyse prend chez lui les mêmes noms, et @surea11y/pack-en301549/public a les exports de la table. Voir comment core rend une norme obsolète.
Le chapitre 9 d’EN 301 549 reprend les critères de succès WCAG de niveau A et AA sous forme de clauses numérotées 9. suivi du numéro du critère : WCAG 1.4.3 est la clause 9.1.4.3. Quand une analyse les demande, chaque résultat atomique et composite porte, après ses entrées WCAG dans meta.normativeMappings, la clause de chacun de ses critères, une fois par version de la norme qui inclut ce critère. Une analyse les demande avec engineOptions.mappings: ['en301549'] (les deux versions) ou ['en301549:V3.2.1'] (une seule), ou en ciblant un profil EN 301 549, qui ajoute la version qu’il nomme ; par défaut, les résultats ne nomment que WCAG (voir Autres normes).
| Version | Fondée sur | Diffère de l’autre par |
|---|---|---|
| V3.2.1 (2021-03) | WCAG 2.1 A et AA, 50 critères | Inclut 9.4.1.1 Analyse syntaxique |
| V4.1.1 (2026-09) | WCAG 2.2 A et AA, 55 critères | Ajoute 2.4.11, 2.5.7, 2.5.8, 3.2.6, 3.3.7, 3.3.8 ; 9.4.1.1 est sans objet |
Chaque entrée de clause nomme aussi le critère qu’elle reprend, sous la forme wcagSc: ["1.4.3"], pour qu’une vue regroupée par critère (les suites JUnit, l’agrégation du rapport HTML) la place sous le bon critère, même pour une règle associée à plusieurs. Les critères AAA n’ont de clause dans aucune version. Les catalogues nomment les clauses avec les mêmes options : getChecksCatalog({ mappings: ['en301549'] }) dans le normativeMappings de chaque entrée, et les entrées composites renvoyées par getRulesCatalog({ mappings: ['en301549'] }) sous meta.standardMappings : on peut donc lire les clauses d’un critère sans lancer d’analyse.
La table est publique sous @surea11y/core/en301549, pour les outils qui ont besoin de la vue inverse, par exemple les critères qu’une version exige et qu’une analyse n’a pas couverts :
const { EN301549_CLAUSES, en301549ClausesForSc } = require('@surea11y/core/en301549');Object.keys(EN301549_CLAUSES['V3.2.1']); // the 50 WCAG 2.1 A and AA criteria V3.2.1 restatesen301549ClausesForSc('2.5.8'); // [{ version: 'V4.1.1', clause: '9.2.5.8', title: 'Target size (minimum)' }]
C’est une correspondance entre deux documents publiés, pas une déclaration de conformité : une clause sur un résultat indique quelle exigence d’EN 301 549 est ce critère WCAG, rien de plus. Quelle version une loi donnée exige ne relève pas du moteur.
EN 301 549 est une entrée d’un registre de normes que lisent l’exécuteur, le catalogue de règles et les générateurs de rapports. Une norme qui a ses propres verdicts (des exigences que vérifient ses propres règles, par exemple une norme nationale comme le RGAA), ou la liste de contrôle d’une organisation, prend la forme d’un pack : un paquet à part, hors de core, qu’une analyse reçoit dans engineOptions.packs et dont l’entrée rejoint le registre pour cette analyse, avec ses propres profils, règles et agrégations. Une règle personnalisée peut nommer n’importe quelle norme dans ses propres normativeMappings, mais seules les normes enregistrées (celles de core et celles des packs de l’analyse) ont un profil, un commutateur mappings, des règles optionnelles, des agrégations ou une place dans SARIF, JUnit et le rapport HTML. La façon d’intégrer une norme à core lui-même est décrite dans WCAG_CONFORMANCE.md, le guide des contributeurs sur GitHub.
Ce que ce moteur ne peut pas vous dire
Aucun outil automatisé, celui-ci compris, ne peut certifier une conformité WCAG complète. Ce n’est pas une limite propre à surea11y ; elle est inhérente aux WCAG elles-mêmes : une part importante des critères de succès exige un jugement humain (ce texte alternatif est-il exact, et pas seulement présent ; ce message d’erreur est-il compréhensible) ou des tests dynamiques que l’architecture d’analyse statique du DOM de ce moteur ne permet pas du tout (détection des pièges au clavier, mise en page et redistribution réelles au zoom). Voir les limites connues pour la liste complète et explicite de ce qui est hors périmètre, et pourquoi.
Ce que surea11y peut vous apporter, honnêtement :
- Les règles intégrées à core sont écrites pour ne renvoyer
failque sur une violation normative et déterministe de la norme et de la version ciblées, et pour renvoyercantTellquand elles ne peuvent pas être sûres. C’est un objectif de conception, pas une garantie : les limites connues listent des cas limites où une règle peut encore se tromper, les occurrences de chaque constat montrent sur quoi il a été décidé, et le catalogue des règles donne la confiance de chaque règle. La précision d’une règle personnalisée, d’une règle de pack ou d’une surcharge dépend de la façon dont elle a été implémentée. Par défaut, cette norme est WCAG. Une règle portant sur une exigence que seule une autre norme impose est optionnelle : par défaut, elle ne s’exécute que si vous ciblez cette norme (voir Règles optionnelles).engineOptions.optInRulesdébloque les règles optionnelles sans retirer le profil ni les filtres de l’analyse. AvecoptInRules: 'all'et sans profil ni filtre, l’exécution couvre toutes les règles et ne vise aucune norme en particulier ; une liste de tags ne débloque que ces groupes de règles optionnelles.engine.optInRulesliste les tags dont les règles se sont exécutées. Sous un profil WCAG, une règle débloquée ne s’exécute que si ses tags correspondent à ce profil, et l’exécution vise toujours WCAG. Parmi les règles de core, une analyse qui vise WCAG n’exécute donc que celles des critères ciblés, plus les règles de bonnes pratiques si vous les ajoutez. Une règle de bonne pratique ne nomme aucun critère, et peut quand même renvoyerfaillà où ce qu’elle trouve est en soi un obstacle, comme un titre vide (empty-heading) ; aucune règle composite WCAG n’en tient compte. Une règle personnalisée, une surcharge ou une règle de pack sans tag optionnel s’exécute quand la sélection l’inclut, et peut alors renvoyer unfailpour une vérification qui lui est propre et que WCAG n’exige pas. - Chaque
cantTellest un signalement explicite pour une revue humaine, pas une incertitude passée sous silence. - Une règle composite ne renvoie jamais
passpour un critère que ses règles ne vérifient qu’en partie : quand elles renvoient toutespass, elle estcantTell(composite.rollup.cantTell.partialCheck), donc unpassne passe jamais sous silence une partie qu’aucune règle ne vérifie. - Le tableau de couverture des facettes vous indique exactement quelles parties de quels CS n’ont aucune couverture automatisée, et donc ce qui revient à une personne.
L’absence de fail composite sur tous les CS de votre niveau cible signifie : toutes les vérifications automatisables pour ce niveau sont revenues sans problème. C’est la part automatisable de la conformité, énoncée précisément, et non un substitut à la revue manuelle que les WCAG exigent elles-mêmes.