UIAutomation API shows menu bar when called from one app, but not another?
I have an application I'm trying to write an automated UI test for. The is a native C++ ATL application, which has a couple of controls, and a MenuBar. An automation client application written in C# can see the menu bar, but for no apparent reason, an equivalent application written in IronRuby cannot.
My C# console app can enumerate children of the main window, and it sees the MenuBar... Here's the code
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">
However, when I write the equivalent code using IronRuby (v1.1.3), the TitleBar and the MenuBar controls are not listed!
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">
As you can see, items with a ClassName property of empty string aren't getting shown (and also note the AutomationId on the statusbar is different)... but why????
The only code not shown here is the the using namespace stuff...
Any ideas what would cause this? Both 开发者_开发知识库my C# app and IronRuby have a single thread which is STA, and neither call CoInitializeSecurity as far as I can tell.
PS: The usual response to these questions is to install the MS UI automation 3.0 update for windows XP, Vista, server2003, etc. I'm running on windows 7, and as far as I know, there are no UIA updates for windows 7
PPS: Here's the code for the Inspect method, which is the same (close enough) for both ruby and 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);
}
After a hunch, I enabled fusion logs, and noticed that my C# application was loading UIAutomationClientSideProviders.dll
, but my IronRuby application was not. Reflector shows that this DLL contains a whole bunch of providers for Windows components, so this looked suspicious.
My next step was to explicitly load that dll from IronRuby, which did nothing.
I then looked up how ClientSide providers work - you need to call ClientSettings.RegisterClientSideProviderAssembly
to register assemblies containing providers. When I attempted to do this, I got the following exception:
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'
Back to reflector to look at the code for LoadDefaultProxies
. I won't copy/paste the code, but it basically does this:
- Use reflection to look at all the Referenced assemblies for the current executable
- Use those references to locate the
UIAutomationClient
assembly. - load
UIAutomationClientsideProviders
by string-name, using the version, culture and public key tokens copied from theUIAutomationClient
assembly
This explains why it works:
My console app has an explicit reference to UIAutomationClient
, and so gets the full public key token, etc, and can load the ClientSideProviders dll from the GAC
IronRuby has has no explicit references as it's all dynamic, so it only loads using the string-name with no public key token, etc, and so can't use the GAC.
Once I discovered this, I copied UIAutomationClientsideProviders.dll
into my IronRuby bin directory (so it would be in the load path), and sure enough, it worked.
You don't need to explicitly load anything, IronRuby simply needs to be able to load UIAutomationClientsideProviders.dll
without a public key token or version info, and all is well.
To avoid having to copy things around, the following code uses the AssemblyResolve
event to return the correct assembly
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
精彩评论