locked
Unable to generate Automation ID for WPF Controls, to be used by coded UI for automation testing RRS feed

  • Question

  • In our application we use various WPF controls for displaying data in a grid, list or tree view. We recently started exploring codedUI for UI test automation and face lots of problems while identifying the controls.

    Problem is the Coded UI (cross hair & UISpy) don’t recognize the type of object returned by the controls and can’t get hold of the text on the elements of list or grid view. So we thought of using Automation ID instead of text property.

    We have tried generating both (static and dynamic) automation IDs but it doesn’t work for elements of grid and listview. We do not see any value for the automation ID neither for the headers nor the cell elements.

    Here is a simple sample code which has two user controls, one using a datagrid and the other a grid definition. As seen in the XAML code, AutomationProperties.AutomationID has been used to generate the Static IDs. But this doesn't work.

    Please could you suggest, what should we change in the code and how do we get the automation IDs?

    <!-- -------- DATAGRID ----------->
    <UserControl x:Class="SampleProjectForCodedUI.ItemControl"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:SampleProjectForCodedUI" 
                 DataContext="{Binding RelativeSource={RelativeSource Self}}"
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <StackPanel Height="69" HorizontalAlignment="Left" Margin="0,1,0,0" Name="stackPanel1" VerticalAlignment="Top" Width="194">
                <DatePicker Width="180" HorizontalAlignment="Left" DatePicker.SelectedDateChanged="TextBox_SelectedDateChanged" />
            </StackPanel>
            <StackPanel Height="69" HorizontalAlignment="Right" Margin="0,1,0,0" Name="stackPanel2" VerticalAlignment="Top" Width="75">
                <Button Content="Add Row" Click="AddRow_Click" Height="26" Width="61" />
            </StackPanel>
            <Grid Grid.Row="1">
                <DataGrid Name="DG1" ItemsSource="{Binding DateCollection}" AutoGenerateColumns="False">
                    <DataGrid.Columns>
                        <DataGridTextColumn Header="Date" Binding="{Binding Date}" AutomationProperties.AutomationId="Date" />
                        <DataGridTextColumn Header="DayOfWeek" Binding="{Binding DayOfWeek}" AutomationProperties.AutomationId="DayOfWeek"/>
                        <DataGridTextColumn Header="Year" Binding="{Binding Year}" AutomationProperties.AutomationId="Year"/>
                    </DataGrid.Columns>
                </DataGrid>            
            </Grid>
        </Grid>
    </UserControl>
    
    <!-- -------- GRID ----------->
    <UserControl x:Class="SampleProjectForCodedUI.UserControl1"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                  xmlns:local="clr-namespace:SampleProjectForCodedUI" 
                 mc:Ignorable="d" 
                 DataContext="{Binding RelativeSource={RelativeSource Self}}"
                 d:DesignHeight="300" d:DesignWidth="300">
       
        <Grid x:Name="grid" Margin="0,0,16,16">
    
            <Grid.Resources>
                <GridView x:Key="DateTracker" x:Shared="False" AllowsColumnReorder="False">
                    <GridViewColumn x:Name="Date" Header="Date" Width="30" >
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Border>
                                    <TextBlock 
                                        Text="{Binding Date}" 
                                        Tag="Caption" 
                                        TextAlignment="Left" 
                                        Margin="0,2,0,0"                                    
                                        AutomationProperties.AutomationId="DateCol" />                                   
                                    
                                </Border>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
    
                    <GridViewColumn x:Name="DayOfWeek" Header="DayOfWeek" Width="140">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Border>
                                    <TextBlock Text="{Binding DayOfWeek}" TextAlignment="Left" Margin="0,2,0,0" Tag="Caption" AutomationProperties.AutomationId="DayOfWeek" />                                    
                                </Border>
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
    
                    <GridViewColumn x:Name="Year" Header="Year" Width="40">
                        <GridViewColumn.CellTemplate>
                            <DataTemplate>
                                <Border>
                                    <TextBlock Text="{Binding Year}" TextAlignment="Left" Margin="0,2,0,0" Tag="Caption" AutomationProperties.AutomationId="Year" />
                                </Border>                           
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>
                </GridView>
            </Grid.Resources>
    
            <Grid.RowDefinitions>
                <RowDefinition x:Name="rowdef_Toolbar" Height="42"/>
                <RowDefinition x:Name="rowdef_NameField" Height="*"/>
            </Grid.RowDefinitions>
    
            <ListView ItemsSource="{Binding DateCollection}" Name="m_Spectra" Grid.Row="1" Margin="0" VerticalAlignment="Stretch" Padding="0"                      
                          View="{StaticResource DateTracker}"
                          ScrollViewer.CanContentScroll="True"
                          ScrollViewer.HorizontalScrollBarVisibility="Disabled" />
            <DatePicker HorizontalAlignment="Left" Margin="5,5,5,5" 
            DatePicker.SelectedDateChanged="TextBox_SelectedDateChanged"/>
            <Button HorizontalAlignment="Right" Margin="5,5,5,5" 
           Content="Add Row" Click="AddRow_Click" />
    
        </Grid>
    </UserControl>
    Friday, July 27, 2012 11:36 AM

Answers

  • Hi Kirti,

    I got you now, and you can refer to below link for your reference:http://stackoverflow.com/questions/9020724/xaml-binding-row-and-column-index-of-cell-to-automation-id

    Code for your reference:

    <DataGrid ... LoadingRow="DataGrid_LoadingRow" > 
     
    <DataGrid.ItemContainerStyle> 
      <Style TargetType="{x:Type DataGridRow}"> 
        <Setter Property="AutomationProperties.AutomationId"> 
          <Setter.Value> 
            <MultiBinding StringFormat="Row{0}"> 
              <Binding Path="(DataGridRow.Tag)" RelativeSource="{RelativeSource Mode=Self}" /> 
            </MultiBinding> 
          </Setter.Value> 
        </Setter> 
        <Setter Property="AutomationProperties.Name"> 
          <Setter.Value> 
            <MultiBinding StringFormat="Row{0}"> 
              <Binding Path="(DataGridRow.Tag)" RelativeSource="{RelativeSource Mode=Self}" /> 
            </MultiBinding> 
          </Setter.Value> 
        </Setter> 
      </Style> 
    </DataGrid.ItemContainerStyle> 
     
    ... 
     
    <DataGrid.CellStyle> 
      <Style> 
        <Setter Property="AutomationProperties.AutomationId"> 
          <Setter.Value> 
            <MultiBinding StringFormat="cell{0}Col{1}"> 
     
              <!-- bind to row automation name (which contains row index) --> 
              <Binding Path="(AutomationProperties.Name)" RelativeSource="{RelativeSource AncestorType=DataGridRow}" /> 
     
              <!-- bind to column index --> 
              <Binding Path="(DataGridCell.TabIndex)" RelativeSource="{RelativeSource Mode=Self}" /> 
     
            </MultiBinding> 
          </Setter.Value> 
        </Setter>  
      </Style> 
    </DataGrid.CellStyle> 
     
    ... 
     
    </DataGrid> 

    Hope it helps.

    Have a  nice day.


    Annabella Luo[MSFT]
    MSDN Community Support | Feedback to us

    • Marked as answer by Kirti Bhogle Thursday, August 2, 2012 10:31 AM
    Wednesday, August 1, 2012 7:54 AM

All replies

  • Hi Kirti,

    Thank  you for your post.

    In WPF, we create condition for automation to find the control which we want to automation, right? So we can use control's automationID to find the control easily. Mean time, I'm not get you that how do you want to get the ID? We should manually add different control automationID in code behind to auto test the UI function. Some thing like below code:

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
            {
                Thread automateThread = new Thread(new ThreadStart(automationbutton));
                automateThread.Start();
            }
            private void automationbutton()
            {
                AutomationElement rootElement = AutomationElement.RootElement;
                if (rootElement != null)
                {
                    System.Windows.Automation.Condition condition = new PropertyCondition(AutomationElement.AutomationIdProperty, "myMainWindow");
                    AutomationElement wndELement = rootElement.FindFirst(TreeScope.Children, condition);
                    if (wndELement != null)
                    {
                        condition = new PropertyCondition(AutomationElement.NameProperty, "Button");
                            AutomationElement btnElement = wndELement.FindFirst(TreeScope.Descendants, condition);
                            InvokePattern btnPattern = btnElement.GetCurrentPattern(InvokePattern.Pattern) as InvokePattern;
                            btnPattern.Invoke();
                    }
                }
            }

    If I misunderstood your issue or if you have any additional questions, please feel free to let me know,

    Have a nice day.


    Annabella Luo[MSFT]
    MSDN Community Support | Feedback to us

    Tuesday, July 31, 2012 10:13 AM
  • Hello Annabella,

    Thanks for your reply but the problem is something else.

    The example that I have quoted here contains a DataGrid and a GridDefinition. My purpose is to generate automation ID for each cell element inside the grid. The data inside the grid is generated at runtime either by a list or user input.

    I have used the AutomationProperties in the XAML code (of the control designer) to generate automation ID. This is similar to what we can do with the code behind. But at run time I cannot see any automation IDs generated. Also I am ABLE to generate Automation IDs for some static controls where data does not get populated at run time. 

    The problem lies with dynamically generated elements.

    Hope this stuff is not too confusing :)

    Looking forward to some help.

    Thanks,

    Kirti


    kirti

    Wednesday, August 1, 2012 6:46 AM
  • Hi Kirti,

    I got you now, and you can refer to below link for your reference:http://stackoverflow.com/questions/9020724/xaml-binding-row-and-column-index-of-cell-to-automation-id

    Code for your reference:

    <DataGrid ... LoadingRow="DataGrid_LoadingRow" > 
     
    <DataGrid.ItemContainerStyle> 
      <Style TargetType="{x:Type DataGridRow}"> 
        <Setter Property="AutomationProperties.AutomationId"> 
          <Setter.Value> 
            <MultiBinding StringFormat="Row{0}"> 
              <Binding Path="(DataGridRow.Tag)" RelativeSource="{RelativeSource Mode=Self}" /> 
            </MultiBinding> 
          </Setter.Value> 
        </Setter> 
        <Setter Property="AutomationProperties.Name"> 
          <Setter.Value> 
            <MultiBinding StringFormat="Row{0}"> 
              <Binding Path="(DataGridRow.Tag)" RelativeSource="{RelativeSource Mode=Self}" /> 
            </MultiBinding> 
          </Setter.Value> 
        </Setter> 
      </Style> 
    </DataGrid.ItemContainerStyle> 
     
    ... 
     
    <DataGrid.CellStyle> 
      <Style> 
        <Setter Property="AutomationProperties.AutomationId"> 
          <Setter.Value> 
            <MultiBinding StringFormat="cell{0}Col{1}"> 
     
              <!-- bind to row automation name (which contains row index) --> 
              <Binding Path="(AutomationProperties.Name)" RelativeSource="{RelativeSource AncestorType=DataGridRow}" /> 
     
              <!-- bind to column index --> 
              <Binding Path="(DataGridCell.TabIndex)" RelativeSource="{RelativeSource Mode=Self}" /> 
     
            </MultiBinding> 
          </Setter.Value> 
        </Setter>  
      </Style> 
    </DataGrid.CellStyle> 
     
    ... 
     
    </DataGrid> 

    Hope it helps.

    Have a  nice day.


    Annabella Luo[MSFT]
    MSDN Community Support | Feedback to us

    • Marked as answer by Kirti Bhogle Thursday, August 2, 2012 10:31 AM
    Wednesday, August 1, 2012 7:54 AM
  • Hi Annabella,

    Thanks a lot for your reply. The solution works perfectly fine for a datagrid and I can see the automation ID for all the cell elements.

    But still problems lies with grid definition which uses a list view to populate data. I use a TextBlock to display each element inside the grid. This TextBlock is difficult to use while automation as it is readonly, does not have any DisplayText property and can't have AutomationIDs.

    So I found a workaround for it, where I use a textBox instead of textBlock and make the border of textbox invisible.

    If I have further queries, shall post back :)

    Regards,

    Kirti


    kirti

    Thursday, August 2, 2012 10:31 AM