Friday 10 May 2013

Metro Apps Automation in C#

After having done heck of research on how do I go about automating my Windows8 app, from among lot many third party tools to Windows UIA itself, I realized UIA was the way to go.

But then there are hardly any samples, that would help you get started with this. There are a couple available for Windows7, but Metro as we all know is a new world of its own.

This post would help you get started with Windows UI Automation for a Windows 8 (aka Metro) app, in C#, in just a few steps. All this information is actually gathered from a variety of tutorials, blogs, msdn links, forums, scattered all over the web and put together in this post.

You can create a Unit Test Project from
 Templates>Other Languages>Visual C# > Test > Unit Test Project
and your test cases would need all the below workflow, after which you can 'assert' all that you want to.
(I'm working on Visual Studio 2012, Ultimate )

1. Auto-launching of your app

This post I must say has been an amazing source of information. It also mentions how do you go about launching your app in C++.
The process and all the content stays true, but there is this below piece of code in C#, doing the same. "<appID>" here is the app ID of the application you want to launch.

 public static void launchApp()
 {
      ApplicationActivationManager appActiveManager =                                       
                              new ApplicationActivationManager();
      uint pid;
      appActiveManager.ActivateApplication("<appID>", 
                            null, ActivateOptions.None, out pid);
 }

public enum ActivateOptions
{
        None = 0x00000000,  
        DesignMode = 0x00000001,  
        NoErrorUI = 0x00000002,             
        NoSplashScreen = 0x00000004,  
}

[ComImport,Guid("2e941141-7f97-4756-ba1d-9decde894a3d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IApplicationActivationManager
{
       IntPtr ActivateApplication([In] String appUserModelId, 
                                  [In] String arguments, 
                                  [In] ActivateOptions options, 
                                  [Out] out UInt32 processId);
       IntPtr ActivateForFile([In] String appUserModelId,
                     [In] IntPtr /*IShellItemArray* */ itemArray, 
                     [In] String verb, 
                     [Out] out UInt32 processId);
       IntPtr ActivateForProtocol([In] String appUserModelId, 
                     [In] IntPtr /* IShellItemArray* */itemArray,                      
                     [Out] out UInt32 processId);
 }


[ComImport, Guid("45BA127D-10A8-46EA-8AB7-56EA9078943C")]
//Application Activation Manager
class ApplicationActivationManager:IApplicationActivationManager
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType =  
                    MethodCodeType.Runtime)/*, PreserveSig*/]
public extern IntPtr ActivateApplication(
                           [In] String appUserModelId,
                           [In] String arguments,
                           [In] ActivateOptions options,              
                           [Out] out UInt32 processId);


[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType =
                                     MethodCodeType.Runtime)]
public extern IntPtr ActivateForFile(
                      [In] String appUserModelId,
                      [In] IntPtr /*IShellItemArray* */itemArray, 
                      [In] String verb, 
                      [Out] out UInt32 processId);
       


[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = 
                                      MethodCodeType.Runtime)]      
public extern IntPtr ActivateForProtocol(
                     [In] String appUserModelId, 
                     [In] IntPtr /* IShellItemArray* */itemArray, 
                     [Out] out UInt32 processId);
 }



2. Accessing the controls :

You can inspect about the controls present in your app, using "Inspect.exe", available with Windows Kits. For me it is present at :
"C:\Program Files\x86)\Windows Kits\8.0\bin\x64".

Now for the below code to get working. Make sure to include these dll's beside the default ones : 
> PresentationCore.dll
> PresentationFramework.dll
> UIAComWrapper.dll [Most important]
> WpfUtilities.dll

I'm attaching them all here, for easy access or you can go ahead and search them in your Visual Studio installed location.

Below piece of code is just a sample to get you started to access any type of controls in your app. Here I'm just trying to find a button and click it.
(Note: All Windows UIA code would work just fine as seen in the msdn docs.)

//Gives you the root element i.e 'Desktop'
AutomationElement aeDesktop = AutomationElement.RootElement;

//Make a condition to find your app by 'NameProperty'
Condition appCondition = 
new PropertyCondition(AutomationElement.NameProperty, 
                    "MyApp", PropertyConditionFlags.IgnoreCase);

AutomationElement aeAppWindow =
                          aeDesktop.FindFirst(TreeScope.Children, 
                          appCondition);


AutomationElement aePane =
     aeAppWindow.FindFirst(TreeScope.Descendants,                                
     new PropertyCondition(AutomationElement.ControlTypeProperty,  
         ControlType.Pane));
            
AutomationElementCollection aeButtons = 
     aePane.FindAll(TreeScope.Descendants,
     new PropertyCondition(AutomationElement.ControlTypeProperty,
         ControlType.Button));

//Click on sample button
System.Drawing.Point p = aeButtons[3].GetClickablePoint();
Mouse.MoveTo(p);
Mouse.Click(MouseButton.Left);
Thread.Sleep(2000);


3. Closing your app

Hitting an 'Alt + F4' closes your app. You can do the same by this below line of code :

SendKeys.SendWait("%{F4}");

["SendKeys" is available in Systems.Windows.Forms dll]


That is all. You have now successfully automated a simple workflow of your Metro app. Good luck automating your next Windows app ! 

Post in your comments if you have any doubts or you find a better way of doing the same thing or better still, you find the post useful ! :)