Saturday, February 26, 2011

Visual Studio CodedUI Tests Search Criteria

CodedUI Test allows developers/testers to perform UI automated Tests over Windows desktop applications.

Theses kind of features are well documented over the web and several blogs.

In this post I’ll show how to modify generated CodedUI tests to overwrite UI elements search criteria.

This kind of test modifications are very frequent in some type of applications.

Some background..

CodedUI technology works over MSAA API (Microsoft Active Accesibility) and in order to automate user interaction with Windows, buttons, tabs, etc. (any interactive UI element), it needs first to find it and then perform one or more actions over it (this is Windows implementation for Assistive Technologies).

The initial finding or search process is a necessary step. The same way we search an element by seeing the screen, MSAA allows automatized applications to find elements in different ways.

Searching an UI element involves matching specific attributes against expected values. When all attributes or properties meet this “search criteria” a control is found and referenced from CodedUI test for its later used.

Sometimes, finding an UI element using an automated algorithm could be a non trivial task, since its properties or attributes change dynamically during the application lifecycle or between different executions (name, position, text, etc.).

Creating a CodedUI tests is done through a Visual Studio Wizard. It allows us to record our actions and then generating the proper source code (in VB.NET, C#, etc.). The generated source code is implemented using partial classes, thus allowing us to add custom behavior without modifying the auto generated file.

Example, finding a WPF button:

this.mUIInnerButton1Button.SearchProperties[WpfButton.PropertyNames.Name] = "Inner Button 1";

The SearchProperties collection contains an standard set of attributes used for searching a certain kind of control (in this case, a WPF Button).

We can always change and pass the values we decide to this property bag or collection (overwriting the generated default behavior). However, finding a control sometimes require us to perform more elaborated procedures.

Overwriting Control Definition and Search Criteria..

This means, modifying the generated code to change default search criteria logic.

We do not modify .Designer.cs file. We add our new search criteria in the .cs file associated with the UIMap visual studio element:

image

Let´s work over the next sample. Suppose we have defined a WPF button with dynamic text (current DateTime for instance, only for demo purposes). The following XAML is the static content of our main window application.

Code Snippet
<Grid>
    <TabControl>
        <TabItem Header="Tab 1">
            <Button>
                <ContentControl>
                    <StackPanel x:Name="ButtonStackPanel">
                        <TextBox>Text 1</TextBox>
                        <TextBox>Text 2</TextBox>
                        <Button Click="Button1_Click">Inner Button 1</Button>
                        <Button Click="Button2_Click">Inner Button 2</Button>
                        <ComboBox>
                            <ComboBoxItem Content="Item 1" IsSelected="True" />
                            <ComboBoxItem Content="Item 2" />
                            <ComboBoxItem Content="Item 3" />
                        </ComboBox>
                    </StackPanel>
                </ContentControl>
            </Button>
        </TabItem>
        <TabItem Header="Tab 2" />
        <TabItem Header="Tab 3" />
    </TabControl>
</Grid>

Then, by code we create a third button with dynamic text on it:

Code Snippet
public MainWindow()
{
    InitializeComponent();
    this.Loaded += (s, e) =>
    {
        var dynamicButton =
            new Button()
                {
                    Content = "Dynamic Button: " + DateTime.Now.ToString(),
                };
        dynamicButton.Click += (sender, args) => MessageBox.Show("Dynamic Button Click");
        this.ButtonStackPanel.Children.Add(dynamicButton);
    };
}

If your record actions using CodedUI wizard you will get something similar to the following search criteria:

this.mUIDynamicButton2602201Button = new WpfButton(this);
#region Search Criteria
this.mUIDynamicButton2602201Button.SearchProperties[WpfButton.PropertyNames.Name] = "Dynamic Button: 26/02/2011 06:22:29 p.m.";
this.mUIDynamicButton2602201Button.WindowTitles.Add("MainWindow");
#endregion

Try running the test and you’ll end up with an error on the button click (note the name attribute contains the current DateTime, thus every run will generate a different button name). This was expected of course.

So, lets modify the generated UI test code to find the control. In the UIMap.cs (non designer file), add the following code:

Code Snippet
public partial class UIMap
{
    public UIMap()
    {
        this.UIMainWindowWindow.UIItemTabList.UITab1TabPage.Find();
        var tabItemStackPanel = this.UIMainWindowWindow.UIItemTabList.UITab1TabPage.GetChildren()[0];
        var dynamicButton =
            tabItemStackPanel.GetChildren().FirstOrDefault(c => c.Name.Contains("Dynamic"));
        this.UIMainWindowWindow.UIItemTabList.UITab1TabPage.UIDynamicButton2602201Button.CopyFrom(dynamicButton);
    }
}
  1. The custom code is added in the constructor of the UIMap, but you can easily create an Init() method (recommended) and then call it before invoking the test.
  2. We first need to find the control by searching the children of the parent StackPanel (note in the XAML that the StackPanel is the first and only child of the TabItem).
  3. Once we’ve found the StackPanel we enumerate its children, by filtering the results by control name (we are looking for something that begins with the word “Dynamic”).
  4. Finally, we need to clear the recorded search properties values and add the correct name filter value. You can do this by copying the button’s definition (with the CopyFrom() method). This copies all properties from the just found button to the one used by the test.

Final notes..

  • This is a simple example to show how you can manually search and iterate through the entire UI structure of an application to find elements. You can do this by position, name, display text, control type, etc.
  • You can implement the same behavior to find any control in the hierarchy.
  • This API could be used to automate applications for any purpose (not only for tests).

No comments: