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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With