W pracy testera automatyzującego jednym z kluczowych zadań jest poprawna obsługa oczekiwań na zdarzenie, która umożliwia nam wykonie asercji na przygotowanym już elemencie. W żargonie testerów spotykamy się z określeniem „waity” od metody wait() i ta nomenklatura została użyta w tym artykule.
We wpisie omówię do czego potrzebne są waity w testach automatycznych, przedstawię porównanie waitów dostępnych w Selenium oraz w Cypressie wraz z przykładową ich implementacją w Cypressie.
Cypress w znaczący sposób wyróżnia się na tle innych dostępnych aplikacji do testowania automatycznego dzięki jednemu specyficznemu waitowi, który również zostanie omówiony wraz z metodami cy.wait() oraz cy.intercept().
Ostatnim aspektem poruszonym w tym artykule będzie rekurencja, która też może być wykorzystana w Cypressie do waitu na element, jako własne niestandardowe podejście.
Na naszym blogu pojawił się wcześniej artykuł wprowadzający do Cypressa. Zachęcam do przeczytania go w celu zapoznania się z pozostałymi funkcjonalnościami narzędzia.
Wait w testach automatycznych
Wait jest jedną z podstawowych i kluczowych części testu automatycznego. Dzięki niemu możemy wykonać opóźnienie w teście po to, aby strona internetowa bądź dowolny element na tej stronie poprawnie się załadował. Umożliwi nam to dokonanie sprawdzenia, które skutkuje pozytywnym bądź negatywnym zakończeniem naszego testu.
Zła implementacja waita może skutkować błędnym wynikiem testu np. poprzez próbę interakcji ze stroną, która nie została do końca załadowana. Innym popularnym problemem jest niewyświetlanie elementu, gdyż czekał on na przykład na odpowiedź serwera. Jednak jedną z większych trudności związaną ze złą implementacją waita jest właśnie niepotrzebne czekanie, które wydłuża nam czas trwania testu.
Wait w Selenium
W jednym z najbardziej popularnych narzędzi do testów automatycznych, jakim jest Selenium, mamy do czynienia z trzema rodzajami waitów, które omówię poniżej.
Implicit wait – oczekiwane niejawne
Polega ono na próbie znalezienia elementu w DOM-ie przez określony czas, jeżeli nie jest on natychmiastowo dostępny. Domyślna wartość tego waita to 0 i kiedy zmienimy tą wartość, niejawne oczekiwanie trwa do końca sesji.
Jak przedstawiono w powyższym kodzie, implementacja tego rozwiązania jest bardzo szybka, jednak ma ona pewne ograniczenia. Ten wait działa tylko z metoda findElement(), co uniemożliwia nam oczekiwanie na załadowanie się przykładowo tekstu w elemencie.
Explicit wait – oczekiwanie jawne
Dzięki niemu, mamy możliwość zatrzymania testu do momentu, aż oczekiwany przez nas warunek zostanie spełniony. Sprawdzenie warunku jest wywoływane z określoną częstotliwością, aż upłynie limit czasu oczekiwania. Oznacza to, że gdy warunek zwraca wartość fałszywą, Selenium będzie czekać i próbować ponownie.
W przedstawionym wyżej kodzie oczekujemy, aż tytuł naszej strony uzyska spodziewaną wartość. Miejmy na uwadze, że wait jest teraz używany tylko w momencie wywołania, a nie jak poprzednio – globalnie. Tutaj znajdziecie listę dostępnych oczekiwanych warunków expicit wait dla Selenium.
FluentWait
Oczekujemy w sposób jawny na określoną sytuacje przez podaną liczbę sekund – przykładowo: poniższy kod przez 10 sekund będzie sprawdzał, czy nasza strona ma oczekiwany tytuł z częstotliwością jednej sekundy.

Wait w Cypressie
Cypress domyślnie wykorzystuje dynamiczne czekanie na elementy bądź akcje dostępne na stronie internetowej użytej w naszym teście automatycznym. Możemy wyróżnić przedstawione niżej sposoby do obsługi waitów.
Domyślne timeouty
W Cypressie dostępnych jest 6 domyślnych timeoutów. Dzięki wbudowanemu mechanizmowi re-try, wykonują sprawdzenia w zadanym przez nas lub w założonym czasie, podobnie do poprzednio omówionego explicit waita:
- defaultCommandTimeout – jest to czas, w którym Cypress stara się dostać do elementu lub akcji podczas wykonywania większości komend na DOM-ie.
- execTimeout – timeout definiujący czas czekania na system, aby zakończył operacje podczas komendy cy.exec().
- taskTimeout – maksymalny czas, w którym czekamy na wykonanie zadania poprzez komendę cy.task().
- pageLoadTimeout – oczekuje na tzw. “page transition event”. Zdarzenie to występuje, gdy użytkownik odwiedza bądź opuszcza daną stronę internetową. Ten domyślny timeout oczekuje również, aż komendy cy.visit(), cy.go(), cy.reload() wykonają load events. Są one wywoływane, gdy cała strona oraz wszystkie jej zasoby tj. arkusz stylów, skrypty, iframe’y i obrazy, będą załadowane.
- requestTimeout – maksymalny czas, w którym czekamy na wykonanie cy.wait().
- responseTimeout – czeka na wykonanie metod cy.request(), cy.wait(), cy.fixture(), cy.getCookie(), cy.getCookies(), cy.setCookie(), cy.clearCookie(), cy.clearCookies() i cy.screenshot().
Jeżeli chcemy nadpisać wartości dla timeoutów, dokonujemy tego w pliku cypress.json:
Możemy również zmienić te czasy dla konkretnego łańcucha akcji bądź samej metody poprzez przesłanie obiektu z oczekiwanym czasem:

W powyższym kodzie użyliśmy implicit subject do stworzenia asercji dzięki metodzie .should(). Najpierw Cypress stara się odnaleźć element przycisku w czasie 20 sekund zdefiniowanych w przesłanym obiekcie {timeout: 20000}, a następnie dokonuje asercji na tym przycisku, by sprawdzić, czy zawiera tekst „Szukaj w Google”. Tutaj ponownie oczekuje do 20 sekund na pojawienie się tego tekstu. Timeout możemy uzyskać podczas wykonywania metody cy.get() oraz should(), który zaneguje nam cały test.
Dla metod get(), find(), cointains() z doświadczenia polecam używanie timeoutu na łańcuchu akcji w przypadku, gdy oczekiwanie na element bądź jego właściwości jest wydłużone. Natomiast czas ustawiony w cypress.json należy ograniczyć do rozsądnego minimum, aby uniknąć niepotrzebnego wydłużenia testów w przypadku, gdy dostajemy fail.
W Cypress mamy możliwość dokonana podobnej asercji z explicit subject poprzez użycie metody .then() i dokonania asercji na elemencie JQuery:
W tym przypadku Cypress najpierw oczekuje do 20 sekund na metodę .get(), a następnie natychmiast wykonuje asercję na elemencie, pomijając zdefiniowany defaultCommandTimeout.
Wbudowany Wait
Cypress posiada własną implementację metody wait, która przyjmuje do dwóch argumentów:
- Jedną z pierwszych wartości jest wartość numeryczna podana w milisekundach. Jest to wartość czasu, która upływa między poprzednią a następna komendą w Cypressowym łańcuchu akcji.
- Kolejną wartością, jaką możemy przesłać w miejsce czasu, jest alias bądź tablica aliasów. Aliasy to potężna konstrukcja w Cypressie, która ma wiele zastosowań i można o niej napisać osobny artykuł. Jednak dla nas, w koncepcji waitów, najważniejsze jest skupienie się na możliwości przypisania aliasu do API calla. Wykonujemy to poprzez metodę cy.intercept() oraz metodę .as(), gdzie metoda as zapisuje nam alias, do którego mamy dostęp z użyciem prefixu @.
- Ostatnią, opcjonalną wartością jest obiekt opcji, który pozwala nam nadpisać zdefiniowane poprzednio, domyślne czasy dla requestTimeout oraz responseTimeout. Dodatkowo, udostępnia nam możliwość manipulacji widocznością logowania waita dzięki przesłaniu obiektu {log: true/false}.
Kod ten przedstawia implementację Cypressowego waita. Ta metoda umożliwia nam czekanie w konkretnym miejscu w kodzie przez dany czas bądź do czasu pojawienia się odpowiedniego aliasu.
Tworzenie aliasu
Aby odpowiednio stworzyć wait na alias, potrzebujemy właśnie tego aliasu. Jedną z metod do jego stworzenia jest podejrzenie zapytań wykonywanych przez przeglądarkę na naszej stronie testowej. Następnie, wybieramy ostatnie zapytanie bądź kilka ostatnich wykonanych zapytań, które z dużą dozą prawdopodobieństwa wskażą nam, że strona jest załadowana.
Gdy znajdziemy już zapytanie, które nas interesuje, pozostaje nam wykorzystać metodę cy.intercept(). Pozwala nam ona śledzić i zarządzać żądaniami oraz odpowiedziami po sieci. Dzięki niej możemy:
- mockować dane,
- walidować zapytania API,
- a dzięki kombinacji z cy.wait(), czekać na zakończenie danego zapytania API.
Następnie, korzystając z metody .as(), możemy zapisać śledzone żądanie jako alias.
Implementacja intercepta
Powyżej przedstawiona została implementacja intercepta, który następnie wykorzystujemy w teście. Ma on za zadanie:
- Odwiedzić stronę Google.pl.
- Zaakceptować ciasteczka.
- Wpisać Sii w pole wyszukiwarki oraz nacisnąć przycisk Enter.
- Czekać na alias @finishedGoogleSearch.
- Dokonać sprawdzenia, czy na załadowanej stronie jest widoczny tekst „Około”.
Doradzam unikać waitów na zadany czas i skupić się na czekaniu na aliasy. Dzięki temu nasze testy będą krótsze, a kod bardziej zoptymalizowany i w znacznym stopniu wolny od nieoczekiwanych wyjątków.
Jedną z dodatkowych paczek do ESLinta jest Cypress ESLint Plugin, który pozwala nam zdefiniować regułę „cypress/no-unnecessary-waiting”: „error” i wykrywać te waity podczas tworzenia testów automatycznych.
Własna implementacja waitów
W przypadku, gdy potrzebujemy własnego waita, który nie kwalifikuje się do wcześniej omówionego cy.wait(), możemy użyć rekurencyjnego czekania ze sprawdzeniem warunku co odwołanie.
Rekurencja, nazywana również rekursją, jest odwołaniem funkcji lub definicji do samej siebie. Występuje ona w matematyce (algorytm Euklidesa, ciąg Fibonacciego), programowaniu, a nawet sztuce (obraz w obrazie bądź umieszczenie naprzeciw siebie dwóch luster – dokładnie wtedy powstaje nam odbicie w odbiciu).
Wróćmy jednak do programowania oraz naszego przykładu. Chcemy napisać waita, który będzie czekał, aż oczekiwany element pojawi się na stronie.
Wait realizuje to zadanie w następującej kolejności:
- Pobranie zawartości body dzięki metodzie .get().
- Następnie, zamienienie tego obiektu na obiekt JQuery, wywołanie na nim metody .find(locator) i finalnie .length, która zwraca 0, gdy element nie znajduje się w body.
- Oczekiwanie w czasie splitu – czyli czas, po upływie którego chcemy dokonać kolejnego sprawdzenia, czy element spełnia zadany warunek.
- Zdefiniowanie totalTime i rozpoczęcie zwiększania go o zadany czas przerwy (splitu) przy każdym następnym rekurencyjnym wywołaniu.
- Sprawdzenie, czy totalTime jest mniejszy bądź równy od timeoutu. Zależnie od wyniku warunku rekurencyjnie wywołuje funkcje checkForElement(), bądź wywołuje cy.get(resultText) w celu negacji testu.
- Na koniec, wywołanie cy.get(resultsText), gdy warunek body.find(elementID).length === 0 nie jest spełniony, co zamyka rekurencję.
Nie jest to idealne rozwiązanie tego problemu i powinno być używane w ostateczności, ponieważ mamy tutaj do czynienia z utrudnioną czytelnością kodu oraz trudnością w utrzymaniu kodu.
Powyższy kod bardzo ciężko debuguje się w Cypressie i nieumiejętne używanie rekurencji bądź pętli może skutkować wpadnięciem w nieskończoną pętlę tzw. infinity loop. Istnieje możliwość jej powstania podczas mieszania kodu synchronicznego i asynchronicznego bądź podczas błędnego sterowanie warunkami inkrementacji. Ostatecznie, nieskończona pętla spowoduję błąd testu i przeglądarka przestanie odpowiadać.
Dostępne zewnętrzne paczki waitów
W tym rozdziale omówię dwie znane mi paczki, które możemy użyć w naszych testach.
- cypress-wait-until – pozwala ona na oczekiwanie na wszystko, na co nie oczekuje cy.wait(). Metoda cy.waitUntil() przyjmuje dwa argumenty. Pierwszym jest funkcja, która wykonuje Cypressową metodę, metody bądź cały łańcuch akcji. Następnym argumentem jest obiekt opcji, który umożliwia sterowanie timeoutem oraz interwałem. Kod stara się dostać do pola przeglądarki przez trzy sekundy z interwałem jednosekundowym. Dzięki edycji wartości domyślnych, możemy dowolnie sterować wartościami timeout oraz interval. Domyślnymi wartościami dla tego waita jest timeout=5000, interval=200.
- cypress-recurse – paczka udostępnia rekurencyjne czekanie, aż oczekiwany warunek zwróci wartość True. Naszym spodziewanym warunkiem w tym przypadku jest expect(button).to.have.value(‘Szukaj w Google’)}. Metoda zaneguje test, jeżeli w ciągu 30 sekund ten warunek nie zwróci wartości True. Sam warunek będzie sprawdzany z częstotliwością 0,5 sekundy. Domyślne wartości dla metody to: timeout=4000, limit=15, delay=800.
Selenium a Cypress waits
Poszukajmy zatem podobieństw między waitami z Selenium a waitami z Cypressa.
Zacznijmy od impicit wait. Podobne zachowanie obserwujemy w Cypressie – poprzez sterowanie „defaultCommandTimeout” bądź samym timeoutem możemy czekać na pojawienie się elementu na stronie dzięki metodzie cy.get() w zadanym przez nas czasie.
Explicit wait w Cypressie jest dość kontrowersyjnym tematem, ponieważ ma on sprawdzać warunek. Cypressowa metoda .should(‘be.visible’) może być traktowana jako odpowiednik expected condition w Selenium elementIsVisible(element).
Jednak: czy asercja to wait? Jeżeli przyjmiemy, że tak, to wszystkie asercje .should() możemy potraktować jako explicit wait. Jestem pewien, że zdania, co do tej teorii, są podzielone, dlatego pozostawiam odpowiedź na to pytanie do własnej, czytelniczej interpretacji 😊
Nie zapominajmy o cy.wait(@alias). To właśnie ten wait może zostać rozważony jako pełnoprawny explicit. Przedstawionym zewnętrznym paczkom cypress-wait-until oraz cypress-recurse jest bliżej do explicit, a nawet fluent waita, ze względu na możliwość sterowania interwałem, zgodnie z którym sprawdzony jest warunek.
Podsumowanie
W artykule przedstawiłem dostępne sposoby czekania na elementy w Selenium oraz Cypressie tzw. waity, które umożliwiają swobodną pracę na większości elementów. Zaprezentowałem różnicę w czasie z Cypressowymi asercjami. Przygotowałem przykładowy kod z własnym waitem oraz omówiłem dwie zewnętrzne paczki, z którymi możemy pracować w Cypressie w zależności od naszych potrzeb.
Jeżeli zaczynacie przygodę z Cypressem, warto, abyście już na początku przyswoili wiedzę z obszaru cy.wait(‘@Alias’). Jest to podstawowa funkcjonalność narzędzia, która podnosi jakość kodu w naszych testach automatycznych oraz wyklucza konieczność oczekiwania na ładowanie się elementu. Powodzenia!
Źródła
- Selenium – Documentation
- Why Cypress?
- GitHub – Cypress ESLint Plugin
- GitHub – Cypress wait until
- GitHub – Cypress recurse
***
Jeżeli interesuje Cię tematyka Cypressa, Selenium lub automatyzacji testów, zachęcamy do zapoznania z innymi artykułami naszych ekspertów.
Fajnie wytłumaczony wait i cypress z przykładowym kodem.
Świetny artykuł
Świetny artykuł. Merytoryczny i z ogromnym ładunkiem wiedzy.
Fajny artykuł
Bardzo rzeczowo i przystępnie.
Fajne dobra robota pozdrawiam
Bardzo przydatna treść, pozdrawiam.
Świetny artykuł:)
Super artykuł, długo szukałem czegoś napisanego tak dobrze merytorycznie a jednocześnie przejrzystego i zrozumiałego. Tak trzymać!
Świetny wpis!
Świetny wpis, dzięki!
Konkrety kawałek wiedzy!
Kawał dobrej roboty! Wszystko fajnie, merytorycznie wytłumaczone.
A o tych Aliasach możesz napisać coś więcej?
Super artykuł, czy masz zamiar je dalej pisać? Za tekst po polsku duży plus.
Polecam 🙂