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
May 5th, 2007 at 03:56 PM
Just to be clear, the “recursive” code mentioned above isn’t actually recursive and won’t eat any stack. When you call waitForLoad(), it will either call goOnWithLife() or it will schedule waitForLoad() to be called again in 100ms. After scheduling the new call to waitForLoad(), the existing call to waitForLoad() will exit and any other code you have following your first call to waitForLoad() will run. When that finishes, javascript will sit and wait for an event to occur of a timer to expire. When the timer associated with the the scheduled waitForLoad() call expires, waitForLoad will be called again. Note that the first call to waitForLoad() is not on the stack when the second call occurs.
May 26th, 2008 at 05:22 AM
function waitForLoad() { if (isThePageLoaded()){ goOnWithLife(); } else { // THIS IS RECERSIVE!
// WE’LL KEEP SCHEDULING // THE CHECK UNTIL isThePageLoaded()==true setTimeout(‘waitForLoad()’, 10); } }
the setTimeout above happens asynchronously, it does not stop javascript code processing and halt, it just schedules a function to be called. This will not cause a wait, instead it will cause something to happen in a while. There is a difference. And also, this function, is NOT recursive. You would have to take the setTimout(‘waitForLoad’, 10), which will only wait 10 milliseconds to call waitForLoad anyways, and replace it with just waitFoarLoad(); but that will present stack problems. The other option of creating a loop of some sort and looping until a condition is satisfied is not using that much of a stack but still causes high utilization. I am still looking it to this, and on my todo is investigating how open source systems like WebLoad accomplish this.
May 26th, 2008 at 10:12 AM
Nice!
June 26th, 2008 at 01:57 PM
Wow, this is pretty dang pro. Nice job!
July 15th, 2008 at 10:21 AM
Did this ever get worked out?
August 8th, 2008 at 09:54 AM
The function
function waitForLoad() { if (isThePageLoaded()){ goOnWithLife(); } else { // THIS IS RECERSIVE!
// WE’LL KEEP SCHEDULING // THE CHECK UNTIL isThePageLoaded()==true setTimeout(‘waitForLoad()’, 10); } } does not work. What I am doing here is, I am waiting for a hidden field’s value to be changed by a pop up window opened by the parent window and then submitting the parent page. But the waitForLoad() is called once and the page is submitted wihtout waiting for the value of the hidden field to be changed.