Po artykule opisującym podstawowe funkcjonalności frameworka JUnit 5
oraz serii trzech artykułów na temat konfiguracji środowiska na potrzeby testowania aplikacji internetowych, przyszedł wreszcie czas na zmierzenie się z automatyzacją testów prawdziwej aplikacji. Zaprezentuję praktyczne połączenie elementów dostarczanych przez technologię Java
, framework JUnit 5
, narzędzie automatyzacji procesu budowania aplikacji Gradle
i bibliotekę Selenium WebDriver
w rzeczywistym projekcie. Zacznę od podstaw, w kolejnych tekstach stopniowo wprowadzając bardziej zaawansowane koncepcje, tworząc tym samym zestaw profesjonalnych testów automatycznych.
O serii
Czytasz część 1 serii artykułów na temat automatyzacji testów aplikacji internetowej TodoMVC. Pozostałe części:
- Część 2 - JUnit 5 i Selenium - Odpowiedzialność metod testowych
- Część 3 - JUnit 5 i Selenium - Wprowadzenie do wzrorca Page Object
- Część 4 - JUnit 5 i Selenium - Optymalizacja konfiguracji i uruchomienia projektu
- Część 5 - JUnit 5 i Selenium - klasa PageFactory z pakietu support biblioteki Selenium
Przygotowanie środowiska - niezbędne kroki
Aby w pełni przygotować środowisko, będziesz potrzebować:
Java SE Development Kit
, w wersji conajmniej 9 (zalecam najnowszą wersję),IntelliJ
, darmowa wersja Community będzie wystarczająca do tworzenia nawet zaawansowanych projektów,Google Chrome
iMozilla Firefox
w najnowszej wersji lub ich odpowiedniki portable (dla systemu Windows),- Sterowniki do wybranych przeglądarek, w najnowszej wersji.
Szczegółowe kroki przygotowania środowiska w systemie Windows
zostały opisane w serii artykułów:
- Narzędzia - system kontroli wersji Git i emulator konsoli Cmder
- Narzędzia - Selenium i przeglądarki internetowe
- Narzędzia - Java, Gradle i IntelliJ
Jeśli używasz systemu macOS
, możesz skorzystać z porad przygotowania środowiska zebranych w tym artykule:
Konfiguracja projektu
Pobranie szablonu projektu i pierwsze uruchomienie
Aby uprościć proces tworzenia i uruchomienia projektu, przygotowałem gotowy do użycia szablon projektu, dostępny na GitLab
.
Przygotuj katalogu projektu i zainicjuj repozytorium Git
:
mkdir my-selenium-project
cd my-selenium-project
git init
Przygotuj własny projekt na podstawie szablonu (fork):
git remote add upstream https://gitlab.com/qalabs/blog/junit5-selenium-gradle-template.git
git pull -s recursive -X theirs upstream master
Uruchom testy:
gradlew clean test
Polecenie uruchomi testy z użyciem Gradle
, pobierając wcześniej niezbędne zależności oraz kompilując kod projektu. Jeśli wszystko zostało poprawnie skonfigurowane, wykonają się dwa testy otwierające stronę https://qalabs.pl w przeglądarkach Google Chrome
i Mozilla Firefox
. W konsoli powinieneś zobaczyć:
1 | λ gradle clean test |
Jeżeli zamiast powyższych komunikatów w konsoli widzisz błędy, wróć do wcześniejszych artykułów na temat przygotowania środowiska i upewnij się, że zostało ono skonfigurowane prawidłowo. W szczególności upewnij się, że:
- Posiadasz
Java SE Development Kit
w wersji conajmniej 9, - Zainstalowałeś przeglądarki internetowe
Google Chrome
iMozilla Firefox
, - Zainstalowałeś sterowniki do powyższych przeglądarek i są one dostępne z poziomu zmiennej systemowej
PATH
.
Tip:
junit5-selenium-gradle-template
został stworzony na podstawie innego projektu, który opisywałem w artykule JUnit 5 - Pierwsze kroki.
Import projektu do IntelliJ
Aby zaimportować projekt w IntelliJ
, wybierz opcję File | Open
i wskaż plik gradle.build
. Otwórz plik jako projekt i poczekaj chwilę, aż zaimportuje się wraz ze wszystkimi zależnościami.
Konfiguracja zależności (ang. dependencies) projektu znajduje się w pliku build.gradle
. W projekcie, który właśnie przygotowałeś, zdefiniowane są zależności JUnit 5
oraz Selenium WebDriver
:
1 | dependencies { |
Tip: Jeżeli nie pracowałeś wcześniej z
IntelliJ
, w artykule Narzędzia - Java, Gradle i IntelliJ znajdziesz linki do dokumentacji, które pomogą ci rozpocząć pracę z tym środowiskiem.
Pierwsze testy aplikacji internetowej
Naszym testowanym systemem (ang. system under test, SUT) będzie prosta aplikacja Todo, umożliwiająca zarządzanie listą zadań do wykonania. Skorzystamy z TodoMVC, czyli strony z przykładowymi implementacjami takiej aplikacji, napisanymi w różnych frameworkach JavaScript
. Użyjemy implementacji Todo w Vanilla JS (inaczej “czystym” JavaScript), którą znajdziesz pod adresem http://todomvc.com/examples/vanillajs.
Nasza testowana aplikacja jest aplikacją typu Single Page Application, czyli między innymi nie przeładowuje strony po wykonaniu akcji przez użytkownika. Do przechowywania listy zadań do wykonania wykorzystuje Local Storage.
Scenariusze testowe, które zostaną zautomatyzowane:
- Utworzenie zadania
- Edycja zadania
- Usunięcie zadania
- Oznaczenie zadania jako zakończonego
- Oznaczenie wielu zadań jako zakończonych
- Usunięcie zakończonych zadań
Tip: Zanim rozpoczniesz automatyzację, zapoznaj się dobrze z testowaną aplikacją. Przemyśl powyższe scenariusze, również pod kątem analizy
DOM
(Document Object Model).
Utworzenie klasy testowej
Zacznijmy od utworzenia klasy testowej, która będzie zawierała metody testowe.
- W katalogu testów (
src/test/java
), utwórz nowy pakiet (package) o nazwiepl.qalabs.blog.junit5.selenium.todomvc
- Dodaj w nim klasę o nazwie
TodoMvcTests
o następującej zawartości:
1 | package pl.qalabs.blog.junit5.selenium.todomvc; |
Uruchamianie testów
Tak utworzony test możesz uruchomić bezpośrednio z IntelliJ
, który wspiera JUnit 5
natywnie, czyli bez konieczności dodatkowej konfiguracji (wystarczy, że twój projekt posiada zależności do JUnit 5
). W celu uruchomienia wszystkich testów z danej klasy testowej wybierz opcję Run <Nazwa klasy testowej>:
Rezultaty wykonania znajdziesz w oknie narzędziowym Run:
Tip: W IntelliJ istnieje kilka możliwości tworzenia konfiguracji i uruchamiania testów. Osobiście polecam korzystanie ze skrótów klawiszowych (
Ctrl+Shift+F10
). Więcej informacji znajdziesz w oficjalnej dokumentacji: https://www.jetbrains.com/help/idea/creating-test-methods.html.
Wybrane testy możesz również uruchomić z wiersza poleceń za pomocą Gradle
.
Uruchom cmder
i w katalogu domowym projektu wprowadź następujące polecenie:
gradlew clean test --tests *TodoMvcTests
Polecenie uruchomi testy z klasy pl.qalabs.blog.junit5.selenium.todomvc.TodoMvcTests
.
Tip: Możesz korzystać z wiersza poleceń bezpośrednio w
IntelliJ
. Terminal możesz uruchomić używając skrótuAlt+F12
. Integracja terminalaIntelliJ
zGit Bash
została opisana tutaj: Narzędzia - Java, Gradle i IntelliJ
Inicjalizacja sterownika
Klasa testowa, oprócz dwóch metod testowych, posiada dwie metody dodatkowe: beforeEach
i afterEach
. W standardowym cyklu życia testu w JUnit 5
, każda metoda oznaczona adnotacją @BeforeEach
i @AfterEach
wykonana zostanie odpowiednio przed i po każdej metodzie testowej w danej klasie testowej. W naszym przykładzie, metody te wykorzystamy do zarządzania środowiskiem dla testów. Przed każdym testem zaincjalizujemy sterownik Selenium WebDriver
, który uruchomi wybraną przeglądarkę. Natomiast po każdym teście zamkniemy przeglądarkę, bez względu na wynik testu.
Zmodyfikuj CreateTodoTest
w następujący sposób:
1 | package pl.qalabs.blog.junit5.selenium.todomvc; |
Metoda beforeEach
inicjalizuje ChromeDriver
z domyślnymi ustawieniami, zmienia domyślny timeout dla operacji wyszukiwania elementów na 500 milisekund oraz otwiera testowaną aplikację.
Uruchom testy i obserwuj, czy przeglądarka Chrome
uruchomiła się i zamknęła dwukrotnie (dla każdego testu).
Tip: Nie jesteś pewien, czy Twoja konfiguracja jest prawidłowa? Zajrzyj tutaj: Narzędzia - Selenium i przeglądarki internetowe
Sterownik chromedriver
musi być dostępny w PATH
. W sytuacji, gdy sterownik nie znajduje się w PATH
lub używasz kilku wersji sterownika, możesz użyć zmiennej systemowej webdriver.chrome.driver
(dla innych sterowników nazwa zmiennej będzie inna) w celu wskazania dokładnej lokalizacji pliku sterownika:
1 |
|
Plik uruchomieniowy przeglądarki wyszukiwany jest w domyślnej lokalizacji dla systemu operacyjnego, w którym test jest uruchamiany. W celu wskazania lokalizacji pliku uruchamiającego przeglądarkę należy odpowiednio zainicjalizować sterownik. Dla ChromeDriver
, jedną z opcji to użycie obiektu ChromeOptions
, który posiada między innymi właściwość zawierającą lokalizację pliku wykonywalnego przeglądarki Chrome
:
1 | import org.openqa.selenium.chrome.ChromeOptions; |
Tip: Zajrzyj do klas
HelloSeleniumChromeTest
orazHelloSeleniumFirefoxTest
, w których znajdziesz przykłady konfiguracji sterowników dla przeglądarkiChrome
iFirefox
.
Test - Tworzenie zadania
Automatyzacja scenariusza tworzenia nowego zadania polegać będzie na wykonaniu następujących akcji użytkownika:
- Utworzenie nowego zadania po wpisaniu tytułu i wciśnięciu <Enter>
- Weryfikacji wartości <number> dla elementu <number> items left
- Weryfikacji, że zadanie pojawiło się na liście
Zmodyfikuj metodę testową createsTodo()
:
1 | import org.openqa.selenium.By; |
W celu utworzenia nowego zadania znajdujemy pole tekstowe o klasie .new-todo
i wpisujemy do niego tytuł a następnie naciskamy <Enter>
(przez wpisanie znaku specjalnego). Po utworzeniu rekordu, wartość w elemencie o selektorze .todo-count > strong
zmieniła się. Pobieramy tę wartość i sprawdzamy, czy jest równa 0
, używając wbudowanej asercji assertEquals
z JUnit 5
. W kolejnym kroku, pobieramy wszystkie zadania używając selektora .todo-list li
i sprawdzamy, czy w liście znajduje się wcześniej dodane zadanie.
Test - Edycja zadania
Automatyzacja scenariusza edycji istniejącego zadania polegać będzie na wykonaniu następujących akcji użytkownika:
- Utworzenie nowego zadania
- Wyszukanie zadania do edycji
- Przejście do edycja zadania
- Edycja zadania i zapisanie zmian
- Weryfikacji, że zmienione zadanie pojawiło się na liście
Zmodyfikuj metodę testową editsTodo()
:
1 | import org.openqa.selenium.interactions.Actions; |
Do edycji potrzebne jest istniejące zadanie, dlatego w teście zostanie ono utworzone. Nowe zadanie należy odszukać na liście zadań, a następnie dwukrotnie je kliknąć. W celu obsługi operacji niskopoziomowych, takich jak podwójne kliknięcie czy ruch myszą, wykorzystany został obiekt klasy org.openqa.selenium.interactions.Actions
.
Podwójne kliknięcie elementu na liście powoduje pojawienie się w DOM pola tekstowego. To pole należy odnaleźć, a następnie wprowadzić do niego nową wartość. Zmiany w rekordzie zapisane będą po naciśnięciu klawisza <Enter>
. Natomiast weryfikacja, czy zmieniony element znajduje się na liście, jest niemal identyczna jak w poprzednim teście.
Tip: Zwróć uwagę na użycie w testach wyrażeń
Lambda
. WyrażeniaLambda
pozwalają między innymi na tworzenie bardziej zwięzłego kodu. Więcej informacji znajdziesz tutaj: https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
Test - Usuwanie zadania
Automatyzacja scenariusza usuwania istniejącego zadania polegać będzie na wykonaniu następujących akcji użytkownika:
- Utworzenie nowego zadania
- Wyszukanie zadania do edycji
- Ruch kursora myszy nad znalezione zadanie
- Usunięcia zadania
- Weryfikacji, że usunięte zadanie nie znajduje się na liście
Zmodyfikuj metodę testową removesTodo()
:
1 |
|
Ruch myszą symulowany jest ponownie z wykorzystaniem obiektu klasy org.openqa.selenium.interactions.Actions
. Usunięcie elementu następuje po kliknięciu przycisku z klasą .destroy
. Podobnie jak wcześniej, weryfikacja polega na sprawdzeniu, czy element nie znajduje się na liście.
Test - Oznaczenie zadania jako zakończonego
Automatyzacja scenariusza oznaczania zadania jako zakończonego polegać będzie na wykonaniu następujących akcji użytkownika:
- Utworzenie dwóch nowych zadań i oznacznie ich jako zakończonych
- Utworzenie kolejnego zadania
- Usunięcie zakończonych zadań
- Weryfikacji liczby elementów (również z użyciem filtrów)
Zmodyfikuj metodę testową togglesTodoCompleted
:
1 |
|
Test - Oznaczenie wielu zadań jako zakończonych
Scenariusz oznaczenia wielu zadań jako zakończonych nie różni się wiele od poprzedniego, zatem nie wymaga dodatkowego wyjaśnienia.
Zmodyfikuj metodę testową togglesAllTodosCompleted
:
1 |
|
Test - Usunięcie zakończonych zadań
Automatyzacja scenariusza usuwania zakończonych zadań polegać będzie na wykonaniu następujących akcji użytkownika:
- Utworzenie dwóch nowych zadań i oznacznie ich jako zakończonych
- Utworzenie kolejnego zadania
- Usunięcie zakończonych zadań
- Weryfikacji liczby elementów (również z użyciem filtrów)
Zmodyfikuj metodę testową clearsCompletedTodos
:
1 |
|
Uruchamianie testów w Firefox
Aktualnie testy uruchamiane są w przeglądarce Chrome
. Ale czy zadziałają w Firefox
? Warto to sprawdzić. Aby to zrobić, wystarczy w metodzie beforeEach()
podmienić implementację klasy WebDriver
z ChromeDriver
na FirefoxDriver
:
1 | import org.openqa.selenium.firefox.FirefoxDriver; |
Po uruchomieniu testów w przeglądarce Firefox
okazuje się, że nie wszystkie zadziałały prawidłowo. Test editsTodo
kończy się wyjątkiem w linii todoEditInput.sendKeys("My Changed Todo 2");
:
org.openqa.selenium.StaleElementReferenceException: The element reference of <input class="edit"> /
is stale; either the element is no longer attached to the DOM, it is not in the current frame context, /
or the document has been refreshed
Sam wyjątek oznacza, że element <input class="edit">
, do którego test próbuje wpisać tekst, nie istnieje już w DOM.
Najbardziej prawdopodobną przyczyną tego błędu jest inny sposób obsługi metody todoEditInput.clear()
w przeglądarce Firefox
(jest ona wywoływana bezpośrednio przed problematyczną instrukcją). Obserwując zachowanie aplikacji w trakcie testu widać, że w Firefox
metoda clear()
powoduje nie tylko usunięcie treści, ale także zapisanie pustej wartości pola, bo po wywołaniu metody todoEditInput.clear()
edytowane zadanie zostaje usunięte.
Ten problem można obejść zmieniając sposób wyczyszczenia zawartości todoEditInput
- zamiast wywoływać metodę clear()
wykonać kod JavaScript
, który zmieni wartość właściwości value
dla tego pola. Nie jest to idealne rozwiązanie, ze względu na fakt, iż rzeczywisty użytkownik nie wykonuje w aplikacji tego typu działań, trzeba jednak pogodzić się z tym, że w testach automatycznych nie zawsze mamy możliwość pełnej symulacji pracy użytkownika.
W celu wykonywania kodu JavaScript
w naszym teście musimy dokonać następujących zmian:
- Zmienić typ pola
driver
zorg.openqa.selenium.WebDriver
naorg.openqa.selenium.remote.RemoteWebDriver
(interfejsorg.openqa.selenium.WebDriver
nie posiada deklaracji potrzebnej nam metodyexecuteScript
) - Zmodyfikować metodę
editsTodo
, w miejsce instrukcjitodoEditInput.clear()
wprowadzić nową instrukcję, która wykona kodJavaScript
:driver.executeScript("arguments[0].value = ''", todoEditInput);
(arguments[0]
totodoEditInput
, którego właściwośćvalue
zostanie zmieniona).
Po dokonaniu powyższych zmian, testy w obu przeglądarkach powinny wykonywać się poprawnie.
Tip: Różnice wynikające z implementacji tych samych metod w różnych przeglądarkach mogą pojawiać się częściej. Jeżeli planujesz uruchamiać testy na kilku przeglądarkach, upewnij się, że przed ich ostateczną akceptacją i umieszczeniem w repozytorium uruchomiłeś je we wszystkich przeglądarkach, których używasz w testach.
Zmiana implementacji sterownika w kodzie nie jest optymalna i na pewno nie jest zalecana. Dlatego w kolejnych artykułach zademonstruję, w jaki sposób można to zoptymalizować.
Dalsze kroki
Ten szczegół to nie jedyny element, który w przedstawionym kodzie można poprawić lub zoptymalizować. Nietrudno zauważyć inne problemy, które mogłyby wpłynąć na łatwość utrzymania i rozszerzania testów w przyszłości. Przykładem mogą być choćby długie i nieczytelne metody testowe oraz liczne powtórzenia kodu, które łamią jedną z fundamentalnych zasad tworzenia oprogramowania, czyli DRY
- Don’t Repeat Yourself. Testy będą rozwijane i poprawiane w kolejnych artykułach, w których pokażę bardziej zaawansowane funkcjonalności frameworka JUnit 5
oraz dobre praktyki tworzenia tego typu kodu.
Repozytorium Git projektu
Kod źródłowy opracowany w artykule znajduje się w repozytorium Git: https://gitlab.com/qalabs/blog/junit5-selenium-todomvc-example w branchu 1-getting-started