Nightwatch.js – case testów automatycznych dla frontu MojeING

10.03.2020 | Mateusz Czerwiński

Wstęp

Decydując się na automatyzację testów w projekcie, bardzo często nie zdajemy sobie sprawy, jak wiele wyborów i decyzji jeszcze przed nami. Rozwiązań, które będą nas wspierać i które są „najlepsze” jest na rynku bardzo wiele. Każde z nich ma oczywiście swoje wady i zalety, każde z nich wspiera konkretne rodzaje testów. Zespoły testujące oprogramowanie stają więc przed sporym wyzwaniem, aby możliwie najlepiej dopasować rozwiązania do projektu.

Gdy zaczynamy przygotowywać testy automatyczne dla naszego projektu dobrze jest zacząć od zdefiniowania tego, na czym nam najbardziej zależy i czego oczekujemy od naszych testów. Jasne określenie potrzeby testów automatycznych i ich wspierania zaoszczędzi nam w późniejszej fazie zawodu i niedomówień pomiędzy członkami zespołu bądź całymi zespołami. Jak do tego podejść? Od czego zacząć?

W pierwszej kolejności dobrze jest zdefiniować warstwy systemu, które chcemy poddać procesowi automatyzacji testów. Dzięki temu liczba rozwiązań, których moglibyśmy użyć znacznie się zmniejszy. Następnie dobrze jest zdefiniować środowiska, w jakich przyjdzie nam testować naszą aplikację. Chodzi tutaj o to, czy testy będą uruchamiane według przyjętego harmonogramu dostarczając nam zbiorczego raportu, będą narzędziem pracy testera pomagając mu zaoszczędzić czas podczas testów regresji czy może mają być wsparciem podczas procesu sprawdzania kodu, a ich uruchomienie będzie warunkowało możliwość dodawania kodu do głównej gałęzi w repozytorium.

W projekcie MojeING w pewnym momencie stanęliśmy przed dylematem: co wybrać, aby podnieść jakość automatyzacji. Przede wszystkim, od początku powstawania MojeING możliwe jest uruchomienie aplikacji lokalnie bez konieczności integracji z innymi podsystemami. To dało nam nowe spojrzenie na sposób wykorzystania testów i otwarło nowe możliwości. Ponieważ część frontowa projektu MojeING rozwijana jest w Node.js, zaczęliśmy szukać rozwiązań takich, aby aplikacja oraz jej testy były jednym spójnym rozwiązaniem, umieszczonym w jednym repozytorium. Zależało nam na rozwiązaniu szybkim, elastycznym i łatwym w kontekście konfiguracji. W taki sposób trafiliśmy na Nightwatch.js. Za główny cel obraliśmy wsparcie testami developerów i proces wytwarzania frontu na możliwie najwcześniejszym jego etapie. Nie zapomnieliśmy również o wsparciu testów regresji na środowisku takim jak UAT. Dlaczego właśnie tak?

Przede wszystkim Nightwatch.js posiada wbudowany mechanizm uruchamiania testów, który oferuje:

  • Uruchamianie pojedynczo bądź równoległe wielu scenariuszy, pozwalając przetestować więcej w danej jednostce czasu,
  • Tagowanie służące do uruchamiania wybiórczo interesujących nas scenariuszy (np. oznaczając smoke testy)
  • Grupowanie czyli uruchamianie konkretnych pakietów z testami
  • Pomijanie scenariuszy podczas uruchomienia np. w sytuacji gdy wiemy, że w danym obszarze są błędy, które już wcześniej zgłosiliśmy
  • Parametryzacja środowisk np. w podziale na przeglądarki, środowiska (developerskie, akceptacyjne), wersje mobile lub desktop

Nightwatch.js bazuje na selenium-server, który dodawany jest do projektu jako zwyczajny pakiet npm. W związku z tym do dyspozycji jest cały potencjał WebDriver API. W pełni pozwala na podejście page object pattern, pomagając organizować metody oraz elementy na poszczególnych stronach z użyciem CSS lub XPath. Dodatkowo Nightwatch.js mocno wspiera rozwiązania chmurowe (SauceLabs, BrowserStack) - tutaj szczególnie interesowało nas wsparcie dla SauceLabs. Daje to możliwości uruchamiania testów na urządzeniach mobilnych zarówno fizycznych jak i wirtualnych z użyciem infrastruktury po stronie zewnętrznego dostawcy.

Generowane w standardzie JUnit XML raporty można zintegrować z popularnymi narzędziami Continuous Integration np. Jenkins. Nightwatch.js jest elastyczny w kwestii wsparcia dla implementacji własnych asercji czy komend - ale o tym za chwilę.

Struktura projektu testów automatycznych MojeING

Poniżej prezentuję zrzut struktury projektu testów w Nightwatch.js dla MojeING. Jest dedykowana dla MojeING i nieco różni się od standardowej wersji Nightwatch.js. Elementy takie jak appiumLibsbin, componentsconfigurationdictionaryenumhelpers oraz postmanCollections są stworzone specjalnie na potrzeby projektu. Pozostałe elementy są standardowo dostarczane przez Nightwatch.js.


Własne komendy

Wcześniej wspominałem o możliwości rozwijania własnych komend oraz asercji. Aby to wyjaśnić, najprościej będzie porównać przykład domyślnej komendy click z implementacją komendy specjalnie na potrzeby MojeING.

Domyślna komenda click, oferowana przez Nightwatch.js wygląda mniej więcej tak:

Czyli: wykonujemy metodę click przekazując jej CSS bądź XPath elementu, który nas interesuje. W sytuacji, gdy to nie jest wystarczające, z pomocą przychodzą własne komendy. Przykładowo w katalogu commands plik Click.js ma następującą implementację:

Jak widać powyżej, click jest tylko częścią tego, co się wykonuje pod „opakowaną” komendą Click. Zanim wykona się faktyczne kliknięcie, następuje czekanie na widoczność elementu waitForElementVisible(), walidacje strony pod względem standardów WCAG WcagValidate(), przewinięcie strony do miejsca gdzie znajduje się poszukiwany element ScrollIntoElement() i na koniec kliknięcie click() zwracające Log(). Należy zwrócić uwagę, że w przykładzie użyto WcagValidate(), ScrollIntoElement(), Log(), które są również komendami - wg przyjętego standardu zaczynają się od dużej litery.

Używanie komend daje gwarancję, że każdy element na stronie obsługiwany jest w ten sam sposób. Gdyby zaszła konieczność dostosowania implementacji do zmian w aplikacji, to wykonać trzeba będzie to tylko w tym jednym miejscu.

Uruchamianie testów

Testy uruchamiamy z linii komend na kilka sposobów: Przykładowo:

npm run test:feature ścieżka/do/pliku/bądź/katalogu

uruchomi ono tylko określone testy, z pominięciem budowania aplikacji (musi być już wcześniej zbudowana).

npm run test:feature:build ścieżka/do/pliku/bądź/katalogu

najpierw wykona budowanie aplikacji i następnie uruchomi określone testy.

Powyższych poleceń używamy zamiennie w zależności od tego, czy posiadamy wybudowaną aktualną wersję aplikacji czy też nie. W obu tych przypadkach aplikacja serwowana jest lokalnie, a link do niej jest automatycznie przekazywany do testów. Inaczej sytuacja wygląda jeśli chcemy uruchomić testy na środowisku innym niż lokalne, np. UAT. W tej sytuacji do komendy należy dodać parametr określający środowisko:

npm run test:feature -- --env uat-chrome ścieżka/do/pliku/bądź/katalogu

W tym przypadku nie używamy już opcji budowania aplikacji, gdyż korzystamy z zewnętrznych środowisk.

Wspomniałem wcześniej o pojęciu tagowania oraz grupowania testów. W jaki sposób można z tego skorzystać? Grupowanie testów rozpoczyna się na etapie tworzenia struktury katalogów z testami. Przykładowo jeśli katalog z testami wygląda tak:

możemy powiedzieć, że testy są pogrupowane. W powyższym przypadku scenariusze przypadekX.js są zgrupowane w katalogach scenariuszeNegatywne, scenariuszePozytywne. Wszystkie te zaś są w nadrzędnym katalogu scenariuszeTestow.

Mechanizm uruchamiania testów Nightwach.js działa w taki sposób, że w zależności od tego, który poziom katalogu z testami określimy podczas uruchamiania, to wszystkie testy w danym katalogu oraz podkatalogach zostaną uruchomione.

Przykładowo:

nightwatch scenariuszeTestow

uruchomi wszystkie testy,

nightwatch scenariuszeTestow/scenariuszePozytywne

uruchomi testy z katalogu scenariuszePozytywne.

Tagowanie różni się od grupowania tym, że podczas uruchomienia z użyciem tagów, przeszukiwany jest katalog z testami i uruchamiane są tylko te testy, które mają odpowiedni tag. Przykładowo:

Dodanie do linii komend parametru --tag tag1 spowoduje uruchomienie wyłącznie testów, które mają tag1. Istnieją też opcje --skiptags oraz --skipgroup które działają odwrotnie do wyżej wymienionych. Wówczas zamiast uruchamiać to pomija dane scenariusze.

Jeden skrypt vs. wiele środowisk

W poprzedniej sekcji przedstawiłem, że w katalogu configuration umieszczone są dane do testów, a w katalogu dictionary umieszczamy wszelkie teksty, jakie używane są w asercjach. Następnie omawiając sposoby uruchamiania testów dowiedzieliśmy się, że aby test uruchomić w środowisku np. UAT wystarczy podać odpowiedni parametr w linii komend, a chcąc uruchomić test lokalnie nie trzeba nic podawać. Spełnia to podstawowe założenia projektu testów dla MojeING tzn. dla danego scenariusza utrzymywana jest jedna wersja skryptu, a zmieniają się tylko dane w zależności od parametru środowiska, na jakim uruchamiamy testy. Pomaga to między innymi utrzymywać na poszczególnych środowiskach identyczne wersje aplikacji. Przykładowo, jeśli skrypt testuje daną funkcjonalność to dopuszczalne są jedynie różnice w danych testowych pomiędzy środowiskami. Przebieg scenariusza musi być taki sam.

Poniżej przykład scenariusza testu, obiektu zawierającego dane do testów oraz słownika z wartościami weryfikowanymi w trakcie testu.

Dzięki takiej konstrukcji, scenariusze pozostają bez zmian do momentu wprowadzenia zmian funkcjonalnych. W module, który zawiera dane do testów można dowolnie modyfikować np. użytkowników, w słowniku zmienia się teksty odpowiednio do aktualnej parametryzacji. Budowanie takich obiektów daje możliwości współdzielenia, dzięki czemu utrzymanie staje się prostsze i szybsze.

Wielowątkowość

Dużą zaletą wbudowanego w Nightwatch.js mechanizmu uruchamiania testów jest wielowątkowość. Aby włączyć taką możliwość w testach i cieszyć się szybszym pozyskiwaniem informacji zwrotnej wystarczy ustawić jeden parametr i gotowe.

Przykładowo ustawienie:

Powoduje, że wielowątkowość zostaje włączona, a ilość wątków jest taka sama jak ilość rdzeni CPU maszyny, na jakiej uruchamiamy testy. Oczywiście, jeśli dysponujemy odpowiednio wydajnym sprzętem możemy zmieniać dowolnie ten parametr.

Wielowątkowość przydaje się nam przede wszystkim podczas uruchamiania testów w Gitlab po każdych zmianach kodu, jakie trafiają do repozytorium. Wtedy najważniejszy jest czas wykonania się testów, dlatego równoległe uruchamianie scenariuszy świetnie się sprawdza minimalizując czas wykonania.  Uruchamianych w ten sposób jest aktualnie 40 różnych scenariuszy. Czasy realizacji takiego zadania wahają się pomiędzy 8 – 10 minut przy czym należy pamiętać, że na to składa się czas potrzebny na pobranie nowej wersji z repozytorium, sprawdzenie i pobranie pakietów npm, zbudowanie nowej wersji aplikacji oraz uruchomienie samych testów.

Podsumowanie

Nightwatch.js przekonał nas szybkością, prostotą działania i możliwościami rozbudowywania. Odkąd używamy go w projekcie, cały czas odkrywamy nowe funkcjonalności, które potrzebujemy wdrożyć. Najnowsza wersja Nightwatch.js przygotowywana jest aby wspierać platformę umożliwiającą uruchamianie testów w infrastrukturze chmurowej.

Na koniec wszystkich chcących lepiej poznać Nightwatch.js zachęcam do pobrania startowej wersji projektu dostępnego pod tym linkiem.

Zagadnienia zawarte w powyższym artykule zostały w nim zaimplementowane. Krótkie README ułatwi skonfigurować stację oraz uruchomić projekt. Powodzenia!