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
PageFactory
posiada 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 document
moż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
tests
pozwala 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 WebDriverManager
dla projektów wJava
- pierwsze kroki - https://blog.qalabs.pl/junit-selenium/selenium-webdriver-manager-pierwsze-kroki- JUnit 5 i Selenium - Wprowadzenie do wzrorca Page Object - https://blog.qalabs.pl/junit-selenium/page-object-pattern-pierwsze-kroki/
PageFactory
na WikiSelenium
- https://github.com/SeleniumHQ/selenium/wiki/PageFactory