MOVGRANTO

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

Test E2E przechodzi przez formularz rejestracji, mail aktywacyjny i pulpit z checklistą.
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.

Porównanie E2E przed i po: wcześniej staging bez stabilnych danych, 111 testów w trzech przeglądarkach, ponad 20 minut i fałszywe alarmy, testy wyłączone na PR. Teraz osobne konta testowe, jedna przeglądarka, poczta przechwycona w bazie, pełna rejestracja od maila i bramka na PR do develop

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) + sent

Do 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.

Przepływ testu pełnej rejestracji: Playwright wypełnia formularz, backend E2E zakłada konto i wysyła mail aktywacyjny, który CapturingEmailBackend zapisuje w bazie zamiast w skrzynce. Test pobiera link przez endpoint test-support z tokenem, potwierdza konto, loguje się, wybiera plan i widzi pulpit. Po teście konto, firma i poczta są usuwane

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.

Łukasz Głowacz

O autorze

Łukasz Głowacz

Full-Stack Engineer · MOVGRANTO

Full-Stack Engineer w MOVGRANTO. Buduje systemy od architektury po produkcję: backend, web, mobile, płatności i CI/CD. Autor BusiKM, SaaS do zarządzania flotą busów.

LinkedInGitHub

BusiKM

Zarządzanie flotą busów 2,5-3,5 t: zlecenia, mapa floty, czas pracy kierowców i koszty w jednym miejscu. 14 dni za darmo.

Sprawdź BusiKM ↗

Budujesz podobny system?

Dołączamy do zespołów jako inżynierowie full-stack albo budujemy produkt od zera. Porozmawiajmy o Twoim projekcie.

Umów rozmowę →

Newsletter

Co tydzień jedna prawdziwa decyzja architektoniczna z budowy produkcyjnego SaaS.