我有一个应用程序,我试图写一个自动化的UI测试。这是一个本地c++ ATL应用程序,它有几个控件和一个菜单栏。用c#编写的自动化客户端应用程序可以看到菜单栏,但不知道为什么,用IronRuby编写的同等应用程序却不能。
我的c#控制台应用程序可以枚举主窗口的子窗口,它看到菜单栏…下面是代码
var desktop = AutomationElement.RootElement;
var walker = TreeWalker.RawViewWalker;
var mainWindow = desktop.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "TheWindowName"));
var child = walker.GetFirstChild(mainWindow);
do
{
Console.WriteLine(child.Inspect());
child = walker.GetNextSibling(child);
}
while (child != null);
---- Output ----
<Static Name="view:" AutomationId="4337">
<Static Name="[ALL]" AutomationId="4341">
<Button Name="View" AutomationId="4322">
<AtlAxWinLic100 Name="" AutomationId="1101">
<ATL:msctls_statusbar32 Name="" AutomationId="StatusBar">
< Name="TheWindowName" AutomationId="TitleBar">
< Name="Application" AutomationId="MenuBar">
然而,当我使用IronRuby (v1.1.3)编写等效代码时,标题栏和菜单栏控件没有列出!
desktop = AutomationElement.RootElement;
walker = TreeWalker.RawViewWalker;
mainWindow = desktop.FindFirst(TreeScope.Children, PropertyCondition.new(AutomationElement.NameProperty, "TheWindowName".to_clr_string));
child = walker.GetFirstChild(mainWindow);
until child.nil? do
Console.WriteLine(Inspect(child));
child = walker.GetNextSibling(child);
end
---- Output ----
<Static Name="view:" AutomationId="4337">
<Static Name="[ALL]" AutomationId="4341">
<Button Name="View" AutomationId="4322">
<AtlAxWinLic100 Name="" AutomationId="1101">
<ATL:msctls_statusbar32 Name="" AutomationId="6872212">
正如你所看到的,具有空字符串的ClassName属性的项目没有显示(并且还注意状态栏上的AutomationId是不同的)…但是为什么? ?
这里唯一没有显示的代码是using namespace的东西…
你知道是什么原因导致的吗?据我所知,我的c#应用程序和IronRuby都有一个STA线程,并且都没有调用CoInitializeSecurity。
PS:对于这些问题,通常的回答是为windows XP、Vista、server2003等安装MS UI自动化3.0更新。我在windows 7上运行,据我所知,windows 7没有UIA更新PPS:这是Inspect方法的代码,这是相同的(足够接近)对于ruby和c#
public static string Inspect(this AutomationElement element)
{
var className = element.GetCurrentPropertyValue(AutomationElement.ClassNameProperty);
var name = element.GetCurrentPropertyValue(AutomationElement.NameProperty);
var id = element.GetCurrentPropertyValue(AutomationElement.AutomationIdProperty);
return String.Format("<{0} Name="{1}" AutomationId="{2}">", className, name, id);
}
直觉之后,我启用了融合日志,并注意到我的c#应用程序正在加载UIAutomationClientSideProviders.dll
,但我的IronRuby应用程序没有。Reflector显示这个DLL包含了一大堆Windows组件的提供程序,所以这看起来很可疑。
我的下一步是显式地从IronRuby加载dll,它什么也没做。
然后我查看了客户端提供程序如何工作-您需要调用ClientSettings.RegisterClientSideProviderAssembly
来注册包含提供程序的程序集。当我尝试这样做时,我得到了以下异常:
UIAutomationClient:0:in `RegisterProxyAssembly': 'UIAutomationClientsideProviders' assembly not found.
(System::Windows::Automation::ProxyAssemblyNotLoadedException)
from UIAutomationClient:0:in `LoadDefaultProxies'
from UIAutomationClient:0:in `RegisterWindowHandlers'
from UIAutomationClient:0:in `RegisterClientSideProviders'
回到反射器查看LoadDefaultProxies
的代码。我不会复制/粘贴代码,但它基本上是这样做的:
- 使用反射查看当前可执行文件的所有引用程序集
- 使用这些引用来定位
UIAutomationClient
组件。 - 通过字符串名称加载
UIAutomationClientsideProviders
,使用从UIAutomationClient
程序集复制的版本、区域性和公钥令牌
这解释了它的工作原理:
我的控制台应用程序有一个对UIAutomationClient
的显式引用,因此获得完整的公钥令牌等,并且可以从GAC
IronRuby没有显式引用,因为它是动态的,所以它只使用没有公钥令牌的string-name加载,所以不能使用GAC。
一旦我发现了这个,我就把UIAutomationClientsideProviders.dll
复制到我的IronRuby bin目录中(这样它就会在加载路径中),果然,它工作了。
你不需要显式加载任何东西,IronRuby只需要能够加载UIAutomationClientsideProviders.dll
而不需要公钥令牌或版本信息,一切都很好。
为了避免复制,下面的代码使用AssemblyResolve
事件返回正确的程序集
require 'UIAutomationClientSideProviders, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL'
System::AppDomain.current_domain.assembly_resolve do |sender, args|
args.name == "UIAutomationClientsideProviders" ?
UIAutomationClientsideProviders::UIAutomationClientSideProviders.to_clr_type.assembly :
nil
end