 | 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:
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: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:# 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/).cick
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.
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:
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:
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.