Category Archives: WinAppDriver

The “Custom” control type and WinAppDriver/Appium

So you’ve and application that you want to UI automation test using WinAppDriver/Appium. You’ve got a property grid with the left hand being the text/label and the right hand being the editor. You decided that a cool way to change values on the edit controls is to inspect what the ControlType is, then customise the code to SendKeys or Click or whatever on those controls.

Sound fair?

Well all this is great if your controls are not (as the title of this post suggests) “Custom” controls. So for WPF this is a UserControl or Control. This is fine if we have a single custom control but no so good if we have multiple custom control types.

This issue raise it’s head due to a HorizontalToggle control which we’re importing into our application via a NuGet package. The control derives from Control and is pretty much invisible to the UI Automation code apart from one Automation Id “SwithThumb”. So to fix this I wrapped the control in a UserControl and added an AutomationProperties.AutomationId attached property. Ofcourse, we could get the source if it’s available and change the code ourselves, but then we’ll have to handle upgrades etc. which may or may not be an issue in the future.

That’s great, now I can see the control but I have some generic code that wants to know the control type, so what can we do on this front?

The truth is we’re still quite limited in what we can do, if we’re getting all elements and trying to decide what to do based upon the ControlType. TextBoxes are Edit control types, Buttons are Button control types, but UserControls are still Custom control types.

Whilst this is NOT a perfect solutions, we can derive a class from a UserControl (which will still be used to wrap the original control), let’s call ours HorizontalToggleControl and it looks like this

public class HorizontalToggleControl : UserControl
{
   protected override AutomationPeer OnCreateAutomationPeer() => 
      new HorizontalToggleControlAutomationPeer(this);
}

What we’re doing here is taking over the OnCreateAutomationPeer and supplying our own automation peer, which will itself allow us to override some of the automation properties, specifically in our case the GetAutomationControlTypeCore.

My HorizontalToggleControlAutomationPeer class looks like this

internal class HorizontalToggleControlAutomationPeer : 
   UserControlAutomationPeer
{
   public HorizontalToggleControlAutomationPeer(UserControl owner) :
      base(owner)
   {
   }

   protected override AutomationControlType GetAutomationControlTypeCore() => 
      AutomationControlType.Thumb;

   protected override string GetLocalizedControlTypeCore() =>
      nameof(HorizontalToggleControl);

}

Now what’s happening in the above code is the we’re creating a localized control name “HorizontalToggleControl”, ofcourse this could literally be localised and read from the resources, but in our case we’re sticking with the actual control name. This, unfortunately is still no use to us as the ControlType in an element will still read as Custom. Changing the GetAutomationControlTypeCore return value fixes this but at the expense of only being able to set the control type to one of the AutomationControlType enums. So it’s of limited use, but as mentioned previously, we only really see the SwitchThumb automation id on the original control and so, Thumb seemed like a possible control type. In reality we might prefer CheckBox, but ofcourse the downside here is if we have check box elements, we’d need to ensure we also look at the automation name or property to determine what type of check box this is, a real Windows one or one that acts like a check box. Either way of doing this is fine.

Is the checkbox checked on my AppiumWebElement ?

From Appium/WinAppDriver you’ll probably want to check if a checkbox or radio button element is checked at some point. For such elements we use the AppiumWebElement Selected property to get the current state. To set it within UI automation we would click on it, so for example we might have code like this

public bool IsChecked
{
   get => Selected;
   set
   {
      if(IsChecked != value)
      {
         Click();
      }
   }
}

Note: In my case I wrap the AppiumWebElement in a CheckBoxElement, but this could be in a subclass or extension method, you get the idea.

WinAppDriver, connecting to existing instance of an application

In a previous post I showed how we can use specflow.actions.json to configure the WinAppDriver with the application name etc. but what if you want to simply connect to an existing instance of an application?

If we’re stick with Specflow then we would probably create a file in the Drivers folder of our test project (or ofcourse add one if it doesn’t exist)

public class WinAppDriver : IDisposable
{
   provate WindowsDriver<WindowsElement> _driver;

   public WindowsDriver<WindowsElement> Current
   {
      get 
      {
         if(!_driver != null) 
         {
            return _driver;
         }

         var appWindowHandle = new IntPtr();
         foreach(var clsProcess in Process.GetProcesses())
         {
            if(clsProcess.ProcessName.Contains("MyApp"))
            {
               appWindowHandle = clsProcess.MainWindowHandle;
               break;
            }
         }

         var appWindowHandleHex = appWindowHandle.ToString("x");

         var options = new AppiumOptions
         {
            PlatFormName = "Windows"
         };

         options.AddAdditionalCapability("deviceName", "WindowsPC");
         options.AddAdditionalCapability("appTopLevelWindows", appWindowHandleHex);

         return _driver = new WindowsDriver<WindowsElement>(new Uri("http://127.0.0.1:4723", options);
      }
   }
}

Testing Windows Package Application

With a packaged application, i.e. from the Window Store we cannot just supply the path of the .exe, instead we need the Package.appxmanifest “Package family name” which can be used as the “app” value