Testy automatyczne przy użyciu SpecFlow, Selenium WebDriver i C# (część IV)

11 Kwiecień 2018
0 Komentarze

Kolejny post serii SpecFlow, Selenium WebDriver i C#. Tym razem skupimy się na Page Object Model / Page Object Pattern Page oraz jego wykorzystaniu wraz z SpecFlow. 

Poprzednie części tej serii wpisów możesz przeczytać >> tutaj <<


Czego dowiesz się z tekstu?

  • Czym jest Page Object Pattern / Page Object Model?
  • Jak stosować go wraz ze SpecFlow?
  • Czym jest kontener DI i poznamy jego implementacje dla SpecFlow.
  • Jak pobrać kod z GitHuba.

Jakich narzędzi będziesz potrzebować?


Zaczynamy!

 

Jak użyć Page Object Pattern w SpecFlow?

Najczęściej wymienianym wzorcem w dziedzinie testów automatycznych jest Page Object Model / Page Object Pattern Page. W artykule przedstawiam, jak użyć go wraz ze SpecFlow. Pokażę także podstawy SourceTree, które pozwolą Ci pobrać ten przykład z GitHuba.

Czym jest Page Object Pattern / Page Object Model?

Jest to wzorzec, który pozwala nam grupować poszczególne IWebElementy w określonych klasach, które nazywane są PageObjectami. W podobny sposób definiujemy metody w PageObjectach, które są powiązane z daną stroną. W sytuacji kiedy potrzebujemy np. IWebElementu Login w kilku testach to odwołujemy się do klasy, która ma zdefiniowaną właściwość do tego obiektu. Dzięki temu możemy uniknąć duplikacji kodu.

Więcej o Page Object Model na moim blogu link.

Jak definiujemy elementy w PageObjectach?

Dla naszych elementów ze strony tworzymy właściwości z zadanym selektorem. Jeżeli chcemy skorzystać z listy elementów korzystamy z IList<IWebElement>();

Zaczynamy od użycia atrybutu FindsBy, który dodajemy przed właściwością typu IWebElement, dla której chcemy mieć określony selector.

Rysunek  1 Definiowanie obiektu typu IWebElement jako właściwości.

W How mamy możliwość zdefiniowania, który typ selektora chcemy użyć.
W
Using wpisujemy jego wartość.

Zapamiętaj: Właściwości w C# zapisujemy z wielkiej litery i mają one modyfikator dostępu jako public.

Kontener DI (Dla bardziej zaawansowanych)

Kontener DI jest to kontener „Dependency Injection”, który pozwala nam na wstrzykiwanie zależności w kodzie. SpecFlow ma zaimplementowany prosty kontener DI, który pozwala nam rejestrować nasze obiekty i używać je tam, gdzie ich akurat potrzebujemy.

Przykładowo:

Dzięki użyciu SimpleDI rejestrujemy drivera raz, a mamy do niego dostęp w całym projekcie. O ile klasa, w której chcemy mieć dostęp do obiektu Drivera ma w parametrze IObjectContainer. Korzystając ze wstrzykiwania obiektu driver – test działa współbieżnie. Jeżeli korzystaliśmy z zapisywania drivera do statycznej klasy ScenarioContext.Current to SpecFlow zwróciłby błąd.


Ważne pojęcia, przed częścią praktyczną

  • GIT – na temat podstaw Gita odsyłam do wpisu Ani link, który przybliży wam podstawy tego narzędzia. Również polecam sprawdzić try git.
  • SourceTree – okienkowy klient dla Gita. Pozwala zrobić potrzebne operacje związane z gitem poprzez graficzny interfejs użytkownika. Został stworzony przez Atlassiana – firmę, która odpowiada za m.in. Jirę, SourceTree, oraz Confluence. SourceTree dostosowany jest do oprogramowania Linux, MacOS oraz Windows.
  • Bitbucket – hosting Gita oferowany przez Atlassiana. Plusem jest to, że pozwala tworzyć za darmo repozytorium prywatne. Repozytorium prywatne możecie współdzielić za darmo do 5 użytkowników.
  • GitHub – najpopularniejszy dostawca hostingu Gita na świecie, w którym swoje projekty mają m.in. donet core, specflow, Selenium WebDriver.  To miejsce, gdzie repozytoria i oprogramowanie jest ogólnodostępne. Dzięki temu możemy poznać kod, którym tworzone są udostępniane w tym miejscu narzędzia.

Instalacja SourceTree

Instalacja SourceTree przebiega standardowo jak każdego oprogramowania niezależnie od systemu. Pierwszym krokiem, aby to zrobić jest założenie darmowego konta do celów niekomercyjnych.

Po instalacji waszym oczom ukażę się wam w SourceTree takie menu:

Rysunek 2 Menu w SourceTree.

SourceTree zawiera pięć podstawowych przycisków. W naszym przypadku interesuje nas Clone, ponieważ chcemy pobrać projekt z GitHuba. Jeżeli projekt nie jest prywatny to nie potrzebujemy uwierzytelnienia.

Jeżeli będziecie korzystać z Gita w firmie to na pewno te uwierzytelnienie będzie potrzebne i prawie zawsze projekt będzie prywatny więc kod nie będzie dostępny publicznie.

Wyjątkiem może być sytuacja gdy wasza firma tworzy również oprogramowanie OpenSource.

Po kliknięciu Clone naszym oczom ukazuje się okno gdzie możemy “sklonować” nasze repozytorium. Udajemy się pod link naszego repozytorium i kopiujemy go z GitHuba.

Rysunek 3 – Pobieranie linku z GitHuba.

Wklejamy adres skopiowany z githuba. Url powinien kończyć się na „.git”.

Rysunek 4 – Okno do klonowania repozytorium.

Wybieramy lokalizacje gdzie chcemy zapisać nasz projekt i klikamy Clone. Po jego ściągnięciu możemy przejść do tej ścieżki na dysku.

Rysunek 5 – Projekt wyświetlanie na dysku.

 

Podczas tworzenia projektu, gdy zaznaczamy checkbox “Add to source control”, Visual Studio dodaje plik .gitgnore. Jest to plik, który pozwala „powiedzieć” GITowi czego nie chcemy commitować. Mogą to być m.in. ustawienia lub pliki, które zmieniają się po każdym budowaniu.

Nie dodajemy plików z folderów bin oraz obj, bo one i tak tworzą się podczas budowania. Również folder .vs są to dynamiczne ustawienia Visual Studio, których nie musimy commitować.

Skąd wziąć plik .gitignore?

W moim przypadku .gitignore możemy podejrzeć w GitHubie.

https://github.com/testingplusme/PageObjectPatternSpecFlow/blob/master/.gitignore

Jeżeli sami będziecie tworzyć swoje repozytoria polecam na początek stronę https://www.gitignore.io/, która pozwoli wam stworzyć plik .gitignore dla technologii, której używacie na co dzień.

Po sklonowaniu możemy udać się do SourceTree.

Naszym oczom powinien ukazać się taki ekran.

Rysunek  6 – SourceTree po ściągnięcie kodu.

Jak widzimy nasze repozytorium ma dwa commity.

Aby zobaczyć jakie pliki zostały zmienione klikamy na nazwę danego commita.
Widzimy linijka po linijce, jakie zmiany zostały dokonane.

Rysunek  7 – Przeglądanie commitu.

Klikamy w:

Rysunek  8 – Możliwość bezpośredniego wejścia do projektu ściągniętego na dysku.

Ta opcja pozwala udać nam się prosto do tego projektu na dysku, który został ściągnięte z GitHuba.

Otwieramy solucje PageObjectPatternPoll.sln. Przechodzimy do „References”. Widzimy, że referencje, które były dodane są na żółto.

Rysunek  9 – Brak referencji.

Kolejnym krokiem, który należy zrobić jest Restore NuGet Packages.

Jak to robimy?

Klikamy prawym przyciskiem myszy na nazwę solucji:

Następnie w menu kontekstowym wybieramy „Restore NuGet Packages”. Budujemy nasz projekt, a referencje powinny zostać w tym momencie dodane.

Rysunek  10 – Po zrobieniu „Restore NuGet Packages”

Przechodzimy do naszego testu, który ma scenariusz:

W ten sposób dodaliśmy tag @pollform do naszego scenariusza.

Dodawanie tagów pomaga, gdy mamy większą ilość testów oraz na build serwerze możemy decydować, które testy chcemy uruchamiać, a które nie.

Nasz scenariusz polega na tym, że chcemy jako użytkownik dokonać głosowania na „Tak” w ankiecie i sprawdzić, czy ilość głosów wzrośnie po głosowaniu.

Rysunek 11 Formularz do głosowania.

Jak sprawdzić formularz do głosowania?

  1. W pierwszym kroku udajemy się pod stronę, która zawiera omawiany formularz.
  2. W drugim kroku sprawdzamy, czy formularz na tej stronie wyświetla się. Jeżeli nie – zwróć błąd.
  3. W trzecim kroku dokonujemy głosowania na “Tak” w naszym formularzu.
  4. W ostatnim kroku sprawdzamy, czy liczba głosów po dodaniu naszego, zwiększyła się o jeden.

W pierwszej kolejności zaczniemy od przygotowania Page objectu dla strony formularza do głosowania.

Rysunek 12 – PollPage – Page Object

Następnie przechodzimy do PollPage.cs. Są to elementy, które są na stronie (niektóre pojawiają się po akcji głosowania).

Hooks.cs Zaczynamy od klasy Hooks.cs, a w niej zarejestrujemy nasz obiekt WebDrivera w prostym kontenerze DI, który posiada SpecFlow (SimpleDI).

Rysunek 13 – Hooks – klasa wraz z metodą przed scenariuszem oraz po scenariuszu. [BeforeScenario] i [AfterScenario].

Zaczynamy od zdefiniowania pola typu IObjectContainer – container.

IObjectContainer pozwala tworzyć prosty kontenery w którym możemy rejestrować obiekty i mieć do nich dostęp tam gdzie potrzebujemy.

Dodajemy konstruktor wraz z parametrem typu IObjectContainer.

Następnie przechodzimy do stworzenia metody BeforeScenario(), która ma stworzony obiekt typu ChromeDriver, który rejestrujemy do kontenera za pomocą metody RegisterInstance<T>(). Gdzie T to dowolny typ.

W naszym przypadku jest to IWebDriver.

Czemu IWebDriver?

IWebDriver pozwala rejestrować dowolny obiekt, który korzysta z tego interfejsu, czyli wszystkie rodzaje Driverów. Gdybyśmy użyli np. ChromeDriver to nie moglibyśmy zmienić tak łatwo implementacji dla np. Firefox Drivera.

Na końcu dodajemy metodę AfterScenario(), która “posprząta” po nas obiekty w pamięci.

Metoda Resolve pozwala wziąć (jeżeli jest) obiekt o danym typie i przypisać do zmiennej. W naszym przypadku jest do driver, którego używaliśmy podczas testu, ale po teście chcemy go zamknąć i usunąć z pamięci. Używamy metody Close() oraz Dispose(), która zamyka console otwierającą się wraz z ChromeDriverem.

Na końcu robimy również Dispose() na kontenerze.

Po co używamy kontenera?

Pozwala to unikać tworzenia wielu obiektów poprzez „new”. Dzięki temu mamy większą kontrolę nad naszymi obiektami.

Przechodzimy do klasy naszych kroków.

Zaczynamy od zdefiniowania potrzebnych obiektów w konstruktorze. Obiekt typu IObjectContainer, bo chcemy mieć dostęp w klasie stepów do obiektu drivera, który zdefiniowaliśmy w Hooks.

Rysunek  14 – Za pomocą metody GoToPage udajemy się pod wskazany adres url testowej strony.

Klasa korzysta z SeleniumHelpera, który udaje się driverem pod żądany adres.

Korzystamy również z klasy TestSettings, która ma zdefiniowaną właściwość dla naszej strony z formularzem do głosowania oraz Timeout dla WaitHelpera (którego zdefiniujemy za chwilę).

Rysunek  15 – Klasa TestingSettings, która zawiera Timeout oraz adres testowej strony.

Rysunek  16 – Klasa SeleniumHelper, która korzysta IObjectContainer oraz posiada metodę GoToPage(url)

SeleniumHelper ma w konstruktorze wstrzykiwany IObjectContainer dzięki temu możemy również tu używać kontenera i wziąć nasz potrzebny obiekt. Przechodzimy do dodania następnego kroku, który sprawdzi nam, czy ten formularz wyświetla się. Jeżeli nie, otrzymamy błąd. W tym miejscu dodajemy metodę WaitForClickable, która pozwala czekać nam wyświetlenie elementu na stronie.

Po raz pierwszy definiujemy obiekt w klasie page objectu korzystając z pollPage.PollBox  zamiast z var pollBox= driver.FindElement(“defineSelector”);

Rysunek  17 – Sprawdzenie wyświetlania się obiektu PollBox na stronie. 

WaitHelper.cs Metoda WaitForClickable

Rysunek  18 – Klasa WaitHelper, która zawiera metodę WaitForClickable, która pozwala nam na dynamiczne czekanie

Również w tym miejscu korzystam IObjectContainer oraz z klasy WebDriverWait (o, której wspominałem w poprzednim wpisie).

Rysunek  19 – Dodajemy głos “Tak” w formularzu.

Zaczynamy od zdefiniowania obiektu ViewResults w klasie PollPage.

Dodajemy tutaj dynamiczne czekanie na ten obiekt.

Następnie klikamy w ten element na stronie.  Jest to element, który po kliknięciu w niego prezentuje rezultaty głosowania. Czekam na załadowanie się elementu odpowiadającego za liczbę głosów.

W tym miejscu używamy bardzo przydatnej klasy, jaką jest scenarioContext. Na początku naszej przygody ze SpecFlow korzystaliśmy z jej statycznej wersji, jaką jest ScenarioContext.Current.

Dzięki temu, że w konstruktor wstrzyknęliśmy nasz obiekt typu ScenarioContext, możemy z niego korzystać w sposób hermetyczny.

Metoda Set w klasie ScenarioContext pozwala na przechowywanie wartości obiektów. Dzięki temu możemy między krokami przekazywać wartości lub dodać do tego klucz w postaci nazwy. Zawsze powinniśmy to robić, bo jeżeli będziemy chcieli dodać dwie wartości int SpecFlow będzie miał problem, o którą nam chodziło.

Definiujemy wartość tuż przed kliknięciem w pozycję „Tak”. Będzie to liczba naszych głosów przed kliknięciem.

Następnie wracamy do głosowania. Klikając w „ReturnToPoll” przechodzimy do zdefiniowania listy możliwych opcji w naszym głosowaniu. Jest to obiekt PollAnswears w klasie PollPage.

Klikamy w „Tak” oraz w przycisk VoteButton, który potwierdza nasz głos. Po dodaniu głosu przechodzimy do ostatniego kroku w naszym scenariuszu, jakim jest sprawdzenie czy ilość głosów się zgadza.

Czekamy na element VotesCounter.

Następnie pobieramy ilość głosów, które były zapisane przed głosowaniem. Inkrementujemy (dodajemy wartość + 1 do naszej zmiennej) wykorzystując nameOfVariable++;. Na końcu porównujemy czy ilość zgadza się z tym co prezentuje nasza strona.


Podsumowanie

Mam nadzieję, że wiecie już jak pobrać projekt dzięki SourceTree z GitHuba oraz w jaki sposób tworzyć page objecty w SpecFlow i podstaw DI.

W kolejnym wpisie przedstawię, czym jest CI (continuous integration). Pokażę, jak  dodać projekt do jednego z dostawców CI, które świetnie sprawdza się dla .NET-u. Dzięki tej wiedzy, dowiecie się więcej na temat SourceTree, która pozwoli Wam tworzyć testy, które działają nieprzerwanie. Na koniec zdradzę również, jaki drobny problem możecie spotkać w metodzie, która sprawdza wyświetlanie się formularza. 


Sprawdź poprzednie posty z serii:

Pierwszy post – Podstawowa wiedza na temat testów automatycznych dla platformy.NET,
Drugi post – 
Pierwszy test akceptacyjny w SpecFlow wraz z użyciem Selenium WebDriver oraz C#,
Trzeci post – 
Jak w czytelny sposób można przekazywać dane testowe w SpecFlow?


Michał Ślęzak
Programista testów w whatclinic.com. Bierze aktywny udział w rozwoju poznańskiej społeczności testerskiej PTaQ przez bycie wiceliderem. Tworzy bloga o testowaniu tesingplus.me kontakt - kontakt@testingplus.me

Dodaj komentarz

avatar
  Subscribe  
Powiadom o