Testy wizualne i dostępności w Playwright

Testy regresji wizualnej wychwytują zmiany w UI, których nie zamierzałeś. Testy dostępności wychwytują bariery, których nie zauważyłeś. Dowiedz się, kiedy testy wizualne pomagają, kiedy są szumem i.

Ten wpis jest częścią serii Podstawy Playwright. Jeśli pominąłeś poprzedni post, przeczytaj najpierw Część 6 — Auth, storage state i scenariusze wielu użytkowników.


Wprowadzenie — automatyzacja nie zastąpi ludzkiego osądu, ale może oznaczyć to, co ludzie przegapią

Testowanie regresji wizualnej i testowanie dostępności znajdują się na niewygodnym skrzyżowaniu: są automatyzowalne, ale wymagają subiektywnego osądu. Różnica w pikselach może być bugiem lub zamierzoną zmianą projektu. Naruszenie dostępności oznaczone przez narzędzie może być rzeczywistą barierą lub fałszywym alarmem w zależności od kontekstu.

Obietnica testowania wizualnego jest taka, że wychwycisz niezamierzone zmiany układu, zanim trafią do produkcji. Rzeczywistość jest taka, że większość zespołów porzuca testy wizualne w ciągu sześciu miesięcy, ponieważ stosunek szumu do sygnału jest zbyt wysoki. Za każdym razem, gdy przycisk przesunie się o dwa piksele, test pada. Za każdym razem, gdy czcionka załaduje się nieco inaczej w CI, test pada. W końcu zespół przestaje przeglądać różnice wizualne i po prostu zatwierdza wszystko.

Testowanie dostępności ma odwrotny problem: automatyczne narzędzia wychwytują może 30–40% rzeczywistych problemów z dostępnością. Powiedzą ci, jeśli obraz nie ma tekstu alternatywnego lub jeśli pole formularza nie ma etykiety, ale nie powiedzą ci, czy kolejność fokusa jest bezsensowna lub czy kontrast kolorów zawodzi dla użytkowników z deuteranopią.

Więc czy powinieneś zautomatyzować te sprawdzenia? Tak, ale z dyscypliną.

Ten post pokaże ci, jak używać toHaveScreenshot() w Playwright do regresji wizualnej, kiedy testy wizualne są warte kosztów utrzymania i jak zintegrować axe-core dla automatycznych sprawdzeń dostępności, które faktycznie wychwytują prawdziwe problemy.


Testowanie regresji wizualnej z toHaveScreenshot()

Playwright ma wbudowane porównywanie wizualne przez asercję toHaveScreenshot(). Działa to tak:

  1. Przy pierwszym uruchomieniu testu Playwright robi zrzut ekranu i zapisuje go jako linię bazową.
  2. Przy kolejnych uruchomieniach Playwright robi nowy zrzut ekranu i porównuje go z linią bazową piksel po pikselu.
  3. Jeśli obrazy różnią się ponad skonfigurowany próg, test pada.

Podstawowe użycie

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

test('strona główna renderuje się poprawnie', async ({ page }) => {
  await page.goto('/');
  await expect(page).toHaveScreenshot('homepage.png');
});

Pierwsze uruchomienie generuje homepage.png w katalogu tests/__screenshots__/. Kolejne uruchomienia porównują z nim.

Jeśli strona się zmieni, test pada i Playwright generuje:

  • homepage-actual.png — jak strona wygląda teraz
  • homepage-diff.png — wizualny diff podkreślający zmiany

Przeglądasz diff, decydujesz, czy zmiana jest zamierzona, i jeśli tak, aktualizujesz linię bazową:

npx playwright test --update-snapshots

Zrzuty ekranu z zakresem

Możesz zrobić zrzut ekranu konkretnych elementów zamiast całej strony:

test('pasek nawigacji renderuje się poprawnie', async ({ page }) => {
  await page.goto('/');
  const nav = page.locator('nav');
  await expect(nav).toHaveScreenshot('nav.png');
});

To jest bardziej stabilne niż zrzuty całej strony, ponieważ niezwiązane zmiany gdzie indziej na stronie nie wywołają awarii.

Tolerowanie drobnych różnic

Czcionki, antyaliasing i renderowanie przeglądarki mogą się nieznacznie różnić między środowiskami. Możesz skonfigurować próg tolerancji:

await expect(page).toHaveScreenshot('homepage.png', {
  maxDiffPixels: 100, // Pozwól na różnicę do 100 pikseli
});

Lub jako procent:

await expect(page).toHaveScreenshot('homepage.png', {
  maxDiffPixelRatio: 0.01, // Pozwól na 1% różnicy pikseli
});

:::warning[Nie ustawiaj tolerancji zbyt wysoko] Jeśli ustawisz maxDiffPixelRatio na 5%, pozwalasz na zmianę 5% ekranu bez padania testu. To wystarczy, aby przegapić zepsuty układ. Zacznij konserwatywnie (0.1–0.5%) i dostosuj w oparciu o rzeczywistą niestabilność. :::


Kiedy testowanie wizualne pomaga

Testy regresji wizualnej są wysokie w utrzymaniu. Często się psują, wymagają ręcznego przeglądu i produkują fałszywe alarmy. Są warte kosztów tylko wtedy, gdy:

1. Masz system projektowania z komponentami wielokrotnego użytku

Jeśli utrzymujesz bibliotekę komponentów (przyciski, karty, modale, formularze) używaną na dziesiątkach stron, regresja wizualna na tych komponentach wychwytuje niezamierzone zmiany stylów.

test('podstawowy przycisk pasuje do projektu', async ({ page }) => {
  await page.goto('/design-system/buttons');
  const button = page.locator('[data-testid="primary-button"]');
  await expect(button).toHaveScreenshot('primary-button.png');
});

Jeśli ktoś przypadkowo zmieni border-radius lub padding przycisku, ten test to wychwytuje, zanim zostanie wysłane.

2. Masz złożone, dynamiczne układy

Tabele z dynamicznymi kolumnami, dashboardy z przeciągalnymi widgetami, wykresy renderowane z danych na żywo — te są trudne do testowania z asercjami funkcjonalnymi. Test wizualny potwierdza, że układ się nie zawala.

test('dashboard z 5 widgetami renderuje się bez nakładania', async ({ page }) => {
  await page.goto('/dashboard');
  await page.waitForSelector('[data-testid="widget"]');
  await expect(page).toHaveScreenshot('dashboard-5-widgets.png', {
    maxDiffPixelRatio: 0.02, // Wykresy mogą się nieznacznie różnić
  });
});

3. Masz stabilną tożsamość wizualną

Jeśli twoje wytyczne marki są ścisłe i zmiany w typografii, odstępach lub kolorach są starannie przeglądane, testy wizualne egzekwują tę spójność.


Kiedy testowanie wizualne szkodzi

Testy wizualne nie są odpowiednie, gdy:

1. Strona ma dynamiczną treść

Jeśli twoja strona główna pokazuje sekcję “Najnowsze wiadomości”, która zmienia się codziennie, test wizualny będzie padać codziennie. Musiałbyś albo:

  • Zmockować API, aby zwracało statyczne dane (wysoki wysiłek)
  • Wykluczyć dynamiczny region ze zrzutu ekranu (podważa cel)
  • Zaakceptować szum (niezrównoważone)

2. Możesz potwierdzić zachowanie funkcjonalnie

Jeśli celem testu jest “przycisk logowania jest widoczny i klikalny”, test wizualny to przesada:

// Źle: Test wizualny dla zachowania funkcjonalnego
await expect(page).toHaveScreenshot('login-page.png');

// Dobrze: Asercja funkcjonalna
await expect(page.locator('button:has-text("Zaloguj się")')).toBeVisible();
await page.click('button:has-text("Zaloguj się")');

Asercje funkcjonalne są szybsze, bardziej stabilne i lepiej opisują intencję.

3. Twoje środowisko CI renderuje inaczej

Jeśli czcionki, renderowanie subpikselowe lub akceleracja GPU różnią się między twoją lokalną maszyną a CI, testy wizualne będą hałaśliwe. Rozwiązania:

  • Użyj kontenerów Docker z identycznymi środowiskami renderowania
  • Wyłącz antyaliasing czcionek w konfiguracji testów
  • Zaakceptuj wyższą tolerancję (co zmniejsza wartość testu)

Żadne z tych nie jest za darmo. Jeśli wysiłek inżynieryjny przekracza wartość wychwytywania bugów, pomiń testy wizualne.


Testowanie dostępności z axe-core

Automatyczne testowanie dostępności różni się od testowania wizualnego. Naruszenia dostępności są obiektywne: brakujący atrybut alt, współczynnik kontrastu poniżej 4.5:1, pole formularza bez <label>. To są mierzalne defekty, nie opinie projektowe.

Najczęściej używaną biblioteką do testowania dostępności jest axe-core, opracowana przez Deque. Playwright integruje się z nią przez @axe-core/playwright.

Konfiguracja

Zainstaluj pakiet:

npm install --save-dev @axe-core/playwright

Podstawowe użycie

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

test('strona główna nie ma naruszeń dostępności', async ({ page }) => {
  await page.goto('/');

  const accessibilityScanResults = await new AxeBuilder({ page }).analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

Jeśli strona ma problemy z dostępnością, test pada i wyświetla:

Expected: []
Received: [
  {
    id: 'label',
    impact: 'critical',
    description: 'Elementy formularza muszą mieć etykiety',
    nodes: [
      {
        html: '<input type="text" name="search">',
        target: ['input[name="search"]'],
      }
    ]
  }
]

To mówi ci:

  • Jakie jest naruszenie: “Elementy formularza muszą mieć etykiety”
  • Gdzie jest: input[name="search"]
  • Jak poważne jest: critical

Zakres do konkretnych regionów

Możesz uruchomić axe na konkretnej części strony:

test('pasek nawigacji nie ma naruszeń a11y', async ({ page }) => {
  await page.goto('/');

  const accessibilityScanResults = await new AxeBuilder({ page })
    .include('nav') // Skanuj tylko element <nav>
    .analyze();

  expect(accessibilityScanResults.violations).toEqual([]);
});

To jest przydatne do testowania na poziomie komponentów.

Wykluczanie znanych problemów

Jeśli masz widget strony trzeciej, którego nie możesz natychmiast naprawić, możesz wykluczyć go ze skanowania:

const accessibilityScanResults = await new AxeBuilder({ page })
  .exclude('.third-party-chat-widget')
  .analyze();

:::warning[Nie nadużywaj wykluczeń] Wykluczanie regionów powinno być tymczasowe. Jeśli wykluczysz połowę swojej strony, test nie ma wartości. Śledź wykluczone regiony jako dług techniczny i naprawiaj je stopniowo. :::

Testowanie konkretnych poziomów WCAG

axe-core wspiera różne poziomy zgodności WCAG. Możesz testować pod kątem konkretnych standardów:

const accessibilityScanResults = await new AxeBuilder({ page })
  .withTags(['wcag2a', 'wcag2aa']) // WCAG 2.0 Poziom A i AA
  .analyze();

Typowe tagi:

  • wcag2a — WCAG 2.0 Poziom A
  • wcag2aa — WCAG 2.0 Poziom AA (najczęstszy wymóg prawny)
  • wcag21aa — WCAG 2.1 Poziom AA
  • wcag22aa — WCAG 2.2 Poziom AA (najnowszy standard)
  • best-practice — Zalecane najlepsze praktyki Deque poza WCAG

Zacznij od wcag2aa. To standard, do którego odnosi się większość regulacji (ADA w USA, EN 301 549 w UE).


Czego automatyczne testowanie dostępności nie wychwytuje

Automatyczne narzędzia wychwytują problemy strukturalne. Nie wychwytują:

  • Kolejności nawigacji klawiaturą — Czy kolejność fokusa jest logiczna?
  • Ogłoszenia czytnika ekranu — Czy strona ma sens, gdy jest czytana na głos?
  • Obciążenie poznawcze — Czy interfejs jest przytłaczający?
  • Kontekstowe problemy z kontrastem — Współczynnik kontrastu 4.5:1 może przejść WCAG, ale nadal być nieczytelny dla niektórych użytkowników w zależności od otaczających kolorów i rozmiaru tekstu.

Wymagają one testowania ręcznego z technologiami wspomagającymi:

  • Testuj tylko klawiaturą (Tab, Shift+Tab, Enter, Escape)
  • Testuj z czytnikiem ekranu (NVDA na Windows, VoiceOver na macOS, JAWS komercyjnie)
  • Testuj z powiększeniem przeglądarki na 200%
  • Testuj z włączonym trybem wysokiego kontrastu

Automatyczne testy są linią bazową, a nie celem. Wychwytują oczywiste problemy, aby testowanie ręczne mogło skupić się na niuansowych problemach.


Integracja sprawdzeń wizualnych i dostępności w CI

Zarówno testy wizualne, jak i dostępności powinny działać w CI, ale z różnymi politykami awarii.

Testy dostępności: zawsze blokuj build

Naruszenia dostępności są obiektywnymi defektami. Powinny powodować awarię buildu:

test('brak naruszeń a11y na stronie checkout', async ({ page }) => {
  await page.goto('/checkout');
  const results = await new AxeBuilder({ page }).analyze();
  expect(results.violations).toEqual([]);
});

Jeśli to pada, PR nie powinien być scalony, dopóki naruszenie nie zostanie naprawione.

Testy wizualne: wymagany przegląd, nie blokuj automatycznie

Awarie testów wizualnych często wymagają ludzkiego osądu. Skonfiguruj CI, aby:

  1. Przesłać różnice wizualne jako artefakty
  2. Wymagać ręcznego przeglądu przed scaleniem
  3. Nie powodować automatycznej awarii buildu, chyba że różnica jest katastrofalna (np. cała strona jest biała)

W GitHub Actions:

- name: Run Playwright tests
  run: npx playwright test

- name: Upload visual diffs
  if: failure()
  uses: actions/upload-artifact@v3
  with:
    name: playwright-screenshots
    path: test-results/

Recenzenci pobierają artefakty, sprawdzają różnice i decydują, czy zaktualizować linie bazowe, czy naprawić kod.


Typowe pułapki

Pułapka 1: Testy wizualne na każdej stronie

Nie rób zrzutów ekranu każdej strony w swojej aplikacji. To setki linii bazowych do utrzymania. Zamiast tego:

  • Zrób zrzuty ekranu komponentów systemu projektowania
  • Zrób zrzuty ekranu stron wysokiego ryzyka (checkout, płatność, dashboard)
  • Zrób zrzuty ekranu złożonych układów, które są trudne do potwierdzenia funkcjonalnie

Pułapka 2: Ignorowanie awarii testów dostępności

Kuszące jest .exclude() naruszenia dostępności, aby uczynić build zielonym. Opieraj się temu. Każde wykluczenie to rzeczywista bariera dla prawdziwych użytkowników.

Jeśli nie możesz naprawić tego natychmiast:

  1. Zgłoś ticket
  2. Dodaj komentarz wyjaśniający, dlaczego jest wykluczone
  3. Ustaw termin naprawy

Pułapka 3: Uruchamianie axe-core zbyt późno

Uruchamiaj skany dostępności wcześnie w procesie rozwoju funkcji, a nie tylko przed wydaniem. Naprawienie problemu dostępności w projekcie jest tanie. Naprawienie go w produkcji jest drogie.


Podsumowanie

Testowanie regresji wizualnej i testowanie dostępności są wartościowe, ale wymagają dyscypliny do utrzymania.

Testowanie wizualne działa najlepiej dla:

  • Bibliotek komponentów
  • Złożonych dynamicznych układów
  • Wymagań ścisłej spójności marki

Testowanie dostępności działa najlepiej dla:

  • Każdej strony w twojej aplikacji (tanie do uruchomienia)
  • Ścieżek użytkownika o wysokim natężeniu ruchu
  • Funkcji krytycznych dla zgodności (checkout, formularze, strony prawne)

Kluczowe wnioski:

  • Użyj toHaveScreenshot() dla regresji wizualnej z zakresem na stabilnych komponentach
  • Ustaw realistyczne progi tolerancji, aby uniknąć szumu
  • Użyj @axe-core/playwright, aby wychwycić obiektywne naruszenia dostępności
  • Zawsze blokuj build na awariach dostępności
  • Uzupełnij automatyczne testy dostępności testowaniem ręcznym

:::tip[Zacznij od dostępności] Jeśli możesz dodać tylko jeden rodzaj testu, dodaj testy dostępności. Są niskie w utrzymaniu, wysokie w wartości i wychwytują rzeczywiste defekty, które wpływają na prawdziwych użytkowników. Testy wizualne są wyższe w utrzymaniu i wymagają więcej osądu. :::

Zadanie na ten tydzień: Dodaj jeden test dostępności do swojej najważniejszej ścieżki użytkownika (logowanie, checkout, rejestracja). Uruchom go lokalnie. Napraw wszelkie naruszenia. Następnie dodaj go do CI i uczyń go wymaganym sprawdzeniem. Właśnie uczyniłeś swoją aplikację bardziej dostępną dla milionów użytkowników.


Następny w tej serii: Część 8 — Równoległość, sharding i Playwright gotowy na CI