Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Tuesday, August 26, 2008

Ajax and Selenium: waitForCondition()

Selenium is a very useful tool but it can be very, very obtuse.

One challenge is dealing with Ajax; you might click on a button, but without a full page refresh, it's hard to know when to look for expected changes via Ajax and DHTML.

In the past, my test suites had short sleeps, a few hundred milliseconds. This makes them fail sporadically ... every once and a while on my MacBook Pro I'm doing so much other stuff while the tests run that the timing goes screwy.

You're then left with a difficult choice: sleep too short and the tests may fail. Sleep too long and your tests will always be slow.

Fortunately, there's a third option: Selenium's waitForCondition call. Of course, their documentation is worthless.

What it is supposed to do is evaluate a JavaScript snippet repeatedly, until the snippet returns true. However, it's tricky to get right. Like much in JavaScript, it's about context.

In my case, I wanted to wait for a client-side popup <div> to appear:

        type("amount", "abc");
        type("quantity", "abc");

        click(SUBMIT);

        waitForCondition("document.getElementById('amount:errorpopup')", "5000");

        assertText("//div[@id='amount:errorpopup']/span", "You must provide a numeric value for Amount.");
        assertText("//div[@id='quantity:errorpopup']/span", "Provide quantity as a number.");

JavaScript treats null as false, and getElementById() returns null if an element with the id does not exist.

I'm making the assumption that once one of two <div> elements appears, they both will. I then use some XPath to get the text inside the <span> inside each <div>, to make sure the correct message was displayed to the user.

But this code doesn't work.

The problem is that document isn't what you'd expect; I'm guessing that it's some other frame inside the browser (Selenium's UI and code executes in one frame, which runs the actual application inside the second frame).

The solution took some research and the sacrifice of a few small furry animals to obtain:

        waitForCondition("selenium.browserbot.getCurrentWindow().document.getElementById('amount:errorpopup')", "5000");

That works, and it works much faster than adding a Thread.sleep() in the middle of my code.

8 comments:

Jesse Kuhnert said...

Ran in to a similar problem/solution wrt ajax and selenium testing.

We also wrapped the base selenium test object so that we could do streamlined chained calls like:

click(SUBMIT).waitForItemAppear("unique id of element");

that's pseudo code but more or less how it worked

WFrag said...

I had same problem, but I solved it by cycle in the Java code which periodically checks for condition with small sleep.

andrey said...

Heya,

Selenium RC is a close relative of Core, and in Core reference we read:

Note that, by default, the snippet will run in the context of the "selenium" object itself, so

this

will refer to the Selenium object. Use

window

to refer to the window of your application, e.g.

window.document.getElementById('foo')

If you need to use a locator to refer to a single element in your application page, you can use

this.browserbot.findElement("id=foo")

where "id=foo" is your locator.


Thats why your code did not work properly - you should use "window.document" instead of just "document". And you can even use (if I am not mistaken) "this.browserbot.findElement()" to use XPath etc.

Andreas Andreou said...

Since it's trivial to add custom commands to selenium, i'd suggest authoring a waitForAjax command.

It would just be used in place of the 'short sleeps'

0gre said...

Hey, I was having similar problems with AJAX, and this solution worked great for me.

Thanks for the help!

One Tostada said...

Rock On!!

riz said...

nice work! that worked for my ajax site.

I had to use window.document.getElementById

JustIntoAutomation said...

My java code is selenium.waitForCondition("function getdivHTML() {" +
" var divArray = selenium.browserbot.getCurrentWindow().document.getElementsByTagName('textarea');" +
" for (var i = 0; i#14) in at line number 14

Suggestions please.