Nawet jeśli nasza aplikacja przejdzie wszystkie testy jednostkowe, nie oznacza to, że będzie działać jako cały system. Jednym ze sposobów upewnienia się, że działa ona jako całość, jest przetestowanie jej z perspektywy użytkownika. Najprostszym sposobem na wykonanie tego typu testu jest ręczne testowanie aplikacji. W przypadku dużych aplikacji może to być bardzo żmudne i nieefektywne, ponieważ zmiana jednej funkcji backendu może zepsuć inne. Tutaj z pomocą przychodzi automatyzacja testów.

Selenium

Selenium to pakiet narzędzi, który pozwala jego użytkownikom na automatyzację testów w przeglądarce. Składa się z trzech części:

  • WebDriver – narzędzie wchodzące w interakcję z przeglądarką
  • IDE – dodatek do przeglądarki, który pozwala testerom na ręczne nagrywanie testów
  • Grid – narzędzie pozwalające na równoległe wykonywanie testów w różnych środowiskach

Selenium występuje również jako biblioteka w wielu różnych językach, w tym popularnych jak Java, C#, Python. Jest również częścią biblioteki ScalaTest + Play (ponieważ jest ona oparta na bibliotece ScalaTest + Selenium).

Konfiguracja

Zanim stworzymy kilka prostych testów, musimy skonfigurować kilka rzeczy. Najpierw musimy stworzyć specyfikację testu, która rozszerza kilka cech:

  • PlaySpec
  • GuiceOneServerPerSuite
  • OneBrowserPerTest – zapewnia, że wszystkie testy zostaną uruchomione w oddzielnych przeglądarkach
  • BeforeAndAfterEach
  • WithInMemoryDatabase – jest to niestandardowa cecha, która konfiguruje w pamięci bazę danych

Ponieważ chcemy używać osobnej bazy danych w pamięci dla naszych testów, musimy nadpisać metodę fakeApplication() która dostarcza instancję Application. Chcemy ją skonfigurować tak, aby używała naszej bazy danych, a nie domyślnej. Powinno to wyglądać podobnie do tego:

override def fakeApplication(): Application = {
   GuiceApplicationBuilder()
       .in(Mode.Test)
       .configure("db.default.url" -> database.url, "db.default.driver" -> "org.h2.Driver",
       "db.default.username" -> "", "db.default.password" -> "", "toggle.rule-deployment.log-rule-id" -> true)
       .build()
}

Wartość database jest dostarczana przez cechę WithInMemoryDatabase. Jest to instancja naszej bazy danych.

Następnie zaimplementujemy pewną funkcję pomocniczą. Będzie ona parsować ciąg Call do absolute URL. Ponieważ będziemy uruchamiać te testy lokalnie, możemy użyć localhost jako domeny. Funkcja ta powinna wyglądać jak ta przedstawiona poniżej.

def absoluteURL(url: Call) = s"http://localhost:$port${url.toString}"

Konfiguracja Selenium

Chociaż Selenium jest wstępnie skonfigurowany z ScalaTest + Play, będziemy musieli wykonać kilka tweaków. W tej konfiguracji Selenium domyślnie używa HtmlUnitDriver, który ma pewne problemy z JS Rest Parameters i Bootstrap 5. Można to łatwo rozwiązać zmieniając silnik przeglądarki. My będziemy używać silnika Chrome.

Pierwszą rzeczą, którą będziemy musieli zrobić jest dodanie WebDriverManager, który zajmuje się większością konfiguracji za nas. Będziemy musieli dodać jego zależność dobuild.sbt

"io.github.bonigarcia" % "webdrivermanager" % "5.2.0"

Następnie musimy nadpisać funkcję createWebDriver() która jest dostarczana przez cechę OneBrowserPerTest. Nasza wersja powinna wyglądać tak jak ta:

 override def createWebDriver(): WebDriver = {
   WebDriverManager.chromedriver().setup()
   val options = new ChromeOptions
   options.addArguments("--headless", "--window-size=1920,1200")
   new ChromeDriver(options)
} 

W pierwszej linii skonfigurujemy ChromeDriver z domyślnymi opcjami. Następnie tworzymy własne opcje:

  • --headless -uruchamia Chrome w trybie bez okna
  • --window-size=1920,1200

First test

Teraz, gdy mamy już wszystko skonfigurowane, możemy stworzyć pierwszy test. Przetestujemy, czy po udanym logowaniu użytkownik zostanie przekierowany na stronę indeksową.

"Login view" must {
   "redirect to index on successful login" in {
       go to absoluteURL(routes.AuthController.login)
       emailField("email").value = users_email
       pwdField("password").value = users_password
       submit()
       eventually(pageTitle mustBe "Main page")
   }
}

Najpierw wykonamy go to tdo strony logowania. Aby mieć pewność, że nasz test zostanie zaliczony nawet jeśli zmienimy adres URL logowania, użyjemy Call z routes oraz wcześniej zdefiniowanej funkcji helper. Następnie chcemy znaleźć na stronie wejście email z atrybutem name=”email” i wypełnić je poprawnym adresem email, który odpowiada jakiemuś użytkownikowi w naszej testowej bazie danych. Następnie chcemy znaleźć pole password z atrybutem name=”password” i wypełnić je hasłem użytkownika. Następnie chcemy przesłać ten formularz. Ponieważ musimy poczekać na przetworzenie tego formularza przez serwer, musimy użyć eventually(). Spróbuje ona uruchomić blok kodu, aż zakończy się on sukcesem lub minie zdefiniowany wcześniej czas. W naszym przypadku blok kodu sprawdzi, czy tytuł strony jest równy „Strona główna” – ponieważ taki jest tytuł naszej strony indeksowej.

Możemy stworzyć bardziej zautomatyzowane testy, które sprawdzą czy na stronie znajduje się określony tekst, czy jakiś element jest w odpowiednim kolorze lub czy paginowana lista ma dokładną liczbę elementów. Ponieważ robimy to w przeglądarce, nie tylko testujemy, czy wszystkie konkretne funkcje działają poprawnie, ale także sprawdzamy, czy wszystkie warstwy komunikują się prawidłowo i – co najważniejsze – czy użytkownik zobaczy to, co chcieliśmy, żeby zobaczył.