Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

System.Windows.Automation is very slow at enumerating table rows vs. UIAutomationCore

I am trying to do automated testing of my application via UI Automation (mainly using TestStack.White to provide a friendly interface; it uses System.Windows.Automation as a back-end). I have a table with ~200 rows that I need to test the values of (actually I only want to test the first and last couple rows). I have discovered that using COM-interop UIAutomationCore by itself, I can enumerate the rows in a fraction of a second, but only when I don't use White or System.Windows.Automation. As soon as System.Windows.Automation initializes, future UI Automation actions to enumerate rows are slow:

First COM run: it took 0.04 seconds to get 102 rows!
First System.Windows.Automation run: it took 7.18 seconds to get 102 rows!
Second COM run: it took 7.87 seconds to get 102 rows!

I created a simple WinForms test application (TableTest.exe to verify that it was System.Windows.Automation and not something to do with my application:

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    var form = new Form() { Text = "TableTest", WindowState = FormWindowState.Maximized };
    var dgv = new DataGridView() { Name = "DGV", Dock = DockStyle.Fill, AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill };
    dgv.Columns.Add("i", "i");
    dgv.Columns.Add("2i", "2i");
    dgv.Columns.Add("i^2", "i^2");
    dgv.Columns.Add("i^i", "i^i");
    for (int i = 0; i < 100; ++i)
        dgv.Rows.Add(i, i * 2, i * i, Math.Pow(i, i));
    form.Controls.Add(dgv);

    Application.Run(form);
}

Then I created another test app to test the first one. It works as either a console app or a WinForms app. First I test with COM automation, then with System.Windows.Automation, then again with COM automation. As you can see from the output I quoted above, the first block executes very quickly, the next two blocks execute excruciatingly slowly. If I comment out the System.Windows.Automation block code then both COM blocks execute quickly.

using UIA = Interop.UIAutomationCore;
static void Main(string[] args)
{
    var process = System.Diagnostics.Process.Start("TableTest.exe");
    System.Threading.Thread.Sleep(500);
    var uia = new UIA.CUIAutomation();
    var rootCom = uia.GetRootElement();
    var windowCom = rootCom.FindFirst(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_NamePropertyId, "TableTest"));
    var dgvCom = windowCom.FindFirst(UIA.TreeScope.TreeScope_Descendants, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_AutomationIdPropertyId, "DGV"));
    var start = DateTime.Now;
    var rowCount = dgvCom.FindAll(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_ControlTypePropertyId, UIA.UIA_ControlTypeIds.UIA_CustomControlTypeId)).Length;
    var elapsed = (DateTime.Now - start).TotalSeconds;
    Console.WriteLine(String.Format("It took {0} seconds to get {1} rows!", elapsed.ToString("f2"), rowCount));
    process.Kill();

    process = System.Diagnostics.Process.Start("TableTest.exe");
    System.Threading.Thread.Sleep(500);
    var root = AutomationElement.RootElement;
    var window = root.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "TableTest"));
    var dgv = window.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.AutomationIdProperty, "DGV"));
    start = DateTime.Now;
    rowCount = dgv.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Custom)).Count;
        elapsed = (DateTime.Now - start).TotalSeconds;
    Console.WriteLine(String.Format("It took {0} seconds to get {1} rows!", elapsed.ToString("f2"), rowCount));
        process.Kill();

    process = System.Diagnostics.Process.Start("TableTest.exe");
    System.Threading.Thread.Sleep(500);
    uia = new UIA.CUIAutomation();
    rootCom = uia.GetRootElement();
    windowCom = rootCom.FindFirst(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_NamePropertyId, "TableTest"));
    dgvCom = windowCom.FindFirst(UIA.TreeScope.TreeScope_Descendants, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_AutomationIdPropertyId, "DGV"));
    start = DateTime.Now;
    rowCount = dgvCom.FindAll(UIA.TreeScope.TreeScope_Children, uia.CreatePropertyCondition(UIA.UIA_PropertyIds.UIA_ControlTypePropertyId, UIA.UIA_ControlTypeIds.UIA_CustomControlTypeId)).Length;
    elapsed = (DateTime.Now - start).TotalSeconds;
    Console.WriteLine(String.Format("It took {0} seconds to get {1} rows!", elapsed.ToString("f2"), rowCount));
    process.Kill();
}

What the heck is System.Windows.Automation doing that kills the performance of UI Automation? I've looked at the White source code and I don't see anything obvious. I can't profile System.Windows.Automation itself because I can't find any PDB for it. I'm not very familiar with UI Automation so maybe it'll be obvious to someone else. The White is: 0.13.0.0 and I'm testing on 64-bit Windows 7.

like image 465
Matt Chambers Avatar asked Feb 05 '15 20:02

Matt Chambers


1 Answers

I cannot answer your question. But many people will come here from Google who search for the keywords "uiautomation slow" and the first result in Google is your question. (You wrote a bestseller)

For all those coming from Google and struggling with slow UIAutomation I post this answer.

System.Windows.Automation is EXTREMELY slow. Obtaining 30 child elements may take 1000ms on a very fast computer! I have even seen it hanging forever while getting the child elements of a Tree in a QT application.

Apart from that the implementation is not even thread safe.

System.Windows.Automation is deprecated. Do not use it!

In the MSDN you find the following note:

UI Automation was first available in Windows XP as part of the Microsoft .NET Framework. Although an unmanaged C++ API was also published at that time, the usefulness of client functions was limited because of interoperability issues. For Windows 7, the API has been rewritten in the Component Object Model (COM). Although the library functions introduced in the earlier version of UI Automation are still documented, they should not be used in new applications.

The solution to slow performance is to use the new IUIAutomationElement COM interface instead of the old System.Windows.Automation C# interface. After that the code will be running lightning fast!

Apart from that the new interface offers much more patterns and Microsoft is extending it continously. In the Windows 10 SDK (UIAutomationClient.h and UIAutomationCore.h) several patterns and properties have been added which are not available in the .NET Automation framework.

The following patterns are available in the COM version of UIAutomation which do not exist in System.Windows.Automation:

  • IUIAutomationLegacyIAccessiblePattern
  • IUIAutomationObjectModelPattern
  • IUIAutomationAnnotationPattern
  • IUIAutomationTextPattern2
  • IUIAutomationStylesPattern
  • IUIAutomationSpreadsheetPattern
  • IUIAutomationSpreadsheetItemPattern
  • IUIAutomationTransformPattern2
  • IUIAutomationTextChildPattern
  • IUIAutomationDragPattern
  • IUIAutomationDropTargetPattern
  • IUIAutomationTextEditPattern
  • IUIAutomationCustomNavigationPattern

Additionally the following Control types have been added:

  • AppBar
  • SemanticZoom

Additionally the following Element's have been added:

  • IUIAutomationElement2
  • IUIAutomationElement3
  • IUIAutomationElement4
like image 170
Elmue Avatar answered Nov 07 '22 09:11

Elmue