Wait For Ajax

http://davidvollbracht.com/2008/6/4/30-days-of-tech-day-3-waitforajax


30 Days of Tech: Day 3 - WaitForAjax

June 3rd, 2008

Here’s a quick one that came out of writing Selenium tests for an application with a fair amount of Ajax calls. Selenium provides a waitForPageToLoad command that does exactly what it says. It’s typically used after an action (such as clicking a link) would cause a page refresh and you don’t want to continue your test until the page is finished loading (which is most of the time).

Unfortunately, waitForPageToLoad is not so useful clicking a link that causes an Ajax request to happen. Since there is no new page loading, waitForPageToLoad doesn’t make any sense. Selenium provides a host of other waitFor commands to wait for such things as elements appearing on the page or form values to change. These waitFor commands can be used to wait until an Ajax request finishes as long as it the request affects the page in a way that can be detected. For example, if the request adds a div whose id is “new_div” to the page, you can use waitForElementPresent(‘new_div’) to have the test wait until “new_div” present, ensuring that the Ajax request has finished.

Unfortunately, this approach turns out to be error prone and brittle. For instance, if the Ajax request doesn’t change the page in the manner the test is expecting (say the you changed the div’s id to “brand_spanking_new_div”), the test must wait for the entire length of the timeout to fail. If only you could simply wait for the Ajax request to finish just like waitForPageToLoad instead of waiting on side effects of the Ajax call.

If you’re using the prototype.js library for Ajax’s requests, this is actually fairly easy. There are two key components. The first is Selenium’s waitForCondition command, which repeatedly executes a snippet of javascript, waiting for it to produce a true value. The second is prototype’s Ajax.activeRequestCount, which indicates the number of pending Ajax requests on the page. By the power of these two tools combined, your tests can wait for the Ajax request to finish nicely and then make assertions about the state of the page following the call. It looks something like this (if you’re using Selenium RC in Ruby):

selenium.open "/page_with_an_ajax_link" 
selenium.click "link_that_triggers_ajax"
selenium.wait_for_condition "selenium.browserbot.getCurrentWindow().Ajax.activeRequestCount == 0", 10000
assert selenium.is_element_present("new_div"), "new_div was not present"

In practice this works amazingly well. Writing tests around Ajax goes much faster since you don’t have to divine what the proper page change to wait for is. Furthermore, the technique naturally works for multiple Ajax requests triggered from a single action. And finally it puts your test assertions where they should be—as assertions rather than flow control waiting for the application. I highly recommend the approach.

There is one caveat, however. At least in prototype 1.5.0 (I haven’t checked other versions), if your application specifies a state change hook (like onComplete) for an Ajax request and that hook causes an exception, prototype never decrements the activeRequestCount even though the request is done. In prototype.js, in the respondToReadyStateChange function, you can change this:

try {
(this.options['on' + state] || Prototype.emptyFunction)(transport, json);
Ajax.Responders.dispatch('on' + state, this, transport, json);
} catch (e) {
this.dispatchException(e);
}

to this:

try {
(this.options['on' + state] || Prototype.emptyFunction)(transport, json);
} catch (e) {
this.dispatchException(e);
} finally {
Ajax.Responders.dispatch('on' + state, this, transport, json);
}

to fix the problem. In 1.5.0 the change is on line 942.

There you have it. A simple recipe to make your tests wait for Ajax requests to finish. I wouldn’t be surprised if a similar technique is applicable when you’re using a javascript library other than prototype. If you implement one, share it with the world!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章