我创建了自己的扩展方法,其中包含自动化元素实现,用于在运行时查找任何 Win32、WPF 控件。
我已将动态属性和父控件作为输入,并使用 UIA 概念找到控件,并再次将控件作为 CodedUI 控件 (WinControls/WpfControls) 返回。
我已经实现了这个概念,因为使用 CodedUI 没有识别一些自定义控件,在实现这个 UIA 概念之后,自动化代码的稳定性得到了提高。
在将脚本作为 Smoke/Regression 执行时,没有遇到任何问题,但是在使用 CycleTest 执行相同操作时(就像我们曾经重复运行 Smoke 脚本 30 次一样)在 Target 应用程序进程中始终出现 GDI 内存泄漏,而我没有面临UIMaps 的这类问题。
我尝试了很多方法来释放 GDI 对象,但没有一个对我有帮助。已将我的包装方法放在这里。
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;
}