Kontynuujemy naszą przygodę z testami automatycznymi na platformie.NET.

 

Dziś trzeci post z serii SpecFlow + Selenium WebDriver + C#.

Poprzedni wpis znajdziesz tutaj... a pierwszy tutaj...

Czego nauczysz się z tego wpisu?

  • Czym są Table i Examples - jak w czytelny sposób można przekazywać dane testowe w SpecFlow.
  • Jak efektywnie używać waitów. Jak radzić sobie z asynchronicznym JavaScriptem i różnymi efektami, które dzieją się w różnym czasie.
  • Ponadto zrefactoryzjuemy (ulepszymy) nasz kod testów, który stworzyliśmy w poprzedniej części.

Zaczynamy.

Wracamy do naszej solucji testowej, którą utworzyliśmy w pierwszym wpisie serii. W dzisiejszym wpisie stworzymy nowy plik typu feature, który będzie zawierał dwa scenariusze. Oba będą powiązane z dodawaniem postu poprzez panel admina.

Polecam Ci założyć własną stronę w platformie WordPress - właśnie do testów automatycznych. Strona może być dostępna lokalnie lub w bezpłatnym hostingu takim jak WordPress.com. W testach będę korzystał z panelu admina i swojego hasła, więc warto posiadać własną testową stronę.

1) Test – jako admin wstawiam post z tytułem i treścią, który następnie publikuję

Scenario Outline: As Admin I want to publish post with title and content
Given As admin I’m logging to admin panel
And As admin I see dashboard
When I’m adding post with <Title> title <Content> content
Then I check viewing of post on main page with <Title> title with <Content> content

Examples:

|Title                |Content|
| TestTitle       |Example content |

Scenario Outline - Jest to potrzebny zapis, w sytuacjach gdy używamy Examples: w naszych testach.

Czym jest Examples: ? Każdy z wierszy w Examples: jest osobnym przypadkiem testowym, który może różnić się przekazywanymi parametrami.

SpecFlow potrafi podstawić sobie wartości, które mamy w Examples: do metod, które ich używają, gdy używamy nawiasów ostrych  np. <Title>. Jeżeli nie znajdzie w Examples: takiej wartości to wstawi po prostu <Title> jako wartość string.

Dzięki temu SpecFlow wie, że dla tego scenariusza ma trzy przypadki testowe, czyli tak naprawdę trzy testy, które będą wyświetlały się w eksploratorze testów.

2) Test -  jako admin wstawiam post z tytułem i treścią, który następnie publikuję

Scenario: As Admin I want to publish post with title, content, category
Given As admin I login to admin panel
And As admin I see Dashboard

And I add post:
| Title        | Content      | Category |
| TestPostTable  | TestContent   |   test  |

Then I check view of post:
| Title  | Content    | Category |
| TestPostTable | TestContent |test  |

Czym jest sposób zapisu tego typu (poniżej)?
| Title        | Content      | Category |
| TestPostTable  | TestContent   |   test  |

Jest to tzw. table, który pozwala nam prezentować dane testowe w postaci tabeli oraz je przekazywać.

Dlaczego jest to takie przydatne? Dzięki temu (jeżeli testy są dobrze napisane) mamy możliwość łatwego dodawania różnych przypadków np. tytuł ze znakami specjalnymi, bez treści, z inną kategorią. Dodatkowo jest to również czytelny sposób zapisu.

Przechodzimy do implementacji naszych scenariuszy

Dodaję folder Admin do Features. W tym folderze będziemy umieszczać testy związane z testowaniem panelu admina, czyli również dzisiejszy nasz feature, który będzie zawierał dwa scenariusze związane z dodawaniem postu.

Dodajemy feature file o nazwie “TestingAddingPostsAsAdmin” Następnie przechodzimy do dodania scenariusza, który napisaliśmy w powyższym tekście.

Scenario: As Admin I want to publish post with title and content
Given As admin I’m logging to admin panel
And As admin I see dashboard
When I’m adding post with <Title> title <Content> content
Then I check viewing of post on main page with <Title> title with <Content> content

Examples:
|Title          |Content|
| TestTitle       |Example content |

Dodajemy kroki do powyższego scenariusza.

As-admin-I-login-to-admin-panel.png

Następnie dodajemy  implementacje dla kroku “As admin I login to admin panel

3 (1).png

W [23] linijce przechodzimy do panelu admina naszego testowego bloga. W następnej linijce [24] inicjalizujemy obiekt IWebElement, który jest elementem pola tekstowego login na stronie.

W [25] dodajemy linijkę, która wymaga wytłumaczenia. Jest to odwołanie do klasy pomocniczej waitHelper, która zawiera metodę pozwalającą czekać w sposób explicit Wait. Jest to klasa, którą musimy sami utworzyć.

Czym jest explicit wait? Explicit wait to typ czekania, który szuka danego obiektu w zadanym czasie. Plusem tego sposobu jest to, że jeżeli jakiś element jest szybciej widoczny na stronie, to metoda znajdzie go szybciej i nie czeka tyle, ile zadaliśmy tylko tyle, ile musi.

Implicit Wait lub Thread Sleep są to odmienne podejścia, które opierają się na “sztywnym” czekaniu - czyli nawet jeżeli element dostępny jest szybciej to te metody czekają.

To również ma swoje minusy:

  • Trzeba dobierać taki czas, aby element zawsze był dostępny (czyli wybraną dużą wartość).
  • Gdy mamy kilka testów to nie zauważamy tego, że zaczynamy potrzebować coraz więcej czasu. Nie są to wielkie różnice. Jednak przy np. 500 testach ma to ogromne znaczenie.

Jestem zwolennikiem dynamicznych waitów i chce żebyście od razu poznali lepszą metodę podejścia do waitów (choć czasem może trudniejszą, ze względu na dobór odpowiedniego selectora).

Klasa WaitHelper.cs

Klasa-WaitHelper-cs.png

W tej klasie tworzymy obiekt WebDriverWait w linijce [16].

Czego wymaga utworzenie tego obiektu?

Należy podać za parametry obiekt typu IWebDriver oraz obiekt typu TimeSpan. W tym celu utworzyłem pomocniczą klasę TimeoutsHelper gdzie dodałem properties zawierający TimeSpan w sekundach.

TimeoutsHelper-1.png

Wracamy do naszej klasy WaitHelper.cs

Klasa-WaitHelper-cs-1.png

W linijce [20] dodajemy metodę WaitForClickable(IWebElement element). Jest to często wystarczający sposób czekania na element w Selenium WebDriver.

Dlaczego?

Ta metoda czeka na właściwości Displayed danego elementu w kodzie HTML  przez maksymalnie zadany przez nas czas. Dla nas oznacza to, że jeżeli Displayed ma właściwości true to element jest możliwy do kliknięcia.

Czasami może być tak, że dany element jest osadzony na stronie i ma właściwość Enabled na true, ale Displayed będzie na false. Wtedy taka metoda zwróci błąd, ponieważ właściwość Displayed będzie na false.

Co w takim wypadku należałoby zrobić?

Jest kilka sposób na wybrnięcie z tej sytuacji:

  • Scrollowanie do elementu - przez JS, lub też przez metodę MoveToElement często jest wystarczające. Po scrollowaniu click. Dlatego również możliwe jest utworzenie jednej metody ScrollToElementAndClick().
  • Każdy IWebElement oprócz metody Click() również posiada Submit(). Wiele osób używa tej metody. Ponadto pomaga to ominąć błąd taki jak np. element is not clickable at point.
  • Czasami trzeba kliknąć jeden element np. checkbox i dopiero inne elementy stają się widoczne.

Teraz pokażę Ci, w jaki sposób można dodać post do naszego bloga, choć oczywiście sposobów na dodawania postu w WordPressie jest kilka lub kilkanaście. Pokażę Ci natomiast dwa przykładowe podejścia.

Pierwszy sposób: Tworzymy całą akcję dodawania za pomocą Selenium WebDrivera.

  • Klikamy “Posts” z naszego bocznego menu.
  • Następnie klikamy przycisk “Add New’ i dopiero znajdujemy się na ekranie dodawania posta.

Drugi sposób: Jako, że znamy adres URL dodawania posta, od razu udajemy się pod ten adres i testujemy dodawanie posta w naszym scenariuszu.

Moim zdaniem lepiej w naszym przypadku skorzystać z drugiego sposobu, bo w teście testujemy dodawanie posta, więc dzięki temu zaoszczędzimy czas na egzekucji testu (mamy mniej kroków). W tym przypadku nie jest to wielka różnica, ale gdy mamy bardziej złożone testy lub większą ich ilość takie detale mają znaczenie.

Tym prostym przykładem chcę abyście następnym razem, gdy będziecie projektować scenariusze i ich implementacje, przemyśleli w jaki sposób to zrobić. Czasami przez inne podejście będziecie mogli zaoszczędzić czas na egzekucji testu.

Oczywiście przypadków może/powinno być kilka.  Jeden, który dodaje z dashboardu post, ale to wtedy gdy w naszym przypadku testowym mamy to wyspecyfikowane i potrzebujemy danych akcji dla naszego scenariusza. Starajmy się tworzyć testy akceptacyjne jak najbardziej zwięzłe w ilości kroków i przejrzystości kodu.

Po dodaniu metody, która dodaje post wraz z treścią i tytułem przechodzimy do implementacji kroku, który sprawdzi nam, czy post został poprawnie dodany.

7.png

zmienna-homePageUrl.png

Zaczynamy od dodania zmiennej homePageUrl (W kąciku refactoringu podam sposób jak lepiej zarządzać takimi danymi jak stałe adresy url, które używamy we wszystkich testach).

Udajemy się do strony głównej naszego bloga. Następnie szukamy tytułów postów jako IwebElement, a w tej kolekcji szukamy pierwszego elementu, który będzie miał taką samą treść tytuł jak ta, której szukamy.

Klikamy w ten tytuł, którego szukamy.

Użycie First() jest swojego rodzaju asercją, bo jeżeli takiego tytułu nie będzie to test zwróci nam błąd. Innym podejściem mogłoby być użycie FirstOrDefault(). Ta metoda jeżeli nie znalazłaby tytułu zwróciłaby null, a moglibyśmy dodać Assert.IsNotNull(postTitle). To już zależy od was jakiego metodę preferujecie.

Na koniec tworzymy obiekt, który odpowiada za treść naszego posta wraz z asercją, która sprawdza poprawność dodanego tekstu.

1) Test jako użytkownik - loguję się jako admin i wstawiam post, następnie publikuję go na stronie

Scenario: As Admin I want to publish post with title, content, category
Given As admin I login to admin panel
And As admin I see Dashboard

And I add post:
| Title      | Content       | Category |
| TestPostTable  | TestContent   |   test   |

Then I check view of post:
| Title         | Content     | Category |
| TestPostTable | TestContent |test      |

Również zajmujemy  się napisaniem drugiego testu, który będzie dodawał post, jednak nie z Examples: a z Table.

Czym jest Table? Table - jest to możliwość przekazania obiektu Table w metodzie jako parametr.

Poprzez Table możemy przekazywać dane -  wiersz możemy mapować na określony obiekt np. table.CreateSet<People>(); co tworzy dla nas kolekcje obiektów People (IEnumerable)
dzięki referencji do klasy SpecFlow.Assist.
Trzeba pamiętać, że te struktury dobrze radzą sobie z tzw. primitive types. Jeżeli będziemy chcieli w kolumnie mieć np. Enum, to obecnie SpecFlow nie zawsze radzi sobie z parsowaniem. Są na to sposoby w dokumentacji SpecFlow.

Przykład:

3.09..png

Dzięki Table dane testowe, które chcemy wprowadzić mogą być przekazywane w postaci czytelnej tabeli.

Z racji napisania poprzedniego testu zostaje nam tylko napisać dwa kroki, aby nasz drugi scenariusz był zaimplementowany.

  • Pierwszym krokiem będzie dodawanie postu poprzez Table.
  • Drugim krokiem, który dodamy będzie sprawdzenie poprawności przez Table

Dodajemy nasz drugi scenariusz.

drugi-scenariusz.png

Następnie przechodzimy do implementacji kroku, który dodaje post.

deklaracja-kolekcji-postow.png

W pierwszej linijce metody [88] deklarujemy kolekcje postów używając w tym celu  table.CreateSet<Post>()

Table jest klasą, która pozwala nam tworzyć obiekty związane z tabelą, używanej przez nas danym kroku. Gdy tworzymy krok przez prawy przycisk myszy - > generate step to parametr w metodzie będzie typu Table.

Czym jest CreateSet<T>() ? CreateSet<T>()  - pozwala nam mapować tabelę, którą napisaliśmy na konkretne potrzebne przez nas obiekty. Jest to dla nas wygodne, gdyż możemy zrobić to tak, że każdy z wierszy tabeli będzie osobnym obiektem typu Post, ale z innymi właściwościami. Wynika z tego, że cały CreateSet<T>() jest kolekcją obiektów z tabeli. W tym przypadku potrzebujemy obiektu Post - więc stworzyłem go dodając do osobnej klasy i umieściłem do folderu Helpers.

CreateSetT.png

Możemy również tworzyć dynamiczny odczyt tabel, ale powyższy sposób z rzutowaniem na konkretny typ jest bardziej jasny na początku drogi ze SpecFlow. Ten sposób rozpoznaje kolumny po nazwach propertiesów (jeżeli byśmy dodali do klasy Post właściwości, której nie ma w tabeli, w obiekcie post będzie ona widoczna z wartość null).

dostep-do-wlasciwosci-obiektu.png

Tak wygląda dostęp do właściwości naszego obiektu. Moim zdaniem jest to czytelny sposób na dostęp do danych zawartych w tabeli.

Następnie przechodzimy do adresu dodawania nowego posta. Tworzymy teraz pętle foreach, która przechodzi po wszystkich elementach w kolekcji posts. Jeżeli mielibyśmy więcej wierszy w naszej tabeli, która dodaje posty, to akcja byłaby wykonywana na wszystkich dlatego, że jest to kolekcja. W tym wypadku mamy dodawanie jednego posta, więc pętla przejdzie raz.

W linijkach [92-94] tworzymy IWebElement naszego pola tekstowego, w które wpisujemy tytuł naszego posta. Również czekamy, bo może okazać się, że element jest osadzony na stronie, ale nie zdążył się jeszcze załadować - mieć właściwość Displayed na true.

Następnie w linijkach [96-99] dodajemy IWebElement obszaru, w którym wpisujemy treść posta w WordPressie.

IWebElement-obszaru-WordPress.png

Ciekawostka #1

Nową rzeczą, którą użyłem jest użycie klasy Actions. To jest klasa, która pozwala nam na używanie różnego rodzaju akcji w Selenium WebDriver na IWebElementach związanych z interakcjami na stronie. Jest zatem więcej możliwości niż w obiekcie IWebElement.

Przykłady:

(Metody dostępne z obiektu klasy Actions)
W tym wypadku pokazane jest dodawanie tekstu, ale nic nie stoi na przeszkodzie, żeby łączyć dodawanie tekstu z kliknięciem etc. Gdy chcemy wykonać jedną lub więcej akcji używamy metody.Perform(). Gdy o tym zapomnimy akcja nie zadziała i możemy się zastanawiać czemu to nie zadziałało.

Po tym kroku dodajemy kod, który doda nam kategorię, o nazwie, którą przekazaliśmy w scenariuszu. Dodajemy obiekt typu IWebElement addCategory również na niego czekamy i klikamy w niego. Rozwija nam się pole tekstowe, w które możemy wprowadzić naszą kategorię i następnie dodać ją klikając w przycisk obiekt-addNewCategory.

addNewCategory.png

Ostatnim etapem będzie dodanie obiektu, który będzie odpowiadał za przycisk Publish na stronie i kliknięcie w niego. Również polecam dodać czekanie.

Następny krok to sprawdzanie poprawności dodania postu.

16.png

sprawdzanie-poprawnosci-dodania-postu.png

Udajemy się do strony głównej naszego bloga.

Tworzymy tak jak wcześniej kolekcje postów z tabeli. Dodajemy pętle foreach, która przechodzi po wszystkich elementach zawartych w tej kolekcji. W pierwszym kroku szukamy elementu, mającego taki sam tytuł jak nasz post, którego szukamy.

Następnie czekamy, aby móc kliknąć w niego.

Tworzymy obiekt kategorii oraz dodajemy asercje, która sprawdza nam, czy kategoria zgadza się z tą, której oczekiwaliśmy. Ostatnim krokiem jest dodanie IWebElementu treści strony i sprawdzenie, czy zgadza się z tą, której oczekiwaliśmy.

Kącik refactoringu #1

Tak jak wspominałem w poprzednim wpisie (drugi wpis) krok po kroku będziemy dokonywać refactoringu (ulepszenia kodu), którego tworzymy:

1) Różne adresy Url naszej strony testowej

Do tego celu warto stworzyć klasę pomocniczą, która będzie zawierała nasz HomeUrl jako właściwość. Również może zawierać AdminDashboardUrl.

Jest to lepszy sposób niż wpisywanie / wklejanie adresu w każdym teście i tworzenie zmiennej do tego.

2) Dodanie Maximize()

Warto dodawać przy inicjalizacji powiększenia strony do maksymalnych możliwych rozmiarów (zależy, jaką macie możliwą rozdzielczość dostępnych), gdyż tak standardowo użytkownicy przeglądają strony. Oczywiście, możemy testować mniejsze rozdzielczości/mobile, ale w tym wypadku zrobiłbym dodatkowy test case np. poprzez Scenario Outline, które dzisiaj poznaliśmy.

3) Enum jako parametr w kroku

W klasie ContactFormSteps mamy metodę GivenIEnterToPage(string name), w której możemy dodać jako parametr wartość enum. O tyle jest to lepszy sposób, że w enum trudniej jest zrobić błąd, gdy porównujemy wartości.

Dodajemy enum do folderu Helpers Pages.
I w nim dodajemy enum “HomePage”
Również tak samo możemy zrobić w metodzie  GivenIClickOnInMenu(string option)

Tutaj zależy od tego, czy preferujemy używanie enumów. Moim zdaniem w tym zastosowaniu sprawdzają się doskonale. Jednak jeżeli ktoś woli używać typu string, to może być używany poprawnie. Należy wówczas pamiętać o poprawności przekazywania. Trzeba zwracać uwagę na wielkość liter, spację, etc.

Podsumowanie

Dzisiaj nauczyliśmy się jak tworzyć testy, które korzystają z różnych sposobów na wprowadzanie danych testowych do naszych scenariuszy testowych. Dowiedzieliśmy się również , w jaki sposób radzić sobie z czekaniem na załadowanie się danego elementu.

W następnym wpisie wprowadzimy Page Object Model, który zrobi, że nasz kod będzie bardziej przyjazny i przyjemniejszy do utrzymywania. Dodatkowo dodamy nasz kod do repozytorium kodu. Dzięki temu nauczymy się jednej z najważniejszych umiejętności w programowaniu. Zachęcam do przekazywania tekstu dalej!