FAQ
« Mes résultats ont changé après la mise à jour vers core 1.14 »
La 1.14 renvoie plus de cantTell et affirme moins, délibérément. Les changements les plus susceptibles de se voir dans un pipeline :
- Aucune règle composite WCAG ne renvoie
pass. Un critère n’est satisfait que si ses règles vérifient tout ce qu’il demande, et aucun n’est encore listé comme vérifié entièrement : un critère dont toutes les règles renvoient pass est donc cantTell (composite.rollup.cantTell.partialCheck). Un critère dont les règles n’ont rien trouvé n’est notApplicable que pour 2.4.3, 2.4.4, 2.4.7, 2.4.9, 2.5.3 et 4.1.1, et cantTell sinon (composite.rollup.cantTell.partialCoverage). Une règle en échec fait toujours échouer son critère. Un tableau de bord ou un contrôle qui compte les critères satisfaits n’en verra aucun ; voir l’entrée ci-dessous et Correspondance de conformité WCAG. - Deux règles demandent davantage.
page-title-patterns pose la question pour chaque titre de page, pas seulement les titres génériques ou issus d’un modèle, car savoir si un titre décrit sa page revient à une personne (G88). duplicate-id pose la question du 4.1.1 aussi avec les WCAG 2.0 et 2.1, que les errata de 2023 considèrent comme satisfait, sauf si un profil déclare keepsParsing: true, comme en301549-v3.2.1. - Onze nouvelles règles, dont les premières pour le 3.3.1 (
invalid-field-error-message), le 4.1.3 (live-region-announcements) et le 1.4.5 (image-of-text). La plupart posent une question ; click-handler-keyboard-operable, focus-not-removed-by-script et aria-owns-valid peuvent échouer, tout comme aria-valid-attr-value, qui vérifie désormais les contraintes entre attributs (aria-valuemin au-dessus de aria-valuemax, par exemple). Voir le catalogue des règles. - Certains échecs deviennent des questions, et de nouveaux échecs apparaissent.
td-has-header pose une seule question sur un grand tableau sans cellule d’en-tête au lieu de faire échouer chaque cellule, et form-control-single-label pose la question pour un champ à deux libellés. Le contraste est mesuré sur une plus grande part de ce que la page dessine (le texte sur le canevas de la page dans chaque schéma de couleurs, ::first-line, font-size-adjust, un placeholder dont la page a choisi les couleurs) : de nouveaux échecs de contraste peuvent donc apparaître. - Certains constats ont un nouveau code de raison. Dix-sept règles qui n’en donnaient pas en donnent un, et quelques codes sont scindés selon la cause. Une baseline écrite par la 1.13 y correspond toujours jusqu’à la 1.15 ; réécrivez-la avec
migrateBaselineEntries (voir l’entrée ci-dessous). Une alerte Code Scanning pour un tel constat se ferme et revient comme nouvelle. - Chaque
cantTell dit comment y répondre. Le constat d’une règle de core porte les étapes de howToCheck, et certains portent un lean, le sens vers lequel pointent les indices de la page. Le résultat reste cantTell, et rien ne bloque sur un lean sans que vous le demandiez (leanFailAs). Le rapport HTML regroupe ces constats en échecs probables, réussites probables et constats à examiner par une personne. Voir le schéma de sortie. - La 1.14.1 échoue moins sur l’espacement du texte.
text-spacing-content-loss ne fait plus échouer un texte que la page ne perd pas. Une page dont le défilement est bloqué par body { position: fixed }, comme beaucoup derrière une bannière de consentement, et une boîte fixe plus haute que le viewport donnent lieu à une question VIEWPORT_SCROLL_LOCKED ; un texte coupé par une boîte que le script de la page ajuste à sa taille, comme le libellé flottant d’un champ Angular Material, à une question TEXT_CLIPPED_SCRIPT_SIZED. Une entrée de baseline ou une alerte Code Scanning pour l’un d’eux se ferme comme pour un bug corrigé. Les étapes de 36 questions demandent aussi si un autre moyen sur la page fait la même chose avant de mener à un échec.
La liste complète se trouve dans le CHANGELOG.md de core.
« runOnly a levé une erreur : no rule or tag named …, ou either rule ids or tags »
Depuis la version 1.10.0, un runOnly passé sous forme de simple tableau ou de chaîne est lu comme des identifiants de règles ou comme des tags (voir ENGINE_OPTIONS.md). Il lève une erreur dans deux cas :
- Une valeur qui ne désigne aucune règle ni aucun tag, en général une faute de frappe :
runOnly: ['img-alt-presnt']. Vérifiez l’identifiant avec getChecksCatalog(). La casse n’y est pour rien : les identifiants de règles et les tags sont reconnus quelle que soit leur casse, donc IMG-ALT-PRESENT sélectionne img-alt-present. - Des identifiants de règles et des tags dans un même tableau :
runOnly: ['img-alt-present', 'wcag2a']. Utilisez la forme objet pour les combiner :
runOnly: {
includeRuleIds: ['img-alt-present'],
tags: ['wcag2a'],
includeMode: 'or',
}
Avant la version 1.10.0, un simple tableau était ignoré et toutes les règles s’exécutaient.
La forme objet, { includeRuleIds }, { tags } ou { type: 'tag', values }, ainsi que engineOptions.rules.include / .tags.include, lèvent aussi une erreur, avec code: 'INVALID_RUN_ONLY', quand aucune valeur de la liste ne désigne une règle ou un tag (runOnly.tags: no tag named "wcag2.2aa".). Auparavant, elles n’exécutaient aucune règle, ce qui se lit comme une analyse sans constat. Une seule valeur inconnue à côté de valeurs connues, ou dans une liste d’exclusion, ne produit qu’un console.warn.
Avec le includeMode: 'and' par défaut, une sélection qui contient à la fois des tags et des identifiants de règles n’exécute que les règles qui correspondent aux deux. .withTags('wcag2a').withRules('region') n’exécute rien, puisque region est une bonne pratique sans tag wcag2a, et une analyse sans aucune règle n’a aucun échec. Le moteur affiche un avertissement dans la console de la page, qu’une exécution de tests ne montre pas, et ne lève INVALID_RUN_ONLY qu’avec strictOptions. Pour exécuter l’un ou l’autre, demandez-le : .includeMode('or') dans les bindings, includeMode: 'or' dans runOnly (ci-dessus), ou --include-mode or dans la CLI. À partir de sa version 1.3.0, la CLI se termine avec le code 2 et cette suggestion quand les deux n’ont aucune règle en commun.
Pour repérer avant l’exécution une sélection qui ne retient rien, vérifiez plannedChecks() sur le builder : les règles que l’analyse exécutera, calculées sans page (un chainable dans Cypress) :
const builder = new A11yCoreBuilder({ page })
.withTags('wcag2a')
.withRules('region')
.includeMode('or'); // the wcag2a rules, plus region
if (builder.plannedChecks().length === 0) {
throw new Error('This selection runs no rule.');
}
Voir Options du moteur pour la façon dont les parties d’une sélection se combinent.
« runOnly.tags: no rules for WCAG 2.2 Level A ("wcag22a") »
Les neuf tags de version et de niveau WCAG, de wcag2a à wcag22aaa, sont toujours reconnus, mais trois ne portent aucune règle : wcag21aaa, wcag22a et wcag22aaa, dont les critères n’ont aucune vérification automatisée. En nommer un à côté d’autres tags journalise cette ligne avec console.info : le reste de la liste s’exécute, et ces critères demandent une vérification manuelle. Passez engineOptions.logUntestedWcag: false pour l’omettre, ou sélectionnez plutôt par cible, ce qui ne nomme aucun tag : runOnly: { wcag: { version: '2.2', level: 'AA' } } (voir Sélectionner par cible WCAG). Une liste composée uniquement de tels tags n’exécuterait rien : elle lève donc INVALID_RUN_ONLY avec la même explication.
« runOnly.wcag.version must be … » ou « runOnly.bestPractices must be true or false … »
runOnly.wcag accepte { version, level }, avec pour version l’une des valeurs '2.0', '2.1', '2.2' (une chaîne : 2.2 sous forme de nombre est refusé) et pour level l’une des valeurs 'A', 'AA', 'AAA' ; runOnly.bestPractices accepte true ou false. Toute autre valeur lève INVALID_RUN_ONLY en nommant le champ et la valeur, plutôt que d’exécuter une sélection que vous n’aviez pas voulue.
« engineOptions: unknown option "lcoale" (did you mean "locale"?) », ou une analyse a levé INVALID_ENGINE_OPTIONS
Une clé d’option à une ou deux lettres près d’une clé connue, quelle que soit la casse, déclenche un avertissement et est ignorée : l’analyse s’exécute comme si elle n’était pas là, corrigez donc l’orthographe. Les autres clés inconnues restent silencieuses, puisqu’une règle personnalisée peut lire ses propres réglages dans ctx.engineOptions. Avec engineOptions.strictOptions: true, l’analyse lève plutôt une erreur, avec code: 'INVALID_ENGINE_OPTIONS', pour toute clé que le moteur ne lit pas (clés imbriquées comprises) et pour une valeur de mauvais type ou hors de son ensemble, en nommant chacune avec la clé connue la plus proche ; la propriété problems de l’erreur les liste. Voir Repérer les erreurs dans les options.
« Ma règle personnalisée échoue, mais pas l’agrégat WCAG »
Une règle personnalisée compte dans l’agrégat WCAG (rulesResults) de chaque critère auquel elle est associée : donnez-lui meta.wcagSc: ['1.1.1'], ou une entrée WCAG dans meta.normativeMappings. Sans l’un ni l’autre, elle n’entre dans aucun agrégat. Une règle personnalisée qui ne s’est pas exécutée (listée dans skippedCustomRules) ne compte pas non plus. Une surcharge d’une règle intégrée ne compte que là où sa propre correspondance l’indique : si elle n’en déclare aucune, elle quitte les agrégats dans lesquels comptait la règle intégrée, et un avertissement (customRules: "…" overrides a built-in rule but maps to none of the criteria of …) les nomme ; donnez-lui les critères de la règle intégrée pour qu’elle y reste.
« Mon analyse ciblée renvoie notApplicable partout », ou « contextSelector a levé une erreur : … is not a valid CSS selector »
Depuis la version 1.10.0, un contextSelector qui ne correspond à aucun élément n’analyse rien, et un sélecteur que le navigateur ne sait pas interpréter lève une erreur (voir ENGINE_OPTIONS.md).
- Tout est
notApplicable : regardez result.contextMatch. elementCount: 0 signifie que le sélecteur n’a rien trouvé, donc que rien n’a été analysé : ce n’est pas un résultat sans erreur. unmatchedSelectors nomme le sélecteur. Vérifiez qu’il ne contient pas de faute de frappe, ou que l’élément est déjà sur la page au moment de l’analyse (un contenu rendu par un script après le chargement, par exemple). - L’analyse a levé une erreur avec
code: 'INVALID_CONTEXT_SELECTOR' : le sélecteur cité dans le message, et dans la propriété selector de l’erreur, n’est pas du CSS valide, par exemple '#main[' ou un combinateur >>>. Ou bien la portée n’est pas du tout un sélecteur, par exemple un élément ou un objet { include, exclude }, et le message indique ce qui a été passé : passez un sélecteur qui correspond à l’élément, et les exclusions dans engineOptions.excludeSelectors.
Dans chacun de ces cas, toute la page était analysée auparavant : jusqu’à la version 1.10.0 pour un sélecteur qui ne correspondait à rien ou ne pouvait pas être interprété, et dans la version 1.10.0 encore pour une portée qui n’est pas un sélecteur.
« Une analyse a levé PAGE_BUILTINS_BROKEN »
Les scripts de la page ont modifié un objet natif JavaScript sur lequel le moteur s’appuie (Array.prototype.filter, Map, JSON.stringify et d’autres), si bien qu’une analyse de cette page pourrait donner un résultat qui paraît correct et qui est faux. Le message de l’erreur et sa liste broken nomment chacun d’eux. Analysez la page avant que ses scripts ne les modifient (injectez le bundle plus tôt, ou analysez un build sans le script en cause). Voir Limites connues.
« Mon analyse ne se termine jamais »
Quelque chose dans la page bloque l’analyse : un getter ou un script qui ne rend jamais la main, ou une boucle dans un pack ou une règle personnalisée. Rien à l’intérieur de la page ne peut arrêter une analyse une fois lancée : placez donc la limite de temps là où vous la lancez, et abandonnez l’onglet quand elle est dépassée. Autonome montre comment faire avec Playwright et Puppeteer ; dans jsdom, exécutez l’analyse dans un worker thread ou un processus enfant.
« Une analyse prend plus de temps après une mise à jour »
L’essentiel du temps d’une analyse est consacré au calcul des styles et de la mise en page dans le navigateur : il dépend donc de la page, de la machine et du navigateur autant que du moteur. Une version peut tout de même le modifier : une règle qui vérifie désormais une plus grande partie de la page coûte davantage, et le CHANGELOG.md de core indique quand une version change le coût d’une règle. PERFORMANCE.md, sur GitHub, consigne la durée d’une analyse version après version, en proportion d’une version antérieure sur les mêmes pages, ainsi que la façon dont elle est mesurée. Une règle plus lente qui trouve davantage paie peut-être ce qu’elle trouve désormais ; une règle plus lente avec les mêmes résultats est une régression qui mérite d’être signalée.
« Un pack ne s’est pas exécuté », ou skippedPacks n’est pas vide
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à) est ignoré : l’analyse s’exécute sans lui, affiche un avertissement et le liste dans result.skippedPacks avec la raison ; avec strictOptions, elle lève une erreur. 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. Les bindings le signalent comme une lacune 'pack-skipped', test-matchers fait échouer l’assertion, et la CLI se termine avec le code 2. 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. Voir Packs.
« Ma règle personnalisée renvoie toujours cantTell sans raison claire »
Regardez d’abord le champ error du résultat : s’il indique "<something> is not defined", votre runInPage fait référence à une variable extérieure au corps de la fonction (une const au niveau du module, un helper importé, tout ce qui n’est pas atteint via ctx.*). C’est un piège réel et courant : runInPage est sérialisé en texte source puis réévalué plus tard dans le contexte de la page, donc le build ne le détecte jamais, seule l’exécution de la règle le révèle, et l’échec ressemble à un résultat normal (quoique peu informatif), pas à un plantage. Voir RULE_AUTHORING.md §1.1 pour l’explication complète et la correction (déplacer la valeur dans runInPage, ou la faire passer par ctx.rule/ctx.helpers).
« Une règle dépendant de la géométrie (par exemple target-size-minimum) indique toujours notApplicable »
jsdom seul (sans vrai navigateur) n’implémente pas la mise en page CSS : getBoundingClientRect() renvoie toujours une géométrie nulle. Les règles qui ont besoin d’une vraie mise en page indiquent notApplicable sous jsdom plutôt que de deviner. Passez plutôt par un vrai navigateur (Puppeteer/Playwright, voir INTEGRATION.md, Pattern 2) pour obtenir de vrais résultats de ces règles. Voir LIMITATIONS.md.
« Un composant web revient en cantTell (name_internals) »
Un élément personnalisé qui se nomme, définit ses états ou se donne un rôle par un ElementInternals conservé dans un champ privé (#internals) est exposé par le navigateur, mais aucun script de la page ne peut le relire. Depuis la 1.12, le moteur lit les internals conservés dans une propriété (_internals, internals, …), et pose la question pour tout autre élément personnalisé défini sans nom lisible ni état requis (name_internals, state_internals) au lieu de le faire échouer.
Pour que l’analyse puisse juger ces éléments, installez le hook des internals avant l’exécution des scripts de la page : installInternalsHook() dans chaque binding (sur la page ou le contexte dans Playwright, la page dans Puppeteer, le driver dans Selenium, le navigateur dans WebdriverIO, le fichier de support ou onBeforeLoad dans Cypress) et dans le fichier de configuration de test-matchers, ou internalsHookScript() de core pour une page à vous. La propriété engine.environment.internalsHook du résultat indique s’il s’est exécuté ('early' ou 'late'). Le hook permet à n’importe quel script de la page de lire les internals de chaque composant : il est donc destiné aux exécutions de test et d’audit. Voir Limites connues.
« Je ne vois que des occurrences fail/cantTell : où est la liste des éléments qui ont réussi ? »
Par conception, ce moteur n’énumère jamais les éléments qu’une règle a validés, seulement ceux qu’elle a signalés. Un outcome: 'pass' global pour une règle signifie « des cibles applicables existaient et aucune n’a été signalée », et occurrences vaut []. Les règles de contraste font exception : leur pass porte une occurrence au selector vide qui décrit l’analyse (la quantité de texte éligible et calculable), et non un élément. Si vous devez savoir quels éléments précis ont été vérifiés et jugés corrects, cette information n’est pas exposée actuellement : voir OUTPUT_SCHEMA.md.
« Une règle qui devait se déclencher a renvoyé notApplicable / n’a rien trouvé sur une page dont je sais qu’elle a le problème »
Deux causes courantes, de la plus probable à la moins probable :
- L’élément est exclu de l’arbre d’accessibilité :
aria-hidden="true", display: none, visibility: hidden, hidden, ou un ancêtre inert. La plupart des règles ignorent le contenu déjà invisible pour les technologies d’assistance, car vérifier un élément masqué n’aurait pas de sens et pourrait produire un fail trompeur sur un contenu qu’aucun utilisateur ne rencontre. Certaines règles se soustraient explicitement à ce filtrage quand il n’aurait pas de sens (par exemple no-autoplay-audio : un audio masqué produit quand même du son) ; consultez le commentaire d’en-tête du fichier de la règle concernée (@applicability) dans src/checks/. excludeSelectors : si vous l’avez configuré (directement ou hérité d’une configuration partagée), vérifiez que l’élément en question n’y correspond pas. Rappelez-vous que cette option peut aussi être limitée à une seule règle via engineOptions.rules[ruleId].excludeSelectors (voir ENGINE_OPTIONS.md) : si une règle censée se déclencher renvoie toujours notApplicable/pass pour un seul élément, vérifiez si cette règle a sa propre liste d’exclusion configurée, et pas seulement la liste globale.
« Toutes les vérifications renvoient pass : pourquoi chaque critère WCAG est-il cantTell ? »
Depuis la 1.14, une règle composite WCAG dans rulesResults ne renvoie pass que pour un critère dont les règles vérifient tout ce qu’il demande, et aucun critère n’est encore listé ainsi (COMPLETE_CHECK dans src/coverage/complete-applicability.js). Quand toutes les règles d’un critère renvoient pass, sa règle composite est cantTell, avec le reasonCode composite.rollup.cantTell.partialCheck : les parties que vérifient les règles sont respectées, et une personne vérifie le reste. html-lang-attr-present constate que <html lang="fr"> désigne une langue, pas que la page est en français. De même, un critère dont les règles n’ont rien trouvé n’est notApplicable que si elles trouvent tous les cas auxquels il s’applique, et sinon cantTell, avec composite.rollup.cantTell.partialCoverage. Une règle en échec fait toujours échouer son critère. Présentez-les comme « vérifications réussies, revue nécessaire » et « rien trouvé à vérifier », jamais comme réussis ; voir Correspondance de conformité WCAG.
« Une analyse sans échec signifie-t-elle que la page est conforme WCAG ? »
Non : voir WCAG_CONFORMANCE.md. Le pass d’une règle signifie que les vérifications exécutées sont revenues sans erreur sur la partie de la page qu’elles ont analysée. Une analyse limitée à une partie de la page, à certaines règles, ou exécutée sous jsdom vérifie moins qu’elle ne le pourrait. Une part non négligeable de WCAG exige un jugement humain (texte alternatif pertinent, messages d’erreur compréhensibles) ou des tests dynamiques que l’architecture de ce moteur ne permet pas du tout (pièges au clavier, redistribution du contenu en cas de zoom) : voir LIMITATIONS.md pour la liste explicite, volontairement non exhaustive.
« Dois-je traiter cantTell comme un échec ? »
Traitez-le comme « une personne doit regarder » : par conception, ce n’est ni une réussite ni un échec. Un choix courant consiste à consigner les résultats cantTell sans faire échouer la CI, car faire échouer un build sur quelque chose que le moteur n’a explicitement pas pu déterminer habitue les gens à ignorer le contrôle. Une équipe qui veut que chacun soit examiné avant une fusion peut au contraire bloquer sur eux ; dans les deux cas, gardez-les à part des échecs. Entre les deux, un build peut ne bloquer que sur les constats qui penchent vers l’échec, là où les indices de la page pointent vers un échec : leanFailAs dans les rapports JUnit et SARIF, ou leaningFailures(result) (voir le schéma de sortie). Chaque constat cantTell d’une règle de core dit comment y répondre dans howToCheck. Voir POLICY.md si vous voulez modifier ce comportement (par exemple via un contrat de politique personnalisé), et INTEGRATION.md pour un exemple concret de contrôle en CI.
« Que se passe-t-il si une langue n’est que partiellement traduite ? »
Les clés manquantes se replient sur l’anglais chaîne par chaîne (jamais de résultat vide ou cassé) : une langue partiellement traduite reste donc utilisable au lieu d’échouer. Voir I18N.md pour le mécanisme et la couverture actuelle. La parité des clés est imposée par le build de core lui-même, pas espérée : toute clé anglaise nouvelle ou renommée est reportée dans chaque autre fichier de langue, avec le texte anglais jusqu’à ce que quelqu’un la traduise, si bien qu’une langue peut être en retard sur la formulation, jamais sur les clés.
« runDomRulesInPage ou runa11yCoreInPage : lequel me faut-il ? »
runDomRulesInPage si vous l’appelez directement dans le même processus Node (jsdom). runa11yCoreInPage si vous transmettez la fonction elle-même à un autre realm JS, presque toujours page.evaluate dans Puppeteer/Playwright, qui sérialise la fonction en texte source et la réexécute dans l’onglet du navigateur (qui n’a pas accès à la portée de vos modules Node). Voir INTEGRATION.md pour les deux patterns détaillés.
« Après une mise à jour, ma baseline signale des constats connus comme nouveaux et leurs entrées comme obsolètes »
L’identité d’un constat comprend son code de raison, et la 1.14 a donné des codes à dix-sept règles qui n’en avaient pas et a scindé quelques codes selon la cause. Une baseline écrite par la 1.13 continue de correspondre à ces constats avec la 1.14.x, grâce à src/baseline-moves.json ; un constat dont le résultat a changé avec la mise à jour est nouveau ou disparaît malgré tout, et le journal des modifications explique pourquoi. Réécrivez les codes du fichier avec migrateBaselineEntries de @surea11y/core/baseline, ou régénérez-le et examinez le diff, avant la 1.15. Voir Baseline / allowlist.
« css-orientation-lock pose une question avec STYLESHEETS_NOT_READABLE »
Le script d’une page ne peut pas lire une feuille de style servie par une autre origine (un CDN, un service de polices) : les règles qui lisent le CSS ne peuvent donc pas savoir ce qu’elle contient. Donnez son texte à l’analyse dans engineOptions.styleSheetTexts : preloadStyleSheets(), ou runa11yCoreInPageAsync dans le bundle navigateur, récupère celles que le serveur partage, et un outil qui pilote le navigateur peut récupérer les autres depuis l’extérieur de la page. Voir « Style sheets from another origin » dans INTEGRATION.md, le guide d’intégration sur GitHub.