0
votes

I experience IMO high delays with the Ruby script using Watir. Here's the problem description: I am testing AJAX based application and I wanted to avoid using of sleep to make sure page gets loaded:

class Page
  attr_accessor :expected_elements

  def loaded?
    # code to make sure AJAX page is loaded
  end
end

So instead of this:

def loaded?
  # static delay sufficient to get page loaded
  sleep(MAX_PAGE_LOAD_TIME)
  true
end

I wanted to have something like this:

def loaded?
  Watir::Wait.until(MAX_PAGE_LOAD_TIME) do
    expected_elements.all? do |element|
      element.present?
    end
  end
end

The problem is the evaluation of the block takes too long. The more elements I check for presence the higher this delay gets. I experienced roughly this delay for one iteration:

Firefox -> 130ms
IE -> 615ms
Chrome -> 115 ms

So to check if N elements are present I get N times corresponding delay... Well the consequence is that eventhough MAX_PAGE_LOAD_TIME expires the Watir::Wait::TimeoutError is not thrown because block evaluation has not been finished yet... So I ended up in the situation where the check for elements presence introduces higher delay than the static delay which is sufficient enough to get page loaded.. I tried to improve performance by locating elements by xpath, but the performance gain was not significant.. What am I doing wrong? Is there a way to speed-up execution time for present? method?? Do these delays correspond with your experience - or are they high?

I checked if the problem could be in the browser-server communication, but here the delays are very low.. I got 100ms time difference for the server response including backend DB request. Of course it takes some time to render page based on this response, but for sure it does not take seconds.

My configuration: - Win7 OS, - Firefox 17.0.1 - IE 8.0.7601.17514 with IEDriverServer_x64_2.26.2 - Chrome 23.0.1271.97 m with chromedriver_win_23.0.1240.0, - Ruby 1.9.3p125, - watir-webdriver (0.6.1), - selenium-webdriver (2.27.2)

Thank you for your help!

Based on the discussion I post a sample of benchmarking code:

Benchmark.bm do |x|
  x.report("text") do
    b.span(:text => "Search").present?
  end
end
Benchmark.bm do |x|
  x.report("xpath") do
    b.span(:xpath => "/html/body/div/div/div[2]/div[2]/div/div/div/div/div/div/div/div[2]/div/div/div[2]/div/div/div/div/div/div[2]/div/div/div/div/div/div/div/div[2]/div/div/div[2]/div/div/div/div/div/div[2]/div/div/div/div/div/span/span").present?
  end
end

        user     system      total        real
text   0.000000   0.000000   0.000000 (  0.140405)
xpath  0.000000   0.000000   0.000000 (  0.120005)

Additional benchmarking results:

container_xpath = "/html/body/div/div/div[2]/div[2]/div/div/div/div/div/div/div/div[2]/div/div/div[2]/div/div/div/div/div/div[2]/div/div/div/div/div/div/div/div/div/div/div/div/div/div/div[2]/div/div/div/div[2]/div/div/div"
Benchmark.bm do |x|
  x.report("cntnr") do
    @c = b.div(:xpath => container_xpath)
    @c.present?
  end
end
Benchmark.bm do |x|
  x.report("lb1") do
    @c.div(:xpath => "div[1]/div/div").present?
    #@c.div(:text => "Company:").present?
  end
end
Benchmark.bm do |x|
  x.report("lb2") do
    @c.div(:xpath => "div[3]/div/div").present?
    #@c.div(:text => "Contact person:").present?
  end
end
Benchmark.bm do |x|
  x.report("lb3") do
    @c.div(:xpath => "div[5]/div/div").present?
    #@c.div(:text => "Address:").present?
  end
end

And the results were:

Results for container reference and relative xpath:
       user     system      total        real
cntnr  0.000000   0.000000   0.000000 (  0.156007)
lb1    0.000000   0.000000   0.000000 (  0.374417)
lb2    0.000000   0.000000   0.000000 (  0.358816)
lb3    0.000000   0.000000   0.000000 (  0.358816)

Results for container reference and div's text:
       user     system      total        real
cntnr  0.000000   0.000000   0.000000 (  0.140402)
lb1    0.000000   0.000000   0.000000 (  0.358807)
lb2    0.000000   0.000000   0.000000 (  0.358807)
lb3    0.000000   0.000000   0.000000 (  0.374407)

When absolute xpaths were used:

container_xpath = "/html/body/div/div/div[2]/div[2]/div/div/div/div/div/div/div/div[2]/div/div/div[2]/div/div/div/div/div/div[2]/div/div/div/div/div/div/div/div/div/div/div/div/div/div/div[2]/div/div/div/div[2]/div/div/div"
Benchmark.bm do |x|
  x.report("cntnr") do
    @c = b.div(:xpath => container_xpath)
    @c.present?
  end
end

lb1_xpath = container_xpath + "/div[1]/div/div"
Benchmark.bm do |x|
  x.report("lb1_x") do
    b.div(:xpath => lb1_xpath).present?
  end
end

lb2_xpath = container_xpath + "/div[3]/div/div"
Benchmark.bm do |x|
  x.report("lb2_x") do
    b.div(:xpath => lb2_xpath).present?
  end
end

lb3_xpath = container_xpath + "/div[5]/div/div"
Benchmark.bm do |x|
  x.report("lb3_x") do
    b.div(:xpath => lb3_xpath).present?
  end
end

Results were:

       user     system      total        real
cntnr  0.000000   0.000000   0.000000 (  0.140404)
lb1_x  0.000000   0.000000   0.000000 (  0.124804)
lb2_x  0.000000   0.000000   0.000000 (  0.156005)
lb3_x  0.000000   0.000000   0.000000 (  0.140405)
1
Why are you wrapping the element checking inside your wait until stuff? Why not write a method that waits until all the ajax calls are finished and then check for the presence of your expected elements?Abe Heward
Well I am new to Watir and I thought using Watir::Wait.until would be a way to have some "watchdog" timeout within which there is done 100ms polling for the presence of html elements. "MAX_PAGE_LOAD_TIME" was meant to replace default 30sec timeout. I expected polling would quickly discover when all expected elements get loaded.. I did not know WATIR API allows you to hook up to AJAX calls - or to detect situation when all AJAX calls are done. May I ask you to provide short code sample which uses this approach or to provide link reference?tom
Is it not possible for you to know what element is going to be the last to load on the page?Abe Heward
When it comes to performance issues, it really helps to given an actual example. The size of the page, the number of elements you are checking and the locator used for the elements will all impact the performance. That said, I would typically just check for the element I want to interact with rather than checking everything on the page.Justin Ko
Can you also benchmark against watir-classic just out of curiousity? One way to speed up would be to set very specific locators to your elements - first set a container with :id, like container = browser.div(:id => "my container") to not search each time the whole DOM. Next search your elements within that container container.div(:class => "my expected element"). So, always use :id when possible and try to narrow down the DOM you're looking the elements from.Jarmo Pertman

1 Answers

1
votes

Okay, this answer assumes your site is using jquery. If it's not, you'll have to figure out the library in use and modify the method accordingly...

Write a method that waits for the Ajax calls to finish...

def wait_for_ajax(timeout = 10)
  timeout.times do
    return true if browser.execute_script('return jQuery.active').to_i == 0
    sleep(1)
  end

  raise Watir::Wait::TimeoutError, "Timeout of #{timeout} seconds exceeded on waiting for Ajax."
end

Call that method when you first load the page you're testing. Then iterate through your expected elements to see if they're present (if you have an array of Watir elements to check, make sure you use .each with it, not .all? as you have in your code there).