Lokalizowanie elementów w Selenium – XPath

XPath nie jest używany przeze mnie tak często, ale to moja osobista preferencja. Jak duże ma możliwości zaraz będziecie mieli okazję się przekonać. Ja go używam przede wszystkim w jednym scenariuszu: kiedy szukam czegoś po tekście. Ale z zasady staram się trzymać jednego rozwiązania, w tym wypadku selektorów CSS.

Czasami żartuję, że świat dzieli się na dwa rodzaje ludzi: tych co używają CSSa i tych co używają XPatha. Jeżeli jeszcze nie miałeś/miałaś okazji zapoznać się możliwościami selektorów CSS to gorąco Cię zachęcam do zajrzenia do poprzedniego wpisu, w którym starałam się w obszerny i zrozumiały sposób wyjaśnić na przykładach ich działanie. Pozwoli Ci to również porównać oba rodzaje selektorów i wybrać ten, który Tobie „leży” lepiej.

Czym jest XPath?

XPath to inaczej XML Path Language, czyli język do opisu ścieżek XML. Mi też to średnio coś mówi, ale podpowiem Ci, że XPath momentami wygląda tak, jakbyśmy podawali ścieżkę do danego elementu. W gruncie rzeczy to właśnie robimy. Możemy podać ścieżkę relatywną oraz absolutną a także użyć atrybutów elementu do jednoznacznego określenia, o który nam chodzi. Pokażę to na przykładach i trochę się rozjaśni – wykorzystam w tym celu znaną nam już stronę automationpractice.com.

Węzły

XPath traktuje dokument HTML jak drzewo. Oznacza to, że mamy element root (document) a pod nim mamy węzeł <html>. On z kolei może zawierać kolejne węzły pod postacią elementów typu <div>, <input> czy <button> ale też wiele innych. Te węzły mogą zawierać inne i tak dalej.

Do zaznaczania węzłów możemy użyć poniższych konstrukcji:

  • / – rozpoczyna wyszukiwanie od element root (czyli w naszym przypadku dokument)
  • // – zaznacza wszystkie węzły w dokumencie
  • nazwa_węzła – wskazuje wszystkie węzły danego typu
  • . – służy do zaznaczenia aktualnego węzła
  • .. – wskazuje rodzica aktualnego węzła
  • @ – służy do określania atrybutów

Użyjemy teraz każdej z konstrukcji na stronie automationpractice.com, by zobaczyć w praktyce ich działanie. Tym razem użyję konsoli deweloperskiej Chrome w trochę inny sposób. Ponieważ wyszukiwarka w zakładce „Elements”, której do tej pory używałam, wyszukuje elementy także po tekście, a nie tylko po selektorach CSS i XPath, użyję zakładki „Console” i tam bezpośrednio wymuszę wyszukiwanie po XPath za pomocą metody:

$x("jakiś-xpath")

Przy okazji dodam, że z konsoli dostępna jest również odpowiednia metoda dla selektorów CSS:

$("selektor-css")

Ścieżki absolutne

Co się stanie gdy użyjemy po prostu „/”? XPath wskaże na cały dokument:

Przy okazji, ten sam wynik otrzymamy używając znaku kropki „.”. Dlaczego? Bo na dzień dobry, aktualnym węzłem dla nas jest „document”.

Bezpośrednio pod nim znajduje się tylko jeden węzeł: html. Możemy więc użyć poniższego XPatha, żeby zaznaczyć węzeł HTML.

//XPath: zaznaczanie węzła html
_driver.FindElement(By.XPath("/html"));

Należy pamiętać, że każda ścieżka do elementu rozpoczynająca się od „/” jest ścieżką absolutną, tzn. że nawigujemy po węzłach zaczynając od roota, czyli dokumentu. Czy domyślasz się, co otrzymamy, jeżeli użyjemy poniższego XPatha?

_driver.FindElement(By.XPath("/html/.."));

Najpierw zaznaczamy roota, czyli „document”, następnie wskazujemy na jego węzeł (html) i jako że to jest teraz nasz element root, to jeżeli chcemy kontynuować nawigację z tego miejsca użyjemy „/”. Dwie kropki „..” oznaczają rodzica aktualnego elementu, a więc ten XPath wskaże na „document”.

Ścieżki relatywne

Teraz jakiś przykład ścieżki relatywnej. Poniższa metoda wskaże nam wszystkie elementy <button> niezależnie od tego gdzie w strukturze dokumentu się znajdują:

_driver.FindElement(By.XPath("//button"));


Jak widzisz jest siedem takich elementów ale pamiętaj, że metoda FindElement(By by) wskaże tylko pierwszy z nich. Jeżeli chcesz metody, która zwróci listę wszystkich siedmiu elementów, użyj FindElements(By by).

XPatha możemy też użyć w celu znalezienia np. takich elementów <button>, które są dziećmi elementów <div>. Jest tylko jeden taki button.

_driver.FindElement(By.XPath("//div/button"));

Analogicznie, możemy znaleźć takie elementy <button>, które są nie dziećmi, a potomkami elementu div.

_driver.FindElement(By.XPath("//div//button"));

Czyli znowu wszystkie siedem elementów tego typu znajdujące się na stronie.

XPath umożliwia również zaznaczanie atrybutów. Poniższa metoda zwróci tam wystąpienia atrybutu „name” w elementach.


W mojej pracy używam przede wszystkim ścieżek relatywnych. W testach Selenium staramy się zasymulować akcje użytkownika, którego zupełnie nie interesuje gdzie w strukturze dokumentu znajduje się przycisk do rejestracji albo logowania. Ważne, że tam jest i spełnia funkcję jaką ma spełniać. Mi, jako testerce, nie jest wszystko jedno jak wygląda wygenerowany dokument HTML ale nie jest to moim zdaniem obszar testów Selenium. Dlatego też w kolejnych przykładach będę używać już tylko ścieżki relatywnej, która po pierwsze często bywa krótsza i czytelniejsza, a po drugie nie jest tak wrażliwa na trwający development. Wyobraź sobie np. sytuację w której używasz ścieżek absolutnych i nagle połowa Twoich testów zaczyna się sypać, bo przyciski których szukasz, dostały jeszcze jednego diva nad sobą. Smuteczek.

Szukanie po atrybutach i ich wartościach

Podobnie jak w selektorach CSS, także w XPath możemy wskazywać elementy po występowaniu w nich jakiegoś atrybutu, po wartości tego atrybutu albo po wartości węzła (to coś, co w selektorach CSS było określane jako text, a więc tekst pomiędzy znacznikami).

Przykłady!

//XPath: zaznacz wszystkie buttony, które posiadają atrybut type 
_driver.FindElement(By.XPath("//button[@type]")); 
//XPath: zaznacz wszystkie buttony, których wartość atrybutu type='submit' 
_driver.FindElement(By.XPath("//button[@type='submit']")); 
//XPath: zaznacz wszystkie buttony, które są dziećmi elementu div i których wartość atrybutu type='submit' 
_driver.FindElement(By.XPath("//div/button[@type='submit']")); 
//XPath: zaznacz wszystkie divy, dla których wartość elementu span wynosi '-5%' 
_driver.FindElement(By.XPath("//div[span='-5%']"));

Możecie wpisać sobie użyte przeze mnie XPathy i zobaczyć, że nie kłamałam ;).

Wrócę na chwilę do ostatniego przykładu – zobacz jak to wygląda w dokumencie strony.

 

Gdybyśmy użyli //span=’-5%’ zamiast //div[span=’-5%’] w odpowiedzi dostalibyśmy po prostu „true”. Dlatego obiektu <span> nie wyszukamy w ten sposób po jego tekście, ale jest na to metoda.

//XPath: zaznacz wszystkie elementy typu span, których wartość wynosi '-5%' 
_driver.FindElement(By.XPath("//span[text()='-5%']"));

A co jeżeli nie chcemy szukać po dokładnej wartości atrybutu ale po jego części? Wszystko się da.

//XPath: zaznacz wszystkie elementy typu div, dla których wartość atrybutu class zaczyna się od 'fb
_driver.FindElement(By.XPath("//div[starts-with(@class, 'fb')]"));
//XPath: zaznacz wszystkie elementy typu div, dla których wartość atrybutu class zawiera 'widget'
_driver.FindElement(By.XPath("//div[contains(@class, 'widget')]"));

A co z ends-with? Przeglądarki używają XPatha w wersji, która tego nie obsługuje. Trudno, trzeba żyć dalej.

Szukanie elementów nieznanych typów

Zauważyliście pewnie, że w każdej ścieżce XPath podawałam typ elementu, czyli np. div. Można to obejść i nie podawać typu elementu stosując symbol „*”. Elementy z powyższych przykładów możemy zatem zlokalizować także za pomocą poniższych metod:

//XPath: zaznacz wszystkie elementy, dla których wartość atrybutu class zaczyna się od 'fb
_driver.FindElement(By.XPath("//*[starts-with(@class, 'fb')]"));
//XPath: zaznacz wszystkie elementy, dla których wartość atrybutu class zawiera 'widget'
_driver.FindElement(By.XPath("//*[contains(@class, 'widget')]"));

Pamiętaj jednak, że jeżeli są w dokumencie elementy innego typu niż div spełniające pozostałe warunki, to one także zostaną zaznaczone.

Znak „*” pełni podobną rolę w przypadku atrybutów.

//XPath: zaznacz wszystkie elementy typu button, które mają jakikolwiek parametr
_driver.FindElement(By.XPath("//button[@*]"));

Użycie kilku ścieżek na raz

Jeżeli szukasz dwóch różnych grup elementów i chcesz to zrobić za jednym zamachem, wystarczy, że pomiędzy ścieżkami wstawisz „|”.

//XPath: zaznacz wszystkie elementy, dla których wartość atrybutu class zawiera 'widget' oraz wszystkie elementy typu button, które mają jakikolwiek parametr
_driver.FindElement(By.XPath("//*[contains(@class, 'widget')]|//button[@*]"));

Szukanie po numerze elementu oraz nawigacja w górę

W testowaniu jak w życiu: niekiedy nie wiemy czego chcemy. W testach nie zawsze umiemy odpowiedzieć jakiego dokładnie elementu szukamy zwłaszcza, gdy jego atrybuty generują się dynamicznie. Ale możemy mieć np. informację, że taki element będzie drugim dzieckiem swojego rodzica. Posłużymy się przykładem użytym już przeze mnie we wpisie o selektorach CSS.

<div class='formularz-rejestracyjny'>
	<div class='formularz-zawodnik'
		<button class='logowanie'>Mam już konto</button>
		<input id='imie'>Podaj imię</input>
		<input id='nazwisko'>Podaj nazwisko</input>
		<button class='rejestracja'>Zarejestruj</button>
	</div>
	<div class='formularz-sedzia'
		<button class='logowanie'>Mam już konto</button>
		<input id='imie'>Podaj imię</input>
		<input id='nazwisko'>Podaj nazwisko</input>
		<button class='rejestracja'>Zarejestruj</button>		
	</div>
</div>

Numer miejsca wskazujemy poprzez liczbę w nawiasach kwadratowych liczone od 1. Zatem, żeby w powyższym formularzu znaleźć drugie dziecko typu <input> elementu typu <div> możemy użyć poniższej metody.

_driver.FindElement(By.XPath("//div/input[2]"));

XPath wskaże na oba inputy o id=nazwisko.

Teraz na żywym przykładzie: na stronie automationpractice.com znajdziemy na dwa sposoby rodzica poniższej ikonki telefonu oraz kilku jego przodków.

Już z samego fragmentu dokumentu html możemy wywnioskować, że rodzicem tego elementu jest element <li>. Zatem obie poniższe metody powinny wskazać ten właśnie element.

_driver.FindElement(By.XPath("//em[@class='icon-phone']/.."));
_driver.FindElement(By.XPath("//em[@class='icon-phone']/parent::li"));

Zwróć uwagę, że w drugim przypadku trzeba było wcześniej znać typ rodzica.

Można także wyszukać dowolnego najbliższego przodka elementu danego typu, w tym celu używamy konstrukcji ancestor::typ. A więc analogicznie, jeżeli szukamy najbliższego przodka typu <li>,  czyli rodzica oraz najbliższego przodka typu <ul>, czyli dziadka, zrobimy to w poniższy sposób.

_driver.FindElement(By.XPath("//em[@class='icon-phone']/ancestor::li"));
_driver.FindElement(By.XPath("//em[@class='icon-phone']/ancestor::ul"));

Ściąga

XPathem można zrobić więcej niż tutaj pokazałam, ale do zastosowań testerskich w zupełności wystarczy. Widzisz już obszary, w których XPath sobie radzi a CSS nie ogarniał? Jeżeli nie, to nic nie szkodzi – o tym będzie kolejny wpis.

Podrzucam jeszcze ściągę z selektorów, która może Ci się przydać na początku. Nie ma szans tego wszystkiego na początku pamiętać ani nie ma sensu. Ważne, żeby wiedzieć, gdzie i czego szukać.

Materiały z zadaniami

Jeżeli potrzebujesz poćwiczyć selektory XPath albo zobaczyć jak to działa na filmie, więcej znajdziesz w lekcjach dotyczących XPatha w kursie Selenium od zera w Javie.

Dodaj komentarz:

 

  1. Bardzo fajnie opisane! Super tutorial, bardzo dokładny.
    Choć najszybszym pod względem utrzymania, wydajności i co tylko, jest po prostu selektor po ID, nie xpath, nie css. Ale pewnie, nie zawsze się da 😀

    Odpowiedz
  2. Hej. Super opis! 🙂

    A jesteś w stanie mi pomóc, jak wydobyć listę znaczników a, ale tylko tych które są w divie o klasie "nazwa_klasy" ?

    Z góry dzięki!

    Odpowiedz