我正在多语言Web应用程序上使用Selenium WebDriver 2.25.0,主要测试页面内容(用于阿拉伯语,英语,俄语等不同语言)。
对于我的应用程序,根据性能最好,并确保它应支持所有浏览器(即IE 7、8、9,FF,Chrome等)。
预先感谢您的宝贵建议。
Answers:
CSS选择器的性能远胜过Xpath,并且在Selenium社区中有很好的记录。这是一些原因,
但是,在某些情况下,您需要使用xpath,例如,搜索父元素或通过其文本搜索元素(我不建议稍后介绍)。
您可以在此处阅读Simon的博客。他还建议在Xpath上使用CSS。
如果要测试内容,请不要使用依赖于元素内容的选择器。这将是每个地区维护的噩梦。尝试与开发人员交谈,并使用他们用来外部化应用程序中文本的技术,例如词典或资源包等。这是我的博客,详细解释了它。
感谢@parishodak,这里的链接提供了证明CSS性能更好的数字。
我将对SO硒标签持有不受欢迎的观点,即从长远来看XPath比CSS更可取。
这篇长篇文章分为两部分-首先,我将提供一张餐巾纸证明,两者之间的性能差异为0.1-0.3毫秒 (是的;这是100微秒),然后我将分享我的看法,为什么XPath功能更强大。
让我们首先解决“房间里的大象”问题– xpath比CSS慢。
使用当前的cpu功能(阅读:自2013年以来生产的任何x86),即使是在浏览器堆栈/ saucelabs / aws VM上,以及浏览器的开发(阅读:过去5年中所有流行的浏览器),也并非如此。浏览器的引擎已经开发出来,xpath的支持是统一的,IE是无用的(对我们大多数人来说是希望的)。另一个地方的这种比较方法在各处都被引用,但这是非常具体的上下文-有多少个正在运行-或在乎-针对IE8的自动化?
如果存在差异,则相差不到一毫秒。
但是,大多数高级框架无论如何都要在原始硒调用上增加至少1ms的开销(包装器,处理程序,状态存储等);我个人选择的武器– RobotFramework –至少增加了2毫秒,为此我很乐意为之牺牲。从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次。输出是1000次重复的总时间(以秒为单位)和一次查找的平均时间(以毫秒为单位)。
定位器是:
故意选择不要过度调音;同样,类选择器被css引用为“仅次于id的第二快”。
环境-Chrome v66.0.3359.139,chromedriver v2.38,cpu:ULV Core M-5Y10通常以1.5GHz运行(是的,是“文字处理”程序,甚至不是普通的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.15毫秒。
作为练习-与在注释/其他答案中的链接博客中进行的测试相同-测试页面是公开的,测试代码也是如此。
他们在代码中做了几件事-单击列对其进行排序,然后获取值,并检查UI排序是否正确。
我要剪掉-毕竟要找到定位器-这是根测试,对吗?
与上面相同的代码,但有以下更改:
现在的网址是http://the-internet.herokuapp.com/tables
;有2个测试。
第一个的定位器-“按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在这里“更快”)。
因此5年后(更好的浏览器引擎),并且只关注定位器的性能(没有在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和class-通过文本确定元素内容)。
进入CSS肯定更容易-只需几分钟即可开始编写选择器;但是经过几天的使用,xpath的强大功能和潜力很快克服了CSS。
纯粹是主观的–复杂的CSS比复杂的xpath表达式难读。
最后,还是很主观的-选择哪个?
海事组织,没有正确或错误的选择-它们是解决同一问题的不同方法,应选择更适合该工作的方法。
作为XPath的“狂热者”,我并不害羞在项目中同时使用这两者-哎呀,有时候抛出CSS会更快,如果我知道它会做得很好的话。
[]
后面//
的索引)。但是在学习和使用它的第一天之后,几乎每个人都越过了学习曲线的临界点:) (恕我直言,css的介入更容易)。
cssSelector与XPath之间的辩论将仍然是Selenium社区中最主观的辩论之一。到目前为止,我们已经可以概括为:
Dave Haeffner在具有两个HTML数据表的页面上进行了测试,其中一个表编写时没有有用的属性(ID和Class),而另一个表则没有帮助。我在讨论中详细分析了测试过程和该实验的结果,为什么我应该使用cssSelector选择器而不是XPath进行自动测试?。尽管该实验表明每种定位器策略在浏览器中都相当等效,但它并不能为我们充分描绘出整体情况。戴夫·哈夫纳(Dave Haeffner)在其他讨论中。X路径,在显微镜下提到,在端到端测试中,Sauce启动,浏览器启动以及被测试应用程序之间的等待时间还有很多其他变量。该实验的不幸结果可能是一个驱动程序可能比另一个驱动程序更快(例如IE vs Firefox),而实际上并非如此。真正了解cssSelector和XPath之间的性能差异是什么,我们需要更深入地研究。我们通过使用性能基准测试实用程序在本地计算机上运行所有程序来做到这一点。我们还专注于特定的Selenium动作,而不是整个测试运行,并且运行了无数次。我已经在讨论硒的cssSelector与XPath的讨论中详细分析了具体的测试程序和该实验的结果。但是测试仍然缺少一个方面,即更多的浏览器覆盖范围(例如Internet Explorer 9和10)以及针对更大更深页面的测试。
戴夫·哈夫纳(Dave Haeffner)在另一个讨论中。提到X路径,在显微镜下(第2部分),为了确保以最佳的方式覆盖了所需的基准,我们需要考虑一个展示大而深的页面的示例。
为了演示这个详细的示例,安装了Windows XP虚拟机并安装了Ruby(1.9.3)。还安装了所有可用的Selenium浏览器及其等效的浏览器驱动程序。为了进行基准测试,使用了Ruby的标准库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次执行的总运行时间。
以表格形式:
以图表形式:
您可以使用该库自行执行基准测试,Dave Haeffner打包了所有代码。