Przejdź do treści

Testy przepływów: Playwright i Maestro, czyli E2E bez dawnego bólu

Na koniec serii zostawiłem szczyt piramidy z poprzedniej części: testy przepływów, zwane też testami end-to-end. Te, które uruchamiają całą aplikację i przechodzą przez nią jak człowiek – logowanie, klik, formularz, wynik.

I znów zacznę od wyznania, bo ta seria inaczej nie umie: latami unikałem testów E2E i miałem do tego dobre powody. Tyle że powody wygasły, a unikanie zostało – dokładnie ten mechanizm, o którym była pierwsza część.

Skąd się wzięła zła opinia E2E

Moje pierwsze testy przeglądarkowe pisałem w Selenium, mobilne – w Appium. Oba narzędzia zrobiły dla świata ogromną robotę i oba potrafiły doprowadzić do szału.

Test w Selenium klikał szybciej, niż strona nadążała się renderować, więc kod obrastał w ręczne czekanie: uśpij sekundę, poczekaj na element, uśpij jeszcze dwie na wszelki wypadek. Za krótko – test padał losowo. Za długo – suita chodziła kwadrans. Do tego selektory: XPath opisujący położenie elementu w strukturze strony łamał się przy każdej przebudowie szablonu, choć z punktu widzenia użytkownika nic się nie zmieniło. A pod spodem trzeba było jeszcze utrzymywać sterowniki przeglądarek w wersjach zgodnych z przeglądarkami, które akurat się zaktualizowały.

Appium dokładało swoje: protokół WebDriver, sesje, capabilities, emulatory – konfiguracja środowiska bywała trudniejsza niż napisanie samych testów. Efekt znany z części o pokryciu: testy niestabilne uczą ignorować czerwony kolor. „A, to znowu ten flaky test” – zdanie, które słyszałem i wypowiadałem, a które unieważnia całą suitę.

Więc unikałem. I w tamtej epoce to była racjonalna decyzja.

Co właściwie łapie test przepływu

Zanim o tym, co się zmieniło – po co w ogóle się męczyć, skoro są testy jednostkowe i integracyjne? Bo test przepływu jest jedynym, który widzi aplikację w całości: routing, szablony, JavaScript, sesję, uprawnienia, konfigurację – naraz. Błędy integracji całości nie wywalają żadnego testu jednostkowego, bo każda część z osobna działa. W mojej epoce ręcznego testowania to właśnie one kończyły się wariantem „leżało kilka dni i nikt nie zgłosił”.

Dlatego flow piszę dla ścieżek krytycznych – takich, których awaria oznacza, że serwis nie robi tego, po co istnieje: użytkownik loguje się, wybiera egzamin, odpowiada na pytania, widzi wynik. Jeśli ta ścieżka działa, serwis żyje. Jeśli nie działa – nie obchodzi mnie, że dziewięćset testów jednostkowych świeci na zielono.

Playwright: czekanie zniknęło z moich testów

Na webie flow klika u mnie Playwright – używam go z Pythonem, przez wtyczkę do pytest, więc siedzi w tej samej suicie co reszta testów. Trzy rzeczy zmieniły w stosunku do Selenium najwięcej.

Automatyczne czekanie. Każda akcja sama czeka, aż element będzie widoczny, stabilny i klikalny. Cała warstwa ręcznych uśpień – największe źródło i powolności, i losowych upadków – po prostu zniknęła z kodu.

Selektory semantyczne. Zamiast XPatha opisującego strukturę strony – rola i nazwa elementu, czyli to, co widzi użytkownik:

# Python (pytest + Playwright)
from playwright.sync_api import Page, expect

def test_exam_flow(page: Page):
    page.goto("/")
    page.get_by_role("link", name="Egzaminy").click()
    page.get_by_role("link", name="INF.02").click()
    page.get_by_role("button", name="Rozpocznij test").click()

    # odpowiedz na pierwsze pytanie i przejdź dalej
    page.get_by_role("radio").first.check()
    page.get_by_role("button", name="Następne pytanie").click()

    expect(page.get_by_role("heading", name="Twój wynik")).to_be_visible()

Taki test przeżywa przebudowę HTML-a, bo nie zna struktury – zna treść. A przy okazji dostaję coś za darmo: get_by_role znajduje tylko elementy z poprawną semantyką dostępności, więc test nie przejdzie po stronie, po której nie przejdzie czytnik ekranu.

Diagnostyka po fakcie. Gdy test padnie w CI, dostaję zapis przebiegu do przejrzenia krok po kroku – z zrzutami, konsolą i ruchem sieciowym. W epoce Selenium diagnoza padniętego testu zaczynała się od „spróbuję odtworzyć lokalnie”, co przy błędach losowych bywało zabawą na godziny.

Maestro: flow mobilny w YAML-u

Na mobile – aplikacje piszemy w React Native i Flutterze – rolę Playwrighta pełni Maestro. Cały flow to plik YAML, czytelny nawet dla kogoś, kto nie pisze testów:

# Maestro (YAML)
appId: pl.example.zawodowe
---
- launchApp
- tapOn: "Egzaminy"
- tapOn: "INF.02"
- tapOn: "Rozpocznij test"
- assertVisible: "Pytanie 1"

Ta prostota to nie lukier – to ta sama filozofia, co automatyczne czekanie Playwrighta: narzędzie samo toleruje opóźnienia i animacje interfejsu, więc scenariusz opisuje co użytkownik robi, a nie jak długo czekać, aż aplikacja pozwoli mu to zrobić. Po doświadczeniach z Appium, gdzie zanim padł pierwszy tap, trzeba było wynegocjować sesję z serwerem WebDrivera, różnica jest trudna do przecenienia.

Stare kontra dziś

KiedyśDziśCo to zmienia
SeleniumPlaywrightauto-czekanie, jeden interfejs na trzy silniki przeglądarek
AppiumMaestroflow w YAML-u zamiast sesji WebDrivera i capabilities
ręczne sleep()wbudowane czekanie na gotowośćkoniec głównego źródła testów niestabilnych
XPath po strukturzerola i widoczna nazwa elementutesty przeżywają przebudowę szablonów, gratis: dostępność
własna ferma sterownikówprzeglądarki instalowane przez narzędziezero „testy padły, bo Chrome się zaktualizował”
zrzut ekranu z momentu upadkupełny zapis przebiegu testudiagnozę robi się po fakcie, nie przez odtwarzanie

Wniosek jest ten sam, co w serii o odruchach: opór przed E2E był kiedyś racjonalny, bo narzędzia były bolesne. Narzędzia dogoniły – a opór, jeśli został, jest już tylko przyzwyczajeniem.

Sekcja uczciwości: E2E nadal jest drogie

Nowe narzędzia zdjęły ból, ale nie zdjęły kosztów – i nie ma co udawać, że jest inaczej:

  • To wciąż najwolniejsze testy w suicie. Sekundy zamiast milisekund, bo uruchamia się cała aplikacja z przeglądarką albo emulatorem. Dlatego piramida obowiązuje: flow jest kilkanaście, nie kilkaset.
  • Środowisko i dane trzeba utrzymywać. Test przepływu potrzebuje działającej aplikacji z sensownym stanem – użytkownikiem, egzaminem, pytaniami. To jest infrastruktura, która nie napisze się sama.
  • Losowość zmalała, ale nie zniknęła. Sieć, animacje, czas – na tym poziomie zawsze będzie drgać. Gdy jakiś flow zaczyna migotać, naprawiam go albo usuwam, od razu – z powodów, które opisałem przy testach niestabilnych.
  • Flow mówi „co się zepsuło” gorzej niż testy niższych pięter. Padnięty test jednostkowy wskazuje funkcję. Padnięty flow mówi „gdzieś na ścieżce logowania jest źle” – i dobrze, że mówi, ale diagnoza zaczyna się od zera.

Puenta

Trzy części tej serii układają się w jedno zdanie: zbyt długo o moich błędach mówiła mi produkcja, a dziś mówią mi testy – jednostkowe o granicach i logice, integracyjne o umowach między elementami, a przepływy o tym, czy aplikacja jako całość robi to, po co istnieje. Żaden poziom nie zastępuje pozostałych i żaden nie obiecuje szczelności – obiecują tylko tyle, że o większości błędów dowiem się przed wdrożeniem, a nie z logów, od użytkownika albo z ciszy.

Deklaracje łatwo składać, więc na koniec zostawiłem liczby. Ile testów naprawdę ma mój produkcyjny projekt – web, mobilka i API, co do sztuki, z pokryciem i historią o tym, przed czym te testy uratowały produkcję – policzę w osobnym studium przypadku.