Testy dostępności z Playwright: kontrole WCAG w zestawie testów
Zaktualizowano 14 października 2026 · 7 min czytania
Playwright steruje prawdziwą przeglądarką, a axe-core to otwartoźródłowy silnik reguł stojący za większością automatycznych kontroli dostępności. Razem zamieniają kontrole WCAG w zwykłe testy: strona, na której pojawi się przycisk bez nazwy albo tekst o niskim kontraście, łamie build, zanim trafi do klientów. Ten poradnik pokazuje działające przykłady z @axe-core/playwright, opcje dla komponentów React i Storybook, wskazówki do CI oraz ograniczenia, o których warto wiedzieć.
Przykłady wzorują się na oficjalnym poradniku Playwright o testowaniu dostępności i README @axe-core/playwright. Tego samego silnika używamy we własnym skanerze.
Instalacja
npm install --save-dev @playwright/test @axe-core/playwright
Wersja pakietu odpowiada wersji axe-core, którą zawiera. Jego README zaznacza, że nie stosuje wersjonowania semantycznego, więc wersję warto przypiąć i aktualizować celowo: nowe wydanie axe może dodać reguły znajdujące nowe problemy.
Pierwszy test WCAG
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
const WCAG_AA = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'];
test('home page has no detectable WCAG A/AA violations', async ({ page }) => {
await page.goto('/');
const results = await new AxeBuilder({ page }).withTags(WCAG_AA).analyze();
expect(results.violations).toEqual([]);
});
withTags ogranicza przebieg do reguł oznaczonych tymi wersjami i poziomami WCAG. Bez tego axe uruchamia też reguły dobrych praktyk, które są przydatne, ale nie są wymaganiami WCAG.
Testowanie stanów, które ludzie naprawdę widzą
Strona to więcej niż jej pierwsze wyrenderowanie. Najpierw należy otworzyć menu, szufladę koszyka albo okno cookies, a potem przeskanować tylko tę część przez include:
test('mobile menu is accessible when open', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: 'Menu' }).click();
const results = await new AxeBuilder({ page })
.include('#navigation-menu')
.withTags(WCAG_AA)
.analyze();
expect(results.violations).toEqual([]);
});
Dobre kandydatury w sklepie: strona produktu z wybranymi opcjami, koszyk z produktami, zamówienie z pokazanymi błędami walidacji, formularz logowania do konta. Używanie getByRole w testach to sam w sobie mały test dostępności: jeśli Playwright nie znajdzie przycisku po roli i nazwie, użytkownik czytnika ekranu prawdopodobnie też nie.
Znane problemy bez wyłączania testów
W prawdziwych projektach są problemy, których nie da się naprawić dziś, np. wewnątrz widżetu firmy trzeciej. README oferuje trzy narzędzia:
const results = await new AxeBuilder({ page })
.exclude('#third-party-chat') // pomiń element
.disableRules(['color-contrast']) // pomiń regułę (używać rzadko)
.withTags(WCAG_AA)
.analyze();
Lepszy wzorzec z poradnika Playwright to migawka odcisków (fingerprints): zapisuje się, które reguły zawodzą na których elementach, a test kończy się niepowodzeniem tylko wtedy, gdy pojawi się coś nowego.
const fingerprints = (results) =>
results.violations.map((v) => ({
rule: v.id,
targets: v.nodes.map((n) => n.target),
}));
expect(fingerprints(results)).toMatchSnapshot();
Dwie uwagi z README: include i exclude przyjmują jeden selektor na wywołanie (dla kilku należy je łączyć łańcuchowo), a ponowne wywołanie withTags, withRules, disableRules lub options nadpisuje wcześniejsze wywołanie.
Najpierw to, co najważniejsze
Gdy zaległości są duże, zaczynamy od najpoważniejszych problemów. Każde naruszenie ma impact: minor, moderate, serious lub critical:
const blocking = results.violations.filter((v) =>
['serious', 'critical'].includes(v.impact),
);
expect(blocking).toEqual([]);
Pełny wynik warto dołączyć do raportu, aby osoba naprawiająca widziała każdy element:
// inside test('…', async ({ page }, testInfo) => { … })
await testInfo.attach('accessibility-scan-results', {
body: JSON.stringify(results, null, 2),
contentType: 'application/json',
});
Poradnik Playwright pokazuje też, jak opakować AxeBuilder w fixture testowy, aby każdy test dostawał te same tagi i wyłączenia z jednego miejsca.
Komponenty React: jest-axe i vitest-axe
W testach komponentów renderowanych w JSDOM jest-axe i vitest-axe dodają matcher toHaveNoViolations:
import { render } from '@testing-library/react';
import { axe } from 'vitest-axe';
import 'vitest-axe/extend-expect';
it('renders an accessible form', async () => {
const { container } = render(<SignupForm />);
expect(await axe(container)).toHaveNoViolations();
});
Są szybkie i wcześnie wychwytują brakujące etykiety i nazwy. Dwa ograniczenia z ich README: jest-axe wyłącza kontrolę kontrastu kolorów, bo nie działa w JSDOM, a vitest-axe nie działa ze środowiskiem happy-dom. Kontrast, układ, powiększenie i fokus wymagają prawdziwej przeglądarki, więc testy Playwright dla pełnych stron warto zachować.
Storybook
Dodatek dostępności Storybook uruchamia axe-core dla każdej historii (story):
npx storybook add @storybook/addon-a11y
Dla historii, które muszą pozostać czyste, należy ustawić parameters.a11y.test na 'error': naruszenia powodują wtedy niepowodzenie testu w interfejsie Storybook i w CI. 'todo' pokazuje je tylko jako ostrzeżenia, a 'off' pomija automatyczną kontrolę.
Wskazówki do CI
- Uruchamiać w tym samym pipeline co pozostałe testy. Poradnik Playwright o CI używa
npm ci,npx playwright install --with-depsinpx playwright testoraz przesyła folderplaywright-report/jako artefakt. - Testować zbudowaną stronę zbliżoną do produkcyjnej, a nie serwer deweloperski z nakładkami do debugowania.
- Wybrać krótką listę kluczowych adresów – strona główna, kategoria, produkt, koszyk, zamówienie, logowanie, strona z treścią – zamiast przeszukiwać wszystko przy każdym commicie.
- Przypiąć wersję axe i aktualizować ją w osobnym pull requeście.
- Nie ukrywać niepowodzeń przez
disableRulesna długo; lepiej użyćexcludedla jednego elementu albo wzorca migawki.
Czego testy automatyczne nie powiedzą
README axe-core mówi, że średnio można automatycznie znaleźć 57% problemów WCAG, a poradnik Playwright równie jasno stwierdza, że testy automatyczne nie wykryją wszystkich rodzajów naruszeń WCAG. Pusta tablica violations nie oznacza, że strona spełnia WCAG 2.2 AA. Nadal potrzebne są przejście klawiaturą, sprawdzenie powiększenia i dopasowania do ekranu oraz test z czytnikiem ekranu. Opisuje to nasz poradnik testowania WCAG, a lista kontrolna WCAG 2.2 zaznacza, które punkty wymagają człowieka.
Gdzie wpisuje się Pandus
Testy w CI chronią kod, który Państwo zmieniają. Nie widzą tego, co dzieje się potem na działającym serwisie: aktualizacja motywu, nowa aplikacja lub wtyczka, baner marketingowy, opis produktu wklejony od dostawcy. Pandus sprawdza serwis produkcyjny: co tydzień każdą stronę, tym samym silnikiem axe-core w prawdziwej przeglądarce, i tworzy raport, który może przeczytać właściciel sklepu.
- API uruchamia kontrole i zwraca wyniki w JSON, z webhookami po zakończeniu kontroli lub pojawieniu się nowych problemów – przydatne po wdrożeniu.
- Rozszerzenie do przeglądarki sprawdza stronę tak, jak widzi ją przeglądarka, także środowisko testowe i strony za logowaniem.
- Darmowy WCAG checker sprawdza jedną stronę publiczną bez konta.
Pandus to automatyczna kontrola, a nie pełny audyt, i nie jest poradą prawną. Porównanie innych narzędzi znajduje się w narzędziach do testowania dostępności.
Najczęstsze pytania
Jak testować dostępność w Playwright?
Należy zainstalować @axe-core/playwright, otworzyć stronę w teście Playwright, uruchomić new AxeBuilder({ page }).analyze() i sprawdzić, że tablica violations jest pusta. Przebieg można ograniczyć do reguł WCAG przez withTags, np. wcag2a, wcag2aa, wcag21a, wcag21aa i wcag22aa.
Czy zaliczony test axe oznacza, że strona spełnia WCAG?
Nie. Reguły automatyczne znajdują tylko część problemów WCAG. Deque, twórca axe-core, szacuje to średnio na około 57% problemów. Przepływ klawiatury, sensowny tekst alternatywny, kolejność fokusu i zrozumiałe błędy nadal wymagają człowieka.
Czy zamiast tego mogę użyć jest-axe lub vitest-axe?
Tak, dla komponentów renderowanych w JSDOM. Są szybkie, ale jest-axe wyłącza kontrolę kontrastu kolorów, ponieważ nie działa w JSDOM. Pełne strony w prawdziwej przeglądarce, gdzie można sprawdzić kontrast, układ i fokus, warto testować w Playwright.
Jak testować dostępność komponentów w Storybook?
Należy dodać @storybook/addon-a11y. Uruchamia axe-core dla każdej historii (story); przy parameters.a11y.test ustawionym na error naruszenia powodują niepowodzenie testu w interfejsie Storybook i w CI.
Czy build ma się wywracać przy każdym naruszeniu?
Dobrze zacząć od niepowodzenia przy problemach poważnych (serious) i krytycznych (critical) oraz zachować migawkę znanych, aby nowe problemy łamały build, a stare były naprawiane krok po kroku. Gdy zaległości znikną, regułę można zaostrzyć.
Źródła
- Playwright, Accessibility testing: https://playwright.dev/docs/accessibility-testing
- Playwright, Setting up CI: https://playwright.dev/docs/ci-intro
- Deque, @axe-core/playwright README: https://github.com/dequelabs/axe-core-npm/blob/develop/packages/playwright/README.md
- Deque, axe-core README: https://github.com/dequelabs/axe-core
- jest-axe README: https://github.com/NickColley/jest-axe
- vitest-axe README: https://github.com/chaance/vitest-axe
- Storybook, Accessibility tests: https://storybook.js.org/docs/writing-tests/accessibility-testing