11

我们正在使用 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);
        }
    }

微信...

当我们像用户一样运行它时(双击应用程序图标),我们可以看到两个自定义控件(使用AutomationElement.FindFirst方法,或通过Visual UI 自动化验证)。但是当我们使用 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();

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

编辑#2 我们发现,prism 库的扩展方法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);

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

此外,我们关于窗口使用某种缓存的建议是错误的——在 UI 自动化客户端中的缓存我们必须显式打开,但我们不这样做。

4

3 回答 3

8

很抱歉我错过了一些细节,这是答案的关键。我认为这不是重要的事情。反正。

我们使用NavBar了来自DevExpress的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>

主窗口.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。

这解决了问题。

于 2012-06-22T06:26:16.240 回答
7

我的猜测是,的自动化对等应在视图更改后ContentControl更新其子级。AutomationPeer.ResetChildrenCache()

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

于 2012-06-20T13:23:09.367 回答
2

stukselbax,尝试查找一系列击键(TAB 和最有可能的 ENTER)以单击使您能够查看项目的按钮。发送击键非常容易,如果这对您有用,我可以在这里添加更多信息。您始终可以在应用程序中建立对用户最有意义的跳位顺序。

------ 2012 年 6 月 20 日更新 --------

您是否尝试过使用 PInvoke 在桌面上双击应用程序的快捷方式以查看以这种方式打开时是否可以看到控件?这是指向 stackoverflow 上的示例的链接:

引导鼠标事件[DllImport("user32.dll")]点击、双击

另一个想法:我目前正在自动化的应用程序上的某些控件不会显示在树中,直到鼠标单击它们。为了在不使用任何硬编码坐标的情况下完成此操作,我在树中找到了一些东西,这正是我需要单击以显示控件的位置(上方/下方/等)。然后,我获取该项目的鼠标坐标,并将鼠标放在距离那里一个小的偏移处并单击。然后我可以在树中找到我的控件。如果应用程序被调整大小、移动等。这仍然有效,因为小偏移量仍然有效。

于 2012-06-14T14:35:56.857 回答