Faire un audit accessibilité avec Playwright

17 février 2025 - 1323 mots - playwright

L’audit d’accessibilité numérique en France évalue la conformité des sites web et services numériques aux normes du RGAA, alignées sur les WCAG, afin de garantir leur accessibilité aux personnes en situation de handicap. Obligatoire pour les organismes publics depuis la Loi pour une République numérique, il identifie les obstacles, propose des corrections et aboutit à une déclaration d’accessibilité. Réalisé via des tests techniques et utilisateurs, l’audit améliore l’expérience utilisateur, optimise le SEO, réduit les risques juridiques et valorise l’engagement inclusif des organisations.

Nous allons étudier comment Playwright peut être utilisé pour auditer l’accessibilité d’une page Web.

Logo Playwright

Limitations avec Playwright

Les outils automatisés comme axe-core (que nous allons utiliser avec Playwright) détectent principalement les erreurs techniques et structurelles (balises manquantes, attributs alt, contraste, etc.), mais ne couvrent qu’environ 30 à 50 % des problèmes d’accessibilité. Ils ne peuvent pas évaluer certains points comme :

  • L’expérience utilisateur d’une personne handicapée,
  • La pertinence des descriptions textuelles,
  • L’efficacité de la navigation au clavier ou l’usage de lecteurs d’écran dans des scénarios complexes,
  • La logique d’interaction et l’ergonomie.

De plus, Playwright, agissant comme un navigateur, n’agit donc pas comme un lecteur d’écran, et ne peut donc simuler les scénarios les impliquant.

Un audit manuel est toujours requis pour :

  • Tester les interactions utilisateur,
  • Vérifier la compréhension du contenu,
  • Valider l’expérience avec des technologies d’assistance (lecteurs d’écran, commandes vocales).

Nous connaissons désormais les limitations de Playwright, mais l’utilisation de Playwright reste avantageuse car elle permet d’automatiser efficacement les tests, d’identifier rapidement les erreurs techniques et d’assurer une vérification continue sur plusieurs navigateurs.

Mise en place

Maintenant qu’on le connait les limitations avec Playwright, nous allons voir comment mettre en place Playwright.

La base reste la même que sur l’article sur les erreurs Javascripts.

Après cela, on installe le package @axe-core/playwright via la commande npm i @axe-core/playwright --save-dev.

Utilisation

On va ensuite créer un scénario qui va détecter deux pages : l’une sans violations (https://playwright.dev) et l’autre avec (https://france.fr).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('homepage', () => {
  test('should not have no violations', async ({ page }, testInfo) => {
    await page.goto('https://playwright.dev');

    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
      .analyze();

    await testInfo.attach('accessibility-scan-results', {
      body: JSON.stringify(accessibilityScanResults, null, 2),
      contentType: 'application/json'
    });

    expect(accessibilityScanResults.violations.length).toEqual(0);
  });
  test('should have multiples violations', async ({ page }, testInfo) => {
    await page.goto('https://france.fr');

    const accessibilityScanResults = await new AxeBuilder({ page })
    .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
    .analyze();

    await testInfo.attach('accessibility-scan-results', {
      body: JSON.stringify(accessibilityScanResults, null, 2),
      contentType: 'application/json'
    });

    expect(accessibilityScanResults.violations.length).not.toEqual(0);
  });
});

Quelques notes à prendre en compte :

  • Vous pouvez définir quels standards d’accesibilités vous voulez prendre en compte (liste complète)
  • Vous pouvez contrôler le nombre de violations retournées (accessibilityScanResults.violations), mais aussi le nombre de règles qui sont ok (accessibilityScanResults.passes), ainsi que celles qui sont non applicables à votre page (accessibilityScanResults.inapplicable).

Violations

Dans notre cas assez simple, on ne vérifie que le nombre de violations, mais la librairie @axe-core permet de récupérer des informations plus précises sur les violations. On va creuser cela :

1
2
3
4
...
    expect(accessibilityScanResults.violations.length).not.toEqual(0);
    console.log(accessibilityScanResults.violations[0]);
...

On peut ainsi voir la première violation :

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
{
  id: 'aria-hidden-focus',
  impact: 'serious',
  tags: [
    'cat.name-role-value',
    'wcag2a',
    'wcag412',
    'TTv5',
    'TT6.a',
    'EN-301-549',
    'EN-9.4.1.2'
  ],
  description: 'Ensure aria-hidden elements are not focusable nor contain focusable elements',
  help: 'ARIA hidden element must not be focusable or contain focusable elements',
  helpUrl: 'https://dequeuniversity.com/rules/axe/4.10/aria-hidden-focus?application=playwright',
  nodes: [
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide AfCarousel-slide--next" aria-hidden="true" style="margin-right:0px;width:calc(100% - (0px * 0) / 1);order:8;" aria-label="2 of 7" data-v-5f6b46e2="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    },
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide" aria-hidden="true" style="margin-right:0px;width:calc(100% - (0px * 0) / 1);order:6;" aria-label="0 of 7" data-v-5f6b46e2="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    },
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide AfCarousel-slide--next" aria-hidden="true" style="margin-right:24px;width:calc(41.5% - (24px * 1) / 2);order:9;" aria-label="2 of 4" data-v-5f6b46e2="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    },
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide" aria-hidden="true" style="margin-right:24px;width:calc(41.5% - (24px * 1) / 2);order:10;" aria-label="3 of 4" data-v-5f6b46e2="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    },
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide AfCarousel-slide--vertical" aria-hidden="true" style="width:calc(100% - (0px * 0) / 1);margin-bottom:0px;" id="item-05cc6bfd-7db2-4e4b-be74-1cbba1ab8692-2" data-v-27c2fee6="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    },
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide AfCarousel-slide--vertical" aria-hidden="true" style="width:calc(100% - (0px * 0) / 1);margin-bottom:0px;" id="item-05cc6bfd-7db2-4e4b-be74-1cbba1ab8692-3" data-v-27c2fee6="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    },
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide AfCarousel-slide--next" aria-hidden="true" style="margin-right:24px;width:calc(25% - (24px * 3) / 4);order:6;" aria-label="2 of 5" data-v-5f6b46e2="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    },
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide" aria-hidden="true" style="margin-right:24px;width:calc(25% - (24px * 3) / 4);order:7;" aria-label="3 of 5" data-v-5f6b46e2="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    },
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide" aria-hidden="true" style="margin-right:24px;width:calc(25% - (24px * 3) / 4);order:8;" aria-label="4 of 5" data-v-5f6b46e2="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    },
    {
      any: [],
      all: [Array],
      none: [],
      impact: 'serious',
      html: '<li class="AfCarousel-slide" aria-hidden="true" style="margin-right:24px;width:calc(25% - (24px * 3) / 4);order:9;" aria-label="0 of 5" data-v-5f6b46e2="" data-v-1a4d3280="">',
      target: [Array],
      failureSummary: 'Fix all of the following:\n' +
        '  Focusable content should have tabindex="-1" or be removed from the DOM'
    }
  ]
}

Chaque violation a cette structure :

  • id: un identifiant unique,
  • impact: le niveau de criticité de la violation,
  • tags: l’ensemble des tags assignées à cette violation,
  • description: une description de la règle,
  • help: une description de la violation,
  • helpUrl: un lien vers dequeuniversity.com avec des informations complètementaire sur la violation
  • nodes: la liste de tous les éléments impactées dans la page
    • html: code de l’élément HTML impacté
    • impact: le niveau de criticité de la violation pour cet élément,
    • failureSummary: une explication de comment corriger la violation

Conclusion

Playwright vous permet de gagner énormément de temps en temps sur les retours possibles d’un audit accessibilité. Pour cela, il faut agir de manière proactive :

  • un script qui lance l’audit AxeBuilder sur un ensemble représentatif des pages de votre site
  • une correction dès la détection d’un souci
  • une CI lancée régulièrement pour éviter les régressions

Laisser un commentaire

Merci. Votre message a bien été enregistré.