Biblioteka Selenium dostarcza klasę PageFactory, która upraszcza implementację wzorca Page Object. Klasa PageFactory pozwala na utworzenie nowej instacji obiektu dowolnej klasy, która deklaruje pola typu WebElement lub List<WebElement> oznaczone adnotacją @FindBy lub posiadające nazwy takie jak atrubut id lub name elementu HTML na stronie. Skuteczność i prostotę tego rozwiązania pokażę na przykładzie testów aplikacji TodoMVC.
O serii
Czytasz część 5 serii artykułów na temat automatyzacji testów aplikacji internetowej TodoMVC. Pozostałe części:
- Część 1 - JUnit 5 i Selenium - Wprowadzenie do projektu automatycznych testów aplikacji internetowej
- 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
Kod źródłowy
Kod źródłowy opracowany w poprzednim artykule znajduje się w repozytorium Git: https://gitlab.com/qalabs/blog/junit5-selenium-todomvc-example w branchu 4-configuration. Zmiany opisywane w tym artykule znajdują się w pakiecie pl.qalabs.blog.junit5.selenium.todomvc_with_page_factory projektu, w branchu 5-selenium-page-factory.
Wprowadzenie do PageFactory
@FindBy
Jak wspominałem na początku artykułu, klasa PageFactory pozwala na utworzenie nowej instacji obiektu dowolnej klasy, która deklaruje pola typu WebElement lub List<WebElement>. Poniższy przykład to uproszczona wersja klasy TodoMvc posiadająca pola newTodoInput, todoCount oraz todos, które zostały oznaczone adnotacją @FindBy:
1 | public class TodoMvc { |
@FindBys, @FindAll
@FindBy to nie jedyna adnotacja, której możemy użyć do lokalizowania elementów. Pozostałe adnotacje, których możemy używać do oznaczania pól to @FindBys i @FindAll.
@FindBys
Adnotacja @FindBys wykorzystywana jest do oznaczenia pól, dla których wykonana zostanie seria wyszukiwań, jedno po drugim:
1 |
|
W powyższym przykładzie, wyszukany zostanie pierwszy element posiadający atrybut class = "button", który występuje w elemencie z atrybutem id = "menu".
@FindAll
Adnotacja @FindAll wykorzystywana jest do wyszukiwania wszystkich elementów spełniających jedno z zadanych kryteriów:
1 |
|
W powyższym przykładzie, wyszukane zostaną wszystkie elementy, posiadające atrybut class = "button" oraz wszystkie elementy posiadające id = "menu". Kolejność zwróconych elementów nie musi być taka w jakiej występują one w dokumencie.
PageFactory
W poniższym przykładzie zaimplementowałem test tworzenia zadania, wykorzystujący wcześniej opisaną klasę TodoMvc:
1 |
|
Obiekt klasy TodoMvc tworzony jest przez PageFactory, przed każdym testem, w metodzie beforeEach: page = PageFactory.initElements(driver, TodoMvc.class);.
W pierwszym kroku metoda PageFactory.initElements(driver, TodoMvc.class) utworzy instancję obiektu klasy TodoMvc z wykorzystaniem mechanizmu refleksji w Javie. W kolejnym kroku zainicjalizowane zostaną pola oznaczone adnotacją @FindBy (lub @FindAll i @FindBys). Użycie metody PageFactory.initElements(driver, TodoMvc.class) wymaga również, aby klasa TodoMvc posiadała konstruktor przyjmujący parametr typu WebDriver.
Tip: Klasa
PageFactoryposiada również inne metody inicjalizujące. Najbardziej iteresująca jest metodainitElements(WebDriver driver, Object page), która jako drugi parametr przyjmuje wcześniej utworzonyPage Object. Metoda ta przyda się szczególnie w przypadku, gdy nasze obiekty wymagają bardziej skomplikowanej inicjalizacji.
Wyszukiwanie elementów
Jak wspominałem, podczas inicjalizacji obiektu TodoMvc nie zostaną wykonane żadne zapytania o elementy w DOM. Właściwe wyszukiwanie elementu zostanie wykonane każdorazowo podczas dostępu do danego pola. Kiedy zatem wykonamy np. instrukcję newTodoInput.sendKeys(todoName + Keys.ENTER)) w metodzie createTodo, Selenium zadba o wyszukanie elementu o klasie todo-input. Sprowadzi się to zatem do wykonania instrukcji driver.findElement(By.className('new-todo')).sendKeys(todoName + Keys.ENTER). Wynika to z deklaracji pola newTodoInput: @FindBy(className = "new-todo"). Możemy się zatem spodziewać, że potencjalny wyjątek wynikający np. z nieodnalezienia elementu w DOM zostanie wyrzucony przy próbie dostępu do pola, a nie podczas inicjalizcji obiektu.
Tip: Wzorzec projektowy wykorzystywany przez Selenium w celu osiągnięcia powyższego efektu to wzorzec
Proxy: https://en.wikipedia.org/wiki/Proxy_pattern#Java
@CacheLookup
Zdarza się jednak, że nie ma potrzeby wykonywania wyszukiwania elementu za każdym razem, gdy potrzebujemy dostępu do jakiegoś pola. W takim wypadku możemy użyć adnotacji @CacheLookup, która poinstruuje Selenium, że element nie zmienia się po pierwszym wyszukaniu i nie chcemy go ponawiać.
W moim przykładzie pole typu input jest niezmienne, zatem mogę oznaczyć je adnotacją @CacheLookup:
1 | public class TodoMvc { |
Od teraz tyko pierwszy dostęp do pola newTodoInput będzie wykonywał właściwe wyszukanie elementu w DOM.
Tip: Wyjątek typu
org.openqa.selenium.StaleElementReferenceException: stale element reference: element is not attached to the page documentmoże sygnalizować niepoprawne użycie adnotacji. Warto się upewnić, że pole jest niezmienne w cyklu życia obiektu strony!
Uruchomienie testów
Nadszedł czas na przetestowanie zmian w projekcie. W tym celu uruchom wiersz poleceń i w katalogu projektu wykonaj polecenie:
./gradlew clean test --tests *with_page_factory.tests.AddTodoTest
Powyższe polecenie wykona testy z klasy AddTodoTest z pakietu pl.qalabs.blog.junit5.selenium.todomvc_with_page_factory.tests.
Tip: Opcja
testspozwala na zaawansowane wybieranie testów do uruchomienia. Więcej informacji o wybieraniu testów do uruchomienia znajdziesz w oficjalnej dokumentacjiGradle: https://docs.gradle.org/current/userguide/java_testing.html#test_filtering
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 pakiecie pl.qalabs.blog.junit5.selenium.todomvc_with_page_factory, w branchu 5-selenium-page-factory.
Podsumowanie
Klasa PageFactory, dostarczana przez biblitekę Selenium, znacznie upraszcza implementację wzorca Page Object dostarczając alternatywny mechanizm wyszukiwania elementów za pomocą adnotacji @FindBy, @FindBys czy w końcu @FindAll. Dzięki użyciu adnotacji, kod staje się znacznie czytelniejszy a programista pozbywa się sporo niepotrzebnego i powtarzającego się kodu, co ostatecznie doprowadzi do łatwiejszego jego utrzymania.
Zobacz również
Selenium WebDriverManagerdla projektów wJava- pierwsze kroki - https://qalabs.codeleak.pl/junit-selenium/selenium-webdriver-manager-pierwsze-kroki- JUnit 5 i Selenium - Wprowadzenie do wzrorca Page Object - https://qalabs.codeleak.pl/junit-selenium/page-object-pattern-pierwsze-kroki/
PageFactoryna WikiSelenium- https://github.com/SeleniumHQ/selenium/wiki/PageFactory