Wyślij zapytanie Dołącz do Sii

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.

Przykład Selenium – Implicit wait
Ryc. 1 Przykład Selenium – Implicit wait

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.

Przykład Selenium – Explicit wait
Ryc. 2 Przykład Selenium – Explicit wait

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.

Przykład Selenium – FluentWait
Ryc. 3 Przykład Selenium – FluentWait

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:

Cypress – cypress.json ustawienie wartości defaultCommandTimeout
Ryc. 4 Cypress – cypress.json ustawienie wartości defaultCommandTimeout

Możemy również zmienić te czasy dla konkretnego łańcucha akcji bądź samej metody poprzez przesłanie obiektu z oczekiwanym czasem:

Cypress – użyty implicit subject oraz nadpisanie defaultCommandTimeout porzez przesłanie obiektu do metody get
Ryc. 5 Cypress – użyty implicit subject oraz nadpisanie defaultCommandTimeout porzez przesłanie obiektu do metody get

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:

Przykład Cypress – asercja z explicit subject
Ryc. 6 Przykład Cypress – asercja z explicit subject

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}.
Cypress – sposoby implementacji cy.wait()
Ryc. 7 Cypress – sposoby implementacji cy.wait()

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.

Lista zapytań API na stronie www.google.pl
Ryc. 8 Lista zapytań API na stronie www.google.pl

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

Przykład Cypress – implementacja metody cy.intercept()
Ryc. 9 Przykład Cypress – implementacja metody cy.intercept()

Powyżej przedstawiona została implementacja intercepta, który następnie wykorzystujemy w teście. Ma on za zadanie:

  1. Odwiedzić stronę Google.pl.
  2. Zaakceptować ciasteczka.
  3. Wpisać Sii w pole wyszukiwarki oraz nacisnąć przycisk Enter.
  4. Czekać na alias @finishedGoogleSearch.
  5. Dokonać sprawdzenia, czy na załadowanej stronie jest widoczny tekst „Około”.
Przykład Cypress – kod testu wykorzystujący czekanie na alias
Ryc. 10 Przykład Cypress – kod testu wykorzystujący czekanie na alias

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.

Przykład Cypress – implementacja rekurencyjnego waita na pojawienie się elementu
Ryc. 11 Przykład Cypress – implementacja rekurencyjnego waita na pojawienie się elementu

Wait realizuje to zadanie w następującej kolejności:

  1. Pobranie zawartości body dzięki metodzie .get().
  2. 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.
  3. Oczekiwanie w czasie splitu – czyli czas, po upływie którego chcemy dokonać kolejnego sprawdzenia, czy element spełnia zadany warunek.
  4. Zdefiniowanie totalTime i rozpoczęcie zwiększania go o zadany czas przerwy (splitu) przy każdym następnym rekurencyjnym wywołaniu.
  5. 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.
  6. 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.
Ryc. 12 Cypress – przykładowa implementacja cypress-wait-until
Ryc. 12 Cypress – przykładowa implementacja cypress-wait-until
  • 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.
Cypress – przykładowa implementacja cypress-recurse
Ryc. 13 Cypress – przykładowa implementacja cypress-recurse

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

***

Jeżeli interesuje Cię tematyka Cypressa, Selenium lub automatyzacji testów, zachęcamy do zapoznania z innymi artykułami naszych ekspertów.

4.9/5 ( głosy: 41)
Ocena:
4.9/5 ( głosy: 41)
Autor
Avatar
Piotr Flis

Przygodę z testami rozpoczął w 2015 roku pracując z Silk Testem oraz Pythonem. Obecnie zajmuje się automatyzacją testów w Cypress z wykorzystaniem JavaScrypt oraz TypeScrypt. Jest założycielem grupy strzeleckiej Sii-ła Magnum w Sii. Po pracy kładzie nacisk na rozwój osobisty w zakresie testów automatycznych i praktykę strzelecką. Z dużą dozą prawdopodobieństwa można go spotkać na największych zawodach IPSC w Polsce w kategorii Strzelba Standard.

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Może Cię również zainteresować

Pokaż więcej artykułów

Bądź na bieżąco

Zasubskrybuj naszego bloga i otrzymuj informacje o najnowszych wpisach.

Otrzymaj ofertę

Jeśli chcesz dowiedzieć się więcej na temat oferty Sii, skontaktuj się z nami.

Wyślij zapytanie Wyślij zapytanie

Natalia Competency Center Director

Get an offer

Dołącz do Sii

Znajdź idealną pracę – zapoznaj się z naszą ofertą rekrutacyjną i aplikuj.

Aplikuj Aplikuj

Paweł Process Owner

Join Sii

ZATWIERDŹ

This content is available only in one language version.
You will be redirected to home page.

Are you sure you want to leave this page?