UIAutomation(UIA) is commonly known to be natively supported by WPF. This has been in place
since .NET 3.0. It can easily be assumed that if you are developing a test automation tool
using the System.Windows.Automation namespace, which targets a WPF application, test automation
should work by default. This is not the case.
If all controls in the application under test (AUT) are standard controls, or derive from
standard control templates, then test automation will be that much easier. Any custom control
that derives from a low-level control that has no standard template will require UIA server
provider support. This means that the developer has to override the custom control’s
OnCreateAutomationPeer method and return an instance of the AutomationPeer for the control. I
posted a reply herein the UIA support forum (I am svb70) that shows an example of how to
do this, even though the example derives from a standard control, the concept is the same.
The interesting thing here is that because the custom control derives from a checkbox you
could cheat and simply call base.Focus() in the custom control’s OnMouseLeftButtonDown method.
You couldn’t do this in a custom control that derives from a low level control.
Ok, so now you have implemented UIA server providers for all your custom controls, job done!
not quite.
Test automation using UIA is a lot more robust and easy to work with when all
controls have a unique AutomationID. The compiler sets this based on the controls name. So you
can name all controls, ensuring names are unique. This could be done in code or
XAML. You can also set the AutomationID using AutomationProperties.AutomationID. But what if the
AUT dynamically creates controls based on custom controls? where do you set the AutomationID and
how do you ensure it is unique? You could say that the developer that creates a control that
derives from the custom control has the responsibility to build in accessibility. Or not, as
this potentially creates a lot of duplication and each developer has to learn UIA.
One approach is to create the AutomationPeer in the base control and define the AutomationID
(and any other required UNIQUE properties) in the base control itself. I say unique as it is ok
to have static values supplied in the AutomationPeer, but any unique values should be set in
the control. Why?
If you look at the custom controls OnCreateAutomationPeer() method,
protected override AutomationPeer OnCreateAutomationPeer()
{
return new CustomAutomationPeer(this);
}
Every time a UIA client (test tool, screen reader) interacts with the custom control, a new
instance of the AutomationPeer is returned. So you can imagine many un-necessary calls for
unique values will be made. It is up to the developer to set the AutomationID based on the AUT.
For example, if your application allows gadgets to be added to a canvas and the gadgets are
based on a custom control, you could set the AutomationID of the control based on the
child control count of the custom control’s parent. This may require a recursive method to also
uniquely name all child controls of the custom control if necessary. This is because you can not
be sure that the root element that has the unique name will receive the user interaction.
This is not to say that AutomationIDs are a must, but with a bit of upfront work they will
save a lot of effort.
We have now dealt with potential UIElement identification issues. It should be said that
although the UIA appears to have a steep learning curve, once you have done one implementation
it becomes very easy. For newbie / intermediate developers or testers new to UIA and WPF,
it may be a lack of experience with interface development that makes it difficult to get your
head around. If this is you and you aim to work with WPF in the future, go and learn WCF. I
think it is a great way of understanding contracts and their implementation, whilst learning
a new skill.
So the entire AUT now has full UIA identification support. We can make an observation. This
test tool is only ever going to work with this UAT. If we introduce a new AUT we have to do the
above all over again for your new AUT. This may be acceptable. Say a developer comes along
and introduces a new toolbar where all of the toolbar buttons have the same name.
You record a new session and click the third button. On playback the first button will be
clicked as it is the first item found with the specified name. The tester has to wait for the
developer to implement UIA support. You could and probably will use conditions to locate
UIElements. The point I am making is that you cannot rely on UIA alone and these quirks may
have to be dealt with in the test tool rather than in the UAT. In my next post I will outline
an algorithm that you can implement to get around a lack or duplication of AutomationIDs.
The next hurdle the developer and tester face is that by default UIA does not support actions
like double-click, drag, right-click and text insertion in certain circumstances. The
implemented UIA control pattern will perform whatever the default action for that control is.
So the developer either has to implement this custom functionality in the UAT or the
QA developer uses Win32 API calls within the test tool. I am in favour of the later. A future
post (soon) will show a working example of how to do this.