Selenium

Michalina Hansdorfer
Bartłomiej Bartnicki



Czym jest Selenium?

Selenium jest frameworkiem używanym do tworzenia automatycznych testów dla aplikacji webowych. Narzędzie to wspiera wszystkie popularne platformy: Windows, Linux oraz OS X.

Selenium zostało napisane przez Jason'a Huggins'a w 2004 roku oraz wydane na licencji Apache 2.0. Kod frameworka jest otwarty, obecnie istnieją dwie jego wersje: Selenium 1 oraz Selenium 2 znana jako WebDriver.

Strona domowa Selenium posiada bardzo dobrą dokumentację - link. Framework składa się z następujących modułów: IDE, ClientApi/WebDriver, RC, GRID.

Selenium IDE

Selenium IDE jest pluginem do Firefoxa pozwalającym na nagrywanie i odtwarzanie testów. Istnieje możliwość pisania w nim prostych skryptów w wewnętrznym skryptowym języku pluginu. Narzędzie to nadaje się do małych projektów.


Selenium Client API i WebDriver

Zbiór bibliotek pozwalających pisać testy automatyczne w Javie, C#, Pythonie, Rubyim. Selenium 1 używało klasy Selenium do wykonywania wszystkich działań na stronie internetowej, Selenium 2 używa obiektów klasy WebDriver, których zadaniem jest operowanie na jednej instacji przeglądarki. Selenium 2 często jest nazywane WebDriverem. Selenium 1 jest wciąż wspierane, jednakże wersja 2 posiada więcej funkcjonalności.

Selenium RemoteControl

Server napisany w Javie, który automatycznie uruchamia i zamyka instancje przeglądarki oraz pozwala na odgórne wprowadzenie pewnych ustawień do testów (na przykład narzucenie łączenia się przez proxy). Server RC rozszerza zbiór języków programowania, w których możemy pisać testy przy pomocy Selenium (php, js, perl...).

Selenium GRID

Server napisany w Javie pozwalający na rozłożenie testów na wiele maszyn oraz uruchomienie testów na różnych przeglądarkach. GRID oraz RC pozwalają na tworzenie farm serwerów, gdzie w sposób automatyczny raz napisane testy będą się wykonywać na różnych przeglądarkach, na różnych systemach, w sposób rozproszony na wielu maszynach.

Instalacja

Aby uruchomić przykładowe kody, wystarczy dodać do Eclipsowego projektu JARa (JAR można pobrać ze strony Selenium, załączony projekt zawiera już odowiedniego JARa). Instalacja RC i GRIDa jest bardzo dobrze opisana w oficialnej dokumentacji, poniższe przykłady jednak nie wymagają tych serwerów. Instalacja IDE odbywa się przez menadżer pluginów firefoxa.

HelloWorld

Pobierzcie Eclipsowy projekt. Projekt ten zawiera już JARa wspomnianego w poprzednim akapicie. Zawiera on wszystkie przykłady, które mamy zamiar Wam pokazać. Na początek otwórzmy klasę Starter. Zawiera ona funkcję main() oraz listę przykładów, które chcemy zaprezentowąć w dalszej części. Uruchamiamy program. Powinna nam się włączyć strona google.com, następnie w polu wyszukiwania wpisana zostanie fraza "UJ", strona zostanie przeładowana się na stronę z wynikami wyszukiwania i finalnie w konsoli zostanie wypisany tytuł obecnie wyświetlanej strony.

W kolejnej części omówimy kod tego przykładu i paru innych.


Czym jest WebDriver?

Przyjrzyjmy się następującym liniom z HelloWorld:

WebDriver driver = new FirefoxDriver();
...
driver.get("http://www.google.com");
...
driver.findElement(By.name("q"));
...
driver.getTitle()

Można z z nich wywnioskować, że obiekty klasy WebDriver służą do "współpracy" ze stroną, którą testujemy. WebDriver będzie klasą, z którą najczęściej współpracuje się, pisząc w Selenium. Obiekt tej klasy jest równoważny jednej instancją przeglądarki (niekoniecznie z jednym oknem lub jedną kartą!).

Różne drivery

Zamiast Firefoxa test możemy uruchomić na innych przeglądarkach i innych symulatorach przeglądarek. HtmlUnit jest właśnie takim symulatorem, który nie uruchamia on rzeczywistej przeglądarki, lecz symuluje jej zachowanie (na przykład wykonuje skrypty js). Możemy uściślić, jaką przeglądarkę chcemy dokładnie zasymulować. HtmlUnit znacząco przyspiesza wykonywanie się testu, ale nie daje 100% pewności, że przeglądarka zachowa się w identyczny sposób. Spróbujcie podmienić odpowiednią linijkę HelloWorlda na jedną z poniższych, która korzysta z HtmlUnitDrivera. Czy dostrzegacie znaczącą różnicę w czasie?

Możemy także uruchomić test na innej przeglądarce, wymaga to jednak trochę zabawy z konfiguracją środowiska lub użycia Selenium RC. Jeżeli jesteście zainteresowani, jak to dokładnie zrobić, odsyłamy do dokumentacji.

WebDriver driver = new HtmlUnitDriver();
WebDriver driver = new HtmlUnitDriver(BrowserVersion.FIREFOX_17);

WebDriver driver = new InternetExplorerDriver();
WebDriver driver = new ChromeDriver();

Podstawowe metody WebDrivera (get(), getTitle(), quit(), close())

Spójrzmy, jakie jeszcze ciekawe metody oferuje nam WebDriver. Metoda driver.get(String url); jak łatwo się domyślić, przenosi nas na stronę podaną jako argument. Metoda ta czeka na załadowanie całej strony. Jeżeli testujesz stronę, która wczytuje się w niestandardowy sposób (wykonanie js onLoad() nie jest faktycznym momentem końca ładowania strony) warto pogooglować: WebDriver.navigate(); oraz poczytać o metodach służących do oczekiwania na pewne eventy (omówimy je później). Metoda driver.quit(); zamyka wszystkie otwarte okna przeglądarki. Warto wspomnieć jeszcze o metodzie driver.close(); która zamyka obecnie aktywne okno przeglądarki (przydatne, kiedy test operuje na paru oknach/kartach. Aby pobrać tytuł strony, użyjemy String title = driver.getTitle();

"Zbadaj element" - przydatne narzędzie FireFoxa

Otwieramy w Firefoxie stronę, klikamy w wybranym miejscu prawym przyciskiem myszki i wybieramy opcję "zbadaj element". Pojawia nam się ramka z kodem HTML strony, a element, który badamy, jest podświetlony. Doklikujemy się do opcji "kopiuj unikalny selektor", w schowku mamy XPath do badanego elementu (co to XPath - powiemy za moment). Ciekawym narzędziem jest także konsola, w której możemy bezpośrednio wpisać kod js, co zrobimy w następnym paragafie.

XPathy

XPath (ang. XML Path Language, w wolnym tłumaczeniu: Język ścieżek XML, Język ścieżek rozszerzalnego języka znaczników) – język służący do adresowania części dokumentu XML. Jest on bardzo podobny do ścięzki do plików, tyle że tutaj operujemy na dokumencie xml.

Na potrzeby tych przykładów zdefinujemy funkcje javscriptową do szybkiego odnajdywania elementu na stronie po XPathie.
var getElementByXpath = function (path) {
return document.evaluate(path, document, null, 9, null).singleNodeValue;
};

Wejdzmy teraz na stronę UJ, w konsoli wklejmy defnicję funkcji getElementByXpath, zbadajmy dowolny element, skopiujmy jego XPath, a następnie wyłuskajmy go za pomocą getElementByXpath(). Zwrócicie uwagę, że argumentem funkcji jest string, więc XPath umieszczamy w cudzysłowie.

XPath są bardzo potężnym narzędziem, podamy tylko kilka przykładów.

/div/p/a
//div/p/a
//div/p//a

Pojedynczy / oznacza, że zaczynamy od korzenia dokumentu xml, to znaczy, że szukany div musi być pierwszy w hierarchii xml. Dwa // oznaczają dowolny ciąg węzłów xmla. Zatem pierwszy XPath wyszuka tylko znacznik a taki, że jego przodkiem jest p, którego przodkiem jest div, który nie ma już przodka. Drugi przykład działa podobnie, tyle że div może mieć dowolnych przodków. Trzeci przykład wyszuka taki znacznik a, który posiada niekoniecznie bezpośredniego przodka p, który posiada niekoniecznie bezpośredniego przodka div.

/div/p/a : /div/p/a
//div/p/a : /html/body/div/p/a
//div/p//a : /html/body/div/p/b/u/a

Zauważcie, że pierwszy XPath zawiera się w drugim, a drugi w trzecim. XPath możemy używać do wyszukiwania zbiorów pasujących elementów.

//div{@id = 'poleTekstowe']
//div[contains(@class , 'czerwony')]

XPathy te odnoszą się do divów postaci: <div id='poleTekstowe' class='czerwony zielony' >.
Więcej przykładów będzie w trzeciej części prezentacji.

Szukanie WebElementów (findElement(), findElements(), By.*)

Oprócz WebDrivera kolejną ważną klasą jest WebElement. Obiekt WebElementu jest tożsamy z pewnym elementem strony - jakimś divem, polem tekstowym lub innym dowolnym obiektem. To właśnie na WebElemencie wykonujemy klinięcia lub wprowadzamy tekst. W HelloWordzie mieliśmy następujący fragment kodu:

WebElement element = driver.findElement(By.name("q"));
element.sendKeys("Cheese!");
element.submit();

Widzimy że WebDriver posiada metodę findElement(), która zwraca element pasujący do wzorca podanego w argumencie. Wzorzec ten podajemy za pomocą statycznej metody klasy By. Klasa By posiada następujące statyczne metody:
By id(String)
By linkText(String)
By name(String)
By partialLinkText(String)
By tagName(String)
By xpath(String)
Same nazwy metod sugerują, jakiego dokładnie Stringa spodziewa się dana metoda. Łatwo zauważyć, że wszystkie wypisane metody da się zastąpić ostatnią. W praktyce najczęściej używa się By.id(String), By.name(String) i by.xpath(String), przy czym xpathów używa się w ostateczności, poniważ są one bardzo nieczytelne.

Po odnalezieniu WebElementu możemy na przykład zasymulować wpisanie w nim tekstu i wysłanie formularza.

Często potrzebujemy wykonać jakąś akcję na wielu elementach. Na przykład zaznaczyć wszystkie checkboxy. Używamy do tego funkcji WebDriver.findWebElements(By), która zwraca listę WebElementów.

MAŁA PIASKOWNICA

Odpalcie przykład Piaskownica.

Dla bardziej wnikliwych, WebElement i WebDriver implementują interfejs SearchContext, który zawiera findElement(By) i findElements(By). Różnica jest taka, że wywołując metodę wyszukiwania dla WebElementu, przeszukujemy tylko wnętrze tego elementu. W dalszej części pokażemy przykład, który to ilustruje.

Podstawowe metody WebElementu

WebElement posiada następujące metody:
void clear(); // If this element is a text entry element, this will clear the value.
void click(); // Click this element.
WebElement findElement(By by); // Find the first WebElement using the given method.
java.util.List<WebElement> findElements(By by); // Find all elements within the current context using the given mechanism.
java.lang.String getAttribute(java.lang.String name); // Get the value of a the given attribute of the element.
java.lang.String getCssValue(java.lang.String propertyName); // Get the value of a given CSS property.
Point getLocation(); // Where on the page is the top left-hand corner of the rendered element?
Dimension getSize(); // What is the width and height of the rendered element?
java.lang.String getTagName(); // Get the tag name of this element.
java.lang.String getText(); // Get the visible
boolean isDisplayed(); // Is this element displayed or not? This method avoids the problem of having to parse an element's "style" attribute.
boolean isEnabled(); // Is the element currently enabled or not? This will generally return true for everything but disabled input elements.
boolean isSelected(); // Determine whether or not this element is selected or not.
void sendKeys(java.lang.CharSequence... keysToSend); // Use this method to simulate typing into an element, which may set its value.
void submit(); // If this current element is a form, or an element within a form, then this will be submitted to the remote server.

Należy uważać na metody getSize() i getLocation(), ponieważ w zależności od implementacji drivera mogą one zwracać niepoprawne wartości. Gorzej zaimplementowane drivery zwracają na przykład zakładany wymiar elementu (css wartość width) i nie uwzględniają, że jakiś wewnętrzy element mógł rozciągnąć badanego rodzica.



Nazwy przykładów są pogrubione, ich kody znajdziecie w załączonym projekcie wraz z obszerniejszym komentarzem.

Kod pocztowy

Przykład: PostCode. Przykład ten obrazuje nam dlaczego "wstukujemy" tekst to pola tekstowego, a nie wpisujemy go na przykład w js.

Aby wstrzyknąć kod js, użyjemy następującego kodu. Ważne aby sprawdzić, czy dany driver obsługuje js (istnieją przeglądarki, które go nie obsługują :) ).
jsCode = "Skrypt js";
JavascriptExecutor js = null;
if (driver instanceof JavascriptExecutor) {
js = (JavascriptExecutor) driver;
}
js.executeScript(jsCode);

findElement() - ciekawszy przykład

Przykład: StrangeElement. Przykład pokazuje nam, jak w sprytny sposób używać wyszukiwania elementu na uprzednio wyszukanym elemencie.

Refleksja użyta do pól tekstowych

Przykład: Reflection. Trik ten raczej był stosowany w Selenium I, nowa wersja posiada adnotacje, które działają w bardzo podobny sposób. Warto go jednak zobaczyć, czasami adnotacje nie spełniają naszych oczekiwań i musimy stowrzyć własne rozwiązanie. Program ma za zadanie wpisać dane nowego użytkownika. Dane te zostaną pobrane z pól klasy NewUser. Oczywiście wypełnianie obiektu klasy NewUser w rzeczywistych systemach raczej będzie bardziej zaawansowane, można spotkać się z fabrykami losowych userów, tworzeniem NewUserów z XMLi i wieloma innymi praktykami.

Ciężki xPath

Czasami zdarza się sytuacje, że elementy na stronie nie są jednoznacznie oznaczone przez id lub name i musimy zlokalizować je za pomocą xPathów. Oto kilka bardziej zaawansowanych przykładów - link

"wait for spinner"

Przykład: Spinner. Bardzo często spotykana sytuacja, mamy stronę z AJAXowymi zapytaniami i musimy sami zadbać o mechanizm oczekiwania na załadowanie danych. Klikamy na stronie przycisk "załaduj", który powoduje wczytanie danych bez przeładowania strony. Użytkownik widzi w tym czasie spinner. Program czeka na zniknięcie Spinnera i następnie pobiera nowo wczytane dane.

Wiele kart

Przykład: MultiTab. Szczególnie przydatne, gdy testujemy mechanizm sesji. Program loguje się na konto pocztowe, otwiera w drugiej karcie skrzynkę, po czym się wylogowuje. Następnie odświeża pierwszą kartę i sprawdza, czy jesteśmy wylogowani.

Page Object Pattern

POP jest wzorcem, w którym każda strona to osobna klasa. Istnieje wiele wersji tego wzorca. Z punktu testera posiadanie wspólnego modelu z programistami jest ideologicznie nie do zakceptowania. Testerzy powinni sami stworzyć swój model strony, nie zależnie od developerów. Dzięki temu unikniemy świadomego lub nieświadomego ukrywania błędów przez programistów. Cechą wspólną większości wersji POP jest to, że klasa-strona przyjmuje w konstruktorze drivera. Selenium posiada pewne wsparcie dla tego wzorca w postaci adnotacji. Dla osób zainteresowanych zalecamy zapoznanie się z dokumetnacją Selenium, gdzie jest to bardzo ładnie wytłumaczone wraz z przykładami.