[英]ContentControl is not visible when application starts via UI Automation test, but its visible when application starts by user
我们正在使用棱镜和WPF来构建应用程序。 最近我们开始使用UI Automation(UIA)来测试我们的应用程序。 但是当我们运行UIA测试时会发生一些奇怪的行为。 这是简化的shell:
<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
从控件树中ErrorPopup
。 我们试图像这样启动应用程序:
System.Diagnostics.Process.Start(pathToExeFile);
我想我们错过了什么。 但是什么?
编辑#1
正如@chrismead所说,我们试图在UseShellExecute
标志设置为true的情况下运行我们的应用程序,但这没有帮助。 但是,如果我们从cmd行启动app,并手动单击该按钮,则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自动化客户端中我们必须明确打开缓存,但我们不这样做。
对不起,我错过了一些细节,这是答案的关键。 我认为这不重要。 无论如何。
我们使用来自DevExpress控件库的NavBar
for WPF 。 事实证明,当NavBar
存在时,动态创建的视图不会出现在UI Automation树上。 从窗口中删除它时,可以查看所有动态加载的视图。 NavBar
是什么 - 对我而言仍然不错。
这里有一个明亮的例子来看看发生了什么,如果NavBar在Window上存在或不存在(需要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答案 :
创建对等体后,侦听自动化事件可能会导致性能问题。 我们已决定清除自动化事件的调用列表以解决它。 在您的特定情况下,您需要禁用清除。 为此,请在Window构造函数中将静态DevExpress.Xpf.Core.ClearAutomationEventsHelper.IsEnabled属性设置为False。
这解决了这个问题。
我的猜测是, ContentControl
的自动化对等体应该在视图更改后用AutomationPeer.ResetChildrenCache()
更新其子节点。
AutomationPeer.InvalidatePeer()
应该具有相同的效果(除了其他副作用),它应该被自动调用以响应LayoutUpdated事件。 您可能希望在视图更改时检查是否引发了LayoutUpdated事件。
stukselbax,尝试找到一系列击键(TAB和最有可能的ENTER)来单击使您能够看到项目的按钮。 发送击键很容易,我可以在这里添加更多内容,如果这对你有用。 您始终可以在应用程序中建立对用户最有意义的Tab键顺序。
------ 2012年6月6日更新--------
您是否尝试使用PInvoke双击桌面上应用程序的快捷方式,看看是否可以通过这种方式打开控件? 以下是stackoverflow上的示例链接:
指挥鼠标事件[DllImport(“user32.dll”)]点击,双击
另一个想法是:我正在自动化的应用程序上的一些控件在它们上面发生鼠标点击之前不会显示在树中。 为了在不使用任何硬编码坐标的情况下实现这一点,我在树中找到了一些东西(上方/下方/等),我需要点击该区域才能显示控件。 然后我获取该项目的鼠标坐标,并将鼠标放在距离那里的小偏移处并单击。 然后我可以在树中找到我的控件。 如果应用程序调整大小,移动等等,这仍然有效,因为小偏移仍然有效。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.