0

I have a WPF app and I am trying to Automate it using FlaUI. I am facing a problem with the DxTabControl. I have provided Automation IDs to the DxTabControl. I am using DXTabControl.ItemHeaderTemplate to generate TabItems dynamically. According to DevExpress Team, The DXTabControl.ItemHeaderTemplate doesnt support AutoamtionPeer so a custom implementation has been added to override its default behaviour.

Now, I am able to see the TabControl and the TabItems in the Inspect.exe. Now , my requirement is to Access the currently selected Tabitem and find the CloseButton using the AutoamtionID mentioned in the XAML below and close it. Pasting below the line again. As there would be multiple TabItems generated, I am unable to get the Currently active/Selected TabItem .

The XAML is below

<dx:DXTabControl AutomationProperties.AutomationId="ViewsParentTabControl"
        MaxWidth="4000"
        MaxHeight="4000"
        Margin="1,0,-1,0"
        Focusable="False"
        ItemsSource="{Binding OpenViews}"
        SelectedIndex="{Binding SelectedTabIndex}"
        TabContentCacheMode="CacheTabsOnSelecting">
        <dx:DXTabControl.ItemHeaderTemplate>
            <DataTemplate DataType="viewModels1:OpenViewDefinitionViewModel">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <TextBlock Name="CreatedViewName"
                        MaxWidth="100"
                        Text="{Binding Data.ViewDefinition.Name}"
                        TextTrimming="CharacterEllipsis"
                        ToolTip="{Binding DisplayName}" />
                    <TextBlock Grid.Row="0" Grid.Column="1"><Run Text=" [" /><Run Text="{Binding ItemsCount, FallbackValue=0, Mode=OneWay}" /><Run Text="]" /></TextBlock>
                    <controls2:ProgressIndicator AutomationProperties.AutomationId="ProgressCurrentView"
                        Grid.Row="0"
                        Grid.Column="3"
                        Width="14"
                        Margin="4,0,0,0"
                        VerticalAlignment="Center"
                        CircleBorder="{StaticResource ListBoxItem.Foreground}"
                        CircleFill="{StaticResource ListBoxItem.Foreground}"
                        IndicatorEnabled="{Binding IsDataLoading}" />
                    <Button AutomationProperties.AutomationId="CloseCurrentViewButton"
                        Grid.Row="0"
                        Grid.Column="2"
                        Width="10"
                        Height="10"
                        Margin="10,1,0,0"
                        Padding="0"
                        HorizontalAlignment="Right"
                        BorderThickness="0"
                        Command="{Binding DataContext.CloseItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=dx:DXTabControl}}"
                        CommandParameter="{Binding}"
                        Focusable="False"
                        Style="{StaticResource MwButtonStyle}"
                        ToolTip="Close">
                        <Path
                            Data="F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z"
                            Fill="White"
                            Stretch="Fill" />
                    </Button>
                </Grid>
            </DataTemplate>
        </dx:DXTabControl.ItemHeaderTemplate>
        <dx:DXTabControl.ItemTemplate>
            <DataTemplate DataType="viewModels1:OpenViewDefinitionViewModel">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                        <RowDefinition Height="Auto" />
                    </Grid.RowDefinitions>

                    <local:VoyagesGridControl />

                    <local:VoyageValidationUserControl
                        Grid.Row="1"
                        Grid.Column="0"
                        MinHeight="100"
                        MaxHeight="300"
                        Visibility="{Binding Path=IsVoyageValidationShowing, FallbackValue=Collapsed, Converter={StaticResource BooleanToVisibilityConverter}}" />
                    <local:VoyageHistoryUserControl
                        Grid.Row="2"
                        Grid.Column="0"
                        MinHeight="300"
                        MaxHeight="300"
                        Visibility="{Binding Path=IsVoyageHistoryShowing, FallbackValue=Collapsed, Converter={StaticResource BooleanToVisibilityConverter}}" />
                    <local:VesselHistoryUserControl
                        Grid.Row="3"
                        Grid.Column="0"
                        MinHeight="300"
                        MaxHeight="300"
                        Visibility="{Binding Path=IsVesselHistoryShowing, FallbackValue=Collapsed, Converter={StaticResource BooleanToVisibilityConverter}}" />
                    <local:VoyageEvents
                        Grid.Row="0"
                        Grid.RowSpan="3"
                        Grid.Column="1"
                        VerticalAlignment="Top"
                        Visibility="{Binding Path=IsVoyageEventsShowing, FallbackValue=Collapsed, Converter={StaticResource BooleanToVisibilityConverter}}" />


                    <controls2:ProgressIndicator AutomationProperties.AutomationId="showProgressForLoadingViews"
                        Grid.Row="0"
                        Grid.RowSpan="3"
                        Grid.Column="0"
                        Width="80"
                        HorizontalAlignment="Center"
                        VerticalAlignment="Center"
                        CircleBorder="{StaticResource ListBox.BorderBrush}"
                        CircleFill="{StaticResource ListBox.BorderBrush}"
                        IndicatorEnabled="{Binding IsDataLoading}" />
                    <!--  Buttons  -->
                    <Grid Grid.Row="4" Grid.Column="0">
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <StackPanel
                            Grid.Column="0"
                            VerticalAlignment="Center"
                            Orientation="Horizontal">
                            <TextBlock Style="{StaticResource MwTextBlockLabelStyle}" Text="Last Refresh:" />
                            <TextBlock
                                Margin="2,0,4,0"
                                VerticalAlignment="Center"
                                Text="{Binding LoadDate, StringFormat=G}" />
                        </StackPanel>
                        <Button AutomationProperties.AutomationId="AddNewVoyageButton"
                            Grid.Row="0"
                            Grid.Column="1"
                            Padding="0"
                            Command="{Binding AddVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Add a new voyage to this View (ALT + A)">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Add}" />
                                <Label Style="{StaticResource MwLabelStyle}">_Add</Label>
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="refreshVoyageButton"
                            Grid.Row="0"
                            Grid.Column="2"
                            Padding="0"
                            Command="{Binding RefreshVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Refresh the this View (modified entries are left unchanged)">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Refresh}" />
                                <TextBlock Style="{StaticResource MwTextBlockLabelStyle}" Text="Refresh" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="showVoyageHistroyButton"
                            Grid.Column="4"
                            Margin="2,2,2,2"
                            Padding="0"
                            VerticalAlignment="Center"
                            Command="{Binding ShowVoyageHistoryCommand}"
                            Focusable="False"
                            ToolTip="Show the selected voyage's change history"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource ShowVoyageHistory}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Hide Voyage History"
                                    Visibility="{Binding IsVoyageHistoryShowing, Converter={StaticResource BooleanToVisibilityConverter}}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Show Voyage History"
                                    Visibility="{Binding IsVoyageHistoryShowing, Converter={StaticResource MwBoolToVisibilityConverterReverse}}" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="showVesselHistroyButton"
                            Grid.Column="5"
                            Margin="2,2,2,2"
                            Padding="0"
                            VerticalAlignment="Center"
                            Command="{Binding ShowVesselHistoryCommand}"
                            Focusable="False"
                            ToolTip="Show the selected vessel's voyage history"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource ShowVesselHistory}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Hide Vessel History"
                                    Visibility="{Binding IsVesselHistoryShowing, Converter={StaticResource BooleanToVisibilityConverter}}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Show Vessel History"
                                    Visibility="{Binding IsVesselHistoryShowing, Converter={StaticResource MwBoolToVisibilityConverterReverse}}" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="showVoyageButton"
                            Grid.Column="6"
                            Margin="2,2,2,2"
                            Padding="0"
                            VerticalAlignment="Center"
                            Command="{Binding ShowVesselVisitsCommand}"
                            Focusable="False"
                            ToolTip="Show the selected voyage's events"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Anchor}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Hide Voyage Events"
                                    Visibility="{Binding IsVoyageEventsShowing, Converter={StaticResource BooleanToVisibilityConverter}}" />
                                <TextBlock
                                    Style="{StaticResource MwTextBlockLabelStyle}"
                                    Text="Show Voyage Events"
                                    Visibility="{Binding IsVoyageEventsShowing, Converter={StaticResource MwBoolToVisibilityConverterReverse}}" />
                            </StackPanel>
                        </Button>

                        <Border Grid.Row="0" Grid.Column="8">
                            <Border.Style>
                                <Style TargetType="Border">
                                    <Style.Triggers>
                                        <DataTrigger Binding="{Binding IsDuplicateView, Mode=TwoWay}" Value="true">
                                            <Setter Property="Background" Value="LightGreen" />
                                        </DataTrigger>
                                    </Style.Triggers>
                                </Style>
                            </Border.Style>
                            <Button AutomationProperties.AutomationId="DuplicateCheckButton"
                                Padding="0"
                                Command="{Binding DuplicateVoyagesCommand}"
                                Focusable="False"
                                Style="{StaticResource MwButtonStyle}"
                                ToolTip="Switch to duplicate Voyages (ALT + D)"
                                Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                                <StackPanel Orientation="Horizontal">
                                    <ContentControl Height="26" Content="{StaticResource Duplicate}" />
                                    <AccessText Style="{StaticResource MwAccessTextLabelStyle}" Text="{Binding VoyageDuplicateText}" />
                                </StackPanel>
                            </Button>
                        </Border>

                        <Button AutomationProperties.AutomationId="PublishVoyagesButton"
                            Grid.Row="0"
                            Grid.Column="9"
                            Padding="0"
                            Command="{Binding PublishVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Publish any modified Voyages (ALT + P)"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Publish}" />
                                <AccessText Style="{StaticResource MwAccessTextLabelStyle}" Text="{Binding VoyagePublishText}" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="UndoSingleVoyageButton"
                            Grid.Row="0"
                            Grid.Column="10"
                            Padding="0"
                            Command="{Binding UndoSingleChangedVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Locally Undo unpublished changes to the selected voyage"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Undo}" />
                                <TextBlock Style="{StaticResource MwTextBlockLabelStyle}" Text="Undo Selected" />
                            </StackPanel>
                        </Button>

                        <Button AutomationProperties.AutomationId="UndoandUnpublishVoyageButton"
                            Grid.Row="0"
                            Grid.Column="11"
                            Padding="0"
                            Command="{Binding UndoChangedVoyagesCommand}"
                            Focusable="False"
                            Style="{StaticResource MwButtonStyle}"
                            ToolTip="Locally Undo any changed and unpublished voyages"
                            Visibility="{Binding Data.ViewDefinition.IsInternalView, Converter={StaticResource MwBoolToVisibilityConverterReverse}}">
                            <StackPanel Orientation="Horizontal">
                                <ContentControl Height="26" Content="{StaticResource Undo}" />
                                <TextBlock Style="{StaticResource MwTextBlockLabelStyle}" Text="Undo All" />
                            </StackPanel>
                        </Button>
                    </Grid>
                </Grid>
            </DataTemplate>
        </dx:DXTabControl.ItemTemplate>
    </dx:DXTabControl>

My FlaUIapproach of locating the Controls is below

public IMainWindow ConfirmCreatedView()
        {
            _logger.Info("Checking the newly created View on the screen");
            //Apoorv: Need to find TabItem here
            _controlAction.Highlight(ViewsTabControl); // This highlights the TabControl- Works
            int NumberOfActiveTabs = ViewsTabControl.TabItems.Length; // This gives me no of TabItems


          TabItem SelectedTab=  ViewsTabControl.SelectedTabItem as TabItem; // Gives me Null here
            var newTab = ViewsTabControl.SelectedTabItemIndex ; // Give me -1 here


            _controlAction.Highlight(ViewsTabControl.TabItems[2]); // Works. It highlights the TabItem at position 2

            _controlAction.ClickWait<TabItem>(ViewsTabControl.TabItems[2]); // This goes and clicks the tab item 

            TabItem SelectedTabs = ViewsTabControl.SelectedTabItem as TabItem;
            var check = ViewsTabControl.TabItems[2].FindAllChildren();
            // TabItem ti = ViewsTabControl.SelectedItem as TabItem;
            //_controlAction.Highlight()



                _controlAction.Highlight(CloseCurrentView); // highlights the close button atTabItem[0]
                _controlAction.Click<Button>(CloseCurrentView); // closes it

                      return this;
        }

I am using FlaUI to Find the TabControl using AutomationID as shown below

 private Tab ViewsTabControl => _uiAutomation.FindElement("ViewsParentTabControl", Automation.FindBy.Id).AsTab();
        private TabItem ViewsTabItem => _uiAutomation.FindElement("DXTabItem", Automation.FindBy.Id).AsTabItem();

I would like to find the curently active TabItem based on the Index and then go and click the close button by automating it.

TabItem SelectedTab=  ViewsTabControl.SelectedTabItem as TabItem; // Gives me Null here
            var newTab = ViewsTabControl.SelectedTabItemIndex ; // Give me -1 here
Apoorv
  • 2,023
  • 1
  • 19
  • 42

2 Answers2

0

It is not TabItem it is DXTabItem. This is the type you should cast to.

DXTabItem SelectedTab = ViewsTabControl.SelectedTabItem as DXTabItem;
Robert
  • 2,407
  • 1
  • 24
  • 35
  • You are trying to cast with as which will not throw an exception but will rather return null since DXTab is not TabItem. This is correct because the ViewsTabControl.SelectedTabItem is of DXTabItem type. – Robert Feb 13 '20 at 09:05
  • But how does this work? private Tab ViewsTabControl => _uiAutomation.FindElement("ViewsParentTabControl", Automation.FindBy.Id).AsTab(); // this is a FlaUI TabControl – Apoorv Feb 13 '20 at 09:06
  • The ViewTabControl references FlaUI.Core.AutomationElements . When I reference the TabItem to DXTabItem, it gives me type conversion errors. – Apoorv Feb 13 '20 at 09:12
  • It seems they are two different tab controls or am I wrong? The DXTabControl can host TabItem as well as DXTabItem. In any case you should inspect what you are doing by putting a breakpoint. This way you can exactly see which type of tabPage is added to the tab control. – Robert Feb 13 '20 at 09:14
  • only 1 tab control that is DxTabControl. If you look at XAML, the TabItems are generated using ItemsHeaderTemplate. they are DxTabItems only. My whole application is using DevExpress Grid but this is the first time I am facing this issue. I always used FlaUI framework and didnt add any DevExpress APIs for finding their controls – Apoorv Feb 13 '20 at 09:18
  • No, tab items are not generated using ItemsHeaderTemplate. They are generated using ItemTemplate and this is exactly what you should expect. You are telling the tab control that for each item in your data source it should create a tab item. It just so happens that this tab item is DXTabItem. – Robert Feb 13 '20 at 09:24
  • Actually, I was wrong when I said DXTabControl can host both DXTabItem and TabItem. It cannot. It is only for DXTabItem. – Robert Feb 13 '20 at 09:36
  • what do you suggest here? see this line here , gives me all the childelements of the TabItem[2] var check = ViewsTabControl.TabItems[2].FindAllChildren(); I am surpirsed how does this work? I am only not getting the selectedITem or SelectedIndex – Apoorv Feb 13 '20 at 09:55
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/207741/discussion-between-rob-and-apoorv). – Robert Feb 13 '20 at 09:58
  • It is done. There was a proble with DevExpress control itself. Posting the answer now – Apoorv Feb 17 '20 at 16:13
0

DevEXpress don't create automation peers for controls within ItemHeaderTemplate. It will be necessary to use a custom automation peer to provide this functionality. For example, I used the following class for test purposes:

public class DXTabItemAutomationPeerEx : DXTabItemAutomationPeer, ISelectionItemProvider {
    private DXTabItem TabItem => base.Owner as DXTabItem;
    private DXTabControl TabControl => TabItem.With((DXTabItem x) => x.Owner);
    public DXTabItemAutomationPeerEx(DXTabItem ownerCore) : base(ownerCore) { }
    protected override List<AutomationPeer> GetChildrenCore() {
        List<AutomationPeer> children = base.GetChildrenCore();
        foreach (var button in LayoutTreeHelper.GetVisualChildren(Owner).OfType<Button>())
            children.Add(new ButtonAutomationPeer(button));
        return children;
    }
    bool ISelectionItemProvider.IsSelected { get {
            if (TabControl != null) {
                return TabControl.SelectedContainer == TabItem;
            }
            return false;
        } 
    }
}

Post adding this code inside my MainPage.Xaml.cs , I added a static constructor to register it

static MainWindow() {
    NavigationAutomationPeersCreator.Default.RegisterObject(typeof(DXTabItem), typeof(DXTabItemAutomationPeerEx), owner => new DXTabItemAutomationPeerEx((DXTabItem)owner));
}

post this , these lines work as charm

TabItem CurrentTab = ViewsTabControl.SelectedTabItem as TabItem;
var Children = ViewsTabControl.FindAllChildren(); 
foreach (var child in Children) {
    var subChildren = child.FindAllChildren();
}
Apoorv
  • 2,023
  • 1
  • 19
  • 42