Bonus: Flaky tests – jak podejść do niestabilnych testów?

TIP: Ta lekcja jest częścią rozwijanego Programu Testy Automatyczne z Playwright 🎭

Flaky tests.
Temat, który spędza sen z powiek, gdy automatyzujemy testy.

W tym kursie, tak samo jak w realnym projekcie, projektujemy framework i musimy stawić czoła tego typu testom.

W tej lekcji pokażę Ci, czym są flaky tests, jak je rozpoznać, i podstawowe techniki postępowania. Wszystko – na podstawie rozwijanego wspólnie frameworka opartego o Playwright.

Prezentacja

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Bonus: Flaky tests - jak podejść do niestabilnych testów?

Flaky tests – czym są?

Flaky tests (niestabilne testy) to testy, które – w prostych słowach – czasem przechodzą, a czasem nie, pomimo tego, że w aplikacji i w testach nie zostały wprowadzone żadne znaczące zmiany pomiędzy uruchomieniem testów.

Dla lepszego zobrazowania wyobraź sobie taki scenariusz:

  • Pierwsze uruchomienie
    Test przechodzi – użytkownik poprawnie loguje się do systemu.
  • Drugie uruchomienie (od razu po pierwszym)
    Test nie przechodzi mimo tego samego scenariusza logowania, pojawia się błąd w testach, na przykład test nie może znaleźć przycisku “Zaloguj”.
  • Trzecie uruchomienie (od razu po drugim)
    Test znowu przechodzi – wszystko działa, jakby nic się wcześniej nie wydarzyło!

Dlaczego flaky testy są problematyczne?

  • Podważają zaufanie do wyników testów

    Gdy testy czasami przechodzą, a czasami nie, zespół przestaje traktować wyniki automatycznych testów jako wiarygodne źródło informacji o stanie aplikacji. Może to prowadzić do nieświadomego pomijania rzeczywistych błędów.

  • Wydłużają czas debugowania

    Znalezienie przyczyny problemu w przypadku flaky testów jest znacznie trudniejsze niż w przypadku testów, które konsekwentnie wskazują na błąd. Testerzy muszą analizować logi, środowisko uruchomieniowe i konfigurację, często wielokrotnie uruchamiając te same testy.

  • Mogą prowadzić do ignorowania wyników testów

    Zespół może zacząć ignorować zarówno niepowodzenia, jak i sukcesy w automatycznych testach, zakładając, że wynik nie ma związku z rzeczywistym stanem aplikacji. W efekcie prawdziwe błędy mogą pozostać niezauważone i trafić na produkcję.

  • Wydłużają czas dostarczania oprogramowania

    Gdy zespół musi regularnie radzić sobie z flaky testami, spowalnia to cały proces wytwarzania oprogramowania (w prostych słowach – tworzenia aplikacji). Dodatkowy czas jest poświęcany na weryfikację wyników i analizowanie problemów.

  • Generują dodatkowe koszty

    Dodatkowy czas poświęcony na analizę i uruchamianie testów to realny koszt, który łatwo można przeliczyć na pieniądze. A klienci nie lubią tracić pieniędzy…

  • Powodują frustrację w zespole

    Ciągłe problemy z niestabilnymi testami mogą prowadzić do spadku morale zespołu. Szczególnie gdy czas i wysiłek są regularnie marnowane na problemy związane z flaky testami (analiza, uruchamianie testów aby zreprodukować losowe błędy) zamiast na rzeczywistą wartość dla klienta i użytkowników.

Powodem mogą być błędy w aplikacji, problemy ze środowiskiem lub błędy w implementacji testów:

  • test próbuje wchodzić w interakcję z elementem, zanim ten element załaduje się na stronienie, czyli niewłaściwe oczekiwanie na elementy lub dane (np. brak odpowiedniego wait)
  • środowisko jest niestabilne, np. sieć działa wolniej w danym momencie, co powoduje timeout lub z dostępnością serwerów
  • test opiera się na dynamicznych danych, które mogą różnić się przy każdym uruchomieniu.
  • w aplikacji występują błędy związane z wyścigiem – gdy dwie operacje wykonują się w tym samym czasie i raz jedna kończy się wcześniej a raz druga
UWAGA: Flaky tests informują o błędzie, jednak dopiero analiza pokaże, czy błąd jest w testach, czy w testowanej aplikacji.

Uruchamianie testów wiele razy i opcja repeat-each

Playwright oferuje opcję repeat-each.

Opcja repeat-each pozwala uruchamiać ten sam test wielokrotnie w jednej sesji. Służy do wykrywania niestabilności w testach (flaky tests) i pomaga sprawdzić, czy wynik danego testu jest powtarzalny.
Czyli czy kończy się powodzeniem przy wielu uruchomieniach.

Jak działa repeat-each?

  • dla każdego testu Playwright uruchomi go określoną liczbę razy
  • jeśli test przechodzi za każdym razem, oznacza to, że jest stabilny
  • jeśli test nie przechodzi w niektórych powtórzeniach – to zostanie oznaczony jako flaky test, może to wskazywać na problem z testem, aplikacją lub środowiskiem

Kiedy używać repeat-each?

  • Do sprawdzania stabilności testów
  • Podczas debugowania niestabilnych testów (flaky tests)
  • Przy testowaniu wprowadzonej zmiany, aby upewnić się, że nie wpływa ona na stabilność testów
  • W symulacji testów na obciążonym środowisku, by sprawdzić ich zachowanie

Przykład zastosowania

Jeśli masz test, który czasami się nie udaje z powodu niestabilności aplikacji, możesz użyć repeat-each, aby wielokrotnie go uruchomić i zobaczyć, czy problem występuje losowo.

Podsumowując, repeat-each można traktować jako narzędzie diagnostyczne do testowania stabilności, które umożliwia wielokrotne wykonywanie tych samych testów.

Polecenie do uruchomienia wszystkich testów, gdzie każdy z testów zostanie uruchomiony 10 razy:

npx playwright test --repeat-each=10

Opcja retries, czyli ponownie uruchomienie testów zakończonych niepowodzeniem

Opcja retries w Playwright pozwala na automatyczne ponowne uruchomienie testu, jeśli za pierwszym razem test zakończy się niepowodzeniem.

Jak działa retries?

  • dla każdego testu Playwright uruchomi go określoną liczbę razy
  • jeśli test przechodzi za każdym razem, oznacza to, że jest stabilny
  • jeśli test nie przechodzi w niektórych powtórzeniach – to zostanie oznaczony jako flaky test, może to wskazywać na problem z testem, aplikacją lub środowiskiem

Kiedy korzystać z retries?

Jest to szczególnie przydatne, gdy testy:

  • są wrażliwe na chwilowe błędy, na przykład problemy z siecią lub czasowe opóźnienia w ładowaniu strony
  • polegają na zewnętrznych usługach, które mogą czasem działać niestabilnie (np. API firm trzecich)
  • testują dynamiczne treści, które mogą zmieniać się w czasie rzeczywistym (np. dane w czasie rzeczywistym, jak kursy walut)
  • mają zależności od środowiska testowego, które może nie być w pełni stabilne (np. problemy z infrastrukturą testową)

Zalety tego mechanizmu:

  • stabilizacja testów – może być przydatne w środowiskach z niestabilnymi elementami.
  • skrócenie czasu analizy błędów, bo niektóre błędy mogą być jednorazowe i nie wymagają natychmiastowej interwencji.
TIP: Opcja retires może być również stosowana do wykrywania niestabilnych testów!

Każdy test, który dopiero przy powtórzeniu zakończył się powodzeniem, jest odpowiednio oznaczany w raportach (jako flaky test).
Dzięki temu podczas analizy wyników możemy łatwo odszukać takie testy a następnie zaplanować ich analizę.

Wady tego podejścia:

  • ukrywanie problemów, bo wielokrotne ponowne uruchomienia mogą maskować rzeczywiste problemy w testach
  • dłuższy czas uruchamiania, bo w przypadku częstych retry czas wykonania testów może się wydłużyć
UWAGA: BARDZO WAŻNE – nie powinno się nadużywać tej opcji, bo może ukrywać prawdziwe problemy!
Lepiej jest najpierw ustabilizować testy, a retry traktować jako ostateczność.

Również przed użyciem tej opcji powinieneś:

  • dobrze przeanalizować występujący błąd
  • zgłosić go w projekcie (aby zespół był tego świadomy)

Najlepsze praktyki:

  • używaj retry w środowiskach, gdzie niestabilność jest znana i akceptowana
  • staraj się minimalizować flaky testy poprzez debugowanie i wprowadzanie poprawek do testów i aplikacji
  • analizuj działanie testów oraz aplikacji, aby zrozumieć przyczyny problemów i ocenić czy wykorzystać mechanizm retries

Jak korzystać z retries?

Opcja retry podczas uruchamiania testów:

npx playwright test --retries=3

Opcja retry w konfiguracji playwright.config.ts:

retries: process.env.CI ? 2 : 2,

Opcja retry dla konfiguracji testów (w kodzie):

test.describe.configure({ retries: 2 });

Podsumowanie

Opcja retries a repeat-each

  • W odróżnieniu od opcji retries, repeat-each nie kończy testów po pierwszym nieudanym przebiegu – każdy test jest uruchamiany określoną liczbę razy, niezależnie od wyniku.
  • Opcja ta jest szczególnie przydatna w środowiskach CI/CD do wykrywania potencjalnych problemów przed wdrożeniem.

Flaky tests w aplikacji Demo Bank

Poprawka w teście i cały kod

W teście można dodać inteligentne czekanie na pełne załadowanie się strony.
Służy do tego metoda:

await page.waitForLoadState("domcontentloaded");

Wywołanie jej spowoduje, że Playwright poczeka, aż cały dokument HTML sie załaduje zanim wykona kolejne operacje.

Zawartość pliku pulpit.spec.ts:

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


test.describe("Pulpit tests", () => {
    test("quick payment with correct data", async ({ page }) => {
        await page.goto("https://demo-bank.vercel.app/");
        await page.getByTestId("login-input").fill("testerLO");
        await page.getByTestId("password-input").fill("password");
        await page.getByTestId("login-button").click();


        await page.waitForLoadState("domcontentloaded"); // wait for all DOM content loaded


        await page.locator("#widget_1_transfer_receiver").selectOption("2");
        await page.locator("#widget_1_transfer_amount").fill("150");
        await page.locator("#widget_1_transfer_title").fill("pizza");


        await page.getByRole("button", { name: "wykonaj" }).click();
        await page.getByTestId("close-button").click();


        await expect(page.locator("#show_messages")).toHaveText(
            "Przelew wykonany! Chuck Demobankowy - 150,00PLN - pizza"
        );
    });
});



Podstawowy algorytm postępowania

  1. Potwierdzenie problemu

    • Sprawdź, czy test faktycznie jest flaky (np. uruchamiając go wielokrotnie za pomocą opcji --repeat-each=10)
    • Przeanalizuj logi i historię testu w CI/CD
  2. Analiza z zespołem

    • Przedyskutuj problem podczas spotkania zespołowego.
    • Omów możliwe przyczyny flaky tests (kod testu, kod aplikacji, środowisko).
    • Oceń koszty naprawy i zdecyduj, kiedy i jak podjąć działania naprawcze.
  3. Tymczasowe rozwiązanie (jeśli aktualnie są inne priorytety)

    • Dodaj opcję retries do konfiguracji danego testu lub przy komendzie do uruchamiania testów:
      npx playwright test  --retries=3
                  	
    • Udokumentuj problem – stwórz notkę (task/issue w danym systemie, np. Jira), w której opiszesz całą dotychczasową analizę, aby wrócić do tego problemu później.
  4. Naprawa

    • Popraw test, kod aplikacji lub dostosuj środowisko.
    • Zoptymalizuj framework (np. timeouty, dynamiczne oczekiwania).
  5. Weryfikacja

    • Uruchom test wielokrotnie, aby upewnić się, że problem został rozwiązany.
    • Obserwuj stabilność testów w CI/CD.
  6. Prewencja

    • Wprowadź monitorowanie flaky tests w CI/CD.
    • Zaktualizuj dobre praktyki w dokumentacji zespołowej.

Podsumowanie

W tym momencie masz już podstawową wiedzę czym są flaky tests i wiesz jak je wykrywać
Poznałeś też prosty algorytm, kroki co zrobić, gdy odkryjesz takie testy w projekcie.

W dalszej części kursu i w części zaawansowanej, gdzie projektujemy profesjonalny framework, pokażemy Ci inne techniki jak analizować problemy i stabilizować testy.
Wszystko w oparciu o naszą praktyczną wiedzę o Playwright, którą pozyskaliśmy w wielu komercyjnych projektach 😉

Zewnętrzne linki i zasoby

4 komentarze

    1. Doskonałe oko testera!
      Faktycznie jest tam literówka – na slajdach pod nagraniem już poprawiłem 😉
      A nagranie wyrenderuje przy kolejnej okazji 😀
      Dzięki wielkie za zgłoszenie!

      Krzysiek Kijas Krzysiek Kijas
  1. Hej!
    1. Powtórzyłem przypadek z nagrania i rzeczywiście dodanie “await page.waitForLoadState(“domcontentloaded”); ” wydaje się rozwiązywać problem w przypadku jednego testu
    2. Jednak w pliku pulpit.spec.ts mam kod z lekcji L06 (formatowanie kodu z prettier), a tam są dwa testy. Uruchomiłem testy poleceniem “npx playwright test –repeat-each=100” i dostałem 3 flaky tests

     3 flaky
        [chromium] › pulpit.spec.ts:7:7 › Pulpit tests › quick payment with correct data ───────────────
        [chromium] › pulpit.spec.ts:7:7 › Pulpit tests › quick payment with correct data ───────────────
        [chromium] › pulpit.spec.ts:30:7 › Pulpit tests › successful mobile top-up ─────────────────────
    

    3. W każdym z nich występuje znany problem “Received string: “Brak wiadomości”” Zastanawiam się dlaczego wystąpił ten przypadek mimo dodania metody “waitForLoadState” ? Może wykonałem test nieprawidłowo? Poniżej kod

    import { test, expect } from '@playwright/test';
    
    test.describe.only('Pulpit tests', () => {
      test.describe.configure({ retries: 3 });
    
    
      test('quick payment with correct data', async ({ page }) => {
        await page.goto('https://demo-bank.vercel.app/');
        await page.getByTestId('login-input').fill('testerLO');
        await page.getByTestId('password-input').fill('password');
        await page.getByTestId('login-button').click();
    
        await page.waitForLoadState("domcontentloaded");
    
        await page.locator('#widget_1_transfer_receiver').selectOption('2');
        await page.locator('#widget_1_transfer_amount').fill('150');
        await page.locator('#widget_1_transfer_title').fill('pizza');
    
        await page.getByRole('button', { name: 'wykonaj' }).click();
        await page.getByTestId('close-button').click();
    
        await expect(page.locator('#show_messages')).toHaveText(
          'Przelew wykonany! Chuck Demobankowy - 150,00PLN - pizza',
        );
      });
    
      test('successful mobile top-up', async ({ page }) => {
        await page.goto('https://demo-bank.vercel.app/');
        await page.getByTestId('login-input').fill('testerLO');
        await page.getByTestId('password-input').fill('password');
        await page.getByTestId('login-button').click();
    
        await page.waitForLoadState("domcontentloaded");
    
        await page.locator('#widget_1_topup_receiver').selectOption('503 xxx xxx');
        await page.locator('#widget_1_topup_amount').fill('50');
        await page.locator('#uniform-widget_1_topup_agreement span').click();
        await page.getByRole('button', { name: 'doładuj telefon' }).click();
        await page.getByTestId('close-button').click();
    
        await expect(page.locator('#show_messages')).toHaveText(
          'Doładowanie wykonane! 50,00PLN na numer 503 xxx xxx',
        );
      });
    });
    
    
    Avatar Karol Horbik
    1. Hej,
      Świetne pytanie i sprawdzenie tego mechanizmu w boju! 😀

      Faktycznie wprowadzenie opcji await page.waitForLoadState("domcontentloaded"); stabilizuje testy, ale nadal nie w 100%. Tutaj też poprawnie zastosowałeś ten mechanizm do pozostałych testów.

      I taka sytuacja może też być w realnym projekcie.
      Zatem kolejnymi krokami byłby powrót do przedstawionego algorytmu:

      – sprawdzamy błąd – znowu dotyczy tego samego obszaru, wiec możliwe, że powodem jest coś innego niż skrypty lub potrzebne jest jakies inne zabezpieczenie w testach, aby upewnić się, że strona jest gotowa do działania

      – moze byc też tak, że jest to jakis ukryty bug w aplikacji.
      Uruchomiłeś npx playwright test –repeat-each=100 al na wszystkich testach (zakładam też ze na domyślnym ustawieniu 4 workerów) – może jest jakiś problem z wydajnością aplikacji?

      – błąd dotyczy tylko wiadomości bo wyliczenia cały czas są poprawne

      – i co ważne – cały czas nie jesteśmy w stanie manualnie tego zreprodukować

      Z tego wynika, że może to nie być trywialny problem i może wymagac dodatkowego czasu na analizę – najlepiej wykonaną z udziałem developera, aby poznać mechanizmy w aplikacji.

      Uwzględniając powyższe – może się okazać, ze aktualnie najlepsze rozwiązanie to będzie dodac opcję retry do tych testów i założyć, że w przyszłości wróci się do analizy tego problemu 🙂

      Z doświadczenia powiem, że czasem takie rozwiązania się stosuje i często rozwiązanie przychodzi w nieoczywistym momencie:
      – gdy wykonywana jest jakaś refaktoryzacja kodu aplikacji
      – gdy naprawiany jest jakiś inny błąd
      – gdy podbijane są biblioteki z których korzystamy w projekcie

      A dzięki raportom i logom, możemy monitorować jakie testy są flaky i jakie błędy występują 🙂

      Ten przypadek świetnie obrazuje niekończącą się walkę z flaky tests 😉

      Krzysiek Kijas Krzysiek Kijas

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *