Coded UI tests : my TextBlock is not found, how to find it ?

, , 4 Comments

In the previous post, we have seen that setting the AutomationId can greatly help us when we want to perform some UI Tests.

Todyay, I struggled with a problem which seems common after some digging on the net : the TextBlock control is not found and no assertion can be set on it.
But it does not always occur and it is then difficult to debug this strange behavior.

In this post we’ll see why this happen and how to solve this issue.

Why and When ?

The problem comes from the TextBlockAutomationPeer. If you digg into its code you can find this :
[csharp]override protected bool IsControlElementCore()
{
return ((TextBlock)Owner).TemplatedParent == null;
} [/csharp]

This basically means that if the TextBlock is inside a template, any kind of template, it is not exposed.
For example, if in an ItemsControl, a Listbox or another control, you use a DataTemplate containing a TextBlock, this one won’t be find by the automation client. Here is a code example :
[xml]<Grid>
<Grid.Resources>
<DataTemplate x:Key="ItemTemplate">
<TextBlock Text="{Binding }" />
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding }"
ItemTemplate="{StaticResource ItemTemplate}" />
</Grid>[/xml]

A solution

The only dirty solution I find is to create your own TextBlock control with its own corrected AutomationPeer.
It will be based on the original TextBlockAutomationPeer but the IsControlElementCore method will always return true.
As a bonus, I let commented a snippet which will expose the TextBlock only if an AutomationId is set on it.

The code to write is really light. The real issue is that you have to update every TextBlock usage in the XAML of your application.
[csharp]
public class AutomatisableTextBlock : TextBlock
{
protected override AutomationPeer OnCreateAutomationPeer()
{
return new AlwaysVisibleTextBlockAutomationPeer(this);
}
}

//The corrected AutomationPeer is based on the TextBlockAutomationPeer
public class AlwaysVisibleTextBlockAutomationPeer : TextBlockAutomationPeer
{
public AlwaysVisibleTextBlockAutomationPeer(TextBlock t)
: base(t) { }

protected override bool IsControlElementCore()
{
//if (String.IsNullOrEmpty(GetAutomationId()))
// return false;
return true;
}
}
[/csharp]

Finally, the demo app XAML needs to be updated:
[xml]
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ItemTemplate">
<local:AutomatisableTextBlock Text="{Binding }" />
</DataTemplate>
</Grid.Resources>
<ListBox ItemsSource="{Binding }"
ItemTemplate="{StaticResource ItemTemplate}" />
</Grid>[/xml]

After the update, the TextBlock is correctly found by any automation client:

 

4 Responses

  1. Biff Turckle

    09/11/2011 19 h 11 min

    Thanks for the explanation. Unfortunately we're not in a position to implement a wholesale change to all the TextBlocks in our app- so this could be a dead end for the coded UI tests unfortunately……

  2. Axel Mayer

    30/05/2012 17 h 21 min

    I have two questions:
    1. If I comment out the snippet which exposes the TextBlock only if an AutomationId is set on it, I get the error message "Recursive call to Automation Peer API is not valid".

    2. In UISpy I don't see the AutomationID. Only the value of the TextBlock. What can I do to use the AutomationID?

Comments are closed.