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.
Ran in to a similar problem/solution wrt ajax and selenium testing.
ReplyDeleteWe 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
I had same problem, but I solved it by cycle in the Java code which periodically checks for condition with small sleep.
ReplyDeleteHeya,
ReplyDeleteSelenium 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.
Since it's trivial to add custom commands to selenium, i'd suggest authoring a waitForAjax command.
ReplyDeleteIt would just be used in place of the 'short sleeps'
Hey, I was having similar problems with AJAX, and this solution worked great for me.
ReplyDeleteThanks for the help!
Rock On!!
ReplyDeletenice work! that worked for my ajax site.
ReplyDeleteI had to use window.document.getElementById
My java code is selenium.waitForCondition("function getdivHTML() {" +
ReplyDelete" var divArray = selenium.browserbot.getCurrentWindow().document.getElementsByTagName('textarea');" +
" for (var i = 0; i#14) in at line number 14
Suggestions please.