저는 다국어 웹 애플리케이션에서 Selenium WebDriver 2.25.0으로 작업하고 있으며 주로 페이지 콘텐츠를 테스트합니다 (아랍어, 영어, 러시아어 등과 같은 다른 언어의 경우).
성능에 따라 더 나은 내 응용 프로그램의 경우 모든 브라우저 (예 : IE 7,8,9, FF, Chrome 등)를 지원해야합니다.
귀하의 소중한 제안에 미리 감사드립니다.
답변:
CSS 선택기는 Xpath보다 성능이 훨씬 뛰어나며 Selenium 커뮤니티에 잘 문서화되어 있습니다. 몇 가지 이유가 있습니다.
그러나 xpath를 사용해야하는 상황이 있습니다. 예를 들어 상위 요소를 검색하거나 해당 텍스트로 요소를 검색합니다 (나중에 권장하지 않음).
여기 에서 Simon의 블로그를 읽을 수 있습니다 . 그는 또한 Xpath보다 CSS를 권장합니다.
콘텐츠를 테스트하는 경우 요소의 콘텐츠에 종속 된 선택기를 사용하지 마십시오. 이는 모든 로케일에 대한 유지 관리 악몽이 될 것입니다. 개발자와 대화를 시도하고 사전 또는 리소스 번들 등과 같이 애플리케이션의 텍스트를 외부화하는 데 사용한 기술을 사용하십시오. 여기 에 자세히 설명하는 블로그 가 있습니다.
@parishodak 덕분에 CSS 성능이 더 우수하다는 것을 증명하는 숫자를 제공하는 링크 가 있습니다.
나는 XPath가 장기적으로 CSS 보다 바람직 하다는 SO 셀레늄 태그 의견에 대해 인기가 없을 것 입니다.
이 긴 게시물에는 두 섹션이 있습니다. 먼저 두 섹션의 성능 차이가 0.1-0.3 밀리 초 (예, 100 마이크로 초) 라는 냅킨 증명을 넣은 다음 그 이유를 공유하겠습니다. XPath가 더 강력합니다.
먼저 "방에있는 코끼리"에 대해 살펴 보겠습니다. xpath가 css보다 느립니다.
현재 CPU 성능 (읽기 : 2013 년 이후로 생산 된 모든 x86) , 심지어 browserstack / saucelabs / aws VM 및 브라우저의 개발 (읽기 : 지난 5 년 동안 인기있는 모든 것) 에서는 거의 해당되지 않습니다. 브라우저의 엔진이 개발되었고 xpath의 지원은 균일하며 IE는 그림에서 벗어났습니다 (대부분의 사람들에게 희망을줍니다) . 다른 답변에 대한이 비교는 곳곳에서 인용되고 있지만, IE8에 대해 자동화를 실행하고 있거나 관심을 갖는 것은 매우 맥락 적입니다.
차이가있는 경우 밀리 초 미만 입니다.
그러나 대부분의 상위 레벨 프레임 워크는 어쨌든 원시 셀레늄 호출 (래퍼, 핸들러, 상태 저장 등)에 대해 최소 1ms의 오버 헤드를 추가합니다. 제가 선택한 무기 인 RobotFramework는 최소 2ms를 추가합니다.이 무기는 제공하는 것을 희생하는 것보다 더 기쁩니다. AWS us-east-1에서 BrowserStack의 허브로의 네트워크 왕복은 일반적으로 11 밀리 초 입니다.
따라서 원격 브라우저에서 xpath와 css 사이에 차이가 있으면 다른 모든 것에 의해 가려집니다.
공개적인 비교는 그리 많지 않습니다 (인용 된 것만 본 적이 있습니다) . 여기에 대략적인 단일 사례, 더미 및 간단한 사례가 있습니다.
두 가지 전략으로 요소를 X 번 찾고 그에 대한 평균 시간을 비교합니다.
대상 – BrowserStack의 랜딩 페이지 및 "가입"버튼 이 게시물을 작성하는 동안의 html 스크린 샷 :
다음은 테스트 코드 (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))
Python에 익숙하지 않은 사용자를 위해 페이지를 열고 요소를 찾습니다. 먼저 css 로케이터를 사용한 다음 xpath를 사용합니다. 찾기 작업이 1,000 회 반복됩니다. 출력은 1,000 회 반복에 대한 총 시간 (초)과 하나의 발견에 대한 평균 시간 (밀리 초)입니다.
로케이터는 다음과 같습니다.
과도하게 조정되지 않도록 의도적으로 선택되었습니다. 또한 클래스 선택기는 CSS에 대해 "id 다음으로 두 번째로 빠른"것으로 인용됩니다.
환경 – Chrome v66.0.3359.139, chromedriver v2.38, cpu : 일반적으로 1.5GHz에서 실행되는 ULV Core M-5Y10 (예, '워드 프로세싱', 일반 i7 야수도 아님) .
출력은 다음과 같습니다.
css total time 1000 repeats: 8.84s, per find: 8.84ms xpath total time for 1000 repeats: 8.52s, per find: 8.52ms
분명히 검색 당 타이밍은 매우 가깝습니다. 차이는 0.32 밀리 초 입니다. "xpath가 더 빠름"으로 점프하지 마십시오. 때로는 그렇고, 때로는 CSS이기도합니다.
조금 더 복잡한 다른 로케이터 세트를 사용해 봅시다. 하위 문자열을 가진 속성 (적어도 저에게는 일반적인 접근 방식, 일부가 기능적 의미를 가질 때 요소의 클래스를 따라갑니다) :
xpath_locator = '//div[contains(@class, "button-section")]'
css_locator = 'div[class~=button-section]'
두 로케이터는 다시 의미 상 동일합니다. "클래스 속성에이 하위 문자열이있는 div 요소 찾기"입니다.
결과는 다음과 같습니다.
css total time 1000 repeats: 8.60s, per find: 8.60ms xpath total time for 1000 repeats: 8.75s, per find: 8.75ms
0.15ms 의 차이 .
실습으로- 링크 된 블로그 의 댓글 / 기타 답변에서 수행 한 것과 동일한 테스트 - 테스트 페이지 는 공개이며 테스트 코드도 마찬가지입니다 .
그들은 코드에서 몇 가지 작업을 수행하고 있습니다. 열을 클릭하여 정렬 한 다음 값을 가져오고 UI 정렬이 올바른지 확인합니다.
잘라 버릴 게요-그냥 로케이터를 가져 와요-이게 루트 테스트 죠, 맞죠?
위와 동일한 코드이며 다음과 같이 변경됩니다.
이제 URL은 다음과 같습니다 http://the-internet.herokuapp.com/tables
. 두 가지 테스트가 있습니다.
첫 번째 항목 인 "ID 및 클래스로 요소 찾기"에 대한 로케이터는 다음과 같습니다.
css_locator = '#table2 tbody .dues'
xpath_locator = "//table[@id='table2']//tr/td[contains(@class,'dues')]"
결과는 다음과 같습니다.
css total time 1000 repeats: 8.24s, per find: 8.24ms xpath total time for 1000 repeats: 8.45s, per find: 8.45ms
차이 0.2 밀리 초.
"순회로 요소 찾기":
css_locator = '#table1 tbody tr td:nth-of-type(4)'
xpath_locator = "//table[@id='table1']//tr/td[4]"
결과:
css total time 1000 repeats: 9.29s, per find: 9.29ms xpath total time for 1000 repeats: 8.79s, per find: 8.79ms
이번에는 0.5 밀리 초 (역으로, XPath는 "더 빨리"여기에서 밝혀졌다).
그래서 오년 이상 (더 나은 브라우저 엔진) 및 단지 로케이터 성능에 초점을 맞추고, 같은 테스트 베드합니다 (UI 등의 정렬과 같은 어떤 행동) - 실제적으로 CSS와 XPath를 사이에 차이가 없다.
그렇다면 xpath와 css 중 성능을 위해 선택해야 할 두 가지는 무엇입니까? 답은 간단합니다. . ID로 찾기를 .
간단히 말해서, 요소의 ID가 고유 한 경우 (사양에 따라야하는 것처럼) 그 값은 브라우저의 DOM 내부 표현에서 중요한 역할을하므로 일반적으로 가장 빠릅니다.
그러나 고유하고 상수 (예 : 자동 생성되지 않음) ID를 항상 사용할 수있는 것은 아니므로 "CSS가있는 경우 XPath가 필요한 이유는 무엇입니까?"
성능이 떨어지면 xpath가 더 나은 이유는 무엇입니까? 단순함 – 다목적 성 및 성능.
Xpath는 XML 문서 작업을 위해 개발 된 언어입니다. 따라서 css보다 훨씬 더 강력한 구성을 허용합니다.
예를 들어, 트리의 모든 방향으로 탐색-요소를 찾은 다음 해당 조부모로 이동하여 특정 속성을 가진 하위 항목을 검색합니다.
포함 된 부울 조건을 허용합니다. – cond1 and not(cond2 or not(cond3 and cond4))
; 포함 된 선택기 – "이러한 속성을 가진 이러한 자식이있는 div를 찾은 다음 그에 따라 탐색"합니다.
XPath는 노드의 값 (텍스트)을 기반으로 검색을 허용합니다. 그러나이 관행에 눈살을 찌푸리는 것은 특히 잘못 구조화 된 문서에서 유용합니다 (동적 ID 및 클래스와 같이 밟아야 할 명확한 속성이 없음) 텍스트로 요소를 찾습니다. 내용) .
css의 스테핑은 확실히 더 쉽습니다. 단 몇 분만에 선택자를 작성할 수 있습니다. 그러나 며칠 사용 후 xpath의 힘과 가능성은 CSS를 빠르게 극복했습니다.
그리고 순전히 주관적입니다. 복잡한 CSS는 복잡한 xpath 표현식보다 읽기가 훨씬 더 어렵습니다.
마지막으로, 다시 매우 주관적입니다. 어떤 것을 선택해야합니까?
IMO는 옳고 그른 선택이 없습니다. 동일한 문제에 대한 다른 솔루션이며 작업에 더 적합한 것을 선택해야합니다.
XPath의 "팬"이기 때문에 내 프로젝트에서 두 가지를 혼합하여 사용하는 것을 부끄러워하지 않습니다. 가끔 CSS를 던지는 것이 훨씬 빠릅니다.
[]
한 후 //
, 예를 들어) . 그러나 학습과 사용의 첫날이 지나면 거의 모든 사람들이 학습 곡선의 전환점을 넘습니다. :) (css 단계는 허용 됩니다. IMHO) .
cssSelector 대 XPath 간의 논쟁은 Selenium 커뮤니티 에서 가장 주관적인 논쟁 중 하나로 남아있을 것 입니다. 지금까지 우리가 이미 알고있는 것은 다음과 같이 요약 할 수 있습니다.
Dave Haeffner 는 두 개의 HTML 데이터 테이블이있는 페이지 에서 테스트 를 수행했습니다. 하나의 테이블은 유용한 속성 ( ID 및 Class ) 없이 작성 되었고 다른 하나는 그들과 함께 작성되었습니다. 자동 테스트를 위해 XPath가 아닌 cssSelector 선택기를 사용해야하는 이유는 무엇입니까? 토론에서 테스트 절차 와이 실험의 결과를 자세히 분석했습니다 . . 이 실험은 각 로케이터 전략 이 브라우저에서 합리적으로 동일 하다는 것을 보여 주었지만 전체 그림을 적절하게 그리지 못했습니다. 다른 토론의 Dave Haeffner CSS의 대 X 경로, 현미경 아래언급했듯이, 엔드 투 엔드 테스트에서 play Sauce 시작 , 브라우저 시작 및 테스트중인 응용 프로그램 과의 지연 시간 에 다른 많은 변수가있었습니다 . 이 실험의 불행한 점은 한 드라이버가 다른 드라이버보다 빠를 수 있다는 것입니다 (예 : IE vs Firefox ). 실제로는 그렇지 않습니다. cssSelector 와 cssSelector 의 성능 차이에 대한 진정한 맛을 얻으려면 XPath, 우리는 더 깊이 파헤쳐 야했습니다. 성능 벤치마킹 유틸리티를 사용하면서 로컬 머신에서 모든 것을 실행했습니다. 또한 전체 테스트 실행이 아닌 특정 Selenium 작업에 중점을 두었고 여러 번 실행했습니다. 셀레늄에 대한 cssSelector 대 XPath 토론에서 구체적인 테스트 절차 와이 실험의 결과를 자세히 분석했습니다 . 그러나 테스트는 여전히 한 가지 측면, 즉 더 많은 브라우저 범위 (예 : Internet Explorer 9 및 10)와 더 크고 더 깊은 페이지에 대한 테스트가 누락되었습니다 .
다른 토론에서 Dave Haeffner Css Vs. X Path, Under a Microscope (Part 2) 는 필요한 벤치 마크가 가능한 최상의 방법으로 다루어 지도록 크고 깊은 페이지를 보여주는 예제 를 고려해야 합니다 .
이 자세한 예를 보여주기 위해 Windows XP 가상 머신이 설정되고 Ruby (1.9.3) 가 설치되었습니다. 사용 가능한 모든 브라우저와 이에 상응하는 Selenium 용 브라우저 드라이버도 설치되었습니다. 벤치마킹을 위해 Ruby의 표준 lib benchmark
가 사용되었습니다.
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
참고 : 출력은 초 단위이며 결과는 총 100 회 실행 시간에 대한 것입니다.
테이블 형식 :
차트 형식 :
당신은 사용하여 벤치 마크 스스로를 수행 할 수있는 이 라이브러리 데이브 Haeffner이 모든 코드를 싸서.