Monday, July 5, 2010

Automation Peers for Automation of WPF applications

We need UI to be tested for the required functionality the way it would be presented to the user. MVVM has provided us the way to test some of the functionalities through view models but we always want to test the behavior of the application as it is being used by end users. The answer is right, write some automation scripts. But how??

WPF provides us the facility to write automation tests using Automation Peer framework. The whole idea is to provide a framework for imitating the actions of actual users. It seems same as calling events directly but it is much better than that. Don’t believe me…see this!

As I said, it is better to use UI automation. It allows behaving as being used by the real user. If the control has some associated command which can’t be executed, automation peer wouldn’t allow the behavior to run.

How does it work?All UI automation works through three Ps (Peer, Provider and Pattern).The whole idea is to get a PATTERN object with for each control. All the automation is done using the properties and methods of this PATTERN object. For getting an automation object, we have to obtain a PEER object, depending upon the control in interest (e.g. TextBoxAutomationPeer for TextBox). The peer object provides a PROVIDER which can be used to get the required PATTERN.

WPF has provided us with different peer objects for different types of available controls. Each Peer defines GetPattern() method, which returns a pattern object depending upon the type of peer (e.g. TextBoxAutomationPeer provides a patterns which implements IValueProvider. These providers are available in UIAutomation.dll (System.Windows.Automation.Providers namespace).

Namespaces:

System.Windows.Automation.Peers

System.Windows.Automation.Providers

Assemblies:- UIAutomation.dll- PresentationCore.dll

Let's see this in PracticeLet us create a small program with the following main form:

In this event, we are getting the values entered in two boxes and adding them. We are displaying result in text block (result). Now let us create the real automation by modifying the constructor of Window1 as follows:

Here we have ValuePattern of two text boxes (operand1 and operand2) using IValueProvider provider obtained through TextBoxAutomationPeer. Finally we have obtained InvokePattern for btnEvaluate using IInvokeProvider and ButtonAutomationPeer.

When we run the application, the form displays as follows:

The form has displayed the exact result in the text block after executing the btnEvaluate_Click event for btnEvaluate method.

Automating Custom Controls:In order to automate custom controls, we would need to define new peers. We will be discussing the details of defining user defined peers later.

Automating from outside:In above discussion, we have seen how to automate the form from within it. Now the problem is when we want to do this automation from outside the form in our test scripts. Do we need to make our controls public? No!!!

WPF allows us to navigate through the peer hierarchy using GetChildren() and GetParent() methods. In order to automate the control, we need to traverse through this hierarchy to the concerned control and automate that. All the automation peers may provide the definition of GetChildrenCore() so that peer system could generate this peer navigation hierarchy. The default definition in UIElementAutomationPeer traverses through the visual tree of elements.

Now we deep dive a bit in the world of Automation Peer. Let's look at the class hierarchy.

Inheritance Hierarchy of Automation Peers:

All control automation peer inherit from FrameworkElementAutomationPeer directly (like TextBlockAutomationPeer) or indirectly (like ButtonAutomationPeer through ButtonBaseAutomationPeer).

Let's play again:Now we provide the same automation to the form from outside the form. Let's remove the automation code from constructor of Window1. Now it should look like as follows:

public Window1(){ InitializeComponent(); }

In order to keep the example simple to understand, We are not creating a separate project. Modify App.xaml as follows:

Now as you might have an idea from the code that we are setting the values of the two operands as before. After invoking the button event, we are getting value of result text box. The message box should show 7.4 on the screen. But it doesn't. Why??

It must be remembered that IInvokeProvider allows an event to be fired, which is asynchronous. It just sends a request to execute its action. It doesn't guarantee when it would be invoked. That is why the message box is always empty because the invoke is executed after we execute the following:

The only solution seems to be invoking the action on a separate thread using dispatcher and waiting for it to return using Thread.join.

Otherwise, just display an empty message box to the user. This is a blocking call as the runtime has to wait for user action. In the mean time, it executes the Invoke(). The new definition of automate() is as follows:

public void automate(){ var myWindow1 = new Window1();

//It is required that a window is available on UI before calling GetChildren on it myWindow1.Show();

Thanks Ajay. From the application side, you don't have to do much except assigning automation Ids.

Your control authors would need to implement automation peer for their custom controls.

Automation is generally a separate project run by a different team in close collaboration with the development team.

In this example, I wanted to present UIAutomation as a concept for WPF applications. You might want to consider some frameworks which provide an abstraction on top of this e.g. White. There are also other good options.