How to wait with Watir

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

When IE.attach does not find a window it throws a NoMatchingWindowFoundException. You can catch the exception instead of letting watir exit your tests. At this point you can build some 'recovery' if you need it or simply continue or exit your test explicitly after logging some test data.

require "watir"

begin
  @ie = Watir::IE.attach(:url , /Webpage Dialog/) #let's say the window does not exist on the desktop
rescue  Watir::Exception::NoMatchingWindowFoundException => e
  puts "Failed to find new pop up window! #{e}"
  # you can now recover or exit or continue
end

When IE.goto is invoked it implicitly waits for the page to load and runs any registered @error_checkers. If you don't have them added to browser then you can explicitly call check_for_http_errors. On HTTP errors encountered the Exception will be thrown. You can rescue the exception and continue with your test and recover or just exit after logging some test data.

require 'watir'
@ie = Watir::IE.new #start new browser window
@ie.goto 'http://asdf asdf.com' #navigation to this page will load Can Not Find Server page #notice the space in url string

begin
  @ie.check_for_http_error # will throw Exception
rescue Watir::Exception::NavigationException => e #catch the Exception
  puts "Page did not load: #{e}" # => 'Cannot find server or DNS Error'
  # you can now recover or exit or just continue
end
 
# OR you can register error_checker which will be called when goto is invoked
require 'watir/contrib/page_checker'
@ie.add_checker(PageCheckers::NAVIGATION_CHECKER)

begin
  @ie.goto "http://marekj.com/asdfasdfasdfa"
rescue Watir::Exception::NavigationException => e #catch the Exception
  puts "Page did not load: #{e}" # => 'HTTP 404 - File not found'
  # you can now recover or exit or just continue
end
Enter labels to add to this page:
Please wait 
Looking for a label? Just start typing.
  1. Jun 20, 2008

    Bret Pettichord says:

    Alan, i reviewed the first part and made several changes based on a review of th...

    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.

  2. Oct 20, 2008

    Victor says:

    Thank you, Alan, this page is very useful one since it can give an ability to un...

    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.

  3. Dec 16, 2008

    rakshasa says:

    Alan, You've got  an odd ")" symbol in your examples. assert(ie.link(:id, ...

    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

  4. Mar 16, 2009

    Danny R. Faught says:

    Where it says "# no need to wait, ie.wait called explicitly" in two places above...

    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.

  5. Mar 24, 2009

    Alan Baird says:

    fixed the above two problems

    fixed the above two problems