Thursday, December 8, 2011

Selenium Web Driver animation synchronization

A simple test case:
  • A user clicks on a button
  • A pop-up slides down
  • The user clicks close button in the pop-up
  • The pop-op is sliding up and closes

That's a simple GUI use case which uses animation (gradual resizing). Normally when automating a pup-up via Selenium you'll click a button, after that check visibility of a DIV and then click the close button. The problem is that the browser won't handle any clicks until animation is over.

Using Thread.Sleep() or any similar execution delay is not a good technique (slow, hard to choose optimal value for both slow and fast environments etc.). Bellow you may find a C# extension method for Selenium IWebDriver interface which utilizes polling mechanism in order trace element style changes and decide whether animation is over:


public static void WaitForCssStyleChange(this IWebDriver webDriver, string xPath,
bool failOnNoCHange = true)
{
var i = 0; // poll counter
var cumulative = 0; // the number of CSS/style checks that didn't find any changes
var element = webDriver.FindElementEx(By.XPath(xPath));
var originalCss = element == null ? String.Empty : element.GetAttribute("class");
var originalStyle = element == null ? String.Empty : element.GetAttribute("style");
var prevCss = originalCss;
var prevStyle = originalStyle;

while (i < SeleniumConfiguration.PollingThreshold) // Do polls until threshold is exceeded
{
element = webDriver.FindElementEx(By.XPath(xPath));
if (element != null)
{
var css = element.GetAttribute("class");
var style = element.GetAttribute("style");

// if the previous CSS/Style is same - increase the counter
if ((css == prevCss || style == prevStyle)) cumulative++;
if (cumulative > 2) return; // most like animation is over

prevCss = css;
prevStyle = style;
}

Thread.Sleep(SeleniumConfiguration.PollingPeriod * 100);
i++;
}

if (element == null) throw new TimeoutException("Element not found "); // fail if no element
if (failOnNoCHange && (originalCss == prevCss) && (originalStyle == prevCss))
throw new TimeoutException("Element was not changed"); //require element style/CSS change
}

The approach is similar to a common AJAX synchronization solution via polling (where a test waits for some element to become present or visible) . The difference is that in our case we are trying not to register a single DOM change, but see when element's style is in progress of changing and when it stops. Style changes stopped - animation completed and the element is ready for use.

Worked for me in IE8, 9 and FF.

1 comment:

  1. if (failOnNoCHange && (originalCss == prevCss) && (originalStyle == prevCss))
    throw new TimeoutException("Element was not changed"); //require element style/CSS change

    there is a typo - (originalStyle == prevCss) should be (originalStyle == prevStyle)

    ReplyDelete