Sunday, September 22, 2013

Use ExpectedCondition over Thread.Sleep() in tests, Selenium 2 Webdriver C#

So the theory is you should never use a Thread.Sleep() in automated UI testing because it will be hard to maintain, prone to failure and perform poorly.
  • Whatever you set the value of the wait to, you might have to increase it in the future. On different hardware the "web site" runs slower, when the "web site" being tested changes the waits will need to change.
  • The smaller you set the wait the bigger the chances of future issues, 1,2,3 seconds waits are your worst enemy.
  • If you set the value very high (say 30sec) so you don't need to adjust it in the future you will waste time in execution. Example in 1200 tests it adds up to about one hour of wasted execution time, and you should aim to way more than 1200 tests.
Below are two code snippets that are functionally the same except for the way each waits for the next element.

The wrong way
In this snippet we are clicking the submit button
next we wait a static 1000 milliseconds using Thread.Sleep()
Finally we assert that ByLabel1Div is present by calling ElementExists
My1Page.SubmitButton.Click();
Threading.Thread.Sleep(1000);
Assert.AreEqual(true, Driver.ElementExists(My2Page.ByLabel1Div));

The right way
In this snippet we are clicking the submit button same as above
Finally we assert that ByLabel1Div is present, by calling WaitUntilExists witch return in 0-10 seconds if the element meets the ExpectedConditions of ElementExists
My1Page.SubmitButton.Click();
Assert.AreEqual(true, Driver.WaitUntilExists(My2Page.ByLabel1Div));

In the cases where ByLabel1Div is loaded  by Javascript and Ajax, the time to load the ByLabel1Div  can be affected by many things such as workload on web server, DB server and local machine as well as the network latency.

Selenium 2 Webdriver provides the following 4 ExpectedConditions for you to use (ElementExists, ElementIsVisible, TitleContains, TitleIs). In my open source project I have created additional ExpectedCondition's (ElementNotExists, ElementTextEquals, ElementTextNotEquals, ElementTextContains, ElementTextNotContains, ElementAttributeEquals, ElementAttributeNotEquals, ElementNotVisible)

Here is the magic behind the WaitUntilExists, It is basically a Extension method extending the IWebDriver interface. Its using a WebDriverWait with the ExpectedConditions ElementExists to return as soon as the condition is met.
public static bool WaitUntil(this IWebDriver iWebDriver, Func condition, int maxWaitTimeInSeconds = 10)
{
   var driver = (IWebDriver)iWebDriver;
   var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(maxWaitTimeInSeconds));
   try
   {
      wait.Until(condition);
   }
   catch (WebDriverTimeoutException)
   {
      return false;
   }
   return true;
}

public static bool WaitUntilExists(this IWebDriver iWebDriver, By locator, int maxWaitTimeInSeconds = 10)
{
   return WaitUntil(iWebDriver, ExpectedConditions.ElementExists(locator), maxWaitTimeInSeconds);
}
For more information here's the full file IWebDriverExtension.cs with more WaitUnils*.

In the snippets above you see Click, and then WaitUntilExists, or more generally stated as a pattern: perform action and then validate. This pattern (perform action and then validate) is one you'll want to use often especially for elements that are loaded by Javascript and Ajax.

No comments: