Rapport EARL
@surea11y/core/earl produit les résultats d’analyse au format EARL 1.0 en JSON-LD : le vocabulaire publié par le W3C pour affirmer « cet outil a testé tel élément et obtenu tel résultat », et le format que le groupe communautaire ACT Rules accepte comme rapport d’implémentation.
const { renderEarlReport } = require('@surea11y/core/earl');const report = renderEarlReport(result, {assertor: { name: 'surea11y', version: '1.14.1' },mode: 'earl:automatic'});fs.writeFileSync('earl.jsonld', JSON.stringify(report, null, 2));
À quoi il sert
Deux publics, qui veulent le même document pour des raisons différentes.
Un rapport d’implémentation indique au groupe communautaire ACT Rules comment ce moteur se comporte sur ses cas de test, et c’est ce qui permet à un moteur d’être listé aux côtés des autres implémentations. Être listé ne vaut pas approbation, et le W3C ne vérifie pas les données : la formulation exacte est « listé comme implémentation ACT », jamais « certifié W3C ». Le rapport de ce moteur est publié à l’adresse https://surea11y.github.io/act-report/act-report.jsonld et régénéré par scripts/act-report.js. --runner=chromium génère le même rapport à partir d’une analyse dans Chromium, comme les bindings pour navigateur exécutent le moteur, avec l’assertor nommé « SureA11y in a browser » et la version de Chromium dans la description du rapport.
Un utilisateur y gagne un format d’échange. EARL est ce que lisent les outils d’accessibilité lorsqu’ils doivent combiner des résultats de plusieurs sources (par exemple une analyse automatisée et un audit manuel, ou plusieurs moteurs), car chaque assertion indique qui l’a émise et comment. C’est utile, que quelque chose soit un jour soumis quelque part ou non.
En quoi il diffère des autres rapports
Le rapport SARIF et le rapport HTML ne contiennent que les violations : ils rapportent les occurrences fail et cantTell et écartent tout le reste. EARL fait l’inverse. Chaque règle exécutée devient une assertion, pass et inapplicable compris, car un rapport d’implémentation affirme ce que le moteur a décidé partout. Une règle restée muette parce qu’elle n’a rien trouvé d’applicable est une information, pas du bruit : c’est ce qui permet au lecteur de distinguer « ce moteur a vérifié et n’a rien trouvé à vérifier » de « ce moteur n’implémente pas du tout cette règle ». Chaque assertion porte sur une règle et un sujet : le passed d’une règle n’est pas un verdict sur le critère WCAG auquel elle est associée, dont la plupart ont des parties qu’aucune règle ne couvre (voir Correspondance de conformité WCAG).
Structure
Le graphe est regroupé par sujet plutôt que d’être une liste plate d’assertions :
{"@context": "https://www.w3.org/WAI/content-assets/wcag-act-rules/earl-context.json","@graph": [{"@type": "TestSubject","source": "https://example.test/","assertions": [{"@type": "Assertion","test": { "title": "img-alt-present", "isPartOf": ["WCAG2:non-text-content"] },"result": { "outcome": "earl:failed" },"assertedBy": {"@type": "Assertor","name": "surea11y","release": { "@type": "Version", "revision": "1.14.1" }},"mode": "earl:automatic"}]}]}
sourceest l’URL analysée, ouabout:blanklorsqu’un résultat n’en contient pas. Lorsque plusieurs résultats n’en contiennent pas, chacun devient un sujet à part entière,about:blank#result-<n>selon sa position dans la liste (à partir de 1), au lieu de tous fusionner en un seul.test.titleest l’id de règle du moteur lui-même. Au sens d’ACT, une règle est la procédure exécutée par l’implémentation, et c’est exactement ce que désigne un id de règle.test.isPartOfliste les critères de succès auxquels cette règle correspond, sous la formeWCAG2:<criterion-id>: chaque entrée denormativeMappingsdont lestandardestWCAGou absent (comme dans SARIF) et qui n’a pas detype. Entièrement omis pour une règle qui ne revendique aucun critère :aria-allowed-role,empty-headingetlandmark-role-name-presentsont les règles automatiques du moteur dans ce cas, et une liste vide se lirait comme « ne correspond à rien que nous ayons trouvé » plutôt que « ne correspond volontairement à aucun ». Une telle règle peut tout de même renvoyerfail(empty-headingfait échouer un titre sans nom) : son assertion est alorsearl:failedsansisPartOf, l’échec d’une règle qui ne revendique aucun critère WCAG.isPartOfsuit le niveau et la version WCAG de l’analyse. Un critère que l’analyse a écarté derulesResultsà cause d’eux n’est pas listé, comme 1.4.6 (AAA) sousengineOptions.wcagLevel: 'AA', ou 4.1.1 quandincludeRuleIds: ['duplicate-id']s’est exécuté sous WCAG 2.2, qui l’a supprimé ; une règle dont chaque critère est écarté n’a pas d’isPartOf. L’assertion elle-même reste, puisqu’elle porte sur la règle. Un critère que l’analyse signale est listé, comme une agrégation nommée par son id au-dessus du niveau, ou 4.1.1 dans une analyse 2.2 qui l’a sélectionné, tout comme chaque critère d’un résultat qui n’enregistre niengine.wcagLevelniengine.wcagVersion.assertedByest toujours présent, sauf si vous passezassertor: null: il vaut par défautsurea11yet la version du moteur que portent les résultats.moden’apparaît que si vous le fournissez.
Les id de critères sont dérivés du titre du critère lui-même (Non-text Content → non-text-content). normativeMappings contient aussi des références aux documents Understanding, qui ont un type, et des entrées d’autres normes (clauses d’EN 301 549, exigences ou points de la liste de contrôle d’un pack), qui nomment leur propre standard ; les unes comme les autres peuvent partager un numéro de requirement avec un critère. Seules les entrées dont le standard est WCAG ou absent et qui n’ont pas de type sont lues comme des critères de succès. Aucun niveau de conformité n’est nécessaire. isPartOf ne liste que des critères WCAG ; les entrées des autres normes en sont écartées.
Un résultat compact (engineOptions.output.detail: 'findings', voir Schéma de sortie) est lu comme le résultat complet qu’il représente ; il est donc rapporté de la même façon.
Résultats
| Moteur | EARL |
|---|---|
pass | earl:passed |
fail | earl:failed |
cantTell | earl:cantTell |
notApplicable | earl:inapplicable |
earl:untested n’a pas d’équivalent : une règle qui ne s’est pas exécutée ne produit aucun résultat sur lequel se prononcer, elle ne donne donc lieu à aucune assertion plutôt qu’à une assertion « non testé ».
cantTell ne pénalise pas l’implémentation au regard d’ACT. Les règles de cohérence d’ACT permettent à une implémentation automatisée de répondre « impossible à déterminer » sur certains exemples (mais pas tous) tout en restant considérée comme cohérente. Ce qu’une implémentation partiellement cohérente ne peut pas faire, c’est produire un faux positif : échouer sur un exemple censé réussir selon la règle, ou censé être inapplicable. C’est ce seuil qu’il faut surveiller, et il dépend des règles, pas de ce rapport. Lors de la dernière exécution complète, le moteur a produit zéro faux positif sur les 957 exemples que le W3C publie pour ses 60 règles correspondantes ; voir ACT_RULE_MAPPING.md, et relancez scripts/act-testcase-check.js pour obtenir le chiffre à jour plutôt que de vous fier indéfiniment à celui-ci.
Plusieurs résultats, un seul rapport
renderEarlReport accepte un tableau aussi bien qu’un résultat unique, car un rapport couvrant de nombreuses pages est le cas normal :
renderEarlReport([homeResult, checkoutResult, searchResult], { assertor });
Un résultat multi-frames de runa11yCoreAcrossFrames est accepté tel quel, seul ou dans le tableau : chaque frame qui a répondu est un résultat de plus et devient un sujet sous sa propre URL. Un frame sous le premier niveau dont l’URL ne le distingue pas, parce qu’elle vaut about:srcdoc ou about:blank ou qu’un autre frame du résultat a la même, est nommé par son URL et son chemin, les sélecteurs par lesquels SARIF et JUnit le nomment, chacun encodé et joints par / : about:srcdoc#frame=%23pay, about:srcdoc#frame=%23widget/%23inner. Là où même le chemin est partagé, comme pour les frames d’un binding, qui ne portent pas de sélecteur, sa place parmi les frames du résultat est ajoutée, à partir de 1 : about:srcdoc#frame=iframe&n=3. Tout le reste qui n’est pas un résultat d’analyse, comme null ou une chaîne, lève une TypeError.
Les résultats qui partagent une URL sont fusionnés en un seul sujet : un appelant qui analyse la même page avec des engineOptions différents décrit toujours une seule ressource, et le contexte n’a aucun moyen d’exprimer deux sujets ayant la même source. Lorsque deux résultats portent sur la même règle pour la même URL, le pire résultat est conservé : earl:failed, puis earl:cantTell, puis earl:passed, puis earl:inapplicable, si bien qu’aucun ordre des entrées ne peut masquer un échec. Cela vaut aussi pour la même page analysée à deux largeurs de viewport : EARL n’a aucun terme pour les conditions dans lesquelles une page a été rendue, si bien qu’un rapport construit à partir de plusieurs largeurs garde, pour chaque règle, un échec survenu à n’importe quelle largeur. Produisez un rapport par largeur si les largeurs elles-mêmes comptent.
La sortie est déterministe : les sujets sont triés par source, les assertions par id de règle, et les mêmes entrées produisent une sortie identique à l’octet près, quel que soit leur ordre. La seule exception concerne les résultats sans URL, qui se distinguent par leur position dans la liste (about:blank#result-2) ; donnez à chaque résultat son URL quand l’ordre ne doit pas compter. C’est ce qui rend significatif un diff entre deux versions du moteur.
Options
| Option | Signification |
|---|---|
assertor | { name, version }. Le nom vaut surea11y par défaut, et la version, donnée comme release de l’assertor, vaut par défaut l’engine.version que portent les résultats lorsqu’ils portent tous la même ; des résultats issus de versions différentes n’ont pas de release, sauf si vous en indiquez une. Passez null pour omettre entièrement assertedBy. |
mode | Un mode de test EARL, par exemple 'earl:automatic'. Omis s’il n’est pas fourni. |
Voir aussi
- Schéma de sortie : le résultat que ce rapport lit
ACT_RULE_MAPPING.md: les règles ACT auxquelles correspondent les règles du moteurAPI_STABILITY.md: ce qui est couvert par semver