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:
- Przy pierwszym uruchomieniu testu Playwright robi zrzut ekranu i zapisuje go jako linię bazową.
- Przy kolejnych uruchomieniach Playwright robi nowy zrzut ekranu i porównuje go z linią bazową piksel po pikselu.
- 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 terazhomepage-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 Awcag2aa— WCAG 2.0 Poziom AA (najczęstszy wymóg prawny)wcag21aa— WCAG 2.1 Poziom AAwcag22aa— 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:
- Przesłać różnice wizualne jako artefakty
- Wymagać ręcznego przeglądu przed scaleniem
- 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:
- Zgłoś ticket
- Dodaj komentarz wyjaśniający, dlaczego jest wykluczone
- 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