我正在使用 Selenium WebDriver 2.25.0 开发多语言 Web 应用程序,主要测试页面内容(针对不同的语言,如阿拉伯语、英语、俄语等)。
对于我的应用程序,根据性能并确保它应该支持所有浏览器(即 IE 7、8、9、FF、Chrome 等)。
提前感谢您的宝贵建议。
我正在使用 Selenium WebDriver 2.25.0 开发多语言 Web 应用程序,主要测试页面内容(针对不同的语言,如阿拉伯语、英语、俄语等)。
对于我的应用程序,根据性能并确保它应该支持所有浏览器(即 IE 7、8、9、FF、Chrome 等)。
提前感谢您的宝贵建议。
CSS 选择器的性能比 Xpath 好得多,并且在 Selenium 社区中有很好的记录。这里有一些原因,
但是在某些情况下,您需要使用 xpath,例如,搜索父元素或通过文本搜索元素(我不推荐后者)。
你可以在这里阅读 Simon 的博客。他还推荐使用 CSS 而不是 Xpath。
如果您正在测试内容,则不要使用依赖于元素内容的选择器。这将是每个地区的维护噩梦。尝试与开发人员交谈并使用他们用来将应用程序中的文本外部化的技术,例如字典或资源包等。这是我的博客,详细解释了它。
感谢@parishodak,这里是提供证明CSS性能更好的数字的链接
我将持有不受欢迎的 SO selenium 标签观点,即从长远来看, XPath 比 CSS 更可取。
这篇长篇文章有两个部分——首先我会做一个简单的证明,证明两者之间的性能差异是0.1-0.3 毫秒 (是的;那是 100微秒),然后我会分享我的观点为什么XPath 更强大。
让我们首先解决“房间里的大象”——xpath 比 css 慢。
以当前的 cpu 能力(阅读:自 2013 年以来生产的任何 x86 产品),即使在 browserstack/saucelabs/aws VM 上,以及浏览器的发展(阅读:过去 5 年中所有流行的浏览器),情况几乎都不是这样。浏览器的引擎已经开发,对 xpath 的支持是统一的,IE 是不可能的(希望对我们大多数人来说)。其他答案中的这种比较被到处引用,但它是非常有上下文的——有多少人正在运行——或关心——针对 IE8 的自动化?
如果存在差异,则在几分之一毫秒内。
然而,大多数高级框架无论如何都会比原始 selenium 调用增加至少 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 次。输出是 1,000 次重复的总时间(以秒为单位),以及一次查找的平均时间(以毫秒为单位)。
定位器是:
故意选择不过度调整;此外,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.15ms的差异。
作为一个练习 - 与评论/其他答案中链接博客中所做的相同测试-测试页面是公开的,测试代码也是如此。
他们在代码中做了几件事——点击列进行排序,然后获取值,并检查 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 和类——通过其文本定位元素内容)。
步入 css 肯定更容易——几分钟就可以开始编写选择器;但是经过几天的使用,xpath 的功能和可能性很快就超越了 css。
而且纯粹是主观的——复杂的 css 比复杂的 xpath 表达式更难阅读。
最后,再次非常主观 - 选择哪一个?
IMO,没有正确或错误的选择-它们是同一问题的不同解决方案,应该选择更适合该工作的方法。
作为 XPath 的“粉丝”,我并不羞于在我的项目中同时使用这两种方法——哎呀,有时只扔一个 CSS 会快得多,如果我知道它可以很好地完成工作的话。
cssSelector与XPath之间的争论仍然是Selenium 社区中最主观的争论之一。到目前为止,我们已经知道的可以概括为:
Dave Haeffner对包含两个 HTML 数据表的页面进行了测试,其中一个表没有有用的属性(ID和Class),另一个则包含它们。我已经在讨论中详细分析了测试过程和该实验的结果为什么我应该使用cssSelector选择器而不是 XPath 进行自动化测试?. 虽然这个实验证明了每个定位器策略在浏览器之间是相当等价的,但它并没有为我们充分描绘出整个画面。Dave Haeffner在其他讨论Css Vs. X 路径,显微镜下提到,在端到端测试中,还有很多其他变量在起作用Sauce startup、Browser start up以及与被测应用程序之间的延迟。不幸的是,从该实验中得出的结论可能是一个驱动程序可能比另一个驱动程序更快(例如IE与Firefox),而事实上,情况并非如此。真正体验cssSelector和XPath之间的性能差异,我们需要深入挖掘。我们通过使用性能基准测试实用程序从本地计算机运行所有内容来做到这一点。我们还专注于特定的 Selenium 操作而不是整个测试运行,并且多次运行。我已经在讨论cssSelector vs XPath for selenium中详细分析了这个实验的具体测试过程和结果。但是测试仍然缺少一个方面,即更多的浏览器覆盖率(例如,Internet Explorer 9 和 10)以及针对更大和更深页面的测试。
Dave Haeffner在另一个讨论Css Vs. 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 打包所有代码的这个库自行执行基准测试。