Windows desktop application as well as rendered Internet Explorer web page elements can be automated for unit testing. There are two ways of doing that, by using System.Windows.Automation or with UIAComWrapper, both of these libraries uses exactly same interface, main difference are that System.Windows.Automation uses older Windows Automation COM API and not all properties are displayed correctly, and UIAComWrapper wraps itself around newer version.

When searching for element to automate in third party applications, there is tool called Uiautomationverify which will allow to easily see application structure.

uiVerify

Simple element search

Simple application that will print all Internet Explorer tabs title to console.

windowHandle = FindWindow(InternetExplorerClass, null);

if (!windowHandle.Equals(IntPtr.Zero))
{
    //create filter to improve search speed
    var localizedControlType = new PropertyCondition(
        AutomationElement.LocalizedControlTypeProperty,
        "tab item");

    //get all elements in internet explorer that match our filter
    var elementCollection =
        AutomationElement.FromHandle(windowHandle)
            .FindAll(TreeScope.Subtree, localizedControlType);

    //iterate through search results
    foreach (AutomationElement item in elementCollection)
    {
        Console.WriteLine(item.Current.Name);
    }
}
else
{
    Console.WriteLine("Internet explorer not found");
}

 

Source code

 

Element patterns

Elements has patterns such as: TextPattern (used to get text, e.g. when element is text box), InvokePattern (when element can be clicked, e.g. button, link) and many more.

This function will find Play or Pause button in active Internet Explorer tab and cache pattern, so we won’t need to search for it all the time:

public bool Initialize()
{
    string lpszParentClass = "IEFrame";
    IntPtr ParenthWnd = new IntPtr(0);
    ParenthWnd = NativeMethods.FindWindow(lpszParentClass, null);
    if (!ParenthWnd.Equals(IntPtr.Zero))
    {
        var namePlayProperty = new PropertyCondition(AutomationElement.NameProperty, "Play");
        var namePauseProperty = new PropertyCondition(AutomationElement.NameProperty, "Pause");

        var frameworkIdProperty = new PropertyCondition(AutomationElement.FrameworkIdProperty, "InternetExplorer");
        var controlTypeProperty = new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button);
        var hasInvokePatter = new PropertyCondition(AutomationElement.IsInvokePatternAvailableProperty, true);

        var condition = new AndCondition(controlTypeProperty, hasInvokePatter, frameworkIdProperty, new OrCondition(namePlayProperty, namePauseProperty));
        var elementCollection = AutomationElement.FromHandle(ParenthWnd).FindAll(TreeScope.Subtree | TreeScope.Element | TreeScope.Children, condition);

        foreach (AutomationElement item in elementCollection)
        {
            InvokePattern = (InvokePattern)item.GetCurrentPattern(InvokePattern.Pattern);
            return true;
        }
    }

    return false;
}

 

And to invoke pattern (left mouse click simulation):

InvokePattern.Invoke();

 

If we would skip this kind of caching and always search for element and then invoke it, whole IE window will be restored and brought to focus and only then element pattern invoked.

Source code

 

Change events

It is also possible to attach event handlers to get notification when one or several element properties changes (AddAutomationPropertyChangedEventHandler) when new element was added or removed (AddStructureChangedEventHandler).

Simple example application that listens window position change and will move with Internet Explorer:

private void Button_Click(object sender, RoutedEventArgs e)
{
    string lpszParentClass = "IEFrame";
    IntPtr ParenthWnd = new IntPtr(0);
    ParenthWnd = FindWindow(lpszParentClass, null);
    if (!ParenthWnd.Equals(IntPtr.Zero))
    {
        var parent = AutomationElement.FromHandle(ParenthWnd);
        Automation.AddAutomationPropertyChangedEventHandler(parent, TreeScope.Element, WindowMoved, AutomationElement.BoundingRectangleProperty);
    }
}

private void WindowMoved(object sender, AutomationPropertyChangedEventArgs e)
{
    App.Current.Dispatcher.BeginInvoke(new Action(() =>
    {
        if (e.NewValue != null)
        {
            var newPosition = (Rect)e.NewValue;
            this.Left = newPosition.Left;
            this.Top = newPosition.Top;
        }
    }));
}

Source code

Comments


Comments are closed