I have created my own extension methods, which contains Automation Element implementation to find any Win32, WPF controls during run time.
I have given the dynamic attributes and parent control as input and find the control using UIA concept and again returning the control as CodedUI controls (WinControls/ WpfControls).
I have implemented this concept since some custom controls were not identified using CodedUI, after implementing this UIA concept, automation code's stability got increased.
While executing the scripts as Smoke/Regression, was not facing any problem, but while doing the same with CycleTest (Like we used to run the Smoke script for 30 repeats) got GDI memory leaks consistently in the Target application process, whereas am not facing this kind of issues with UIMaps.
I have tried many ways to release the GDI objects, but none of them were helpful for me. Have placed my wrapper method here.
public static T DetectControl<T>(this Object parentControl, Object attributes,
[Optional]bool shouldUseAEToFind, [Optional] PropertyExpressionOperator propertyExpression,
bool isLogMandatory = true, bool shouldScrollIntoView = false) where T : UITestControl, new()
{
#region Local Variables
AE.AutomationElement outputAutomationElement = null;
AE.AutomationElement inputAutomationElement = null;
List<AE.Condition> list_conditions = new List<AE.Condition>();
string technologyName = string.Empty;
string controlType = string.Empty;
T returnControl = new T();
if (!returnControl.GetType().Name.ToLower().Equals("uitestcontrol"))
{
technologyName = returnControl.TechnologyName;
if (!returnControl.GetType().Name.ToString().EndsWith("Control"))
{
controlType = returnControl.ControlType.Name;
}
}
StringBuilder allAttributesValue = new StringBuilder();
string localizedControlType = string.Empty;
bool flag = false;
ICollection<KeyValuePair<string, object>> IColl_properties = null;
List<T> list_matchingControls = null;
IntPtr intPtr = IntPtr.Zero;
#endregion
try
{
LogWriter.WriteDayLog(messageType.information,
"DetectControl method successfully called to find control with the given properties",
logStatus.DONE);
if (!shouldUseAEToFind)
returnControl.Container = (UITestControl)parentControl;
if (attributes.GetType().Name.Contains("Expando"))
{
IColl_properties = (ICollection<KeyValuePair<string, object>>)attributes;
}
else
{
IColl_properties = attributes.GetType().GetProperties().ToDictionary(x => x.Name.ToString(), x => x.GetValue(attributes));
}
foreach (KeyValuePair<string, object> keyValyePair in IColl_properties)
{
if (shouldUseAEToFind == false)
{
if (propertyExpression == PropertyExpressionOperator.EqualTo)
{
returnControl.SearchProperties.Add(keyValyePair.Key, keyValyePair.Value.ToString());
}
else
{
returnControl.SearchProperties.Add(keyValyePair.Key, keyValyePair.Value.ToString(), PropertyExpressionOperator.Contains);
}
}
else
{
if (keyValyePair.Key == "LocalizedControlType")
{
localizedControlType = keyValyePair.Value.ToString();
}
if (propertyExpression == PropertyExpressionOperator.EqualTo)
{
list_conditions.Add(new AE.PropertyCondition(GetAutomationProperty(keyValyePair.Key), (object)keyValyePair.Value));
}
else
{
if (keyValyePair.Key == "LocalizedControlType")
{
list_conditions.Add(new AE.PropertyCondition(GetAutomationProperty(keyValyePair.Key), (object)keyValyePair.Value));
}
}
}
if (IColl_properties.Last().Key != keyValyePair.Key)
{
allAttributesValue.Append(keyValyePair.Key + " : " + keyValyePair.Value + " | ");
}
else
{
allAttributesValue.Append(keyValyePair.Key + " : " + keyValyePair.Value);
}
}
if (localizedControlType == string.Empty && shouldUseAEToFind && propertyExpression == PropertyExpressionOperator.Contains)
{
throw new InvalidDataException("Please provide a LocalizedControlType to proceed further.");
}
LogWriter.WriteDayLog(messageType.trace, "Started to find the control with the given properties --> "
+ allAttributesValue.ToString(), logStatus.DONE);
if (shouldUseAEToFind)
{
LogWriter.WriteDayLog(messageType.information, "Finding the control based on the Automation Element", logStatus.DONE);
inputAutomationElement = parentControl.GetInputAutomationElement(isLogMandatory);
if (propertyExpression == PropertyExpressionOperator.EqualTo)
{
try
{
outputAutomationElement = inputAutomationElement.FindFirst(AE.TreeScope.Children
| AE.TreeScope.Descendants | AE.TreeScope.Element,
new AE.AndCondition(list_conditions.ToArray()));
}
catch (Exception ex)
{
if (isLogMandatory)
{
LogWriter.WriteDayLog(messageType.exception, "Failed to do FindFirst, encountered exception", logStatus.FAIL);
}
else
{
LogWriter.WriteDayLog(messageType.warning, "Failed to do FindFirst, encountered exception", logStatus.WARNING);
}
throw ex;
}
if (outputAutomationElement != null)
{
LogWriter.WriteDayLog(messageType.information, "outputAutomationElement is found", logStatus.DONE);
}
else
throw new Exception("outputAutomationElement is null");
if (shouldScrollIntoView)
{
LogWriter.WriteDayLog(messageType.information, "shouldScrollIntoView is true",
logStatus.DONE);
outputAutomationElement.ScrollIntoView();
}
bool visibleFlag = outputAutomationElement.Current.BoundingRectangle.Location.X == 0 ? outputAutomationElement.Current.BoundingRectangle.Location.Y > 0 : true;
if (visibleFlag)
{
switch (technologyName)
{
case "UIA":
LogWriter.WriteDayLog(messageType.information, "Try to find the control using FromNativeElement, UIA",
logStatus.DONE);
returnControl = (T)UITestControlFactory.FromNativeElement(outputAutomationElement, "UIA");
break;
default:
LogWriter.WriteDayLog(messageType.information, "technologyName is " + technologyName,
logStatus.DONE);
if (string.IsNullOrWhiteSpace(technologyName) || technologyName.Equals("MSAA"))
{
try
{
LogWriter.WriteDayLog(messageType.information, "Try to find the control using FromPoint",
logStatus.DONE);
if (localizedControlType.ToLower() != "window")
{
returnControl = (T)UITestControlFactory.FromPoint(outputAutomationElement.GetClickablePoint());
}
else
{
intPtr = new IntPtr(outputAutomationElement.Current.NativeWindowHandle);
returnControl = (T)UITestControlFactory.FromWindowHandle(intPtr);
}
}
catch (AE.NoClickablePointException)
{
LogWriter.WriteDayLog(messageType.information, "Unable to find the control using GetClickablePoint, try FromWindowHandle", logStatus.DONE);
intPtr = new IntPtr(outputAutomationElement.Current.NativeWindowHandle);
returnControl = (T)UITestControlFactory.FromWindowHandle(intPtr);
}
catch (Exception ex)
{
if (!ex.Message.Contains("Access is denied"))
{
LogWriter.WriteDayLog(messageType.exception, "Failed to find the control, encountered exception",
logStatus.FAIL);
throw new Exception(ex.Message);
}
else
{
intPtr = new IntPtr(outputAutomationElement.Current.NativeWindowHandle);
returnControl = (T)UITestControlFactory.FromWindowHandle(intPtr);
}
}
}
else
{
throw new Exception("Different Technology has been captured : " + technologyName.ToUpper());
}
break;
}
}
else
{
returnControl = null;
}
}
else
{
list_matchingControls = inputAutomationElement.DetectIdenticalControls<T>(new { LocalizedControlType = localizedControlType }, true, isLogMandatory: isLogMandatory);
foreach (KeyValuePair<string, object> keyValyePair in IColl_properties)
{
switch (keyValyePair.Key.ToLower())
{
case "name":
returnControl = (T)list_matchingControls.First(x => x.GetInspectProperty(InspectProperty.NAME).ToLower().Contains(keyValyePair.Value.ToString().ToLower()));
break;
case "classname":
returnControl = (T)list_matchingControls.First(x => x.GetInspectProperty(InspectProperty.CLASSNAME).ToLower().Contains(keyValyePair.Value.ToString().ToLower()));
break;
case "automationid":
returnControl = (T)list_matchingControls.First(x => x.GetInspectProperty(InspectProperty.AUTOMATIONID).ToLower().Contains(keyValyePair.Value.ToString().ToLower()));
break;
}
}
}
}
else
{
LogWriter.WriteDayLog(messageType.information, "Finding the control based on default CodedUI Search properties", logStatus.DONE);
flag = returnControl.TryFind();
}
}
catch (InvalidCastException ex)
{
throw new Exception(ex.Message);
}
catch (InvalidDataException idex)
{
throw new Exception(idex.Message);
}
catch (Exception e)
{
if (isLogMandatory)
{
LogWriter.WriteDayLog(messageType.exception, "Failed to find the control with the given properties --> " + allAttributesValue.ToString() + ", it throws >> " + e.ToString(), logStatus.FAIL);
}
else
{
LogWriter.WriteDayLog(messageType.warning, "Failed to find the control with the given properties --> " + allAttributesValue.ToString() + ", it throws >> " + e.ToString(), logStatus.WARNING);
}
returnControl = null;
}
finally
{
if (flag || returnControl != null)
{
LogWriter.WriteDayLog(messageType.trace, "Successfully found the control with the given properties --> " + allAttributesValue.ToString(), logStatus.DONE);
}
else
{
if (isLogMandatory)
{
LogWriter.WriteDayLog(messageType.error, "Unable to find the control with the given properties --> " + allAttributesValue.ToString(), logStatus.FAIL);
}
else
{
LogWriter.WriteDayLog(messageType.warning, "Unable to find the control with the given properties --> " + allAttributesValue.ToString(), logStatus.WARNING);
}
returnControl = null;
}
#region Variable Cleanup
list_conditions = null;
technologyName = null;
controlType = null;
allAttributesValue = null;
localizedControlType = null;
IColl_properties = null;
list_matchingControls = null;
parentControl = null;
AEDispose(inputAutomationElement);
AEDispose(outputAutomationElement);
AEDispose(null, intPtr);
#endregion
}
return returnControl;
}
internal static void AEDispose(AE.AutomationElement automationElement, [Optional]IntPtr wHandle)
{
if (automationElement != null)
{
IntPtr windowHandle = IntPtr.Zero;
if (wHandle == IntPtr.Zero)
windowHandle = new IntPtr(automationElement.Current.NativeWindowHandle);
else
windowHandle = wHandle;
var deviceContext = GetWindowDC(windowHandle);
var compatibleDeviceContext = CreateCompatibleDC(deviceContext);
DeleteDC(compatibleDeviceContext);
ReleaseDC(windowHandle, deviceContext);
DeleteObject(windowHandle);
}
}
[DllImport("GDI32.dll")]
private static extern bool DeleteDC(IntPtr hDC);
[DllImport("GDI32.dll")]
private static extern bool DeleteObject(IntPtr hObject);
[DllImport("GDI32.dll")]
private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("User32.dll")]
private static extern IntPtr GetWindowDC(IntPtr hWnd);
[DllImport("User32.dll")]
private static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("GDI32.dll")]
private static extern IntPtr CreateCompatibleDC(IntPtr hDC);
internal static AE.AutomationProperty GetAutomationProperty(string propertyName)
{
#region Local Variables
AE.AutomationProperty property = null;
#endregion
try
{
switch (propertyName.ToLower())
{
case "localizedcontroltype":
property = AE.AutomationElement.LocalizedControlTypeProperty;
break;
case "name":
property = AE.AutomationElement.NameProperty;
break;
case "automationid":
property = AE.AutomationElement.AutomationIdProperty;
break;
case "classname":
property = AE.AutomationElement.ClassNameProperty;
break;
}
}
catch (Exception e)
{
LogWriter.WriteDayLog(messageType.exception, "Unable to get the automation property, it throws exception >> " + e.Message, logStatus.FAIL);
}
return property;
}
internal static AE.AutomationElement GetInputAutomationElement(this Object parentControl, bool isLogMandatory = true)
{
#region Local Variables
UITestControl uiControl = null;
AE.AutomationElement inputAutomationElement = null;
#endregion
try
{
if (parentControl != null)
{
if (!parentControl.GetType().Name.ToLower().Contains("automation"))
{
uiControl = (UITestControl)parentControl;
try
{
inputAutomationElement = (AE.AutomationElement)(uiControl.ControlType.ToString() == "Window" ? AE.AutomationElement.FromHandle(uiControl.WindowHandle) : uiControl.NativeElement);
LogWriter.WriteDayLog(messageType.information, "inputAutomationElement is found, uiControl.ControlType is "
+ uiControl.ControlType.ToString(), logStatus.DONE);
}
catch (InvalidCastException ex)
{
if (ex.Message.Contains("Unable to cast object of type 'System.Object[]' to type 'System.Windows.Automation.AutomationElement"))
{
inputAutomationElement = AE.AutomationElement.FromHandle(uiControl.WindowHandle);
}
}
}
else
{
inputAutomationElement = (AE.AutomationElement)parentControl;
}
}
else
{
inputAutomationElement = AE.AutomationElement.RootElement;
}
LogWriter.WriteDayLog(messageType.information, "Successfully converted the given control into Automaiton Element", logStatus.PASS);
}
catch (Exception ex)
{
if (isLogMandatory)
{
LogWriter.WriteDayLog(messageType.exception, "Unable to convert given control to Automation Element, throws exception >> "
+ ex.Message, logStatus.FAIL);
}
else
{
LogWriter.WriteDayLog(messageType.trace, "Unable to convert given control to Automation Element, throws exception >> "
+ ex.Message + " So assigning Desktop as Root AutomationElement", logStatus.DONE);
inputAutomationElement = AE.AutomationElement.RootElement;
}
}
#region Variable Cleanup
uiControl = null;
#endregion
return inputAutomationElement;
}
The reason I have experienced these in the past is because I was constantly getting a control that was never garbage collected in our VB6 application. I would suggest only getting one instance of any controls that will live for the entirety of the applications life cycle. For us this happened to be a ribbon bar. Every time I got a new automation element it forced the VB6 application to create an automation peer backing the control and would leak memory and GDI objects.
This blog has a really good illustration of how the communication works between the client (Your UIA test) and server (the application under test) in UIA.