Saturday, January 16, 2010

Waiting for Windows to Load in Automated GUI Testing

I recently started using Microsoft's UiAutomation framework to do some automated GUI testing. It's slightly better than using Win32 directly (subject of a separate post), but, like all things based in reality, leaves a little something to be desired.

The Ui Automation framework is built around the AutomationElement. The AutomationElement provides methods for finding child windows by their name (or title) and by Automation ID. This is great. With the help of UISpy it's very easy to find any element in an application (even ones with no title).

In the wonderful world of automated GUI testing you often cannot predict how long it will take for a particular UI element to load. Consequently, you are forever writing Thread.Sleep(3000). Not only does this get real old real fast, but it's not very robust since the continuous integration (CI) server running the tests could run significantly slower then your development box.

To solve this problem I wrapped AutomationElement.FindFirst() in a new method FindElementByName() and added some logic to handle the fact that the element may not be immediately available.
/// <summary>
/// Find an element by its Title or Name
/// </summary>
/// <param name="root">
/// The root element to start searching from
/// </param>
/// <param name="name">
/// The Title or Name of the element to search for
/// </param>
/// <param name="retries">
/// The maximum number of times to retry if the element is
/// not found
/// </param>
/// <returns></returns>
public AutomationElement FindElementByName (AutomationElement root,
   string name, uint retries)
{
   Util.ValidateNotEmpty ("name", name);

   Condition c = new PropertyCondition (
   AutomationElement.NameProperty, name,
   PropertyConditionFlags.IgnoreCase);

   if (null == root)
   {
      root = AutomationElement.RootElement;
   }

   // First look for imediate children
   AutomationElement ae = root.FindFirst (
      TreeScope.Element | TreeScope.Children, c);

   if (null == ae)
   {
      ae = root.FindFirst (
         TreeScope.Element | TreeScope.Descendants, c);
   }

   // If we have not found the window by now then it may be slow in
   // opening, so we wait 'time' sec and try again.
   if (null == ae && retries < RETRY_LIMIT)
   {
      retries++;
      uint time = RETRY_FACTOR * retries;

      Log ("  Element \"{0}\" not found, retrying in {1} ms",
         name, time);

      Thread.Sleep ((int)time);

      ae = FindElementByName (root, name, retries);
   }
   return ae;
}

No comments:

Post a Comment