0

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)
tom
  • 123
  • 1
  • 11
  • 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 Jan 10 '13 at 19:43
  • 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 Jan 10 '13 at 20:34
  • Is it not possible for you to know what element is going to be the last to load on the page? – Abe Heward Jan 11 '13 at 13:17
  • 1
    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 Jan 11 '13 at 14:43
  • @AbeHeward: I can not tell which element gets loaded as the last - I would need to do some serious JS debugging to get to know this. But I can not do that now with my level of experience and I think it is not needed.. – tom Jan 11 '13 at 16:38
  • @JustinKo: I posted sample benchmarking code - as I wrote in the comment for Abe, problem with performance could be the DOM structure goes many levels deep.. But 140ms seems to me quite long time anyway. – tom Jan 11 '13 at 16:39
  • 2
    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 Jan 11 '13 at 17:06
  • I'm with Justin in that I seriously question the value of a test that simply checks for the existence of a large number of elements on the page. – Abe Heward Jan 11 '13 at 17:13
  • @JarmoPertman: I wish I could use ids to locate elements - but I can not coz generated html does not have them. I have to you use other means - oftentimes xpath. I tried this container approach but results were surprising. In contrary they were worse as if I would be addressing elements directly - please check out updated post. I have not tested with Watir-clasic yet, results are with watir-webdriver and Firefox. Chrome performs better but results follow very same pattern. Frankly I was expecting this container approach would improve performance. Hope I didnt do some stupid coding mistake.. – tom Jan 12 '13 at 14:48
  • @tom, your new code is getting terribly confusing to read, but it doesn't actually look like your Watir code follows Jarmo's suggestion. `b.div(:xpath => lb3_xpath)` does not narrow down the DOM by first specifying a parent container for that div. The same goes for every other Watir element you put into your example code. – Abe Heward Jan 15 '13 at 13:04
  • @Abe: sorry for the messy code.. there were two code samples added - one with narrowed DOM where container reference was stored at instance variable @c and the child element was localized using relative xpath... `@c.div(:xpath => "div[3]/div/div").present?` These were benchmarked against code examples using absolute xpath: `b.div(:xpath => lb3_xpath).present?` The surprise was localization by absolute xpath performed better than localization by relative xpath. While opposite results were expected.. – tom Jan 15 '13 at 13:24

1 Answers1

1

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).

Abe Heward
  • 515
  • 3
  • 16
  • Thank you for your help. The problem is I am not JS person and the application I am testing is GWT-based, so it translates to native JS. And there is still open point - your proposal suggests to poll for the state when there are no AJAX requests pending and then to check out presence of elements. But the same thing should be done by using until/until_present method. Simply I poll to check the presence of the elements which returns true when there are no pending AJAX requests. So I give you 1+ for the new info about how Watir might talk with JS framework. But I'll wait if other comments come. – tom Jan 11 '13 at 09:03
  • Thanks for the upvote, though I'd really like to actually have an answer for you, here. :) Another question for you: What does your typical element definition look like? If you're using a lot of regex in identifying the elements, or else they're contained in tables on the page, then things can get a lot slower. Take a look at Justin Ko's answer to the question here, for discussion of using nokogiri to improve performance, in that case: http://stackoverflow.com/questions/14128901/a-better-way-of-iterating-and-filling-data-in-html-table-using-watir – Abe Heward Jan 11 '13 at 14:00
  • Actually I am not using regexp to locate elements.. I also checked referenced code - Nokogiri is used to get element's xpath. But I tried to locate elements by their xpath and the performance gain was not significant - I checked with the Benchmark gem and the time difference was about 20ms in favour of using explicit xpath.. Please check out edited post for benchmarking code sample. The problem could be the DOM generated by the GWT is quite deep and it just simply takes that long to traverse it. – tom Jan 11 '13 at 16:22