Testy E2E na prawdziwym backendzie: rejestracja od maila
Testy E2E w BusiKM były wyłączone przez fałszywe alarmy. Teraz idą na prawdziwym backendzie, a test rejestracji czyta mail aktywacyjny z bazy.
Opublikowano: 5 min czytania
Autor: Łukasz Głowacz, Full-Stack Engineer · Read in English

Spis treści
Do października testy E2E w panelu webowym BusiKM leżały w repozytorium i niczego nie pilnowały. Workflow w CI był wyłączony dla pull requestów, a w nagłówku miał komentarz z wyjaśnieniem: brak stabilnych danych na stagingu dawał masę fałszywych alarmów, a 111 testów razy trzy przeglądarki z ponowieniami trwało ponad 20 minut i zwykle kończyło się przekroczeniem czasu. Testy uruchamiały się tylko po pushu do main, czyli wtedy, gdy na reakcję było już za późno. W październiku przerobiłem je na bramkę dla PR-ów do develop i dołożyłem test, którego wcześniej nie dało się napisać: pełną rejestrację z mailem aktywacyjnym.
Dlaczego nie mocki
Najszybszą drogą do zielonych testów byłoby zamockowanie backendu w Playwright. Testy przechodziłyby zawsze, tylko że nic by nie mówiły o tym, czy web i backend naprawdę ze sobą działają. A to jest dokładnie to miejsce, w którym psuje się najwięcej, zwłaszcza gdy kontrakt API zmienia się co kilka dni. Wybrałem drugą drogę: testy na prawdziwym backendzie, ale na środowisku przygotowanym pod testy, a nie na stagingu z przypadkowymi danymi.

Zmiany są trzy. Środowisko E2E ma własne konta zakładane komendą seed_e2e: zwykła firma z potwierdzonymi kontami, a nie konto demo. Testy idą w jednej przeglądarce, chromium, tak jak przy lokalnej weryfikacji, więc przebieg nie mnoży się razy trzy. A workflow włącza się zmienną w repozytorium, żeby PR-y nie czerwieniały, zanim środowisko ma konta testowe.
Test, który sprawdza, czy firma przyjmuje zapisy
Konto demo w BusiKM działa w trybie tylko do odczytu, więc test, który coś zapisuje, odbije się od niego błędem 402. Lokalnie testy chodzą na kontach demo, w CI na kontach z seed_e2e. Zamiast zgadywać, na czym test działa, helper pyta o to backend wprost:
/** Stops the test if the company does not accept writes: an empty POST gives 400 (validation) or 402. */
export async function requireWritable(request: APIRequestContext): Promise<void> {
const probe = await request.post("/api/proxy/v1/counterparties/", { data: {} });
if (probe.status() !== 402) return;
if (REQUIRE_WRITES) throw new Error(READ_ONLY_REASON); // CI gate: read-only is an error
test.skip(true, READ_ONLY_REASON); // locally: skip
}Pusty POST nie przejdzie walidacji, więc niczego nie zapisze. Odpowiedź 400 znaczy, że zapisy działają, a 402, że firma jest tylko do odczytu. W bramce CI to drugie jest błędem konfiguracji środowiska, lokalnie powodem do pominięcia testu.
Mail, którego nie ma gdzie odebrać
Najtrudniejszy był test rejestracji. Użytkownik wypełnia formularz, dostaje maila z linkiem aktywacyjnym, potwierdza konto, loguje się, wybiera plan i trafia na pulpit z checklistą. Bez maila tej ścieżki nie da się przejść, a na środowisku E2E nie ma skrzynki, do której Playwright miałby dostęp.
Rozwiązaniem jest osobna aplikacja w backendzie, test_support. Jej backend poczty przechwytuje wiadomości do adresów z domeny testowej i zapisuje je w bazie zamiast wysyłać. Wiadomości do każdego innego adresu idą dalej zwykłą drogą.
class CapturingEmailBackend(BaseEmailBackend):
"""Mail to the E2E domain is stored in the database, the rest goes on as usual."""
def send_messages(self, email_messages):
captured = [m for m in email_messages
if m.recipients() and all(map(is_e2e_address, m.recipients()))]
delivered = [m for m in email_messages if m not in captured]
for message in captured:
CapturedEmail.objects.bulk_create(
CapturedEmail(recipient=r, subject=message.subject,
body=message.body, html_body=_html_body(message))
for r in message.recipients()
)
sent = 0
if delivered:
connection = get_connection(settings.E2E_DELEGATE_EMAIL_BACKEND)
sent = connection.send_messages(delivered) or 0
return len(captured) + sentDo tego dwa endpointy: jeden zwraca ostatnią wiadomość do adresu razem z wyciągniętymi z niej linkami, drugi usuwa konto testowe po teście. Pierwszy ma filtr contains, bo po rejestracji wychodzą dwa maile, aktywacyjny i powitalny, a „ostatni” to akurat powitalny. Drugi usuwa firmę tylko wtedy, gdy nie ma w niej innych użytkowników, bo konto testowe mogło zostać dopisane do cudzej firmy.

Sam test w Playwright odpytuje ten endpoint w pętli przez 30 sekund, aż mail się pojawi. Każdy przebieg zakłada osobną firmę z losowym, ale poprawnym numerem NIP z sumą kontrolną, więc testy nie wpadają na siebie, a po teście konto znika.
Kod testowy w kodzie produkcyjnym
Endpointy, które oddają czyjąś pocztę i usuwają konta, w kodzie aplikacji brzmią jak proszenie się o kłopoty. Dlatego mają kilka warstw zabezpieczeń:
- działają tylko tam, gdzie w ustawieniach włączono wsparcie E2E, a ustawienia produkcyjne wyłączają je na stałe,
- gdy są wyłączone, odpowiadają 404, żeby nie zdradzać, że takie endpointy w ogóle istnieją,
- wymagają tokenu środowiska w nagłówku, porównywanego funkcją
hmac.compare_digest, odporną na pomiar czasu, - przyjmują wyłącznie adresy z domeny testowej, więc nie da się nimi odczytać ani usunąć prawdziwego konta.
Endpoint skrzynki nie ma limitu zapytań, bo test odpytuje go w pętli. Dostęp i tak wymaga tokenu, więc limit nie dodawałby tu ochrony, a psułby test.
To samo w aplikacji kierowcy
Aplikacja mobilna korzysta z tej samej infrastruktury. Smoke test w Maestro buduje wersję release na emulatorze Androida. Przed testem właściciel przez API zaprasza kierowcę, a kod zaproszenia jest czytany z maila przechwyconego przez backend. Test przechodzi ekran powitalny, wejście kodem z zaproszenia, założenie konta, zgody na uprawnienia i trasę bez zlecenia od startu do potwierdzenia. Kolejne kroki z listy kontrolnej wydania, czyli paragon, tryb offline i reset hasła, są osobnym zadaniem na później.
Ile to kosztuje
Środowisko E2E to kolejne środowisko do utrzymania, z własnymi kontami i sekretami. Token wsparcia E2E musi być ten sam w backendzie środowiska i w sekretach CI webu oraz aplikacji mobilnej. Jeśli go zabraknie, test rejestracji jest pomijany, a nie zgłasza błąd, więc trzeba pamiętać, że pominięty test to nie zielony test.
Druga rzecz to kod testowy w repozytorium backendu. Alternatywą byłaby osobna usługa do przechwytywania poczty, ale to kolejna rzecz do postawienia, utrzymania i udostępnienia dla CI. Aplikacja test_support ma własne testy i jest wyłączona tam, gdzie nie powinna działać.
Najważniejsza zmiana nie była techniczna. Wcześniej testy E2E sprawdzały ekrany, a teraz sprawdzają drogę, którą przechodzi nowy klient: od formularza, przez maila, do pierwszego ekranu w aplikacji. Jeśli ta droga się zepsuje, nikt nie zapłaci za resztę funkcji. Jeśli chcesz porozmawiać o testach w swoim SaaS, napisz do mnie.
