Saturday, June 4, 2011

UIA - how to automate WPF Ribbon control

Microsoft Ribbon for WPF 4.0 is not well working with Coded UI Test. UI recording against WPF ribbon control might work but replaying automation is likely not working. One way of getting around this problem is to use UIA (aka UIAutomation).

Here is an example of WPF Ribbon control in XAML.

<ribbonwindow height="480" title="MainWindow" width="640" x:class="WpfRibbonApplication1.MainWindow" x:name="RibbonWindow" xmlns:ribbon="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

 <grid x:name="LayoutRoot">
        <grid.rowdefinitions>
            <rowdefinition height="Auto"></rowdefinition>
            <rowdefinition height="*"></rowdefinition>
        </grid.rowdefinitions>

        <ribbon automationproperties.name="myRibbon" x:name="myRibbon">
            <ribbon.applicationmenu>
                <ribbonapplicationmenu smallimagesource="Images\SmallIcon.png">
                    <ribbonapplicationmenuitem header="Hello _Ribbon" imagesource="Images\LargeIcon.png" x:name="MenuItem1"></ribbonapplicationmenuitem>
                </ribbonapplicationmenu>
            </ribbon.applicationmenu>
            <ribbontab header="Home" x:name="HomeTab">
                <ribbongroup header="Group1" x:name="Group1">
                    <ribbonbutton label="Button1" largeimagesource="Images\LargeIcon.png" x:name="Button1"></ribbonbutton>

                    <ribbonbutton label="Button2" smallimagesource="Images\SmallIcon.png" x:name="Button2"></ribbonbutton>
                    <ribbonbutton label="Button3" smallimagesource="Images\SmallIcon.png" x:name="Button3"></ribbonbutton>
                    <ribbonbutton label="Button4" smallimagesource="Images\SmallIcon.png" x:name="Button4"></ribbonbutton>
                    
                </ribbongroup>
                
            </ribbontab>
            <ribbontab header="View" x:name="ViewTab">
                <ribbongroup header="Group2" x:name="Group2">
                    <ribbonbutton click="Button11_Click" label="Button11" largeimagesource="Images\LargeIcon.png" x:name="Button11"></ribbonbutton>
                </ribbongroup>
            </ribbontab>
        </ribbon>        
    </grid>
</ribbonwindow>

If the application is run, it displays ribbon menus as follows.
















In order to automate the ribbon control, one can use UIAutomation and here is an example. Please note that two references - UIAutomationClient and UIAutomationTypes - should be added to the Test Project.
using System.Threading;
using System.Windows.Automation;  //UIAutomationClient
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UIATestProject
{
    [TestClass]
    public class RibbonTest
    {
        [TestMethod]
        public void TestMethod1()
        {
            AutomationElement root = AutomationElement.RootElement;

            PropertyCondition appCond1 = new PropertyCondition(AutomationElementIdentifiers.NameProperty, "MainWindow");
            PropertyCondition appCond2 = new PropertyCondition(AutomationElementIdentifiers.ClassNameProperty, "Window");
            AndCondition appCond = new AndCondition(appCond1, appCond2);
            AutomationElement app = root.FindFirst(TreeScope.Descendants, appCond);

            PropertyCondition ribbonCond1 = new PropertyCondition(AutomationElementIdentifiers.ClassNameProperty, "Ribbon");
            PropertyCondition ribbonCond2 = new PropertyCondition(AutomationElementIdentifiers.NameProperty, "myRibbon");
            AndCondition ribbonCond = new AndCondition(ribbonCond1, ribbonCond2);
            AutomationElement custom = app.FindFirst(TreeScope.Descendants, ribbonCond);

            PropertyCondition homeCond = new PropertyCondition(AutomationElementIdentifiers.NameProperty, "Home");
            AutomationElement homeTab = custom.FindFirst(TreeScope.Descendants, homeCond);
            PropertyCondition viewCond = new PropertyCondition(AutomationElementIdentifiers.NameProperty, "View");
            AutomationElement viewTab = custom.FindFirst(TreeScope.Descendants, viewCond);
            
            SelectMenu(homeTab);            
            SelectMenu(viewTab);
        }

        private void SelectMenu(AutomationElement elem)
        {
            SelectionItemPattern patt = elem.GetCurrentPattern(SelectionItemPattern.Pattern) as SelectionItemPattern;
            patt.Select();
        }
    }
}

Basically what the code does is to look for UI elements from the UI hierarchy and select UI menu element by using SelectionItemPattern. Typically UISpy.exe is used to check UI hierarchy and to get element detail information.