Dashboard > Watir > ... > Advanced Examples > How to wait with Watir
Watir Log In View a printable version of the current page.
How to wait with Watir
Added by Alan Baird, last edited by Olle Jonsson on Jun 03, 2009  (view change) show comment
Labels: 
(None)

IN PROGRESS

Still developing this topic. If you have suggestions, edit the page or post me a comment.

How to wait with Watir

How not to wait

If you've come to this page, you are probably wondering, "How do I tell Watir to wait in my scripts." This is a complicated question and we will get to that. But first, consider why you may not want to wait.

By design, Watir does not wait for new page to load, except:

  • when you "attach" to an existing window
  • use the "click" method
  • "set" or "clear" radio buttons or checkboxes
  • when you select an item in a select list
  • when you clear a text field
  • when you "goto" a new page
  • when you "submit" a form
  • when you "back", "forward" or "refresh" the browser
  • when you "fire_event" on any element

This means that you do not have explicitly code for waiting for a browser page to load. Consider the following:

WRONG

ie = Watir::IE.attach(:title, 'My Window')
ie.wait
ie.link(:id, 'mylink').click
ie.wait
assert(ie.link(:id, 'mynewlink).exists?)

RIGHT

ie = Watir::IE.attach(:title, 'My Window')
# no need to wait, ie.wait called implicitly
ie.link(:id, 'mylink').click
# no need to wait, ie.wait called implicitly
assert(ie.link(:id, 'mynewlink).exists?)

Consider also the following:

"Watir is deterministic. Watir does not wait X seconds. It waits until the page is loaded. Period."
- Bret Pettichord

When a page loads, the only thing Watir waits for is the page to finish loading. If Watir is pointed to a browser instance that has finished downloading, it works off of whatever is loaded without implicitly waiting for things to exist. So, if you direct Watir to interact with an element that does not exist, Watir will throw an ObjectNotFound error immediately without waiting additional time to fail.

If you have to wait...

However, there are some circumstances where you will need to wait.

1 - New Windows

Navigating to a new page with the existing IE window will not require you to define any special behavior. However, if you perform an action that brings up a secondary page (pop-up) you will need to define some code to wait on it's existence. Luckily, if you can deal with pop-ups with the "attach" method, there is already an implicit wait in the attach method. If you have a modal dialog box or other windows related dialog box (Security Alert Box, Login, File Download, etc.) you will need to create your own wait methods. See this section for more info.

2 - Objects that depend on Javascript to show up

Sometimes IE thinks it's done before everything is loaded. Unfortunately, IE#wait doesn't always work here because it exits whenever IE thinks it's done. So, if this is your problem, you will need to tell Watir to wait. There are several ways to solve this, here are two.

If you want to wait forever, consider the following:

sleep 1 until ie.text.include? "new results are loaded"

This code will keep sleeping until the text "new results are loaded" appears on the page. However, you may not want to wait forever. Using the following code will wait; for the text "new results are loaded" to appear on the page OR 60 seconds, whichever comes first.

wait_until {ie.text.include? "new results are loaded"}

Sometimes you need to wait for something to happen in the Application under test before you interact with it. Sleep statements are hardcoded and lock you down into a certain number of seconds before moving through your test. To avoid that, we've written a polling mechanism in the latest versions of Watir - the wait_until method.

An example might be that you're loading the Google home page and for some reason it's taking time to load. Here's a basic contrived script with a sleep statement.

require 'watir'

browser = Watir::Browser.start('http://www.google.com')
sleep 5     # we need to wait for the page to load and on a subjective basis I've chosen 5 seconds which works on my machine
browser.text_field(:name, 'q').set('ruby poignant')
....

Unfortunately the sleep is hardcoded and doesn't work for anyone else on my team who have slower network connections, my connection has gotten faster, but it still waits for 5 seconds before setting the text field.

Watir has a Watir::Waiter class with a wait_until method that can poll for a certain condition to return true before continuing on or erroring out. By default it checks the condition every half second up until 60 seconds. So I rewrite my code to look like this:

require 'watir'
browser = Watir::Browser.start('http://www.google.com')
# In this case all I care about is the text field existing, you could
# check title, text, anything you're expecting before continuing
Watir::Waiter::wait_until { browser.text_field(:name, 'q').exists? }
browser.text_field(:name, 'q').set('ruby poignant')
...

It now works for me with a half second delay, but also works for the other members of my team who have network delays up to a minute. If you're considering using sleep, use wait_until instead. It will make your test code more resilient to timing issues in those cases where you really need to use it.

3 - Handling asynchronous javascript / AJAX

if you include asynchronous JS in your definition of done, you'll need to code around that specifically. There's an open feature request to have Watir's wait method track XHRs and timers that are launched when the page is loaded, and wait for them as well. That may be tricky to do though, and with an all-volunteer project, it really depends on someone making the time to do it.

In lieu of that, one option is having the application keep track if XHRs and timers that it kicks off, and setting the some value to true when they are all complete. For example:

def wait_until_loaded(timeout = 30)
    start_time = Time.now
    until (however_your_application_reports_its_loaded == 'true')  do
      sleep 0.1
      if Time.now - start_time> timeout
        raise RuntimeError, "Timed out after #{timeout} seconds"
      end
    end
  end

A simpler option is:

tries = 0
until browser.link(:text, /link_to_wait_for/).exists? do
  sleep 0.5
  tries += 1
end
browser.link(:text, /link_to_wait_for/).click
end

Another option is to retry until timeout or no exception is raised.

def rescue_wait_retry(exception = Watir::Exception::UnknownObjectException, times = 10, seconds = 2, &block)
  begin
    return yield
  rescue exception => e
    puts "Caught #{exception}: #{e}. Sleeping #{seconds} seconds." if $DEBUG
    sleep(seconds)
    @ie.wait
    if (times -= 1)> 0
      puts "Retrying... #{times} times left" if $DEBUG
      retry
    end
  end
  yield
end

@ie.link(:url, %r|confirmdelete\.action\?educationId|).click #This starts some ajax stuff
rescue_wait_retry { @ie.button(:id, 'form_0').click }

Not sure about this one but this is a good introduction to the problem.

Also, from the thread "[wtr-general] SUMMARY: Enable/disable JS support in IE"

Q - How to check from watir code whether web page was reloaded or it was updated dynamically withJS.
A - Not so easy to implement for HCOM application due to a lot of ajax stuff. The following code should work when JS is disabled, plus we can pass additional condition to check here (e.g. waiter for object appearance on the page). Note: $ie is global IE instance in our framework. Also I think that we need to use something like click_no_wait method to reload the page, otherwise IE.wait() method won't let us do the check until page is ready. What I was asking here is how to check that page is reloading rather than just some elements on the page are updated with JS.

class Page
    def loading?(additionalCondition = false)
      if $ie.ie.busy || ($ie.ie.readyState != 4) || additionalCondition
        true
      else
        false
      end
    end
end

4 - Yourself while you watch a script execute

Maybe you are watching the script execute and you just want to slow things down for troubleshooting reasons. Of course, there is always the venerable (and oft overused) "sleep" method. Using "sleep( x )" will cause the script to pause for x seconds.

If, however, you are annoyed by the slowness of your script, you can always make it faster:

ie = Watir::IE.new
ie.speed = :fast

or

ie = Watir::IE.new
ie.speed = :zippy

See this for more information.

click_no_wait

Information here about what this method is all about.

Continuing testing if IE refuses to load a page

Explain these:

begin
  ie = Watir::IE.start(:url , /Webpage Dialog/)
rescue
  Watir::Exception::NoMatchingWindowFoundException
  puts "Failed to find new pop up window!"
  exit
end
require 'watir'
include Watir
@ie = IE.new

begin
  @ie.goto('http://here.com') #the code you want to try
rescue #the code you will throw exceptions
  Watir::Exception::NavigationException
  puts "Failed to find the URL"
  exit
end

Alan, i reviewed the first part and made several changes based on a review of the code. Surprisingly most input element changes trigger an implicit wait, except for changing text fields value. This was probably done to improve performance, but may be an example of something that we really should make easy for people to configure.

Thank you, Alan, this page is very useful one since it can give an ability to understand how watir drives browser (considering execution of commands one by one).
Also, it would be nice to have detailed description of 'click_no_wait' functionality as well as filling this page with additional info.

Posted by Victor at Oct 20, 2008 10:11; last updated at Oct 20, 2008 10:24

Alan,

You've got  an odd ")" symbol in your examples.
assert(ie.link(:id, 'mynewlink).exists?))
This is not a serious problem but it would be better to delete it

Where it says "# no need to wait, ie.wait called explicitly" in two places above, shouldn't it actually say:

  # no need to wait, ie.wait called implicitly 

It's great to see testers encouraged to do proper synchonization.

fixed the above two problems

Site running on a free Atlassian Confluence Community License granted to OpenQA. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.6 Build:#812 Aug 06, 2007) - Bug/feature request - Contact Administrators