I gave a presentation called ”Being good at waiting – Using Selenium to test Ajax-intensive pages” in an unconf session at the Selenium Conference in London.
The audience was great! Thanks everybody! I certainly didn’t know everything there’s to know about the subject, and that resulted in an interactive session where people from the audience would share their experience and answer some questions. That was so cool 🙂
Anyway, the point I wanted to make was this: when you google for Selenium/WebDriver and Ajax, you still find plenty of examples where the naïve approach, sleeping (as in Thread.sleep()
and similar constructs) is used. My main message was – don’t ever do that! Use a proper WebDriver wait, since it handles the sleeping correctly and plays well with ElementNotFoundExceptions.
I also mentioned implicit waits as something that can be used if you don’t have too many Ajax calls and want to hide that complexity behind a global timeout.
My second point was that there is a category of Ajax calls that have no side effects on the DOM, i.e. when the call completes, nothing happens in the DOM. The example was validation by third party libraries. If you control the code, you can always make it affect the DOM somehow, but not if a third-party library handles the call. In such cases you can intercept the Ajax sending function and the callback using Javascript. In the sending function, a counter can be incremented, and it can be decremented in the callback. This is a canonical way of counting the number of ongoing Ajax calls, and variations of this technique should work with every Javascript library out there with some tweaking. This might not be a silver bullet, but it has worked well enough in our tests.
I actually never got the question why I ran all my code samples as unit tests. None of them actually verified anything; all they did was to print things to stderr. Apparently the reason wasn’t interesting enough, but if it made someone wonder: It was because I wanted my samples to be easy to turn into tests if necessary.
By the way… I totally forgot to mention the AjaxElementLocator* classes.
Some comments and suggestions from the audience:
- Implicit waits don’t help when waiting for elements that are not there
- You can check for non-presence of elements by waiting for the element to be present for 0 seconds and accepting the exception.
- If you need to, and can, add Ajax call counting in the code of the page, not by injecting the variable through Javascript. (The latter assumes that you don’t have control over the tested page.)
- Don’t rely on jQuery’s active variable to check for ongoing Ajax calls. It may indicate other activity as well. (True, but you would probably want to wait for that too in your test, so it may not be that big a problem.)
- There is a class called
org.openqa.selenium.support.ui.ExpectedConditions
(in Java) that has a lot of static methods that can be used as predicates forWebDriverWait
. That way, you don’t have to implement them from scratch. (I simply didn’t know that.)
All in all, I think we had a productive session, in which we unfortunately didn’t find a sliver bullet for the asynchronicity challenges, but we managed to exchange a lot of knowledge and got a common view of where the API is, and what techniques we can use.
The slides are here, and I plan to post the code samples on Github soon. Stay tuned.
Enjoyed the talk, specially the zero side effect wait. Any update on the code?
Thanks!
I’ll publish the code this weekend. I’ve been away on vacation.
I also enjoyed your talk, some new things in there for me.
Any update in the code?
Can’t find it yet on github
Thank you. Much appreciated. The code is here: https://github.com/alexander-t/selenium-samples. I’m sorry about the delay.
Thanks for sharing.
The jquery status check already fixed a bug here.
Hi Alexander,
First thanks for your talk, I really enjoyed it.
I tried one of your examples but I get the following exception being thrown. Any ideas?
org.openqa.selenium.WebDriverException: Script execution failed. Script: window.ajaxCallCount = 0;var oldOpen = xmlhttp.open;var oldOnreadestatechange = xmlhttp.onreadystatechange;xmlhttp.open = function() { window.ajaxCallCount++; oldOpen.apply(this, arguments);};xmlhttp.onreadystatechange = function () { if (xmlhttp.readyState == 4) { window.ajaxCallCount–; } oldOnreadestatechange.apply(this, arguments);};;
xmlhttp is not defined (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 29 milliseconds
Build info: version: ’2.23.1′, revision: ‘unknown’, time: ’2012-06-08 12:33:29′
System info: os.name: ‘Windows 7′, os.arch: ‘amd64′, os.version: ’6.1′, java.version: ’1.7.0_02′
Driver info: driver.version: Selenium2Driver
Session ID: a4d0dc10e173c7cd338aaaba24bb40fe
Thanks,
James