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>

在我们的应用程序中,我们使用层(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;
        
    

与此类似,我们在应用程序的所有元素上显示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 行启动应用程序,并手动单击按钮,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();

我们的一个建议是,当我们使用方法FindAllFindFirst 搜索要单击的按钮时,窗口会以某种方式缓存其 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 事件。

【讨论】:

非常感谢您 - 我已经为 AutomationElements 突然消失的类似问题苦苦挣扎了 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 自动化测试启动时不可见,但在用户启动应用程序时可见的主要内容,如果未能解决你的问题,请参考以下文章

7. 组合你的UI

为动态内容绑定 ContentControl Content

WPF-通过点击按钮,实现不同界面切换

ContentControl 不显示内容

当 ContentControl 的内容为空或为空时,在 ContentControl 中显示默认的 DataTemplate?

如何在受保护的 Word 文档中使用 Word RepeatingSection ContentControl