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 |
|---|---|---|
| Selenium | Playwright | auto-czekanie, jeden interfejs na trzy silniki przeglądarek |
| Appium | Maestro | flow 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 strukturze | rola i widoczna nazwa elementu | testy przeżywają przebudowę szablonów, gratis: dostępność |
| własna ferma sterowników | przeglądarki instalowane przez narzędzie | zero „testy padły, bo Chrome się zaktualizował” |
| zrzut ekranu z momentu upadku | pełny zapis przebiegu testu | diagnozę 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.