当应用程序通过UI自动化测试启动时,ContentControl不可见,但是当应用程序由用户启动时,它可见

stu*_*bax 9 c# wpf xaml devexpress ui-automation

我们正在使用棱镜和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>
Run Code Online (Sandbox Code Playgroud)

在我们的应用程序中,我们使用图层(PopupErrorPopup)来隐藏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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

与此类似,我们展示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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

Mistics ...

当我们以用户身份运行它时(双击应用程序图标),我们可以看到两个自定义控件(使用AutomationElement.FindFirst方法,或通过Visual UI自动化验证).但是当我们使用UI自动化测试开始它时 - ErrorPopup从控件树中消失.我们试图像这样启动应用程序:

System.Diagnostics.Process.Start(pathToExeFile);
Run Code Online (Sandbox Code Playgroud)

我想我们错过了什么.但是什么?

编辑#1

正如@chrismead所说,我们试图运行我们的应用程序,UseShellExecute标志设置为true,但这没有帮助.但是,如果我们开始从应用CMD线,并手动点击按钮,PopupErrorPopup在自动化控制树可见.

    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();
Run Code Online (Sandbox Code Playgroud)

我们的一个建议是当我们使用方法FindAllFindFirst搜索按钮点击时,窗口以某种方式缓存其UI自动化状态,并且不更新它.

编辑#2 我们发现,棱镜库的扩展方法IRegionManager.RegisterViewWithRegion(RegionNames.OurRegion, typeof(Views.OurView))有一些奇怪的行为.如果我们停止使用它,这特别解决了我们的问题.现在我们可以看到ErrorView和任何类型的视图PopupContentControl,并且应用程序更新UIA元素树结构.但这不是答案 - "请停止使用此功能"!

MainViewArea我们有一个ContentControl,这将更新其内容根据用户的行为,我们都能够看到只有第一个加载UserControl到该ContentControl.Content属性.这是这样执行的:

IRegionManager regionManager = Container.Resolve<IRegionManager>();
regionManager.RequestNavigate(RegionNames.MainContentRegion, this.Uri);
Run Code Online (Sandbox Code Playgroud)

如果我们更改视图,则不会在UI自动化树中执行更新 - 第一个加载的视图将在其中.但在视觉上我们观察另一个View,WPFInspector正确显示它(它显示的不是UI自动化树),但Inspect.exe - 不是.

另外我们建议窗口使用某种缓存是错误的 - 在UI自动化客户端中我们必须明确打开缓存,但我们不这样做.

stu*_*bax 7

对不起,我错过了一些细节,这是答案的关键.我认为这不重要.无论如何.

我们NavBarDevExpress控件库中使用WPF.结果是,当NavBar存在时,UI自动化树上不会显示动态创建的视图.从窗口中删除它时,可以查看所有动态加载的视图.这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>
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑

根据他们支持网站上的DevExpress答案:

创建对等体后,侦听自动化事件可能会导致性能问题.我们已决定清除自动化事件的调用列表以解决它.在您的特定情况下,您需要禁用清除.为此,请在Window构造函数中将静态DevExpress.Xpf.Core.ClearAutomationEventsHelper.IsEnabled属性设置为False.

这解决了这个问题.


mgr*_*ber 5

我的猜测是,ContentControl自动化对等体应该AutomationPeer.ResetChildrenCache()在视图发生变化后更新其子节点.

AutomationPeer.InvalidatePeer()应该具有相同的效果(除了其他副作用)并且它应该被自动调用以响应LayoutUpdated事件.您可能希望在视图更改时检查是否引发了LayoutUpdated事件.