Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ContentControl is not visible when application starts via UI Automation test, but its visible when application starts by user

We are using the prism and WPF to build application. Recently we started using UI Automation (UIA) to test our app. But some strange behavior occurred when we run UIA test. Here's simplified 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>

In our app, we use layers (Popup and ErrorPopup) to hide MainViewArea, to deny access to the controls. To show Popup, we use next method:

    //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;
        }
    }

Similar to this, we show ErrorPopup over all elements of our application:

    // 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...

When we run it as users do it (double click on app icon), we can see both custom controls (using AutomationElement.FindFirst method, or through Visual UI Automation Verify). But when we start it using UI Automation test - ErrorPopup disapears from the tree of the controls. We trying to start the application like this:

System.Diagnostics.Process.Start(pathToExeFile);

I think that we missed something. But what?

Edit #1

As @chrismead said, we tried to run our app with UseShellExecute flag set to true, but this does not help. But if we start app from cmd line, and manually click the button, Popup and ErrorPopup are visible in automation controls tree.

    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();

One of our suggestion is when we use method FindAll or FindFirst to search the button to click, window somehow cached its UI Automation state, and does not update it.

Edit #2 We have find, that extension method of prism library IRegionManager.RegisterViewWithRegion(RegionNames.OurRegion, typeof(Views.OurView)) have some strange behavior. If we stopped use it, this solve our problem particulary. Now we able to see ErrorView and any kind of view in PopupContentControl, and application updates UIA elements tree structure. But this is not an answer - "Just stop use this feature"!

In MainViewArea we have a ContentControl, which updates it content depending on user actions, and we are able to see only the first loaded UserControl to that ContentControl.Content property. This is performed like this:

IRegionManager regionManager = Container.Resolve<IRegionManager>();
regionManager.RequestNavigate(RegionNames.MainContentRegion, this.Uri);

And if we change the view, no updates will performed in UI Automation tree - the first loaded view will be in it instead. But visually we observe another View, and WPFInspector shows it properly (its show not a UI Automation tree), but Inspect.exe - not.

Also our suggestion that window use some kind of caching is wrong - caching in UI Automation client we have to turn on explicitly, but we don't do it.

like image 923
stukselbax Avatar asked Jun 13 '12 12:06

stukselbax


2 Answers

I'm sorry that I've missed some detail, that was the key to the answer. I think that it was not important thing. Anyway.

We used NavBar from DevExpress controls library for WPF. What turns out, is when NavBar is present, dynamically created views are not appears on the UI Automation tree. When remove it from the window, there was an ability to see all dynamically loaded views. What does the NavBar - still mistic for me.

Here bright example to see what happened, if NavBar is present or absent on the Window (DevExpress is required).

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);
    }
}

Edit

According to the DevExpress answer on their support site:

After a peer is created, listening of automation events may cause performance issues. We have decided to clear invocation lists of automation events to resolve it. In your specific situation, you need to disabling clearing. To do it, please set the static DevExpress.Xpf.Core.ClearAutomationEventsHelper.IsEnabled property to False in the Window constructor.

This solve the problem.

like image 133
stukselbax Avatar answered Oct 19 '22 01:10

stukselbax


My guess is that the ContentControl's automation peer should update its children with AutomationPeer.ResetChildrenCache() after the view has been changed.

AutomationPeer.InvalidatePeer() should have the same effect (in addition to other side effects) and it is supposed to be called automatically in response to the LayoutUpdated event. You might want to check that the LayoutUpdated event is raised when the view changes.

like image 40
mgronber Avatar answered Oct 19 '22 01:10

mgronber