Selektory CSS działają znacznie lepiej niż Xpath i jest to dobrze udokumentowane w społeczności Selenium. Oto kilka powodów,
- Silniki Xpath są różne w każdej przeglądarce, dlatego są niespójne
- IE nie ma natywnego silnika xpath, dlatego selen wstrzykuje własny silnik xpath w celu zapewnienia kompatybilności z jego API. Dlatego tracimy przewagę korzystania z natywnych funkcji przeglądarki, które WebDriver nieodłącznie promuje.
- Xpath ma tendencję do stawania się złożonym i dlatego moim zdaniem jest trudny do odczytania
Są jednak sytuacje, w których musisz użyć xpath, na przykład wyszukując element nadrzędny lub szukając elementu po jego tekście (nie polecałbym później).
Możesz przeczytać bloga Simona tutaj . Zaleca również CSS zamiast Xpath.
Jeśli testujesz zawartość, nie używaj selektorów zależnych od zawartości elementów. To będzie koszmar konserwacji dla każdej lokalizacji. Spróbuj porozmawiać z programistami i użyj technik, których używali do eksternalizacji tekstu w aplikacji, takich jak słowniki lub pakiety zasobów itp. Oto mój blog, który szczegółowo wyjaśnia to.
edytuj 1
Dzięki @parishodak, oto link, który zawiera liczby potwierdzające, że wydajność CSS jest lepsza
Mam zamiar trzymać się niepopularnej opinii w tagach selenu SO, że XPath jest lepszy niż CSS w dłuższej perspektywie.
Ten długi post ma dwie sekcje - najpierw umieszczę dowód z tyłu serwetki, że różnica w wydajności między nimi wynosi 0,1-0,3 milisekundy (tak; to 100 mikrosekund ) , a potem podzielę się opinią, dlaczego XPath jest potężniejszy.
Różnica w wydajności
Najpierw zajmijmy się „słoniem w pokoju” - ta ścieżka jest wolniejsza niż css.
Przy obecnej mocy procesora (czytaj: cokolwiek x86 wyprodukowano od 2013 roku) , nawet na maszynach wirtualnych browserstack / saucelabs / aws i rozwoju przeglądarek (czytaj: wszystkie popularne w ciągu ostatnich 5 lat) to prawie nie ma miejsca. Silniki przeglądarki rozwinęły się, wsparcie dla xpath jest jednolite, IE nie ma na myśli (miejmy nadzieję, że dla większości z nas) . To porównanie w drugiej odpowiedzi jest przytaczane wszędzie, ale jest bardzo kontekstowe - ilu z nich działa - lub obchodzi - automatyzację przeciwko IE8?
Jeśli jest różnica, jest to ułamek milisekundy .
Jednak większość frameworków wyższego poziomu i tak dodaje co najmniej 1 ms narzutu nad wywołaniem surowego selenu (opakowania, programy obsługi, przechowywanie stanu itp.); moja ulubiona broń - RobotFramework - dodaje co najmniej 2 ms, które z przyjemnością poświęcę dla tego, co zapewnia. Podróż w obie strony z AWS us-east-1 do centrum BrowserStack trwa zwykle 11 milisekund .
Tak więc w przypadku zdalnych przeglądarek, jeśli istnieje różnica między xpath i css, jest ona przyćmiona przez wszystko inne, pod względem wielkości.
Pomiary
Nie ma zbyt wielu porównań publicznych (tak naprawdę widziałem tylko cytowane) , więc - oto przybliżony pojedynczy przypadek, atrapa i prosty.
Lokalizuje element za pomocą dwóch strategii X razy i porównuje średni czas dla tego.
Cel - strona docelowa BrowserStack i jej przycisk „Zarejestruj się”; zrzut ekranu HTML podczas pisania tego postu:
Oto kod testowy (python):
from selenium import webdriver import timeit if __name__ == '__main__': xpath_locator = '//div[@class="button-section col-xs-12 row"]' css_locator = 'div.button-section.col-xs-12.row' repetitions = 1000 driver = webdriver.Chrome() driver.get('https://www.browserstack.com/') css_time = timeit.timeit("driver.find_element_by_css_selector(css_locator)", number=repetitions, globals=globals()) xpath_time = timeit.timeit('driver.find_element_by_xpath(xpath_locator)', number=repetitions, globals=globals()) driver.quit() print("css total time {} repeats: {:.2f}s, per find: {:.2f}ms". format(repetitions, css_time, (css_time/repetitions)*1000)) print("xpath total time for {} repeats: {:.2f}s, per find: {:.2f}ms". format(repetitions, xpath_time, (xpath_time/repetitions)*1000))
Dla niezaznajomionych z Pythonem - otwiera stronę i wyszukuje element - najpierw z lokalizatorem css, potem z xpath; operacja wyszukiwania jest powtarzana 1000 razy. Dane wyjściowe to całkowity czas w sekundach dla 1000 powtórzeń i średni czas jednego znalezienia w milisekundach.
Lokalizatory to:
Celowo wybrany, aby nie być przestrojonym; również selektor klasy jest cytowany w css jako „drugi najszybszy po id”.
Środowisko - Chrome v66.0.3359.139, chromedriver v2.38, cpu: ULV Core M-5Y10 zwykle pracujący z częstotliwością 1,5 GHz (tak, "edytor tekstu", nawet nie zwykła bestia i7) .
Oto wynik:
Oczywiście czasy na znalezienie są dość bliskie; różnica wynosi 0,32 milisekundy . Nie przeskakuj "xpath jest szybszy" - czasami tak jest, czasami jest to css.
Spróbujmy z innym zestawem lokalizatorów, trochę bardziej skomplikowanym - atrybutem posiadającym podciąg (powszechne podejście przynajmniej dla mnie, podążanie za klasą elementu, gdy jego część ma znaczenie funkcjonalne) :
xpath_locator = '//div[contains(@class, "button-section")]' css_locator = 'div[class~=button-section]'
Te dwa lokalizatory są ponownie semantycznie takie same - „znajdź element div mający w swojej klasie atrybut ten podciąg”.
Oto wyniki:
Diff z 0.15ms .
Jako ćwiczenie - ten sam test, co w przypadku linku na blogu w komentarzach / innej odpowiedzi - strona testowa jest publiczna, podobnie jak kod testowy .
Robią kilka rzeczy w kodzie - klikają kolumnę, aby posortować według niej, następnie pobierają wartości i sprawdzają, czy sortowanie w interfejsie użytkownika jest poprawne.
Wytnę to - w końcu weź lokalizatory - to jest test rootowania, prawda?
Ten sam kod co powyżej, z tymi zmianami w:
Adres URL to teraz
http://the-internet.herokuapp.com/tables
; są 2 testy.Lokalizatory pierwszego z nich - „Wyszukiwanie elementów według identyfikatora i klasy” - to:
css_locator = '#table2 tbody .dues' xpath_locator = "//table[@id='table2']//tr/td[contains(@class,'dues')]"
A oto wynik:
Różnica 0,2 milisekundy.
„Znajdowanie elementów przez przechodzenie”:
css_locator = '#table1 tbody tr td:nth-of-type(4)' xpath_locator = "//table[@id='table1']//tr/td[4]"
Wynik:
Tym razem jest to 0,5 ms (odwrotnie, xpath okazał się tutaj „szybszy”).
Tak więc 5 lat później (lepsze silniki przeglądarek) i skupiając się tylko na wydajności lokalizatorów (brak działań typu sortowanie w interfejsie użytkownika itp.), To samo stanowisko testowe - praktycznie nie ma różnicy między CSS a XPath.
A więc z xpath i css, który z dwóch wybrać pod kątem wydajności? Odpowiedź jest prosta - wybierz lokalizację według identyfikatora .
Krótko mówiąc, jeśli identyfikator elementu jest unikalny (zgodnie ze specyfikacją), jego wartość odgrywa ważną rolę w wewnętrznej reprezentacji DOM w przeglądarce i dlatego jest zwykle najszybszy.
Jednak unikalne i stałe (np. Nie generowane automatycznie) identyfikatory nie zawsze są dostępne, co prowadzi nas do pytania „dlaczego XPath, skoro istnieje CSS?”
Zaleta XPath
Skoro wydajność nie jest widoczna, dlaczego myślę, że xpath jest lepszy? Prostota - wszechstronność i moc.
Xpath to język opracowany do pracy z dokumentami XML; jako taka pozwala na znacznie potężniejsze konstrukcje niż css.
Na przykład nawigacja w każdym kierunku w drzewie - znajdź element, następnie przejdź do jego dziadka i poszukaj jego dziecka o określonych właściwościach.
Pozwala na osadzone warunki boolowskie -
cond1 and not(cond2 or not(cond3 and cond4))
; wbudowane selektory - „znajdź element div mający te elementy podrzędne z tymi atrybutami, a następnie przejdź zgodnie z nim”.XPath umożliwia wyszukiwanie w oparciu o wartość węzła (jego tekst) - jakkolwiek jest to niezadowolone z tej praktyki, przydaje się, zwłaszcza w dokumentach o złej strukturze (brak określonych atrybutów do nadepnięcia, takich jak dynamiczne identyfikatory i klasy - zlokalizuj element według tekstu zawartość) .
Wkraczanie w css jest zdecydowanie łatwiejsze - można zacząć pisać selektory w ciągu kilku minut; ale po kilku dniach użytkowania, moc i możliwości xpath szybko pokonuje css.
I czysto subiektywne - złożony css jest znacznie trudniejszy do odczytania niż złożone wyrażenie xpath.
Outro;)
Wreszcie znowu bardzo subiektywne - który wybrać?
IMO, nie ma dobrego lub złego wyboru - są to różne rozwiązania tego samego problemu i należy wybrać to, co jest bardziej odpowiednie do pracy.
Będąc „fanem” XPath, nie wstydzę się używać w swoich projektach mieszanki obu - do diabła, czasami o wiele szybciej jest po prostu wrzucić CSS, jeśli wiem, że zadziała dobrze.
źródło
[]
po//
) . Ale po pierwszym dniu nauki i korzystania z niego prawie każdy przekracza punkt krytyczny krzywej uczenia się :) (krok CSS jest wprawdzie łatwiejszy, IMHO) .Debata pomiędzy cssSelector a XPath pozostałaby jedną z najbardziej subiektywnych debat w społeczności Selenium . To, co już wiemy, można podsumować jako:
Dave Haeffner przeprowadził test na stronie z dwiema tabelami danych HTML , jedna jest zapisana bez pomocnych atrybutów ( ID i Klasa ), a druga z nimi. Szczegółowo przeanalizowałem procedurę testową i wyniki tego eksperymentu w dyskusji. Dlaczego powinienem używać selektorów cssSelector zamiast XPath do testów automatycznych? . Chociaż ten eksperyment wykazał, że każda strategia lokalizatora jest w miarę równoważna w różnych przeglądarkach, nie przedstawił nam odpowiednio całego obrazu. Dave Haeffner w innej dyskusji Css Vs. Ścieżka X pod mikroskopemwspomniano, w teście end-to-end, było wiele innych zmiennych w grę starcie Sauce , Przeglądarka rozruchu i opóźnienia do i z zastosowania badanego. Niefortunnym wnioskiem z tego eksperymentu może być to, że jeden sterownik może być szybszy od drugiego (np. IE vs Firefox ), podczas gdy w rzeczywistości wcale tak nie było. Aby uzyskać prawdziwy smak różnicy w wydajności między cssSelector i XPath, musieliśmy sięgnąć głębiej. Zrobiliśmy to, uruchamiając wszystko z lokalnego komputera, używając narzędzia do testowania wydajności. Skoncentrowaliśmy się również na konkretnym działaniu Selenium, a nie na całym przebiegu testowym, i uruchamialiśmy wiele razy. Szczegółowo przeanalizowałem konkretną procedurę testową i wynik tego eksperymentu w dyskusji cssSelector vs XPath dla selenu . Jednak w testach nadal brakowało jednego aspektu, tj. Większego pokrycia przeglądarki (np. Internet Explorer 9 i 10) oraz testów na większej i głębszej stronie.
Dave Haeffner w kolejnej dyskusji Css Vs. X Path, Under a Microscope (Część 2) wspomina, aby upewnić się, że wymagane testy porównawcze są uwzględnione w najlepszy możliwy sposób, musimy rozważyć przykład, który pokazuje dużą i głęboką stronę .
Konfiguracja testowa
Aby zademonstrować ten szczegółowy przykład, skonfigurowano maszynę wirtualną Windows XP i zainstalowano Ruby (1.9.3) . Zainstalowano również wszystkie dostępne przeglądarki i odpowiadające im sterowniki przeglądarki Selenium. Do testów porównawczych
benchmark
użyto standardowej biblioteki Rubiego .Kod testowy
require_relative 'base' require 'benchmark' class LargeDOM < Base LOCATORS = { nested_sibling_traversal: { css: "div#siblings > div:nth-of-type(1) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3) > div:nth-of-type(3)", xpath: "//div[@id='siblings']/div[1]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]/div[3]" }, nested_sibling_traversal_by_class: { css: "div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1 > div.item-1", xpath: "//div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]/div[contains(@class, 'item-1')]" }, table_header_id_and_class: { css: "table#large-table thead .column-50", xpath: "//table[@id='large-table']//thead//*[@class='column-50']" }, table_header_id_class_and_direct_desc: { css: "table#large-table > thead .column-50", xpath: "//table[@id='large-table']/thead//*[@class='column-50']" }, table_header_traversing: { css: "table#large-table thead tr th:nth-of-type(50)", xpath: "//table[@id='large-table']//thead//tr//th[50]" }, table_header_traversing_and_direct_desc: { css: "table#large-table > thead > tr > th:nth-of-type(50)", xpath: "//table[@id='large-table']/thead/tr/th[50]" }, table_cell_id_and_class: { css: "table#large-table tbody .column-50", xpath: "//table[@id='large-table']//tbody//*[@class='column-50']" }, table_cell_id_class_and_direct_desc: { css: "table#large-table > tbody .column-50", xpath: "//table[@id='large-table']/tbody//*[@class='column-50']" }, table_cell_traversing: { css: "table#large-table tbody tr td:nth-of-type(50)", xpath: "//table[@id='large-table']//tbody//tr//td[50]" }, table_cell_traversing_and_direct_desc: { css: "table#large-table > tbody > tr > td:nth-of-type(50)", xpath: "//table[@id='large-table']/tbody/tr/td[50]" } } attr_reader :driver def initialize(driver) @driver = driver visit '/large' is_displayed?(id: 'siblings') super end # The benchmarking approach was borrowed from # http://rubylearning.com/blog/2013/06/19/how-do-i-benchmark-ruby-code/ def benchmark Benchmark.bmbm(27) do |bm| LOCATORS.each do |example, data| data.each do |strategy, locator| bm.report(example.to_s + " using " + strategy.to_s) do begin ENV['iterations'].to_i.times do |count| find(strategy => locator) end rescue Selenium::WebDriver::Error::NoSuchElementError => error puts "( 0.0 )" end end end end end end end
Wyniki
W formie tabeli:
W formie wykresu:
Analiza wyników
Podsumowanie
Drobnostki
Możesz wykonać benchmarking samodzielnie, korzystając z tej biblioteki, w której Dave Haeffner zawarł cały kod.
źródło