Debugowanie padających testów Playwright jak pro

Nieudane testy to nie problemy — to wskazówki. Dowiedz się, jak używać trybu UI Playwright, przeglądarki trace, debugowania headed i analizy artefaktów, aby izolować niestabilne kroki, zrozumieć.

Ten wpis jest częścią serii Podstawy Playwright. Jeśli pominąłeś poprzedni post, przeczytaj najpierw Część 8 — Równoległość, sharding i Playwright gotowy na CI.


Wprowadzenie — debugowanie to umiejętność, nie zgadywanie

Najgorszy sposób debugowania padającego testu to wpatrywanie się w kod, zmiana czegoś, co “może pomóc”, ponowne uruchomienie testu i powtarzanie, aż przejdzie. To wolne, demoralizujące i nie uczy cię niczego o przyczynie źródłowej.

Najlepszy sposób debugowania padającego testu to obserwacja tego, co faktycznie się stało. Playwright daje ci narzędzia, aby zobaczyć dokładnie, co zrobiła przeglądarka, czego oczekiwał test i gdzie nastąpiła niezgodność. Dzięki tym narzędziom debugowanie staje się systematyczne: zbierz dowody, sformułuj hipotezę, zweryfikuj hipotezę, napraw przyczynę źródłową.

Ten post pokaże ci, jak używać narzędzi debugowania Playwright:

  • Tryb UI — interaktywny eksplorator testów z podglądem przeglądarki na żywo
  • Przeglądarka trace — debugger podróży w czasie z pełną historią sieci, DOM i akcji
  • Tryb headed — oglądaj testy działające na żywo z DevTools przeglądarki
  • Tryb --debug — przejdź przez testy linia po linii jak tradycyjny debugger
  • Analiza artefaktów — zrzuty ekranu, wideo i logi z awarii CI

Pod koniec będziesz wiedział, jak diagnozować awarie testów szybciej niż ktokolwiek w twoim zespole.


Tryb UI: interaktywny eksplorator testów

Tryb UI to najlepiej strzeżony sekret Playwright. To interaktywny runner testów, który pokazuje ci:

  • Podgląd przeglądarki na żywo podczas działania testu
  • Oś czasu każdej akcji i asercji
  • Żądania sieciowe, logi konsoli i snapshoty DOM
  • Status pass/fail w czasie rzeczywistym dla każdego kroku

Uruchom tryb UI:

npx playwright test --ui

To otwiera interfejs oparty na przeglądarce. Wybierz test z paska bocznego, kliknij Run i oglądaj jego wykonywanie krok po kroku. Jeśli krok pada, oś czasu podświetla awarię i możesz sprawdzić stan DOM w tym dokładnym momencie.

Kiedy używać trybu UI

Użyj trybu UI, gdy:

  • Piszesz nowy test — Uruchom go interaktywnie, aby sprawdzić, czy każdy krok działa zgodnie z oczekiwaniami
  • Debugujesz lokalną awarię — Zobacz dokładnie, co robi przeglądarka przy każdym kroku
  • Izolujesz niestabilny test — Uruchom test 10 razy w trybie UI i obserwuj, kiedy/gdzie pada

Kluczowe funkcje

1. Oś czasu akcji: Każdy page.goto(), page.click(), expect() jest logowany. Kliknij dowolną akcję, aby zobaczyć stan DOM w tym momencie.

2. Inspektor sieci: Zobacz każde żądanie HTTP, kod statusu i payload odpowiedzi. Jeśli twój test pada, ponieważ API zwraca 500, zobaczysz to tutaj.

3. Logi konsoli: Jeśli aplikacja loguje błędy do konsoli, pojawiają się w pasku bocznym trybu UI. Nie musisz sprawdzać terminala.

4. Zwolnione tempo: Spowolnij wykonywanie testu, aby zobaczyć animacje, stany ładowania i przejścia, które dzieją się zbyt szybko, aby je normalnie obserwować.


Przeglądarka trace: debugger podróży w czasie

Przeglądarka trace to najpotężniejsze narzędzie debugowania w Playwright. Trace to kompletne nagranie wykonania testu: każda akcja, każde żądanie sieciowe, każdy snapshot DOM, każdy log konsoli. Możesz przechodzić do przodu i do tyłu przez test, jakbyś tam był, gdy działał.

Generowanie traces

Skonfiguruj zbieranie trace w playwright.config.ts:

export default defineConfig({
  use: {
    trace: 'on-first-retry',
  },
});

To generuje traces tylko dla testów, które padają i są ponawiane. Traces są duże (~2–10 MB każdy), więc nie generuj ich dla każdego testu.

Jeśli test pada lokalnie i chcesz sprawdzić trace:

npx playwright test --trace on

Otwieranie przeglądarki trace

Po tym, jak test pada, Playwright zapisuje trace do test-results/. Otwórz go:

npx playwright show-trace test-results/<test-name>/trace.zip

Lub jeśli przesłałeś traces z CI, pobierz artefakt i uruchom:

npx playwright show-trace trace.zip

Co możesz zobaczyć w trace

1. Oś czasu akcji: Pełna lista każdej akcji wykonanej przez test. Kliknij dowolną akcję, aby zobaczyć:

  • Stan DOM przed akcją
  • Stan DOM po akcji
  • Użyty lokator
  • Czas trwania akcji

2. Zrzuty ekranu przy każdym kroku: Trace zawiera zrzut ekranu przed i po każdej akcji. Jeśli przycisk się przesunął lub zniknął, zobaczysz to.

3. Karta sieci: Każde żądanie sieciowe z:

  • URL żądania, metoda, nagłówki, payload
  • Status odpowiedzi, nagłówki, body
  • Informacje o czasie (jak długo trwało żądanie)

4. Logi konsoli: Każdy console.log(), console.error() i console.warn() z aplikacji.

5. Snapshoty: Kliknij “Before” lub “After” przy dowolnej akcji, aby zobaczyć pełny snapshot DOM. Możesz sprawdzać elementy, sprawdzać style i weryfikować stan aplikacji.

Przykład: debugowanie niestabilnego testu logowania

Wyobraź sobie, że ten test pada sporadycznie:

test('użytkownik może się zalogować', async ({ page }) => {
  await page.goto('/login');
  await page.fill('input[name="email"]', 'user@example.com');
  await page.fill('input[name="password"]', 'haslo123');
  await page.click('button:has-text("Zaloguj się")');
  await expect(page).toHaveURL('/dashboard');
});

Przechodzi lokalnie, ale pada w CI. Otwórz trace z CI:

  1. Oś czasu akcji pokazuje: Test kliknął “Zaloguj się” i czekał, aż URL będzie /dashboard
  2. Karta sieci pokazuje: Żądanie POST logowania zwróciło 200, ale odpowiedź zajęła 8 sekund
  3. Snapshot przy awarii pokazuje: Strona nadal jest na /login, pokazując spinner ładowania

Przyczyna źródłowa: Domyślny timeout (30 sekund) wygasł, ponieważ serwer zbyt długo odpowiadał. Test jest poprawny, ale aplikacja jest wolna.

Naprawa: Albo zoptymalizuj serwer, albo zwiększ timeout dla tej konkretnej akcji:

await page.click('button:has-text("Zaloguj się")');
await expect(page).toHaveURL('/dashboard', { timeout: 60000 }); // 60 sekund

Bez trace zgadywałbyś. Z trace wiedziałeś.


Tryb headed: oglądaj testy działające na żywo

Domyślnie Playwright działa w trybie headless (bez widocznej przeglądarki). Podczas debugowania przydatne jest zobaczyć przeglądarkę:

npx playwright test --headed

To otwiera prawdziwe okno przeglądarki i uruchamia test wewnątrz niego. Możesz obserwować każdą akcję w czasie rzeczywistym.

Kiedy używać trybu headed

  • Debugowanie problemów wizualnych — Czy przycisk jest naprawdę ukryty, czy tylko poza ekranem?
  • Zrozumienie problemów z czasem — Czy animacje zakłócają kliknięcia?
  • Weryfikacja przepływów użytkownika — Czy test pasuje do tego, jak prawdziwy użytkownik wchodziłby w interakcję ze stroną?

Zwolnione tempo

Połącz tryb headed ze zwolnionym tempem, aby spowolnić wykonywanie testu:

npx playwright test --headed --slow-mo=1000

To dodaje 1-sekundowe opóźnienie między każdą akcją. Przydatne do obserwowania rozwijania się złożonych interakcji.


Tryb debug: przejdź przez testy linia po linii

Tryb debug zatrzymuje wykonywanie testu na początku i otwiera Playwright Inspector, wbudowany debugger krokowy:

npx playwright test --debug

Inspektor pozwala:

  • Step over — Wykonaj następną linię i zatrzymaj się
  • Step into — Jeśli następna linia to wywołanie funkcji, wejdź do niej
  • Resume — Uruchom do następnego breakpointa lub końca testu

Możesz też ustawić breakpointy w swoim kodzie testowym:

test('użytkownik może się zalogować', async ({ page }) => {
  await page.goto('/login');
  await page.pause(); // Wykonanie zatrzymuje się tutaj
  await page.fill('input[name="email"]', 'user@example.com');
  await page.fill('input[name="password"]', 'haslo123');
  await page.click('button:has-text("Zaloguj się")');
});

Gdy test dotrze do page.pause(), zatrzymuje się. Możesz sprawdzić stronę w przeglądarce, uruchomić polecenia w konsoli Playwright Inspector i wznowić, gdy będziesz gotowy.

Kiedy używać trybu debug

  • Pisanie nowego testu — Zatrzymaj się po każdym kroku, aby sprawdzić, czy zadziałał
  • Naprawianie padającego testu — Zatrzymaj się w punkcie awarii i sprawdź stan strony
  • Izolowanie niestabilnego zachowania — Uruchom test wiele razy i zatrzymaj się, gdy pada

Analiza artefaktów CI

Gdy test pada w CI, nie masz dostępnego trybu UI ani debugowania headed. Zamiast tego polegasz na artefaktach: traces, wideo, zrzuty ekranu i logi.

Pobieranie artefaktów z GitHub Actions

Jeśli twój workflow CI przesyła artefakty (jak pokazano w części 8), pobierz je:

  1. Przejdź do nieudanego uruchomienia GitHub Actions
  2. Przewiń do Artifacts
  3. Pobierz playwright-results-<shard> lub playwright-report-<shard>

W środku znajdziesz:

  • trace.zip — Otwórz przez npx playwright show-trace trace.zip
  • video.webm — Oglądaj test w odtwarzaczu wideo
  • screenshot.png — Stan DOM przy awarii
  • stdout.txt / stderr.txt — Wyjście konsoli

Na co zwracać uwagę

1. Trace: Zawsze najpierw sprawdź trace. Ma najwięcej informacji.

2. Żądania sieciowe: Czy API zwróciło błąd? Czy żądanie było nieoczekiwanie wolne?

3. Błędy konsoli: Czy aplikacja zalogowała błędy JavaScript?

4. Czas: Czy test przekroczył timeout czekając na element? Czy akcja była wolniejsza niż oczekiwano?

5. Stan DOM: Czy element był obecny, ale niewidoczny? Czy był zasłonięty przez inny element?

Przykład: padający test w CI

Test pada z: Error: Timeout 30000ms exceeded waiting for locator('button:has-text("Wyślij")').click()

Krok 1: Pobierz trace z CI i otwórz go.

Krok 2: Sprawdź oś czasu. Test wywołał page.click('button:has-text("Wyślij")') i czekał 30 sekund.

Krok 3: Sprawdź snapshot. Przycisk jest obecny, ale ma disabled="true".

Krok 4: Sprawdź kartę sieci. API walidacji formularza zwróciło 200, ale 3 sekundy przed timeoutem testu.

Przyczyna źródłowa: Przycisk jest wyłączony, dopóki API walidacji nie odpowie. API jest wolne w CI, więc przycisk pozostaje wyłączony dłużej niż test czeka.

Naprawa: Poczekaj, aż przycisk będzie włączony przed kliknięciem:

await page.click('button:has-text("Wyślij")'); // Stare: pada, jeśli przycisk jest wyłączony
await page.waitForSelector('button:has-text("Wyślij"):not([disabled])');
await page.click('button:has-text("Wyślij")'); // Nowe: czeka na stan włączony

Lub użyj auto-waitingu Playwright z wyższym timeoutem:

await page.click('button:has-text("Wyślij")', { timeout: 60000 });

Izolowanie niestabilnych testów

Niestabilne testy to testy, które czasami przechodzą, a czasami padają bez zmian w kodzie. To najbardziej frustrujące awarie do debugowania, ponieważ są niedeterministyczne.

Typowe przyczyny niestabilności

1. Problemy z czasem — Test klika przycisk, zanim zostanie włączony, lub czeka na element, który jeszcze się nie pojawił.

2. Race conditions — Dwa testy modyfikują ten sam rekord bazy danych jednocześnie w równoległym wykonaniu.

3. Przejściowe awarie sieci — API losowo zwraca 500.

4. Interferencja animacji — Element się przesuwa, podczas gdy test próbuje go kliknąć.

5. Zanieczyszczenie testów — Test A zmienia stan aplikacji, od którego zależy Test B.

Debugowanie niestabilnych testów

Krok 1: Reprodukuj lokalnie

Uruchom test 20–50 razy, aby zobaczyć, czy pada lokalnie:

npx playwright test <test-name> --repeat-each=50

Jeśli pada, debuguj go w trybie UI lub z traces.

Krok 2: Sprawdź trace z CI

Jeśli pada tylko w CI, pobierz trace i porównaj:

  • Żądania sieciowe (czy CI jest wolniejsze?)
  • Błędy konsoli (czy CI ma problemy specyficzne dla środowiska?)
  • Czas (czy CI przekroczyło timeout, który przechodzi lokalnie?)

Krok 3: Dodaj jawne waity

Zamień niejawne waity na jawne waity:

// Źle: Niejawny wait (Playwright czeka, ale może nie wystarczająco długo)
await page.click('button:has-text("Wyślij")');

// Dobrze: Jawny wait (upewnij się, że przycisk jest widoczny i włączony)
await page.waitForSelector('button:has-text("Wyślij"):not([disabled])');
await page.click('button:has-text("Wyślij")');

Krok 4: Izoluj współdzielony stan

Jeśli test modyfikuje dane, od których zależą inne testy, albo:

  • Użyj .serial(), aby uruchamiać testy sekwencyjnie
  • Daj każdemu testowi własne dane testowe (unikalne konta użytkowników, unikalne rekordy)

Najlepsze praktyki debugowania

1. Zawsze najpierw sprawdź trace

Trace ma więcej informacji niż jakikolwiek inny artefakt. Zacznij od tego.

2. Reprodukuj lokalnie przed zgadywaniem

Nie zmieniaj kodu na podstawie logu awarii CI. Pobierz trace, reprodukuj problem i zweryfikuj swoją naprawę.

3. Używaj page.pause() obficie podczas pisania testów

Podczas pisania nowego testu zatrzymuj się po każdym kroku, aby sprawdzić, czy zadziałał:

await page.goto('/login');
await page.pause(); // Sprawdź, czy strona się załadowała
await page.fill('input[name="email"]', 'user@example.com');
await page.pause(); // Sprawdź, czy email został wypełniony

Usuń pauzy, gdy test jest stabilny.

4. Nie ignoruj niestabilnych testów

Śledź metryki niestabilnych testów. Jeśli test jest niestabilny > 5% czasu, zbadaj natychmiast. Niestabilne testy podważają zaufanie do pakietu testów.

5. Dodaj kontekstowe logowanie

Jeśli test pada w CI, ale przechodzi lokalnie, dodaj logowanie, aby zrozumieć środowisko:

test('użytkownik może się zalogować', async ({ page }) => {
  console.log('Rozpoczynam test logowania');
  await page.goto('/login');
  console.log('Nawigowano do /login');
  await page.fill('input[name="email"]', 'user@example.com');
  console.log('Wypełniono email');
  await page.fill('input[name="password"]', 'haslo123');
  console.log('Wypełniono hasło');
  await page.click('button:has-text("Zaloguj się")');
  console.log('Kliknięto przycisk logowania');
});

To pomaga zawęzić, gdzie test pada.


Typowe scenariusze debugowania

Scenariusz 1: “Element nie jest widoczny”

Błąd: Error: locator('button').click() — element is not visible

Kroki debugowania:

  1. Otwórz trace
  2. Sprawdź snapshot przy awarii — czy element faktycznie jest obecny?
  3. Sprawdź CSS — czy ma display: none lub visibility: hidden?
  4. Sprawdź z-index — czy inny element go zasłania?

Typowa naprawa: Poczekaj, aż element będzie widoczny:

await page.waitForSelector('button', { state: 'visible' });
await page.click('button');

Scenariusz 2: “Timeout czekając na element”

Błąd: Error: Timeout 30000ms exceeded waiting for locator('div.results')

Kroki debugowania:

  1. Sprawdź trace — czy element kiedykolwiek się pojawił?
  2. Sprawdź żądania sieciowe — czy API zawiodło lub zwróciło późno?
  3. Sprawdź logi konsoli — czy JavaScript rzucił błąd?

Typowa naprawa: Albo napraw aplikację (opóźnienie API, błąd JS), albo zwiększ timeout:

await page.waitForSelector('div.results', { timeout: 60000 });

Scenariusz 3: Test przechodzi lokalnie, pada w CI

Kroki debugowania:

  1. Pobierz trace z CI
  2. Porównaj czas sieci — czy CI jest wolniejsze?
  3. Porównaj zmienne środowiskowe — czy CI brakuje konfiguracji?
  4. Sprawdź race conditions — czy CI uruchamia więcej testów równolegle?

Typowa naprawa: CI jest często wolniejsze. Zwiększ timeouty lub zoptymalizuj aplikację.


Podsumowanie

Debugowanie to nie zgadywanie. Playwright daje ci obserwowalność: traces, zrzuty ekranu, logi sieci, wyjście konsoli. Użyj tych narzędzi, aby zobaczyć, co się stało, sformułować hipotezę i zweryfikować swoją naprawę.

Kluczowe wnioski:

  • Użyj trybu UI podczas pisania lub debugowania testów lokalnie
  • Użyj przeglądarki trace, aby zbadać awarie CI
  • Użyj trybu headed, aby oglądać testy działające na żywo
  • Użyj trybu debug, aby przechodzić przez testy linia po linii
  • Zawsze pobieraj artefakty z CI i sprawdzaj trace

:::tip[Zbuduj checklistę debugowania] Gdy test pada, postępuj według tej checklisty:

  1. Pobierz trace
  2. Sprawdź oś czasu akcji — który krok padł?
  3. Sprawdź snapshot — jaki był stan DOM?
  4. Sprawdź kartę sieci — jakieś nieudane żądania?
  5. Sprawdź logi konsoli — jakieś błędy JavaScript?
  6. Sformułuj hipotezę i zweryfikuj ją lokalnie :::

Zadanie na ten tydzień: Następnym razem, gdy test pada, nie zgaduj. Otwórz przeglądarkę trace i systematycznie badaj. Zmierz się. Po kilku awariach będziesz debugować szybciej niż ktokolwiek, kto po prostu zgaduje i ponownie uruchamia testy.


Następny w tej serii: Część 10 — Praktyki Playwright, które przetrwają wzrost