Click here to Skip to main content
Click here to Skip to main content
Technical Blog

UIAutomation, Coded UI Tests, AutomationPeer, and WPF/Silverlight custom controls

, 25 Mar 2011 Ms-PL
Rate this:
Please Sign up or sign in to vote.
In this post, we will see how to make a custom control fully useable in a Coded UI Tests scenario.

Introduction

The Coded UI Tests, available in Visual Studio Ultimate or Premium, enable the creation of automated tests for the User Interface. This is a really nice feature because you are no more forced to make "hand made" tests which take hours to be performed. The WPF controls in the framework are ready to be used by the Microsoft UI Automation which is itself used by the coded UI tests. This means that when you use the screen recorder to record the tests on your UI, it is able to find the several controls used in your application.

When you create your own custom controls or extend a standard one, the recorder would not be able to find them at first and so a whole part of the screen may not be available for tests. Actually, it is possible to record a test but every step will be done using the screen position: click at (120,30), drag from (120,30) to (10,40). This is really annoying because any change in the UI may break all your tests.

In this post, we will see how to make a custom control fully useable in a Coded UI Tests scenario. We will answer the question "Why cannot the code UI test recorder find anything inside my WPF or Silverlight custom control ?"

Note: the same technique is used by the accessibility client and by enabling this feature, you also put at ease people using your application through UI automation clients, like the partially-sighted person.

How it works

Everything is based on a class named AutomationPeer. When a UI automation client analyzes your user interface, it looks for an automation peer and uses it to walk through the tree of peers. The tree of peers is nearly a visual tree but it exposes only the relevant part of the interface.

Every standard control has a corresponding automation peer already defined. But if you add new parts on them or if you build a new control from scratch, you have to provide a related automation peer.

If not, the automation peer will not be aware of these parts and won’t be available to the automation clients. Also, if a standard control is used but the “normal” visual tree is broken, for example, by splitting the normal items in several parts, it is necessary to expose the new parts. I’ve seen this in some controls where the content of the standard content presenter was taken apart to be placed in another place of the Visual Tree. As you will see, this is done in the GetChildrenCore method of the new AutomationPeer.

How to make your controls available to the UI Automation client

The AutomationPeers are created and retrieved for each control in the method named OnCreateAutomationPeer. The custom control must override this method to provide an AutomationPeer related to its specific implementation.

If the control is built from scratch and its base class is Control, there is no built-in automation peer because there is simply none at all for the Control class. However, the FrameworkElementAutomationPeer class can be used as a base class.

If the controls inherits from another standard one like TabControl, it’s possible to inherit from its corresponding AutomationPeer and customize its behavior. This is especially needed if new controls are added to the template of the control because the basic AutomationPeer (let’s say TabControlAutomationPeer) does not know about them. By convention, the automation peer class name starts with the name of the related control and ends with AutomationPeer.

There are several methods in the AutomationPeer that can be overridden to make it work the right way. The methods in bold are the ones which are required at the least:

  • GetClassNameCore: it returns the name of the class the AutomationPeer is built for.
  • GetAutomationControlTypeCore: returns an enum telling which behavior implements the automated control. For example, it can a be Button, Tab, Window, etc.
  • GetPattern: tells which kind of functional behavior is fulfilled by the control: IRangeValueprovider, ITextProvider, IValueProvider, etc.
  • GetChildrenCore: this method returns a list of AutomationPeers which are related to the children of your control. If you add a new visual part to a standard control, this is where you must add the new child.
  • IsControlElementCore and IsContentElementCore: tell the UI Automation client which type of automated control it is: for reading or for interactive purpose. These can improve the performance when being used as a filter.

Also, you can use the static method CreatePeerForElement of the UIElementAutomationPeer class to create an AutomationPeer of a known control. This is especially helpful when a new control has to be added.

Finally, the UI Automation clients do not follow the changes in the UI and they have to be noticed manually. This is done via the RaisePropertyChanged on the relative automation peer and has to be done on every change. This is very similar to what is necessary to be done in the INotifyPropertyChanged to notify the binding of a change.

Here is an example of an AutomationPeer for the HeaderedControl in the Amazing WPF control library.

1: public class HeaderControlAutomationPeer : FrameworkElementAutomationPeer
   2: {
   3:     public HeaderControlAutomationPeer(FrameworkElement owner)
   4:         : base(owner) 
   5:        { 
   6:        if (!(owner is HeaderedControl)) 
   7:          throw new ArgumentOutOfRangeException(); 
   8:        }
   9:  
  10:     protected override string GetNameCore()
  11:     {
  12:         return "HeaderContentControl";
  13:     }
  14:  
  15:     protected override AutomationControlType GetAutomationControlTypeCore()
  16:     {
  17:         return AutomationControlType.Header;
  18:     }
  19:  
  20:     protected override System.Collections.Generic.List<AutomationPeer> 
                    GetChildrenCore()
  21:     {
  22:         List<AutomationPeer> peers = base.GetChildrenCore();
  23:         var peer = UIElementAutomationPeer
  24:                    .CreatePeerForElement(MyOwner.PART_Header);
  25:         if (peer != null) peers.Add(peer);
  26:         return peers;
  27:     }
  28:  
  29:     private HeaderedControl MyOwner
  30:     {
  31:         get { return (HeaderedControl)base.Owner; }
  32:     }
  33: }

And here is an example of code you can implement to raise the property changed event on a peer:

   1: if (AutomationPeer.ListenerExists(AutomationEvents.PropertyChanged))
   2: {
   3:     HeaderControlAutomationPeer peer =
   4:         UIElementAutomationPeer.FromElement(PART_Header) 
   5:                 as HeaderControlAutomationPeer;
   6:  
   7:     if (peer != null)
   8:     {
   9:         peer.RaisePropertyChangedEvent(
  10:             RangeValuePatternIdentifiers.ValueProperty,
  11:             (double)oldValue,
  12:             (double)newValue);
  13:     }
  14: }

Interesting links

Please let me know if you have any questions!

 You can also read this article on my blog. 

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

jmix90
Software Developer http://wpf-france.fr
France (Metropolitan) France (Metropolitan)
Jonathan creates software, mostly with C#,WPF and XAML.

He really likes to works on every Natural User Interfaces(NUI : multitouch, touchless, etc...) issues.



He is awarded Microsoft MVP in the "Client Application Development" section since 2011.
 

You can check out his WPF/C#/NUI/3D blog http://www.jonathanantoine.com.

He is also the creator of the WPF French community web site : http://wpf-france.fr.

Here is some videos of the projects he has already work on :
Follow on   Twitter

Comments and Discussions

 
GeneralMy vote of 5 Pinmembernagancy27-Apr-11 23:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141030.1 | Last Updated 25 Mar 2011
Article Copyright 2011 by jmix90
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid