Selenium to zestaw narzędzi pozwalających na automatyzację przeglądarki internetowej. Obsługuje wszystkie dostępne na rynku przeglądarki. Współpracuje z następującymi językami:  Python, Java, CSharp, Ruby, Kotlin, JavaScript.

Kod z wyjaśnieniem. Page Object Pattern.

Napisałam dla Ciebie kod w Pythonie, który testuje stronę internetową w dwóch przeglądarkach: Chrome i Firefox. Poniższy kod uruchamia przeglądarkę, otwiera stronę https://testuj.pl, akceptuje ciasteczka, przechodzi do zakładki szkolenia, wyszukuje szkolenia, które w nazwie mają słowo Selenium, klika w Wypełnij test predyspozycji.

Test automatyczny w Firefox

from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from webdriver_manager.firefox import GeckoDriverManager
from selenium.webdriver.firefox.service import Service as FirefoxService
from selenium import webdriver
def test_Firefox():
driver = webdriver.Firefox(service=FirefoxService(executable_path=GeckoDriverManager().install()))
driver.get('https://testuj.pl/')
assert 'testuj.pl' in driver.title
driver.maximize_window()
wait = WebDriverWait(driver, 10)
cookie = (By.CSS_SELECTOR, "[class='cookie']")
wait.until(expected_conditions.element_to_be_clickable(cookie))
driver.find_element(By.CSS_SELECTOR, "[class='agree right']").click()
driver.find_element(By.CSS_SELECTOR, "[class='button']").click()
assert driver.find_element(By.CLASS_NAME, 'coursesListHeader__list-url').is_displayed()
driver.find_element(By.CSS_SELECTOR, "[name='name']").send_keys('Selenium')
driver.find_element(By.CSS_SELECTOR, "[class='formField__button']").click()
driver.quit()
def test_testnatestera_Firefox():
driver = webdriver.Firefox(service=FirefoxService(executable_path=GeckoDriverManager().install()))
driver.get('https://testuj.pl/')
wait = WebDriverWait(driver, 10)
cookie = (By.CSS_SELECTOR, "[class='cookie']")
wait.until(expected_conditions.element_to_be_clickable(cookie))
driver.find_element(By.CSS_SELECTOR, "[class='agree right']").click()
driver.maximize_window()
driver.find_element(By.CSS_SELECTOR, "[class='button button--bigger']").click()
driver.quit()

Test automatyczny w Chrome

from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium import webdriver
def test_Chrome():
driver = webdriver.Chrome(service=ChromeService(executable_path=ChromeDriverManager().install()))
driver.get('https://testuj.pl/')
assert 'testuj.pl' in driver.title
driver.maximize_window()
wait = WebDriverWait(driver, 10)
cookie = (By.CSS_SELECTOR, "[class='cookie']")
wait.until(expected_conditions.element_to_be_clickable(cookie))
driver.find_element(By.CSS_SELECTOR, "[class='agree right']").click()
driver.find_element(By.CSS_SELECTOR, "[class='button']").click()
assert driver.find_element(By.CLASS_NAME, 'coursesListHeader__list-url').is_displayed()
driver.find_element(By.CSS_SELECTOR, "[name='name']").send_keys('Selenium')
driver.find_element(By.CSS_SELECTOR, "[class='formField__button']").click()
driver.quit()
def test_testnatestera_Chrome():
driver = webdriver.Chrome(service=ChromeService(executable_path=ChromeDriverManager().install()))
driver.get('https://testuj.pl/')
wait = WebDriverWait(driver, 10)
cookie = (By.CSS_SELECTOR, "[class='cookie']")
wait.until(expected_conditions.element_to_be_clickable(cookie))
driver.find_element(By.CSS_SELECTOR, "[class='agree right']").click()
driver.maximize_window()
driver.find_element(By.CSS_SELECTOR, "[class='button button--bigger']").click()
driver.quit()

Przygotowanie do pisania testów

Pierwszym krokiem jaki wykonałam było pobranie bibliotek: Selenium, Webdriver-Manager oraz Pytest. Poniżej przedstawiam screen z wyszukiwania Webdriver - Manager.

unnamed-5-1 (1).jpg

Krok drugi to instalacja sterowników wybranych przeglądarek i rozpoczęcie sesji. Pamiętaj, że linie kodu z instrukcjami from… import… należy umieścić na górze strony, nad testem.

Firefox

from selenium.webdriver.firefox.service import Service as FirefoxService
from webdriver_manager.firefox import GeckoDriverManager
from selenium import webdriver
driver = webdriver.Firefox(service=FirefoxService(executable_path=GeckoDriverManager().install()))

Chrome

from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from selenium import webdriver
driver = webdriver.Chrome(service=ChromeService(executable_path=ChromeDriverManager().install()))

Sterowniki obsługują komunikację między przeglądarką a Selenium.

Wytłumaczenie kodu

Zwróć uwagę na to, że poniższy kod będzie wyglądał identycznie w testach na Firefox i Chrome.

Otworzenie strony internetowej https://testuj.pl

driver.get('https://testuj.pl/')

Na zmiennej driver wywołuję metodę get(). Wartość parametru, czyli zawartość nawiasu to nazwa strony, którą zamierzam otworzyć.

Asercje w Selenium

assert 'testuj.pl' in driver.title

Weryfikuje, czy w tytule strony jest tekst 'testuj.pl'.

 assert self.driver.find_element(By.CLASS_NAME, 'coursesListHeader__list-url').is_displayed()

Sprawdza, czy klasa o nazwie 'coursesListHeader__list-url' wyświetla się na stronie.

unnamed-3-1.jpg

Asercje są bardzo ważne podczas pisania testów. Tworząc asercje możesz porównać rzeczywisty wynik z oczekiwanym lub sprawdzić informacje o wybranym elemencie, na stronie. Dzięki nim sprawdzisz czy jesteś na właściwej stronie, czy dany element jest na niej dostępny, czy posiada określony tekst. Wynik asercji to prawda(True) lub fałsz(False). 

Przykładowe informacje o elemencie:

  • is displayed - czy jest wyświetlony
  • is enabled - czy jest włączony
  • is selected - czy jest wybrany, dotyczy np. pól wyboru, radio buttons

Maksymalizacja okna przeglądarki w Pythonie

driver.maximize_window()

Oczekiwanie w Selenium (wait)

Czekamy na załadowanie elementu na stronie, czyli jego pojawienie się, inaczej zaistnienie w modelu DOM. Jeśli element nie zdąży załadować się Python zgłosi wyjątek NoSuchElementException.

from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
wait = WebDriverWait(driver, 10)
cookie = (By.CSS_SELECTOR, "[class='cookie']")
wait.until(expected_conditions.element_to_be_clickable(cookie))

Klasę By należy zaimportować. Metody w klasie lokalizują element. Zaimportować należy także WebDriverWait i expected_conditions.

Chcąc swobodnie poruszać się po stronie, zamierzam zaakceptować pliki cookie. Powyższy kod mówi, że poczekam maksymalnie 10 sekund, do czasu pojawienia się możliwości kliknięcia w okno, zawierające klasę, o nazwie 'cookie'.

Sposoby oczekiwania w Selenium:

1.ImplicitWait – niejawny czas oczekiwania. WebDriver odpytuje DOM przez określony czas. Przykład:

implicitly_wait(10)

2. ExplicitWait – jawny czas oczekiwania. Webdriver odpytuje DOM do momentu spełnienia warunku, przez określony czas. Przykład:

timeout=10.until(some_condition

Przykładowe zdarzenia, na które możemy poczekać:

unnamed-2-1.jpg

3. FluentWait – płynne oczekiwanie. WebDriver odpytuje DOM przez określony czas, w ustalonej przez nas częstotliwości, ignorując pewne typy wyjątków. Przykład:

timeout=10, poll_frequency=1, ignored_exceptions=[ElementNotVisibleException]

Kod, który znajduje element na stronie i wybiera go (klika)

Podczas testów klikam najpierw przycisk akceptujący ciasteczka

driver.find_element(By.CSS_SELECTOR, "[class='agree right']").click(),

następnie element o nazwie Szkolenia

driver.find_element(By.CSS_SELECTOR, "[class='button']").click(),

lupę podczas szukania szkolenia o nazwie Selenium

driver.find_element(By.CSS_SELECTOR, "[class='formField__button']").click() 

i pole Test predyspozycji

driver.find_element(By.CSS_SELECTOR, "[class='button button--bigger']").click().

Lokalizatory, pozwalające odwołać się do elementu:

  • Class name
  • CSS selector
  • ID
  • Name
  • Link text
  • Partical link text
  • Tag name
  • Xpath

Wybrałam CSS selector, bo lubię.

'agree right', 'button', 'formField__button', 'button button--bigger' są  to selektory.

Szukanie selektora w DevTools

Otwórz konsolę programistyczną, naciskając F12, fn + F12 lub prawym przyciskiem myszy element, którego selektor chcesz poznać i wybierz Zbadaj/Inspect.

unnamed-4-1 (1).jpg

W sekcji Inspector (Firefox)/ Elementy (Chrome) widzisz jak zostało zbudowane dane pole.

unnamed-6 (1).jpg

Możesz podejrzeć dane sąsiadujących elementów, poruszając się strzałkami w górę i w dół.

Teraz naciśnij przycisk inspekcji kodu (kwadrat z kursorem) i wskaż myszką interesujący Cię, dowolny element na stronie.

unnamed-1-1 (1).jpg

Użyj selektor, który jest niepowtarzalny. Wybierz identyfikator (accessibility id, id) jeśli jest dostępny.

Na elementach możesz wykonać następujące polecenia:

  • click
  • send keys lub clear (przy polach tekstowych i elementach edytowalnych)
  • select (przy liście elementów)

Wyszukanie wybranego słowa na stronie, przy pomocy lupy

driver.find_element(By.CSS_SELECTOR, "[name='name']").send_keys('Selenium') 

Powyższy kod zawsze będzie wyszukiwał słowo 'Selenium'.

Zamknięcie przeglądarki

driver.quit() 

Fixtures

Z pewnością zauważyłaś/eś, że znaczna część kodu powtarza się w kolejnych testach. Rozwiązaniem na powtórzenia jest dekorator @pytest.fixture. Opowiedziałam Ci o nim w artykule https://testuj.pl/blog/testy-z-uzyciem-frameworka-pytest-fixture-i-parametryzacja-testu/. Przeczytaj go, proszę jeśli chcesz zrozumieć dalszą część artykułu. Poniżej użyję również klasę Pythonową. We wspomnianym artykule, znajdziesz także wytłumaczenie klas.

Zerknij ponownie na testy w Chrome

from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.service import Service as ChromeService
from selenium import webdriver
def test_Chrome():
   driver = webdriver.Chrome(service=ChromeService(executable_path=ChromeDriverManager().install()))
   driver.get('https://testuj.pl/')
   assert 'testuj.pl' in driver.title
   driver.maximize_window()
   wait = WebDriverWait(driver, 10)
   cookie = (By.CSS_SELECTOR, "[class='cookie']")
   wait.until(expected_conditions.element_to_be_clickable(cookie))
   driver.find_element(By.CSS_SELECTOR, "[class='agree right']").click()
   driver.find_element(By.CSS_SELECTOR, "[class='button']").click()
   assert driver.find_element(By.CLASS_NAME, 'coursesListHeader__list-url').is_displayed()
   driver.find_element(By.CSS_SELECTOR, "[name='name']").send_keys('Selenium')
   driver.find_element(By.CSS_SELECTOR, "[class='formField__button']").click()
   driver.quit()
def test_testnatestera_Chrome():
   driver = webdriver.Chrome(service=ChromeService(executable_path=ChromeDriverManager().install()))
   driver.get('https://testuj.pl/')
   wait = WebDriverWait(driver, 10)
   cookie = (By.CSS_SELECTOR, "[class='cookie']")
   wait.until(expected_conditions.element_to_be_clickable(cookie))
   driver.find_element(By.CSS_SELECTOR, "[class='agree right']").click()
   driver.maximize_window()
   driver.find_element(By.CSS_SELECTOR, "[class='button button--bigger']").click()
   driver.quit() 

Teraz zobacz, jak będzie wyglądał kod, z użyciem dekoratora fixture

import pytest
@pytest.fixture()
def driver():
   driver = webdriver.Chrome(service=ChromeService(executable_path=ChromeDriverManager().install()))
   driver.get('https://testuj.pl/')
   assert 'testuj.pl' in driver.title
   driver.maximize_window()
   wait = WebDriverWait(driver, 10)
   cookie = (By.CSS_SELECTOR, "[class='cookie']")
   wait.until(expected_conditions.element_to_be_clickable(cookie))
   driver.find_element(By.CSS_SELECTOR, "[class='agree right']").click()
   yield driver
   driver.quit()
def test_Chrome(driver):
 driver.find_element(By.CSS_SELECTOR, "[class='button']").click()
 driver.find_element(By.CSS_SELECTOR, "[name='name']").send_keys('Selenium')
 driver.find_element(By.CSS_SELECTOR, "[class='formField__button']").click()
def test_testnatestera_Chrome(driver):
 driver.find_element(By.CSS_SELECTOR, "[class='button button--bigger']").click() 

Fixture dzieli się na 3 części. Pierwsza część wykona się przed testem. Będzie to uruchomienie przeglądarki, otworzenie strony, maksymalizacja okna i akceptacja ciastek. Druga to yield, czyli zwróć drivera. Trzecia część wykona się po teście. W naszym przypadku będzie to zamknięcie przeglądarki. Od teraz nasze testy zawierają tylko akcje na stronie.

Page Object Pattern

Wzorzec projektowy polegający na odizolowaniu testów od akcji, które wykonujemy na elementach. W POP jedna strona internetowa odpowiada jednej klasie, a każda klasa znajduje się w osobnym pliku.

Przekładając zasadę na nasz kod:

  • pierwszy plik, pierwsza klasa - akcje na stronie głównej testuj.pl, czyli akceptacja ciastek, kliknięcie w Szkolenia i Wypełnij test predyspozycji 
  • drugi plik i klasa w nim zawierałaby akcje na drugiej stronie, otworzonej po kliknięciu w zakładkę Szkolenia. Akcja jaką wykonałam na stronie to wyszukanie w lupce kursów, które w nazwie mają słowo Selenium
  • trzeci plik i kolejna klasa odnosiłaby się do strony, otwartej po kliknięciu Wypełnij test predyspozycji 

Celem POP jest łatwiejsza modyfikacja kodu w przyszłości.  Pliki z klasami powinny być zapisane w folderze o nazwie Pages, natomiast testy w folderze Tests.

Przebuduję kod zgodnie z Page Object Pattern. Użyję przeglądarki Chrome

Stworzyłam nowy plik o nazwie first_page a w nim klasę o nazwie FirstPage. Klasa zawiera akcje jakie wykonam na pierwszej stronie, czyli czekanie aż okno ciastek będzie klikalne, akceptację ciastek, kliknięcie w Szkolenia i Wypełnij test.

from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
class FirstPage:
   def __init__(self, driver):
       self.driver = driver
   def wait_for_cookie(self):
       wait = WebDriverWait(self.driver, 10)
       cookie = (By.CSS_SELECTOR, "[class='cookie']")
       wait.until(expected_conditions.element_to_be_clickable(cookie))
   def accept_cookie(self):
       self.driver.find_element(By.CSS_SELECTOR, "[class='agree right']").click()
   def go_to_training(self):
       self.driver.find_element(By.CSS_SELECTOR, "[class='button']").click()
       assert self.driver.find_element(By.CLASS_NAME, 'coursesListHeader__list-url').is_displayed()
   def go_to_test(self):
       self.driver.find_element(By.CSS_SELECTOR, "[class='button button--bigger']").click() 

Po kliknięciu zakładki Szkolenia, uruchomię drugą stronę. Tworzę więc drugi plik o nazwie second_page ,a w nim klasę SecondPage. W klasie znajduje się metoda wyszukująca szkolenia, które w nazwie mają słowo Selenium.

from selenium.webdriver.common.by import By
class SecondPage:
   def __init__(self, driver):
       self.driver = driver
   def find_training(self):
       self.driver.find_element(By.CSS_SELECTOR, "[name='name']").send_keys('Selenium')
       self.driver.find_element(By.CSS_SELECTOR, "[class='formField__button']").click() 

Do pliku testowego zaimportuje stworzone klasy. Zerknij, jak teraz wyglądają testy.

from first_page import FirstPage
from second_page import SecondPage
@pytest.fixture()
def driver():
   driver = webdriver.Chrome(service=ChromeService(executable_path=ChromeDriverManager().install()))
   driver.get('https://testuj.pl/')
   assert 'testuj.pl' in driver.title
   driver.maximize_window()
   yield driver
   driver.quit()
def test_first_page(driver):
   first_page = FirstPage(driver)
   first_page.wait_for_cookie()
   first_page.accept_cookie()
   first_page.go_to_training()
   first_page.go_to_test()
def test_second_page(driver):
   first_page = FirstPage(driver)
   first_page.wait_for_cookie()
   first_page.accept_cookie()
   first_page.go_to_training()
   second_page = SecondPage(driver)
   second_page.find_training() 

Test pierwszy wykona operacje na stronie https://testuj.pl/. Z fixture pobierze otwarcie przeglądarki i strony, maksymalizację ekranu. Następnie użyję klasę o nazwie FirstPage, z pliku first_page do czekania na okno ciastek, akceptacji ciastek, kliknięcia zakładek Szkolenia i Wypełnij test.

Test drugi wykona operacje na stronie https://testuj.pl/lista-szkolen. Będzie to możliwe po ponownym wykonaniu kroków z testu pierwszego,a następnie odwołaniu do klasy SecondPage, z pliku second_page. Na drugiej stronie przetestuję wpisanie nazwy Selenium w lupę i wyszukaniu szkoleń zawierających tą nazwę.

Podsumowanie

W artykule opowiedziałam Ci o testach automatycznych w Selenium. Językiem programowania, który użyłam był Python. W ostatecznej wersji kodu zastosowałam fixture i Page Object Pattern. Podpowiedziałam Ci jak poczekać na element, kliknąć w niego i wyszukać konkretne słowo. W artykule opisałam także znajdowanie selektorów w DevTools.

Życzę Ci powodzenia w pisaniu testów automatycznych.