XPath contains() w Selenium. Jak tworzyć stabilne lokatory?

Sukienka letnia z nadrukiem, cena $28.98, stara cena $30.51, zniżka -5%. W kodzie widoczny jest selektor xpath dla elementu span.price-percent-reduction.

Autor

Dawid Kowalczyk

Opublikowano

20 paź 2026

Spis treści

Gdy interfejs aplikacji zmienia klasy CSS albo generuje identyfikatory przy każdym uruchomieniu, test automatyczny łatwo zaczyna trafiać w niewłaściwy element. Funkcja contains() w XPath pomaga budować bardziej elastyczne lokatory, które wyszukują fragment tekstu, wartości atrybutu lub klasy HTML. Pokażę, jak działa, kiedy rzeczywiście zwiększa stabilność testów i w jakich sytuacjach lepiej użyć innego rozwiązania.

Najważniejsze zasady pracy z contains()

  • Składnia funkcji to contains(tekst, szukany_fragment), a wynikiem jest wartość logiczna.
  • Wielkość liter ma znaczenie, dlatego tekst „Login” i „login” nie są traktowane identycznie.
  • Do klas CSS używaj wzorca z spacjami ochronnymi, aby nie dopasować przypadkiem podobnej nazwy.
  • Krótki XPath oparty na stabilnym atrybucie zwykle działa lepiej niż długa ścieżka zależna od struktury DOM.
  • contains() nie zastępuje asercji i nie gwarantuje, że wybrany zostanie tylko jeden element.

Co naprawdę robi contains() w XPath

Funkcja contains() sprawdza, czy pierwszy argument zawiera drugi jako fragment tekstu. Zwraca true albo false, więc najczęściej używa się jej wewnątrz predykatu, czyli warunku umieszczonego w nawiasach kwadratowych.

contains(tekst, szukany_fragment)

Przykładowy selektor wybierający przycisk, którego tekst zawiera słowo „Zaloguj”, wygląda tak:

//button[contains(., 'Zaloguj')]

Kropka oznacza tutaj tekst aktualnego węzła. Dzięki temu lokator może znaleźć zarówno przycisk z napisem „Zaloguj”, jak i „Zaloguj się teraz”. To wygodne, ale ma też konsekwencję: częściowe dopasowanie może wskazać więcej elementów, niż zakładaliśmy.

Funkcja działa również na atrybutach. Jeśli aplikacja generuje identyfikatory w rodzaju user-48392, można odwołać się do stałego fragmentu:

//*[@id and contains(@id, 'user-')]

W praktyce często nie trzeba dodawać warunku @id, bo samo użycie atrybutu wystarczy:

//*[contains(@id, 'user-')]

Trzeba pamiętać, że porównanie jest czułe na wielkość liter. XPath 1.0 nie oferuje prostego przełącznika ignorowania wielkości liter, dlatego przy danych o nieprzewidywalnym zapisie stosuje się między innymi funkcję translate(). W testach UI lepiej jednak najpierw znaleźć stabilny atrybut lub tekst, zamiast komplikować lokator.

Najpraktyczniejsze selektory dla automatyzacji testów

Największą wartość funkcja daje tam, gdzie część wartości jest zmienna, a część pozostaje stała. Dotyczy to dynamicznych identyfikatorów, nazw klas, komunikatów oraz tekstów przycisków.

Fragment tekstu elementu

//a[contains(., 'Szczegóły')]

Ten zapis pasuje do linków zawierających wskazane słowo. Używam go chętnie, gdy tekst może zostać rozszerzony na przykład z „Szczegóły” do „Zobacz szczegóły zamówienia”. Jeśli jednak na stronie występuje kilka podobnych linków, dodaję kontekst rodzica:

//tr[.//td[contains(., 'Zamówienie 123')]]//a[contains(., 'Szczegóły')]

Selektor najpierw znajduje wiersz tabeli z właściwym zamówieniem, a dopiero potem link w tym wierszu. To ważna różnica. Samo szukanie tekstu na całej stronie może przejść przypadkiem, dopóki interfejs nie zyska drugiego podobnego elementu.

Fragment wartości atrybutu

//input[contains(@name, 'email')]

Takie wyrażenie pasuje do pól o nazwach email, billing-email czy user-email-confirmation. Jeżeli aplikacja ma kilka takich pól, warto ograniczyć wybór do formularza albo konkretnego kontenera.

//form[@id='register-form']//input[contains(@name, 'email')]

Przeczytaj również: Selenium 4 - Czy Twoje testy są gotowe na rewolucję?

Klasa CSS bez przypadkowych dopasowań

Prosty zapis poniżej wygląda niewinnie, ale może zwrócić element z klasą btn-primary-large, choć szukaliśmy klasy btn-primary:

//*[contains(@class, 'btn-primary')]

Bezpieczniejszy wzorzec otacza wartość spacjami. Funkcja concat() tworzy tekst, w którym każda nazwa klasy jest traktowana jako osobny token:

//*[contains(concat(' ', normalize-space(@class), ' '), ' btn-primary ')]

To jeden z tych zapisów, które na początku wydają się przesadnie długie, ale w większym projekcie szybko się zwracają. Unikają fałszywych dopasowań, zwłaszcza gdy nazwy klas mają wspólne prefiksy.

Fragment kodu HTML z zaznaczonym elementem `<input>`, który można zlokalizować za pomocą `xpath contains` w Selenium.

Jak używać contains() w Selenium

W Selenium wyrażenie XPath przekazuje się jako drugi argument do metody lokalizującej element. Przykład w Pythonie może wyglądać tak:

from selenium.webdriver.common.by import By

login_button = driver.find_element(
    By.XPATH,
    "//button[contains(., 'Zaloguj')]"
)
login_button.click()

W tym przypadku test nie zależy od pełnego tekstu przycisku. To przydatne, gdy aplikacja zmienia dopisek, ale podstawowa etykieta pozostaje taka sama. Sam stosuję tę technikę głównie dla tekstów kontrolowanych przez produkt, a nie dla komunikatów, które mogą być tłumaczone lub często redagowane.

Można też pobrać wszystkie pasujące elementy:

buttons = driver.find_elements(
    By.XPATH,
    "//button[contains(@class, 'primary')]"
)

Metoda find_elements() zwróci pustą listę, jeśli nic nie pasuje, albo wiele elementów, gdy warunek jest zbyt szeroki. Dlatego po znalezieniu elementu dobrze sprawdzić, czy liczba wyników odpowiada założeniu:

buttons = driver.find_elements(
    By.XPATH,
    "//button[contains(., 'Zapisz')]"
)

assert len(buttons) == 1

Sam lokator nie powinien pełnić roli pełnej walidacji interfejsu. Jego zadaniem jest wskazanie elementu, natomiast asercja potwierdza, że strona zachowuje oczekiwany stan.

Typowe błędy i ograniczenia częściowego dopasowania

Najczęstszy problem nie wynika z błędnej składni, tylko z tego, że warunek jest zbyt ogólny. XPath działa poprawnie, lecz wybiera więcej elementów albo element niewłaściwego rodzaju.

Problem Ryzyko Lepsze podejście
contains(@class, 'card') Dopasowanie klas discount-card lub card-large Użycie wzorca z normalize-space() i spacjami
contains(., 'Usuń') Wybór kilku przycisków usuwania Dodanie kontekstu wiersza, formularza lub identyfikatora
contains(@id, 'item') Zależność od szczegółu, który może zostać zmieniony Wybór stabilnego atrybutu testowego, jeśli jest dostępny
Tekst o innej wielkości liter Brak dopasowania mimo podobnego wyglądu Ujednolicenie danych albo świadome użycie translate()

Drugie nieporozumienie dotyczy białych znaków. Tekst elementu może zawierać przejście do nowej linii lub dodatkowe spacje, dlatego dokładne porównanie text()='Zaloguj' bywa kruche. W takich przypadkach przydaje się:

//button[contains(normalize-space(.), 'Zaloguj')]

normalize-space() usuwa zbędne spacje na początku i końcu oraz zamienia ich ciągi na pojedynczą spację. Nie rozwiązuje każdego problemu z formatowaniem, ale często eliminuje awarie wynikające wyłącznie z układu tekstu w HTML.

Kiedy wybrać contains(), a kiedy inną metodę

Nie traktuję tej funkcji jako domyślnego rozwiązania dla każdego lokatora. Jeśli element ma stabilny identyfikator albo specjalny atrybut testowy, dokładne dopasowanie jest zwykle prostsze i bardziej jednoznaczne.

Potrzeba Przykład Kiedy ma sens
Dokładna wartość //button[@data-testid='save-order'] Gdy atrybut jest stabilny i kontrolowany przez zespół
Fragment wartości //button[contains(@data-testid, 'save-')] Gdy część identyfikatora jest stała, a reszta dynamiczna
Początek wartości //input[starts-with(@id, 'user-')] Gdy ważny jest prefiks, a nie dowolne wystąpienie tekstu
Selektor CSS button[data-testid='save-order'] Gdy prosty selektor CSS wystarcza i nie trzeba przechodzić po tekście

W testach przeglądarkowych XPath daje dużą elastyczność, lecz bywa trudniejszy w debugowaniu niż CSS. Dlatego wybieram go wtedy, gdy potrzebuję relacji między elementami, wyszukiwania po tekście albo warunku, którego CSS nie wyraża równie czytelnie.

Dobrym kompromisem jest dodanie do aplikacji stabilnego atrybutu, takiego jak data-testid. Wtedy lokator nie musi analizować klas generowanych przez framework ani zależeć od tekstu widocznego dla użytkownika.

Jak budować stabilne wyrażenia XPath

Najpierw szukam elementu, który ma najbardziej trwałą cechę. Może to być atrybut testowy, nazwa pola albo kontekst konkretnego formularza. Dopiero gdy wartość jest częściowo zmienna, dokładam contains().

  1. Sprawdź, czy istnieje stabilny id, name albo data-testid.
  2. Jeśli wartość jest dynamiczna, wybierz stały fragment i ogranicz zakres wyszukiwania.
  3. Przy klasach używaj dopasowania całego tokenu, a nie przypadkowego fragmentu.
  4. Zweryfikuj, ile elementów zwraca lokator.
  5. Przetestuj go także po zmianie tekstu, kolejności elementów i rozmiaru danych.

Unikam długich ścieżek w rodzaju /html/body/div[2]/div[1]/section/div[3]/button. Taki XPath może działać dziś, ale jest mocno związany z aktualnym DOM-em. Krótszy zapis oparty na stabilnym atrybucie i rozsądnym warunku zwykle lepiej znosi rozwój interfejsu.

Warto też sprawdzić, czy lokator nie pasuje do elementu ukrytego. contains() filtruje węzły na podstawie tekstu lub atrybutów, ale sama funkcja nie wie, czy element jest widoczny, aktywny albo możliwy do kliknięcia. To powinien zweryfikować test lub mechanizm oczekiwania Selenium.

Od elastycznego dopasowania do pewnego testu

contains() jest bardzo dobrym narzędziem do pracy z dynamicznym HTML-em, ale nie zastąpi projektowania stabilnych selektorów. Najlepsze rezultaty daje połączenie częściowego dopasowania z kontekstem rodzica, normalizacją tekstu i kontrolą liczby znalezionych elementów.

Moja praktyczna zasada jest prosta: używam tej funkcji, gdy naprawdę zmienna jest tylko część wartości. Jeżeli mogę oprzeć test na jednoznacznym data-testid lub dokładnym identyfikatorze, wybieram prostszy lokator, bo każdy dodatkowy warunek zwiększa koszt późniejszego debugowania.

Tak zbudowane wyrażenia są czytelniejsze, mniej podatne na przypadkowe dopasowania i znacznie lepiej sprawdzają się w długowiecznych testach automatycznych.

FAQ - Najczęstsze pytania

Warto jej użyć, gdy część wartości pozostaje stała, a reszta jest dynamiczna, na przykład w identyfikatorach takich jak user-48392. Sprawdza się też przy fragmentach tekstu przycisków i nazwach atrybutów, ale może zwrócić kilka elementów, dlatego trzeba ograniczyć kontekst lub sprawdzić liczbę wyników.

Proste contains(@class, 'btn-primary') może dopasować także klasę btn-primary-large. Bezpieczniejszy jest wzorzec contains(concat(' ', normalize-space(@class), ' '), ' btn-primary '), który traktuje nazwy klas jako osobne tokeny.

Jeśli dostępny jest jednoznaczny id, name lub data-testid, zwykle lepsze będzie dokładne dopasowanie, na przykład //button[@data-testid='save-order']. Gdy ważny jest tylko początek zmiennej wartości, można użyć starts-with(), a prosty selektor CSS wystarczy, jeśli nie trzeba wyszukiwać po tekście ani relacjach między elementami.

W Selenium użyj find_elements() i zweryfikuj, czy lista ma oczekiwaną liczbę elementów, na przykład assert len(buttons) == 1. Przy problemach z dodatkowymi spacjami pomocne jest normalize-space(), natomiast widoczność i możliwość kliknięcia trzeba sprawdzić osobnym mechanizmem oczekiwania.

Oceń artykuł

Ocena: 0.00 Liczba głosów: 0

Tagi:

xpath selenium lokatory selektory css data-testid

Udostępnij artykuł

Dawid Kowalczyk

Dawid Kowalczyk

Nazywam się Dawid Kowalczyk i od 6 lat zajmuję się automatyzacją testów oraz zapewnieniem jakości oprogramowania. Moje zainteresowanie tymi tematami zrodziło się z potrzeby zrozumienia, jak kluczowe jest dostarczanie produktów wysokiej jakości w dzisiejszym świecie technologii. Lubię dzielić się swoją wiedzą i doświadczeniem, pomagając innym w zrozumieniu złożonych zagadnień związanych z QA i automatyzacją. W moich tekstach staram się przedstawiać praktyczne rozwiązania, porównując różne metody i narzędzia, które mogą ułatwić pracę w branży. Zawsze stawiam na rzetelne źródła informacji oraz aktualne trendy, aby moje artykuły były nie tylko zrozumiałe, ale i przydatne. Wierzę, że jasna i uporządkowana prezentacja wiedzy jest kluczem do skutecznej nauki i rozwoju w obszarze jakości oprogramowania.

Napisz komentarz