Jak zacząć testowanie dostępności (accessibility) z Playwright?

Autor artykułu: Grzegorz Skierkowski
Head of QA z 7-letnim doświadczeniem w branży e-commerce. Specjalizuje się w budowaniu procesów QA, stawiając m.in. na automatyzację testów w Playwright.



Dostępność cyfrowa projektowana jest dla ludzi. Z myślą o tych, którzy na co dzień korzystają jedynie z klawiatury i polegają na czytnikach ekranu. Testy manualne są zatem bezcenne, bo pozwalają naprawdę poczuć, jak to jest poruszać się po interfejsie w ten sposób. Tylko że bardzo często pełny audyt dostępności wykonywany jest tylko raz. Aplikacja jednak nigdy nie stoi w miejscu: ciągle dodajemy nowe funkcje, coś zmieniamy, a przy tym nie trudno przypadkiem coś zepsuć. Tutaj właśnie mogą przydać się testy automatyczne, pilnujące, byśmy nie zgubili dostępności po drodze.

W dalszej części pomoże nam Playwright – bardzo popularne narzędzie do automatyzacji testów przeglądarkowych od Microsoftu. W tym wpisie użyjemy go w TypeScript. Aby rozpocząć, potrzebujemy systemu Windows, macOS lub Linux oraz środowiska Node.js. Gdy już to wszystko mamy, możemy zainicjalizować nowy projekt testów poleceniem:

npm init playwright@latest

W świecie Playwrighta do testowania dostępności najczęściej używa się axe-core od Deque System. Ale uwaga – to narzędzie nie znajdzie wszystkiego. Sami twórcy otwarcie mówią o około 57% wykrywalnych problemów, np.:

  • brak tekstu alternatywnego przy obrazach (reguła image-alt),
  • niewłaściwe użycie ARIA (np. reguła aria-command-name),
  • zbyt niski kontrast kolorów (reguła color-contrast).

To narzędzie oferuje również całkiem sporo tagów – czyli gotowych zestawów reguł – do których możemy sięgnąć w dokumentacji i włączyć do naszych testów. Wśród nich znajdziemy też te związane z WCAG (Web Content Accessibility Guidelines):

  • wcag2a – WCAG 2.0 A,
  • wcag2aa – WCAG 2.0 AA,
  • wcag2aaa – WCAG 2.0 AAA,
  • wcag21a – WCAG 2.1 A,
  • wcag21aa – WCAG 2.1 AA,
  • wcag22aa – WCAG 2.2 AA.

Dzięki takiemu podziałowi możemy zacząć od jednego wybranego poziomu, a z czasem – wraz z rozwojem dostępności naszej aplikacji – stopniowo rozszerzać zakres testów.

Teraz użyjemy biblioteki @axe-core/playwright, która świetnie adaptuje axe-core do pracy z Playwrightem:

npm i @axe-core/playwright

Jak poniżej, wystarczy, że do AxeBuilder przekażemy obiekt Page:

import AxeBuilder from '@axe-core/playwright';
import { expect, test } from '@playwright/test';

test(/* Tytuł */, async ({ page }) => {
  // 1. Otwórz stronę.
  await page.goto('/');

  // 2. Wykonaj automatyczną analizę dostępności.
  const { violations } = await new AxeBuilder({ page })
    .withTags(['wcag21a', 'wcag21aa'])
    .analyze();

  // 3. Upewnij się, że analiza nie wykryła problemów z dostępnością.
  expect(violations).toEqual([]);
});

Jeżeli w testowanej przez nas aplikacji możemy natknąć się na komponenty, które pokazują się dopiero po tym, jak użytkownik coś kliknie – na przykład wysuwane panele czy okienka dialogowe – to spokojnie możemy tę akcję zautomatyzować, a dopiero potem uruchomić analizę dostępności:

import AxeBuilder from '@axe-core/playwright';
import { expect, test } from '@playwright/test';

test(/* Tytuł */, async ({ page }) => {
  // 1. Otwórz stronę.
  await page.goto('/');

  // 2. Wykonaj niezbędne interakcje.
  await page.locator(/* Selektor przycisku. */).click();
  await page.locator(/* Selektor innego przycisku. */).click();
  await page.locator(/* Selektor jeszcze innego przycisku. */).click();

  // 3. Upewnij się, że wysuwany panel pojawił się w DOM (Document Object Model).
  await expect(page.locator(/* Selektor wysuwanego panelu */)).toBeVisible();

  // 4. Wykonaj automatyczną analizę dostępności.
  const { violations } = await new AxeBuilder({ page })
    .withTags(['wcag21a', 'wcag21aa'])
    .analyze();

  // 5. Upewnij się, że analiza nie wykryła problemów z dostępnością.
  expect(violations).toEqual([]);
});

Twórcy Playwrighta sugerują również dodanie własnego mechanizmu fixture, który tworzy instancję AxeBuilder z naszymi ustawieniami. Dzięki temu konfiguracja stanie się spójna w całym projekcie testów.

Proponowana przez twórców fabryka działa tylko na domyślnej stronie kontrolowanej przez Playwrighta. Możemy ją jednak uogólnić, dodając do funkcji makeAxeBuilder argument typu Page, żeby móc testować dostępność w wielu kartach przeglądarki, np. w karcie z komponentem płatności:

// test.ts

import AxeBuilder from '@axe-core/playwright';
import { expect, test as base, type Page } from '@playwright/test';

const test = base.extend<{ makeAxeBuilder: (page: Page) => AxeBuilder }>({
  // eslint-disable-next-line no-empty-pattern
  makeAxeBuilder: async ({}, use) => {
    const makeAxeBuilder = (page: Page) => new AxeBuilder({ page })
      .withTags(['wcag21a', 'wcag21aa'])
      .exclude('iframe');

    await use(makeAxeBuilder);
  }
});

export { expect, test };
// accessibility.spec.ts

import { expect, test } from './test';

test(/* Tytuł */, async ({ context, page, makeAxeBuilder }) => {
  // 1. Otwórz stronę.
  await page.goto('/');

  // 2. Wykonaj interakcję, która otworzy nową kartę w przeglądarce, np. kliknij przycisk.
  await page.locator(/* Selektor przycisku */).click();

  // 3. Wykonaj automatyczną analizę dostępności na drugiej karcie przeglądarki.
  const [, secondPage] = context.pages(); // lub za pomocą page.waitForEvent(‘popup’)

  const { violations } = await makeAxeBuilder(secondPage).analyze();

  // 4. Upewnij się, że analiza nie wykryła problemów z dostępnością.
  expect(violations).toEqual([]);
});

Naszymi największymi przyjaciółmi podczas ustawiania instancji AxeBuilder będą metody:

  • withTags – do definiowania tagów (np. wcag2aa),
  • withRules – do definiowania konkretnych reguł (np. aria-roles),
  • exclude – do wykluczania elementów HTML z audytu (np. komponentów od zewnętrznych dostawców).

Ale taka analiza wciąż jest zaledwie statyczna, prawda? Playwright pozwala nam jednak wykonywać całe scenariusze, np. logowanie się, używając jedynie klawiatury. Taki test nie znajdzie samodzielnie wyjścia z labiryntu, ale za to pozwoli nam upewnić się, że zadana sekwencja interakcji wciąż prowadzi do celu:

import { expect, test } from '@playwright/test';

test(/* Tytuł */, async ({ page }) => {
  // 1. Otwórz stronę.
  await page.goto('/');

  // 2. Wciśnij przycisk Tab, żeby ustawić fokus na polu "username" (zakładamy, że tak jest w testowanym UI).
  await page.keyboard.press('Tab');

  // 3. Upewnij się, że pole "username" ma fokus.
  await expect(page.locator(/* Selektor pola "username" */)).toBeFocused();

  // 4. Wpisz nazwę użytkownika, używając klawiatury.
  await page.keyboard.type('<nazwa użytkownika>');

  // 5. Zmień fokus na pole "password", używając klawisza Tab.
  await page.keyboard.press('Tab');

  // 6. Upewnij się, że pole "password" ma fokus.
  await expect(page.locator(/* Selektor pola "password" */)).toBeFocused();

  // 7. Wpisz hasło, używając klawiatury.
  await page.keyboard.type('<hasło>');

  // 8. Zmień fokus na link "Forgot password?", używając klawisza Tab (zakładamy, że tak jest w testowanym UI).
  await page.keyboard.press('Tab');

  // 9. Zmień fokus na przycisk typu "submit", używając klawisza Tab.
  await page.keyboard.press('Tab');

  // 10. Upewnij się, że przycisk typu "submit" ma fokus.
  await expect(page.locator(/* Selektor przycisku typu "submit" */)).toBeFocused();

  // 11. Wciśnij Enter, aby wysłać formularz.
  await page.keyboard.press('Enter');

  // 12. Upewnij się, że użytkownik został zalogowany (np. poprzez sprawdzenie URL, jeśli jest to wystarczające).
  await expect(page).toHaveURL(/\/dashboard/);
});

Z powodu uzależnienia od interfejsu, być może najrozsądniej byłoby ograniczyć się jedynie do kilku najważniejszych scenariuszy, np. rejestracji, logowania się lub procesu zakupowego. Wtedy projekt automatycznych testów dostępności moglibyśmy zatrzymać na testach statycznych dla każdej strony, np:

  • strony głównej,
  • strony produktu,
  • koszyka

i na pewnym ograniczonym zestawie scenariuszy. Moglibyśmy też pokusić się o wykorzystanie AI do oceny zrozumiałości komunikatów. Ale których? I jak przekazać do modelu ich przeznaczenie i wystarczający kontekst? To już jest temat na oddzielny wpis.

Testowanie dostępności w sposób automatyczny nie zastąpi empatii ani testów manualnych, ale może skutecznie wspierać je na co dzień. Gdy potraktujemy je jako narzędzie wczesnego ostrzegania, może pomóc naszym zespołom dbać o dostępność w sposób ciągły – dokładnie tam, gdzie powstają nowe funkcje i potencjalne regresje.

Produkt zostały dodany do koszyka

Przejdź do koszyka

Twój koszyk

Twój koszyk jest pusty.

Łącznie: 0,00 zł

facebook instagram pinterest twitter youtube linkedin tiktok twitch spotify website search menu close shopping-cart information menu-arrow check arrow-left-short arrow-right-short arrow-right-long