Najpierw ustal, co naprawdę testujesz
Pod wspólną nazwą E2E często kryją się scenariusze o zupełnie innym zasięgu. Test bez sztucznych danych przechodzi przez przeglądarkę, Next.js i prawdziwy backend. Z kolei test, w którym cy.intercept() podsuwa gotowe dane, omija API oraz bazę – staje się wtedy szybkim testem samego interfejsu, który nie weryfikuje już poprawności działania całego systemu.
| Rodzaj scenariusza | Własny backend | Typowe zastosowanie |
|---|---|---|
| Prawdziwy E2E | działa naprawdę | logowanie, checkout, zapis krytycznych danych |
| UI ze stubem | odpowiedź zwraca cy.intercept() | błędy 500, puste listy, opóźnienia |
| Usługa zewnętrzna | sandbox lub stub | płatności, e-mail, API partnera |
Najpraktyczniejszy zestaw składa się z kilku w pełni rzeczywistych ścieżek sukcesu (tzw. happy paths) oraz większej liczby szybkich testów ze sztucznymi danymi dla rozmaitych wariantów interfejsu. Dzięki temu pakiet zachowuje stabilność, a jednocześnie bezbłędnie wykrywa zerwany kontrakt między klientem a serwerem.
Instalacja i konfiguracja Cypress E2E
Wybierz E2E Testing → Chrome → Cypress wygeneruje strukturę plików.
Kilka porad:
-
Wykorzystuj
baseUrlz umiarem, bo jest to opcja konfiguracyjna Cypressa, a nie wymóg App Routera, który służy po prostu do tego, by pisaćcy.visit('/products')zamiast pełnego adresu URL. -
Nie manipuluj globalnym timeoutem. Zwiększanie go tylko po to, by ukryć problemy z synchronizacją lub niestabilnym stanem aplikacji, to leczenie objawów, a nie przyczyn problemów.
-
Nie ufaj mechanizmowi powtórzeń (
retries), ponieważ ponowienia nie naprawiają chwiejnych testów (flaky tests), a jedynie maskują fakt, że test nie jest w pełni deterministyczny.
Skrypty Cypress w package.json
Narzędzie start-server-and-test uruchamia serwer, czeka, aż będzie gotowy, i dopiero wtedy przechodzi do testów. Z kolei komenda w CI samodzielnie buduje aplikację, dzięki czemu cały proces nie wymaga żadnych wcześniejszych, ręcznych kroków:
Fixtures: szybki test UI, nie pełne E2E
Dane testowe to statyczne zasoby wykorzystywane do stubowania żądań wychodzących z przeglądarki. Świetnie sprawdzają się przy symulowaniu pustych list, błędów czy rozmaitych stanów interfejsu, ale całkowicie pomijają rzeczywistą odpowiedź backendu. Oznacza to, że poniższy scenariusz nie potwierdza ani poprawnego działania endpointu /api/products, ani tego, że produkcyjny backend zwraca identyczny kontrakt danych.
Plik cypress/fixtures/api-responses/products.json może na przykład zawierać dwa przykładowe produkty. Nie trzeba go wcześniej ładować do osobnej zmiennej ani przypisywać mu aliasu, ponieważ cy.intercept() potrafi obsłużyć plik fixture bezpośrednio:
Dane testowe i logowanie programowe
Rzetelne testy E2E wymagają przewidywalnego backendu i dlatego, najlepiej resetować bazę danych w bloku beforeEach, a następnie wstrzykiwać wymagany zestaw danych za pomocą cy.task(). Implementacja takiego taska stanowi swego rodzaju adapter projektowy – inaczej będzie wyglądać dla Prismy, Drizzle czy Supabase. Każda wersja musi jednak bezwzględnie sprawdzać warunek APP_ENV === 'test' i odmawiać działania w przypadku wykrycia produkcyjnego adresu bazy.
Czyszczenie bazy przed testem jest bezpieczniejsze niż sprzątanie w afterEach. W sytuacji, kiedy poprzedni test lub sam runner ulegnie awarii, kolejny scenariusz i tak wystartuje z pewnego, znanego stanu.
Adapter bazy danych zarejestruj wewnątrz setupNodeEvents. Importowane poniżej funkcje należą do kodu aplikacji i to one powinny fizycznie wykonywać reset oraz zasilenie bazy danymi:
Logowanie przez UI zostaw dla testu samego procesu logowania, ponieważ pozostałe testy
mogą użyć endpointu aplikacji i zachować wynik przez cy.session(). Poniższe
adresy są przykładowym kontraktem projektu, a nie wbudowanymi endpointami Next.js
lub Auth.js.
Type definitions dla custom commands
W sytuacji, kiedy odpowiedź logowania ustawia ciasteczko HttpOnly, cy.request() automatycznie zapisuje nagłówek Set-Cookie w pamięci przeglądarki, w związku z czym nie trzeba ręcznie kopiować tokenu do localStorage. Pamiętaj tylko, że endpoint do testowego logowania musi być bezwzględnie niedostępny poza środowiskiem testowym.
Z kolei w projektach opartych na Auth.js zamiast tego rozwiązania możesz owinąć standardowy proces uwierzytelniania albo zainicjować sesję bezpośrednio przez adapter bazy danych.
Granice App Routera w testach Cypress
Loading states i Suspense w testach E2E
Najpierw ustal, gdzie wykonywany jest request. cy.intercept() przechwytuje ruch
wychodzący z przeglądarki, więc poniższy test jest poprawny dla Client Component:
Wywołanie fetch() w komponencie serwerowym wykonuje się w procesie Next.js i w ogóle nie przechodzi przez proxy przeglądarki. Dane do takiego testu przygotujesz za pomocą cy.task('db:seed', ...), a następnie sprawdzisz rezultat po wywołaniu cy.visit(). Kiedy musisz w sposób deterministyczny wymusić wyświetlenie pliku loading.tsx, dodaj kontrolowane opóźnienie w testowym adapterze backendu – przeglądarkowy cy.intercept() nie wpłynie na żądanie fetch() wykonywane po stronie serwera.
Cypress ponawia zapytania do DOM i sprawdza, czy element nadaje się do
interakcji, dlatego nie dodawaj globalnego waitForHydration(). W sytuacji, kiedy aplikacja naprawdę renderuje aktywny przycisk przed podpięciem handlera Reacta, wystaw jawny
znacznik gotowości dla tej części UI i testuj ten konkretny kontrakt.
Server Actions w testach E2E
Uzależnianie testów od wewnętrznych mechanizmów Next.js to najłatwiejsza droga do stworzenia niestabilnego zestawu, który wymaga poprawek po każdej aktualizacji frameworka.
Testuj efekt widoczny dla użytkownika, a nie mechanizm. Sam toast jest za słabą asercją, ponieważ może pojawić się po optymistycznej aktualizacji. Przeładowanie strony potwierdzi, że zmiana dotarła do backendu:
Intercepcję zachowaj do zewnętrznych usług, na przykład gdy formularz wywołuje Stripe, zewnętrzne partnera albo webhook.
Parallel Routes i Intercepting Routes w testach E2E
Ten test obejmuje najważniejszą różnicę Intercepting Routes: miękka nawigacja otwiera modal, natomiast bezpośrednie żądanie dokumentu renderuje pełną stronę.
Sekrety i konfiguracja środowiska
Nie zapisuj haseł w cypress.config.ts. Lokalnie możesz użyć ignorowanego przez
Git pliku cypress.env.json, a w GitHub Actions przechowuj wartości w Secrets i
przekazuj je jako CYPRESS_TEST_USER_EMAIL oraz
CYPRESS_TEST_USER_PASSWORD. Cypress usuwa prefiks CYPRESS_, dlatego test
odczytuje klucze TEST_USER_EMAIL i TEST_USER_PASSWORD przez cy.env().
Oddziel dane jawne (takie jak nazwa środowiska) od poufnych sekretów. Nigdy nie wykonuj bezpośrednich asercji na haśle czy tokenie – w razie błędu ich wartości mogłyby wylądować w logach CI.
Cypress E2E w GitHub Actions CI
Testy CI uruchamiaj przeciwko next build + next start, ponieważ serwer
deweloperski nie odtwarza dokładnie produkcyjnego routingu, bundlingu i cache.
Zmienne potrzebne podczas buildu i działania serwera ustawiaj na poziomie joba,
nie tylko kroku Cypress.
Sekrety GitHub nie są automatycznie udostępniane w procesach uruchamianych z forków. W sytuacji, kiedy Twoje repozytorium przyjmuje zewnętrzne pull requesty, podziel proces na dwie części: bezpieczny zestaw testów bez dostępu do sekretów oraz pełny pakiet E2E, który rusza dopiero po zatwierdzeniu zmian.
Pamiętaj też, że opisane zadanie odpowiada za CI, a nie za CD. Wdrożenie powinno być osobnym etapem z klauzulą needs: e2e lub osobnym procesem uruchamianym dopiero po uzyskaniu zielonego buildu. Sam job testów E2E warto ustawić jako wymagany status check dla chronionej gałęzi – dzięki temu nikt nie ominie ich przy scalaniu pull requesta.
Optymalizacja czasu testów E2E w CI
Wprowadź paralelizację dopiero wtedy, gdy czas wykonywania testów zacznie realnie blokować pracę zespołu. Cypress Cloud potrafi automatycznie rozdzielać pliki testowe pomiędzy maszyny, a bez płatnej wersji można rozbić foldery na osobne zadania za pomocą macierzy w CI. Oba te rozwiązania wiążą się jednak z większym kosztem konfiguracji i na niewiele się zdadzą, jeśli Twoje testy są zależne od współdzielonego stanu.
Co powinno wejść do pierwszego zestawu
Na start w zupełności wystarczą logowanie lub rejestracja, kluczowy zapis przez Server Action, proces zakupu (checkout) lub główna funkcja produktu, jeden test nawigacji w App Routerze oraz jeden przypadek kontrolowanego błędu obsłużony przez cy.intercept().
Atrybuty data-cy wybieraj wtedy, gdy treść elementu może się zmieniać bez wpływu na jego działanie. Z kolei wyszukiwanie po tekście lub roli sprawdza się lepiej, gdy to właśnie one są kluczowym wymaganiem, przykładowo w przypadku komunikatów o błędach czy dostępnych etykiet przycisków. Poza tym, unikaj również sztywnych opóźnień cy.wait(ms) na rzecz aliasów zapytań lub asercji weryfikujących widoczny efekt na ekranie.


