Using Selenium's waitForValue, waitForCondition for Ajax Tests
January 4th, 2007
NOTE: You might have been redirected here. Don’t worry! I’ve moved my active blog to 40. (with egg)
We’re trying to use Selenium to test our ajax-ruby-on-rails projects (a-rails? r-jax? rail-jax? We gotta get some marketing folks on this…) We’ve ended up extending Selenium via their extension framework to write our own custom asserts and such, and things are going pretty well, though we’ve had to spelunk into the Selenium code quite a bit. One of the biggest problem has been the Ajax – since we don’t know when our app is done doing it’s thing (that would be the asynchronous part) the test cases would charge ahead, testing results that weren’t there yet. Selenium has many “AndWait” functions that are handy for synchronous actions: clickAndWait, openAndWait, getMeAChantico™WithAShotAndWait, etc., but they often do not work well for Ajax, since the asynchronous nature of the action does not lend itself (for us anyway) to have an action “complete” in the traditional submit-reply paradigm.
Luckily, Selenium has a feature that is horribly documented: waitForValue, which you can use in the test case directly, and waitForCondition, callable in a JavaScript-based extension. Using a combination of these, you can make your Ajax call and wait for any condition that should happen, such as the DOM changing or values updating. Why the Selenium folks don’t have bright red “hey, use this for your Ajax testing!” flag on these two items is beyond me. We’ve only just had our first success using this and I’ll write more details later.
8 Comments (from old blog):
At 11/30/2005 2:04 PM, Jason Huggins said…
That’s it– I’m firing my marketing department. Seriously though, thanks for your post and we’ll try to do a better job at getting the word out about testing Ajax with Selenium.
Jason “the guy who wrote Selenium” Huggins
At 12/05/2005 11:48 PM, Joseph Moore said…
Hi Jason – I hope I didn’t come across as harsh! We’re having a lot of fun with Selenium and writing more tests every day, and staring to use it to test other projects. I’ll admit that my Ajax knowledge is still green (as in young, not as in not-red :) ) so I was surprised when I found that waitForValue and waitForCondition were exactly what we were looking for.
Also, I have a history of making a fool of myself: a few years ago I was at the Eclipse BOF at JavaOne when I started talking to a guy running a demo. He was showing off a Swing GUI builder that looked very much like VisualAge for Java’s GUI builder, and I mentioned this. “Oh yeah, what did you think of the VisualAge tool?” He asked, provoking a major rant from me about how I hated VAJ’s Swing tools.
“Oh,” he said. “I wrote it”.
Find rock. Crawl under. Hide.
At 12/08/2005 3:17 PM, Dan Fabulich said…
Where exactly did you find the waitForCondition user extension?
At 12/08/2005 7:01 PM, Joseph Moore said…
waitForCondition is implemented in Selenium’s JavaScript; it’s actually how they implemented waitForValue.
Take a look at Selenium’s API for waitForValue: selenium-api.js, line 508. For examples of how they used it, do a search for waitForCondition on in their source. For example, selenium-executionloop.js, line 81:
this.lastCommandResult = result;
if (result.processState == SELENIUM_PROCESS_WAIT) {
this.waitForCondition = function() {
return selenium.browserbot.isNewPageLoaded();
};
}
At 12/16/2005 11:38 AM, Dan Fabulich said…
Thanks. I’ve taken your suggestion and baked it into a packaged user extension, including a new feature allowing you to specify a timeout for how long you want to wait:
http://wiki.openqa.org/display/SEL/waitForCondition
At 12/18/2005 5:01 PM, Joseph Moore said…
Hi Dan –
Nice! We’ll have to download it and give it a try
– Joe
At 6/16/2006 9:38 AM, DrydenMaker said…
Indeed. The wait function worls well. Selenium is certainly at the top of my testing tools list.
At 10/25/2006 1:50 PM, Evan Farrar said…
wait_for_condition(‘Ajax.activeRequestCount == 0’,’5000’)
A Non CPU Intensive JavaScript Wait Loop?
January 4th, 2007
NOTE: You might have been redirected here. Don't worry! I've moved my active blog to 40. (with egg)
I spent much of the week pairing on UI changes on 2 projects, and again the power of CSS is very impressive. It took us about half a day on Monday implementing the UI mockups created by a design firm in CSS, and it make a big difference in the look and feel for the app. I also was exposed to some IE vs. Firefox CSS issues, so that's good stuff to know.
We are now trying to convert Selenium to drive our UI acceptance tests. After investigating Selenium for a few days we realized that we needed a lot more control over the how we wrote our tests, and we also need to take advantage of the drag-and-drop support from the Prototype JavaScript libraries that ship with Ruby on Rails. Selenium definitely does not have the support we need, but it has solved what has turned out to be the Hardest Problem Ever: getting the browser to sit and wait for some content to load. Here's the issue:
- We load our test in an iframe
- The setup code in the test loads our target page in another iframe.
- The test needs to load the target page and wait for it to be fully loaded before simulating clicking on links buttons, etc.
Good luck. Firefox doen't have document.readyState, which is an IE extension. So, we wrote a "wait" loop which just spins for a second or two while the content loads... except that JavaScript loops such as these cause the browser to consume 100% of the CPU, as described in this conversation, preventing the content from loading.
Another option is to use setTimeout('loadMyPage()', 2000). The problem here is that setTimeout() schedules the function to be fired XXX milliseconds in the future, and returns control back to the browser in the mean time. That didn’t do us much good, as we need to sit and wait for the content to load, not to schedule it to load in the future.
After digging into Selenium's code, it looks like they have a clever solution: they use setTimeout() to recursively call the same method again and again, stacking up the calls so that no other browser actions can happen until a state is detected. It's something like this, but don’t quote me since I haven't validated this code yet:
function waitForLoad() {
if (isThePageLoaded()){
goOnWithLife();
} else {
// THIS IS RECERSIVE!
// WE'LL KEEP SCHEDULING
// THE CHECK UNTIL isThePageLoaded()==true
setTimeout('waitForLoad()', 10);
}
}
That's the idea -- keep scheduling the check until the state is reached. Right now we're don't have it working completely yet, but the idea makes sense and I think we're close.
6 Comments (From Old Blog):
At 5/02/2006 12:06 PM, DHTML Kitchen said… Hi there fellow selenium user!
Just in reading your post, I'm wondering: can't you just add event listener to the iframe?
iframe.contentWindow.addEventListener("DOMContentLoaded",
iframeLoaded, false);
I haven't tested it, but it should work...
At 6/28/2006 8:37 AM, SebiF said…
Does waitForLoad work in IE? It doesn't seem to work for me. Any ideas?
At 6/29/2006 8:01 AM, Joseph Moore said… Hi Sebif --
I'm not sure about IE, but there must be a solution because Selenium uses some veriation of this for it's waitForCondition loop. If you're using this for Selenium, take a look at the Using Selenium's waitForValue, waitForCondition for Ajax Tests thread.
I'll try to whip up an example and try it in both browsesrs.
At 10/09/2006 12:20 AM, Anonymous said…
Hi,
Just happened across your blog via Google.
I'd suggest that if you're waiting for something to occur, that you use a while() loop, rather than a recursive function call.
The reason is that if it's done recursively, every time your interval times out, you get stuff accumulating on your stack which could eventually chew up all your memory.
Instead, use this:
while( !isThePageLoaded() )
{
setTimeout('waitForLoad()', 100)
}
At 11/03/2006 6:02 AM, Anonymous said…
Check out the jQuery javascript library. This has a cross browser 'document ready' function...
At 11/03/2006 8:09 AM, Joseph Moore said…
Thanks for the comments, folks. I'll check out jQuery and will (hopefully) post the latest incarnation of this loop that we use in out apps.
I'm