ContentControl 在应用程序通过 UI 自动化测试启动时不可见,但在用户启动应用程序时可见
Posted
技术标签:
【中文标题】ContentControl 在应用程序通过 UI 自动化测试启动时不可见,但在用户启动应用程序时可见【英文标题】:ContentControl is not visible when application starts via UI Automation test, but its visible when application starts by user 【发布时间】:2012-06-16 09:42:20 【问题描述】:我们正在使用 prism 和 WPF 来构建应用程序。最近我们开始使用 UI 自动化 (UIA) 来测试我们的应用程序。但是当我们运行 UIA 测试时发生了一些奇怪的行为。这是简化的外壳:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0" Grid.Column="0"
Name="loadingProgressText"
VerticalAlignment="Center" HorizontalAlignment="Center"
Text="Loading, please wait..."/>
<Border
Grid.Row="0"
x:Name="MainViewArea">
<Grid>
...
</Grid>
</Border>
<!-- Popup -->
<ContentControl
x:Name="PopupContentControl"
Grid.Row="0"
prism:RegionManager.RegionName="PopupRegion"
Focusable="False">
</ContentControl>
<!-- ErrorPopup -->
<ContentControl
x:Name="ErrorContentControl"
Grid.Row="0"
prism:RegionManager.RegionName="ErrorRegion"
Focusable="False">
</ContentControl>
</Grid>
在我们的应用程序中,我们使用层(Popup
和 ErrorPopup
)来隐藏 MainViewArea,以拒绝对控件的访问。为了显示Popup
,我们使用下一个方法:
//In constructor of current ViewModel we store _popupRegion instance to the local variable:
_popupRegion = _regionManager.Regions["PopupRegion"];
//---
private readonly Stack<UserControl> _popups = new Stack<UserControl>();
public void ShowPopup(UserControl popup)
_popups.Push(popup);
_popupRegion.Add(PopupView);
_popupRegion.Activate(PopupView);
public UserControl PopupView
get
if (_popups.Any())
return _popups.Peek();
return null;
与此类似,我们在应用程序的所有元素上显示ErrorPopup
:
// In constructor we store _errorRegion:
_errorRegion = _regionManager.Regions["ErrorRegion"]
// ---
private UserControl _error_popup;
public void ShowError(UserControl popup)
if (_error_popup == null)
_error_popup = popup;
_errorRegion.Add(_error_popup);
_errorRegion.Activate(_error_popup);
Mistics...
当我们像用户一样运行它时(双击应用程序图标),我们可以看到两个自定义控件(使用AutomationElement.FindFirst
方法,或通过Visual UI Automation Verify)。但是当我们使用 UI 自动化测试启动它时 - ErrorPopup
从控件树中消失。我们尝试像这样启动应用程序:
System.Diagnostics.Process.Start(pathToExeFile);
我认为我们错过了一些东西。但是什么?
编辑#1
正如@chrismead 所说,我们尝试将UseShellExecute
标志设置为true 来运行我们的应用程序,但这并没有帮助。但是如果我们从 cmd 行启动应用程序,并手动单击按钮,Popup
和 ErrorPopup
在自动化控件树中可见。
Thread appThread = new Thread(delegate()
_userAppProcess = new Process();
_userAppProcess.StartInfo.FileName = pathToExeFile;
_userAppProcess.StartInfo.WorkingDirectory = System.IO.Directory.GetCurrentDirectory();
_userAppProcess.StartInfo.UseShellExecute = true;
_userAppProcess.Start();
);
appThread.SetApartmentState(ApartmentState.STA);
appThread.Start();
我们的一个建议是,当我们使用方法FindAll
或FindFirst
搜索要单击的按钮时,窗口会以某种方式缓存其 UI 自动化状态,并且不会更新它。
编辑#2
我们发现,prism 库IRegionManager.RegisterViewWithRegion(RegionNames.OurRegion, typeof(Views.OurView))
的扩展方法有一些奇怪的行为。如果我们停止使用它,这将特别解决我们的问题。现在我们可以在PopupContentControl
中看到ErrorView 和任何类型的视图,并且应用程序更新UIA 元素树结构。但这不是答案——“请停止使用此功能”!
在MainViewArea
中,我们有一个ContentControl
,它会根据用户操作更新其内容,并且我们只能看到第一个加载到ContentControl.Content
属性的UserControl
。这是这样执行的:
IRegionManager regionManager = Container.Resolve<IRegionManager>();
regionManager.RequestNavigate(RegionNames.MainContentRegion, this.Uri);
如果我们更改视图,则 UI 自动化树中不会执行任何更新 - 第一个加载的视图将在其中。但视觉上我们观察到另一个View
,WPFInspector 正确显示它(它显示的不是 UI 自动化树),但 Inspect.exe - 不是。
我们关于窗口使用某种缓存的建议也是错误的 - 在 UI 自动化客户端中缓存我们必须显式打开,但我们不这样做。
【问题讨论】:
那么,说简单的双击启动应用程序会导致控件在树中,但 Process.Start 启动不会正确吗? 是的,它是正确的。但是我们尝试了 3 种从代码启动应用程序的方法 - 没有人能让我们找到正确的解决方案... 您是否尝试过从 cmd 窗口启动应用程序?如果这样可行,那么使用 ProcessStartInfo.UseShellExecute 标志可能会起作用。 我们已经尝试了您的建议。这没有帮助。 可能不相关,但在 Silverlight(所以不是 WPF)中,我们的ContentControl
直到实际在屏幕上查看时才会初始化/呈现;具体来说,它的Loaded
事件没有被触发。如果浏览器窗口被最小化,或者在其他窗口之后,它永远不会触发Loaded
事件。但是,一旦用户恢复窗口或重新组织其内容以便 Silverlight 运行时呈现屏幕,Loaded
事件就会自动触发。所以是的,不确定这是否相关,甚至不确定如何解决这个问题以进行自动化测试。
【参考方案1】:
对不起,我错过了一些细节,这是答案的关键。我认为这不是重要的事情。无论如何。
我们使用了 DevExpress 控件库中的 NavBar
用于 WPF。事实证明,当NavBar
存在时,动态创建的视图不会出现在 UI 自动化树上。当从窗口中移除它时,可以看到所有动态加载的视图。 NavBar
对我来说仍然是什么意思。
这里是一个很好的例子,看看发生了什么,如果窗口上存在或不存在 NavBar(需要 DevExpress)。
MainWindow.xaml:
<Window xmlns:dxn="http://schemas.devexpress.com/winfx/2008/xaml/navbar"
x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
>
<Grid Name="ContentGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<!--Comment NavBar to see dynamic control in UI Automation tree-->
<dxn:NavBarControl Name="asdasd">
<dxn:NavBarControl.Groups>
<dxn:NavBarGroup Header="asdasdasdasd" />
</dxn:NavBarControl.Groups>
</dxn:NavBarControl>
<TextBox Grid.Column="1" Name="Statictb" Text="static is visible in ui automation tree" />
<Button Grid.Row="1" Content="Create controls" Height="25" Click="Button_Click"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
private void Button_Click(object sender, RoutedEventArgs e)
TextBox tb = new TextBox();
Grid.SetRow(tb, 1);
Grid.SetColumn(tb, 1);
tb.Text = "dynamic is not visible, if NavBar here...";
ContentGrid.Children.Add(tb);
编辑
根据他们支持网站上的DevExpress answer:
创建对等点后,监听自动化事件可能会导致性能问题。我们决定清除自动化事件的调用列表来解决它。在您的具体情况下,您需要禁用清除。为此,请在 Window 构造函数中将静态 DevExpress.Xpf.Core.ClearAutomationEventsHelper.IsEnabled 属性设置为 False。
这样就解决了问题。
【讨论】:
【参考方案2】:我的猜测是 ContentControl
的自动化对等点应该在视图更改后使用 AutomationPeer.ResetChildrenCache()
更新其子级。
AutomationPeer.InvalidatePeer()
应该具有相同的效果(除了其他副作用)并且应该自动调用它以响应LayoutUpdated 事件。您可能想检查视图更改时是否引发了 LayoutUpdated 事件。
【讨论】:
非常感谢您 - 我已经为AutomationElement
s 突然消失的类似问题苦苦挣扎了 24 小时。阅读您的答案后,我检查了在LayoutUpdated
期间是否重新创建了给定控件的AutomationPeer
子级(但它们不是) - 所以您通过调用ResetChildrenCache()
和InvalidatePeer()
的建议起到了作用。再次感谢。 +1。【参考方案3】:
stukselbax,尝试查找一系列击键(TAB 和最有可能的 ENTER)以单击使您能够查看项目的按钮。发送击键非常容易,如果这对您有用,我可以在这里添加更多信息。您始终可以在您的应用程序中建立对用户最有意义的 Tab 键顺序。
----- 2012 年 6 月 20 日更新--------
您是否尝试过使用 PInvoke 在桌面上双击应用程序的快捷方式,以查看以这种方式打开时是否可以看到控件?这是一个指向 *** 示例的链接:
Directing mouse events [DllImport("user32.dll")] click, double click
另一个想法:我目前正在自动化的应用程序上的某些控件不会显示在树中,直到鼠标单击它们。为了在不使用任何硬编码坐标的情况下完成此操作,我在树中找到了一些东西,这正是我需要单击以显示控件的位置(上方/下方/等)。然后,我获取该项目的鼠标坐标,并将鼠标放在距离那里一个小的偏移处并单击。然后我可以在树中找到我的控件。如果应用程序被调整大小、移动等。这仍然有效,因为小偏移量仍然有效。
【讨论】:
想想吧。您可能希望先使用带有快捷方式的 Process.Start 而不是实际的 exe,看看是否有帮助。以上是关于ContentControl 在应用程序通过 UI 自动化测试启动时不可见,但在用户启动应用程序时可见的主要内容,如果未能解决你的问题,请参考以下文章
为动态内容绑定 ContentControl Content
当 ContentControl 的内容为空或为空时,在 ContentControl 中显示默认的 DataTemplate?