Are :xpath locators slower?

Problem

In the past, the Watir community has been resistive to using XPath locators. One of the reasons being the poor performance when locating elements. Occasionally I still see warnings of poor performance, but there seems to be more uncertainty.

Some comments I have seen over time include:

Is the concern about poor XPath performance still valid?

Setup

IE11 (for Watir-Classic) and Chrome (for Watir-Webdriver) were used during the benchmark tests. The test page was a simple table:

<html>
  <body>
    <table id="report" class="results_grid" data-rows="1">
      <tr>
        <th>Heading 1</th>
        <th>Heading 2</th>
      </tr>
      <tr>
        <td>Data 1</td>
        <td>Data 2</td>
      </tr>
    </table>
  </body>
</html>

Watir-Classic

As the performance concern started a long time ago, it makes sense to investigate Watir-Classic first.

The following benchmark located the table by its id (100 times):

n = 100
Benchmark.bm do |x|
  x.report("watir:") { n.times { browser.table(id: 'report').exists? } }
  x.report("xpath:") { n.times { browser.table(xpath: '//table[@id="report"]').exists? } }
  x.report("css:") { n.times { browser.table(css: 'table#report').exists? } }
end

The results show that XPath (and CSS-Selectors) are a bit slower:

            user     system      total        real
watir:  0.031000   0.000000   0.031000 (  0.124800)
xpath:  0.406000   0.156000   0.562000 (  0.748801)
css:    0.296000   0.031000   0.327000 (  0.546001)

When locating nested elements (elements within an element object as opposed to within the browser object):

n = 100
Benchmark.bm do |x|
  x.report("watir:") { n.times { browser.table(id: 'report').tr.exists? } }
  x.report("xpath:") { n.times { browser.tr(xpath: '//table[@id="report"]//tr').exists? } }
  x.report("css:") { n.times { browser.tr(css: 'table#report tr').exists? } }
end

The poor XPath performance is more evident:

            user     system      total        real
watir:  0.093000   0.015000   0.108000 (  0.202801)
xpath:  0.968000   0.344000   1.312000 (  2.277604)
css:    0.842000   0.171000   1.013000 (  2.043603)

Watir-Webdriver

When it comes to locating elements, Watir-Webdriver takes a very different approach compared to Watir-Classic. There are various code paths depending on what the locator is. For simplicity, we will ignore the special handling for Regexp values.

If there is a single locator that is supported by Selenium-WebDriver, there is no processing by Watir-Webdriver. The locator is simply passed on as written. Benchmarking the most common locator – :id:

n = 100
Benchmark.bm do |x|
  x.report("watir:") { n.times { browser.table(id: 'report').exists? } }
  x.report("xpath:") { n.times { browser.table(xpath: '//table[@id="report"]').exists? } }
  x.report("css:") { n.times { browser.table(css: 'table#report').exists? } }
end

We see very little difference between using :id and :xpath:

            user     system      total        real
watir:  0.531000   0.156000   0.687000 (  6.984375)
xpath:  0.641000   0.063000   0.704000 (  7.093750)
css:    0.609000   0.078000   0.687000 (  6.843750)

When the locator is not supported by Selenium-WebDriver, Watir-WebDriver will build an XPath (or CSS-selector). For example, this occurs when locating an element by a data-* attribute:

n = 100
Benchmark.bm do |x|
  x.report("watir:") { n.times { browser.table(data_rows: '1').exists? } }
  x.report("xpath:") { n.times { browser.table(xpath: '//table[@data-rows="1"]').exists? } }
  x.report("css:") { n.times { browser.table(css: 'table[data-rows="1"]').exists? } }
end

As the locator from Selenium-WebDriver’s perspective is still just XPath, it is no surprise that the performance differences are insignificant:

            user     system      total        real
watir:  0.203000   0.093000   0.296000 (  6.781250)
xpath:  0.484000   0.235000   0.719000 (  7.093750)
css:    0.344000   0.125000   0.469000 (  6.656250)

Selenium-WebDriver only supports a single locator, so Watir-WebDriver will also build an XPath if there are multiple locators:

n = 100
Benchmark.bm do |x|
  x.report("watir:") { n.times { browser.table(data_rows: '1', class: 'results_grid').exists? } }
  x.report("xpath:") { n.times { browser.table(xpath: '//table[@data-rows="1" and @class="results_grid"]').exists? } }
  x.report("css:") { n.times { browser.table(css: 'table[data-rows="1"][class="results_grid"]').exists? } }
end

Again, there is no performance impact for writing your own XPath locator:

            user     system      total        real
watir:  0.562000   0.125000   0.687000 (  7.125000)
xpath:  0.625000   0.235000   0.860000 (  7.203125)
css:    0.625000   0.171000   0.796000 (  7.187500)

We can find a performance difference if we return to our nested element scenario:

n = 100
Benchmark.bm do |x|
  x.report("watir:") { n.times { browser.table(id: 'report').tr.exists? } }
  x.report("xpath:") { n.times { browser.tr(xpath: '//table[@id="report"]//tr').exists? } }
  x.report("css:") { n.times { browser.tr(css: 'table#report tr').exists? } }
end

However, using XPath actually gives us better performance!

            user     system      total        real
watir:  1.203000   0.625000   1.828000 ( 14.015625)
xpath:  0.329000   0.078000   0.407000 (  6.593750)
css:    0.375000   0.079000   0.454000 (  6.796875)

Adding a third level of nesting:

n = 100
Benchmark.bm do |x|
  x.report("watir:") { n.times { browser.table(id: 'report').tr.th.exists? } }
  x.report("xpath:") { n.times { browser.tr(xpath: '//table[@id="report"]//tr//th').exists? } }
  x.report("css:") { n.times { browser.tr(css: 'table#report tr th').exists? } }
end

We see that each additional element method call has a significant impact, while the XPath performance is not impacted:

            user     system      total        real
watir:  1.828000   0.547000   2.375000 ( 25.328125)
xpath:  0.312000   0.110000   0.422000 (  7.296875)
css:    0.406000   0.125000   0.531000 (  6.703125)

Summary

The impact of XPath locators on performance depends on the Watir gem:

  • When using Watir-Classic, using XPath will always be slower.
  • When using Watir-Webdriver, using XPath does not have a negative performance impact. In some cases, XPath actually improves performance.

If you are looking to get the best performance:

  • When using Watir-Classic, avoid using XPath.
  • When using Watir-Webdriver, replace multiple nested element method calls with a single XPath or CSS-selector.
Advertisements
This entry was posted in Watir, Watir-Classic, Watir-Webdriver and tagged , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s