PrzedsprzedażAkademia AI w testach i jakości oprogramowania: 1 350 zł zamiast 1 750 zł netto do 30 listopada

Startuje przedsprzedaż Akademii AI w testach i jakości oprogramowania: 5 tygodni z Tomaszem Stelmachem, start 11 stycznia 2027. Wyjątkowa cena 1 350 zł netto zamiast 1 750 zł tylko do 30 listopada.

zostało 38 dniZobacz program i zapisz się
Selektory w Selenium: 8 lokatorów, XPath, CSS i lokatory względne z kodem
Czas czytania: 12 minut

Selektory w Selenium, nazywane też lokatorami, to instrukcje, po których WebDriver znajduje element na stronie: pole, przycisk, wiersz tabeli. W Javie zapisujecie je obiektem By, na przykład By.id("email"), i przekazujecie do findElement. Dobry selektor wskazuje dokładnie jeden element i przeżywa zmianę wyglądu strony, zły psuje test przy każdej przebudowie layoutu.

Poprzednia wersja tego artykułu opisywała osiem strategii słowami, bez jednej linijki kodu. Teraz każda ma przykład na tym samym fragmencie HTML, a wszystkie testy skompilowaliśmy i uruchomiliśmy 11.10.2026 na Selenium 4.51.0, JUnit 6.1.3 i Javie 25. Jeśli pytacie, czym w ogóle jest Selenium, zacznijcie od przewodnika Selenium: co to jest i od czego zacząć. Tutaj zajmujemy się wyłącznie lokalizowaniem elementów.

Czym są selektory w Selenium?

Selektor to adres elementu w drzewie DOM. Test nie widzi strony tak jak człowiek, nie wie, że „ten niebieski przycisk na dole” to przycisk logowania. Wie tylko to, co mu podacie: identyfikator, atrybut, położenie w hierarchii albo tekst linku. Od jakości tego adresu zależy, czy test będzie odporny na zmiany, czy zacznie padać przy pierwszym redesignie.

W naszych projektach automatyzacji kruche selektory są obok źle dobranych waitów najczęstszą przyczyną testów, które „same” się psują. Dlatego oba tematy idą w parze: poprawny lokator i poprawne czekanie na element. O czekaniu piszemy osobno w tekście Explicit Wait vs Implicit Wait.

Wszystkie przykłady poniżej działają na jednej stronie testowej. Zapiszcie ją jako logowanie.html w src/test/resources i otwórzcie w teście przez Path.of(...).toUri():

<nav> <a href="#konto">Moje konto</a> <a href="#reset">Nie pamiętasz hasła? Zresetuj je</a> </nav> <form id="logowanie" class="form form--login"> <label for="email">E-mail</label> <input id="email" name="email" type="email" data-testid="login-email"> <label for="haslo">Hasło</label> <input id="haslo" name="password" type="password" data-testid="login-password"> <button type="submit" class="btn btn-primary" data-testid="login-submit">Zaloguj</button> </form> <table id="zamowienia"> <tr><th>Nr</th><th>Status</th><th>Akcja</th></tr> <tr><td>1041</td><td>Wysłane</td><td><button>Szczegóły</button></td></tr> <tr><td>1042</td><td>W realizacji</td><td><button>Szczegóły</button></td></tr> </table> <koszyk-widget></koszyk-widget> <!-- licznik koszyka w Shadow DOM -->

Jakie lokatory ma Selenium i jak wygląda każdy z nich w kodzie?

Według dokumentacji Selenium biblioteka obsługuje 8 tradycyjnych strategii lokalizowania. Poniższa tabela zestawia je z przykładem dla strony testowej i oceną stabilności z naszego doświadczenia. Zanim wybierzecie lokator w Waszym projekcie, sprawdźcie ostatnią kolumnę.

Lokator Przykład w Javie Co znajdzie Stabilność
id By.id("email") pole e-mail wysoka, gdy id jest stałe i unikalne
name By.name("password") pole hasła wysoka w formularzach
className By.className("btn-primary") przycisk Zaloguj niska: klasy służą do stylów i często się zmieniają
tagName By.tagName("form") formularz niska: zwykle wiele elementów, przydatny w findElements
linkText By.linkText("Moje konto") link do konta średnia: pada po zmianie tekstu i tłumaczeniu
partialLinkText By.partialLinkText("Zresetuj") link do resetu hasła średnia, ryzyko trafienia w inny link
cssSelector By.cssSelector("form#logowanie input[type='email']") pole e-mail wysoka przy atrybutach, niska przy długich ścieżkach
xpath By.xpath("//button[normalize-space()='Zaloguj']") przycisk Zaloguj zależy od zapisu: względny dobry, absolutny kruchy

Źródło: dokumentacja Selenium „Locator strategies”, ocena stabilności: praktyka Quality Island

A tak wygląda każda z ośmiu strategii w działającym teście JUnit. Klasa uruchamia jedną przeglądarkę w @BeforeAll i zamyka ją w @AfterAll, bo testy tylko czytają stronę:

@Test void osiemStrategiiBy() { assertEquals("email", driver.findElement(By.id("email")).getDomAttribute("name")); assertEquals("haslo", driver.findElement(By.name("password")).getDomAttribute("id")); assertEquals("Zaloguj", driver.findElement(By.className("btn-primary")).getText()); assertEquals(1, driver.findElements(By.tagName("form")).size()); assertEquals("#konto", driver.findElement(By.linkText("Moje konto")).getDomAttribute("href")); assertEquals("#reset", driver.findElement(By.partialLinkText("Zresetuj")).getDomAttribute("href")); assertEquals("login-email", driver.findElement( By.cssSelector("form#logowanie input[type='email']")).getDomAttribute("data-testid")); assertEquals("Zaloguj", driver.findElement( By.xpath("//button[normalize-space()='Zaloguj']")).getText()); }

Mało kto o tym wie

Według specyfikacji W3C WebDriver w szkicu z 02.07.2026 standard definiuje tylko 5 strategii: CSS selector, link text, partial link text, tag name i XPath. Skąd więc By.id, By.name i By.className? Biblioteka Selenium dla Javy tłumaczy je w locie na CSS: w wersji 4.51.0 By.id("email") wysyła do przeglądarki selektor #email, a By.className("btn-primary") selektor .btn-primary, co sprawdziliśmy w kodzie bajtowym biblioteki. Czyli kłótnia „id czy CSS” w Waszym zespole to w praktyce kłótnia CSS z CSS.

Proste lokatory krok po kroku, z ćwiczeniami, omawia osobna lekcja o selektorach prostych id, name, className, tagName i linkText. Tutaj idziemy dalej: jak pisać selektory, które nie psują się przy każdej zmianie.

Jak pisać selektory CSS, które przetrwają zmiany w aplikacji?

Według dokumentacji Selenium, gdy unikalne i przewidywalne id jest dostępne, to ono powinno być pierwszym wyborem, a gdy go brakuje, dobrze napisany selektor CSS. Słowo „dobrze” robi tu całą robotę. Porównajcie dwa selektory, które w teście znajdują ten sam element:

@Test void selektoryDobreIZle() { // kruche: zależne od układu strony WebElement kruchy = driver.findElement(By.xpath("/html/body/form/input[2]")); // stabilne: atrybut przeznaczony dla testów WebElement stabilny = driver.findElement(By.cssSelector("[data-testid='login-password']")); assertEquals(kruchy, stabilny); } @Test void cssHas() { WebElement przycisk = driver.findElement( By.cssSelector("form:has(input[type='password']) button[type='submit']")); assertEquals("Zaloguj", przycisk.getText()); }

Pierwszy selektor opisuje drogę od korzenia dokumentu. Wystarczy, że ktoś doda nad formularzem baner z informacją o cookies albo owinie pola w div, i test przestaje działać, choć logowanie dalej działa. Drugi opisuje, czym element jest, a nie gdzie leży. Poniżej typowe pary, które poprawiamy przy audytach frameworków:

Kruchy selektor Stabilna wersja Dlaczego
/html/body/div[2]/form/input[2] [data-testid='login-password'] nie zależy od układu strony
div.col-md-6 > div > button.btn.btn-primary button[type='submit'] klasy Bootstrapa zmieniają się przy redesignie
#mat-input-3 input[name='email'] id generowane przez framework zmienia się między renderami
//span[text()='Zaloguj'] //button[normalize-space()='Zaloguj'] normalize-space usuwa spacje i łamania wierszy
(//button)[5] //tr[td[1]='1042']//button indeks psuje się po dodaniu przycisku, kontekst nie

Źródło: praktyka Quality Island z audytów frameworków automatyzacji

Najmocniejsze narzędzie to atrybut przeznaczony wyłącznie dla testów, na przykład data-testid. Developer wie, że nie wolno go zmieniać bez powodu, a projektant może przebudować cały wygląd bez wpływu na testy. Uzgodnijcie z Waszym zespołem, że każdy element, którego dotyka test, dostaje taki atrybut w tym samym zadaniu, w którym powstaje. Koszt to minuta pracy developera, a Wam oszczędza godziny debugowania.

Nowością ostatnich lat jest pseudoklasa :has() ze specyfikacji W3C Selectors Level 4. Pozwala wybrać element na podstawie tego, co zawiera, czyli zrobić coś, co przez lata było argumentem za XPath. W teście wyżej selektor form:has(input[type='password']) button[type='submit'] znajduje przycisk wysyłki tylko w formularzu, który ma pole hasła.

Kiedy XPath jest lepszy od CSS?

Według W3C XPath, czyli XML Path Language w wersji 1.0, to rekomendacja z 16.11.1999, i właśnie tę wersję obsługują przeglądarki. Jego przewaga nad CSS sprowadza się do dwóch rzeczy: potrafi szukać po tekście elementu i swobodnie poruszać się w górę drzewa. Najlepiej widać to w tabelach, gdzie wiersz identyfikuje wartość w komórce, a nie atrybut.

@Test void xpathPoTekscieWiersza() { WebElement status = driver.findElement( By.xpath("//table[@id='zamowienia']//tr[td[1]='1042']/td[2]")); assertEquals("W realizacji", status.getText()); WebElement przycisk = driver.findElement( By.xpath("//tr[td[1]='1042']//button[text()='Szczegóły']")); assertTrue(przycisk.isDisplayed()); }

Pierwszy selektor czyta status zamówienia 1042, drugi klika „Szczegóły” w tym samym wierszu. W CSS nie da się wskazać wiersza po tekście komórki, więc tu XPath jest po prostu właściwym narzędziem. Kilka funkcji, które warto znać:

  • normalize-space() porównuje tekst bez spacji z początku i końca, odporny na formatowanie HTML
  • contains(@class, 'aktywny') dla elementów z wieloma klasami, gdy CSS nie wystarcza
  • starts-with(@id, 'zamowienie-') dla identyfikatorów z ruchomą końcówką
  • ancestor:: i following-sibling:: do poruszania się po drzewie, na przykład od etykiety do pola
  • // na początku wyrażenia zamiast pełnej ścieżki od /html, zawsze

Selektory sprawdzajcie, zanim trafią do Waszego kodu. W konsoli Chrome DevTools $$("css") zwraca listę elementów dla CSS, a $x("xpath") dla XPath. Jeśli lista ma więcej niż jeden element, a test używa findElement, Selenium weźmie pierwszy i możecie kliknąć nie ten przycisk, który chcieliście.

Czy CSS jest szybszy od XPath?

W starszych poradnikach, także w poprzedniej wersji tego tekstu, pada teza, że CSS jest zwykle szybszy od XPath. Dokumentacja Selenium nadal pisze, że XPath bywa wolny, bo producenci przeglądarek go nie optymalizują. Sprawdziliśmy, ile to znaczy w praktyce: dane z naszego pomiaru pochodzą z 4 przebiegów po 500 wywołań findElement dla każdego typu selektora na tym samym przycisku.

@Test void pomiarCssKontraXpath() { By css = By.cssSelector("[data-testid='login-submit']"); By xpath = By.xpath("//button[@data-testid='login-submit']"); for (int i = 0; i < 50; i++) { driver.findElement(css); driver.findElement(xpath); } long t1 = System.nanoTime(); for (int i = 0; i < 500; i++) driver.findElement(css); long t2 = System.nanoTime(); for (int i = 0; i < 500; i++) driver.findElement(xpath); long t3 = System.nanoTime(); System.out.printf("POMIAR 500x css: %d ms, 500x xpath: %d ms%n", (t2 - t1) / 1_000_000, (t3 - t2) / 1_000_000); }

Nasz pomiar z 11.10.2026, Selenium 4.51.0, Chrome 154 headless

2 do 4 ms

trwało jedno wywołanie findElement, niezależnie od typu selektora

4 z 4

przebiegów: XPath nie był wolniejszy od CSS na tej stronie

1 016 ms

500 wyszukań przez CSS w najszybszym przebiegu

937 ms

500 wyszukań przez XPath w tym samym przebiegu

Źródło: dane własne Quality Island, klasa SelektoryTest uruchomiona lokalnie 4 razy

Czas jednego wyszukania zjada komunikacja test, sterownik, przeglądarka, a nie silnik selektorów. Na stronie z kilkudziesięcioma elementami różnica między CSS a XPath ginie w szumie pomiaru. Może mieć znaczenie przy ogromnym DOM i wyrażeniach typu //*[contains(., 'x')], które przeglądają cały dokument, ale wtedy problemem jest konstrukcja wyrażenia, a nie wybór języka. Wybierajcie więc w Waszych testach lokator ze względu na stabilność i czytelność, nie na szybkość.

Jak działają lokatory względne w Selenium 4?

Według bloga projektu Selenium 4 ogłoszono 13.10.2021 i ta wersja wprowadziła lokatory względne, wcześniej nazywane „friendly locators”. Opisują element przez jego położenie względem innego: nad, pod, na lewo, na prawo, w pobliżu. Selenium liczy położenie przez funkcję JavaScript getBoundingClientRect(), więc patrzy na to, co widać na ekranie, a nie na strukturę DOM.

import static org.openqa.selenium.support.locators.RelativeLocator.with; @Test void lokatoryWzgledne() { WebElement haslo = driver.findElement(By.id("haslo")); WebElement nadHaslem = driver.findElement(with(By.tagName("input")).above(haslo)); assertEquals("email", nadHaslem.getDomAttribute("id")); WebElement przyciskPodHaslem = driver.findElement(with(By.tagName("button")).below(haslo)); assertEquals("Zaloguj", przyciskPodHaslem.getText()); WebElement etykieta = driver.findElement(with(By.tagName("label")).near(By.id("email"))); assertEquals("E-mail", etykieta.getText()); }

Według dokumentacji Selenium near znajduje element oddalony najwyżej o 50 pikseli. Ta właściwość ma swoją cenę, co sami odczuliśmy przy pisaniu tego przykładu: przy domyślnym układzie strony pola formularza stały w jednej linii, etykieta była dalej niż 50 px od pola i test padł. Pomogło dopiero ustawienie formularza w kolumnie. Wniosek: lokatory względne zależą od rozdzielczości i układu, więc traktujcie je jako ostatnią deskę ratunku, gdy element nie ma żadnego sensownego atrybutu.

Jak znaleźć element w Shadow DOM?

Komponenty webowe, coraz częstsze w bibliotekach UI, chowają swoje wnętrze w Shadow DOM. Zwykły findElement z poziomu dokumentu go nie widzi. Selenium 4 daje metodę getShadowRoot(), która zwraca kontekst wyszukiwania wewnątrz komponentu:

@Test void shadowDom() { SearchContext cien = driver.findElement(By.cssSelector("koszyk-widget")).getShadowRoot(); assertEquals("3", cien.findElement(By.cssSelector(".licznik")).getText()); }

W korzeniu cienia działają selektory CSS. XPath już nie: w teście na Chrome 154 wywołanie By.xpath na korzeniu cienia skończyło się wyjątkiem InvalidArgumentException z komunikatem „invalid locator”. Jeśli więc Wasza aplikacja intensywnie używa komponentów webowych, macie kolejny argument, by standardem zespołu był CSS.

Jaką kolejność wyboru lokatora przyjąć w zespole?

Ustalona kolejność oszczędza dyskusji w każdym code review. Z naszego doświadczenia przy budowie frameworków działa taka lista, od najlepszego do ostatecznego wyboru:

1

data-testid

Atrybut tylko dla testów, uzgodniony z developerami. Najbardziej odporny na redesign.

2

id i name

Gdy są stałe i unikalne. Uważajcie na id generowane przez frameworki, na przykład mat-input-3.

3

CSS po atrybutach

type, name, aria-label, role. Krótki, czytelny, działa też w Shadow DOM.

4

XPath względny

Gdy trzeba szukać po tekście albo kontekście wiersza. Nigdy absolutny od /html.

5

Lokatory względne

Ostatnia deska ratunku. Zależą od układu strony i rozdzielczości.

Podobną filozofię promuje biblioteka Testing Library, która w wytycznych priorytetu zapytań stawia na pierwszym miejscu rolę elementu i jego dostępną nazwę, a identyfikator testowy na końcu jako wyjście awaryjne. Różnica wynika z celu: Testing Library chce, by test zachowywał się jak użytkownik. W Selenium podobny efekt daje selektor CSS po role i aria-label, a przy okazji wymusza na zespole dostępne etykiety.

Druga zasada, ważniejsza od kolejności: selektory trzymajcie w jednym miejscu, w obiektach stron, a nie w testach. Gdy element się zmieni, poprawiacie jedną stałą zamiast trzydziestu testów. Jak to zorganizować, pokazujemy w artykule o wzorcach projektowych w Selenium WebDriver. O narzędziach, które próbują same naprawiać zepsute selektory, i o tym, kiedy to pułapka, pisze Strefa QA w tekście o samonaprawiających się testach.

Selektory, waity i Page Object ćwiczymy na prawdziwej aplikacji na szkoleniu Automatyzacja testów Selenium WebDriver. Pracę z DevTools, w tym sprawdzanie selektorów w konsoli, rozwija szkolenie Testowanie interfejsów webowych z użyciem DevTools, a osoby, które chcą przejść całą ścieżkę na projekcie, zapraszamy do projektu szkoleniowo stażowego tester automatyzujący. Gdy potrzebujecie zespołu, który zbuduje albo naprawi testy za Was, sprawdźcie usługę automatyzacja testów UI.

Co zabrać z tego artykułu

01Selenium ma 8 strategii lokalizowania, ale specyfikacja W3C tylko 5. Id, name i className biblioteka zamienia na CSS.

02Selektor ma opisywać, czym jest element, a nie gdzie leży. Absolutne ścieżki od /html psują się przy każdej zmianie układu.

03Najstabilniejszy jest atrybut tylko dla testów, na przykład data-testid, uzgodniony z developerami.

04XPath wybierajcie, gdy szukacie po tekście albo kontekście wiersza tabeli. W pozostałych przypadkach CSS jest krótszy.

05W naszym pomiarze CSS i XPath trwały po 2 do 4 ms na wywołanie. Wybierajcie po stabilności, nie po szybkości.

06Lokatory względne i Shadow DOM to narzędzia Selenium 4 na trudne przypadki, nie na co dzień.

Źródło: dokumentacja Selenium i W3C, pomiar Quality Island z 11.10.2026

Wasze testy padają po każdej zmianie w UI? Przejrzymy selektory i strukturę frameworka, a potem pomożemy go ustabilizować.

Automatyzacja testów UI


Powiązane na blogu Quality Island

Pełna lista źródeł

Co o tym sądzisz?

Dodaj komentarz

Bądź na bieżąco
Strefa QA, portal z eksperckimi artykułami o jakości oprogramowania
Bądź na bieżąco
Bezpieczne programowanie z AI
Bezpieczne programowanie z AI: tworzenie oprogramowania z asystentami kodu

1 690,00 zł

26.11.26, 17.12.26, 28.01.27, 04.03.27, 25.03.27
1 dzień
AI Act i RODO w firmie
AI Act i RODO w firmie: bezpieczne korzystanie ze sztucznej inteligencji

1 290,00 zł

24.11.26, 15.12.26, 26.01.27, 02.03.27, 30.03.27
1 dzień
Testy bezpieczeństwa dla początkujących
Testy bezpieczeństwa dla początkujących: OWASP Top 10 i pierwsze testy aplikacji webowych

1 000,00 zł

18.11.26, 09.12.26, 13.01.27, 03.02.27, 24.02.27, 17.03.27
1 dzień
Popularne artykuły
Język Gherkin: co to jest i jak go używać w testowaniu oprogramowania
Smoke test: co to jest, kiedy go uruchamiać i czym różni się od sanity testu
Jak zostać testerem oprogramowania: ścieżka krok po kroku bez dyplomu informatyka
Najnowsze artykuły
Testowanie migracji danych i systemów: co sprawdzić, zanim przełączycie produkcję
Metryki DORA psują się w erze AI: dlaczego szybkość wydawania przestaje być dowodem jakości
OWASP Top 10:2025 okiem testera: co sprawdzić w każdej z dziesięciu kategorii
Popularne kategorie