I like your idea of polling the HTML until it's stable. I may add that to my own solution. The following approach is in C# and requires jQuery.
I'm the developer for a SuccessFactors (SaaS) test project where we have no influence at all over the developers or the characteristics of the DOM behind the web page. The SaaS product can potentially change its underlying DOM design 4 times a year, so the hunt is permanently on for robust, performant ways to test with Selenium (including NOT testing with Selenium where possi ble!)
Here's what I use for "page ready". It works in all my own tests currently. The same approach also worked for a big in-house Java web app a couple of years ago, and had been robust for over a year at the time I left the project.
Driver
is the WebDriver instance that communicates with the browser
DefaultPageLoadTimeout
is a timeout value in ticks (100ns per tick)
public IWebDriver Driver { get; private set; }
// ...
const int GlobalPageLoadTimeOutSecs = 10;
static readonly TimeSpan DefaultPageLoadTimeout =
new TimeSpan((long) (10_000_000 * GlobalPageLoadTimeOutSecs));
Driver = new FirefoxDriver();
In what follows, note the order of waits in method PageReady
(Selenium document ready, Ajax, animations), which makes sense if you think about it:
- load the page containing the code
- use the code to load the data from somewhere via Ajax
- present the data, possibly with animations
Something like your DOM comparison approach could be used between 1 and 2 to add another layer of robustness.
public void PageReady()
{
DocumentReady();
AjaxReady();
AnimationsReady();
}
private void DocumentReady()
{
WaitForJavascript(script: "return document.readyState", result: "complete");
}
private void WaitForJavascript(string script, string result)
{
new WebDriverWait(Driver, DefaultPageLoadTimeout).Until(
d => ((IJavaScriptExecutor) d).ExecuteScript(script).Equals(result));
}
private void AjaxReady()
{
WaitForJavascript(script: "return jQuery.active.toString()", result: "0");
}
private void AnimationsReady()
{
WaitForJavascript(script: "return $(\"animated\").length.toString()", result: "0");
}
findElement
waits for an element to be available, but sometimes the element is available before the javascript code is initialized completely, that's why it's not an option. – psadac