Searching Elements Faster using .Exists<>

258 views
Skip to first unread message

frankselec...@gmail.com

unread,
Aug 9, 2018, 1:26:46 PM8/9/18
to TestStack.White
Hey all,

So what I'm doing is using the .Exist<T> method in TestStack.White to see if an error dialog box exists. The problem I'm having is that finding it is EXTREMELY slow. This is due to the fact the the program has a data table in it with over 400 items, and then sub items of those items. I'm not really sure how I can make this search quicker, here's the box I'm trying to see if exists:

Screenshot_1.png

 


















And here is the information on it:

"No Rates Returned":
How found: Selected from tree...
Name: "No rates returned."
ControlType: UIA_TextControlTypeId (0xC364)
LocalizedControlType: "text"
BoundingRectangle: {l:1635 t:462 r:1731 b:477}
IsEnabled: true
IsOffscreen: false
IsKeyboardFocusable: false
HasKeyboardFocus: false
AccessKey: ""
ProcessId: 3036
RuntimeId: [2A.6105AE]
AutomationId: "65535"
FrameworkId: "Win32"
ClassName: "Static"
NativeWindowHandle: 0x6105AE
ProviderDescription: "[pid:10424,providerId:0x6105AE Main:Nested [pid:3036,providerId:0x6105AE Annotation(parent link):Microsoft: Annotation Proxy (unmanaged:uiautomationcore.dll); Main:Microsoft: MSAA Proxy (unmanaged:uiautomationcore.dll)]; Hwnd(parent link):Microsoft: HWND Proxy (unmanaged:uiautomationcore.dll)]"
IsPassword: false
HelpText: ""
HeadingLevel: [Not supported]
LegacyIAccessible.ChildId: 0
LegacyIAccessible.DefaultAction: ""
LegacyIAccessible.Description: ""
LegacyIAccessible.Help: ""
LegacyIAccessible.KeyboardShortcut: ""
LegacyIAccessible.Name: "No rates returned."
LegacyIAccessible.Role: text (0x29)
LegacyIAccessible.State: read only (0x40)
LegacyIAccessible.Value: ""
IsAnnotationPatternAvailable: false
IsDragPatternAvailable: false
IsDockPatternAvailable: false
IsDropTargetPatternAvailable: false
IsExpandCollapsePatternAvailable: false
IsGridItemPatternAvailable: false
IsGridPatternAvailable: false
IsInvokePatternAvailable: false
IsItemContainerPatternAvailable: false
IsLegacyIAccessiblePatternAvailable: true
IsMultipleViewPatternAvailable: false
IsObjectModelPatternAvailable: false
IsRangeValuePatternAvailable: false
IsScrollItemPatternAvailable: false
IsScrollPatternAvailable: false
IsSelectionItemPatternAvailable: false
IsSelectionPatternAvailable: false
IsSpreadsheetItemPatternAvailable: false
IsSpreadsheetPatternAvailable: false
IsStylesPatternAvailable: false
IsSynchronizedInputPatternAvailable: false
IsTableItemPatternAvailable: false
IsTablePatternAvailable: false
IsTextChildPatternAvailable: false
IsTextEditPatternAvailable: false
IsTextPatternAvailable: false
IsTextPattern2Available: false
IsTogglePatternAvailable: false
IsTransformPatternAvailable: false
IsTransform2PatternAvailable: false
IsValuePatternAvailable: false
IsVirtualizedItemPatternAvailable: false
IsWindowPatternAvailable: false
IsCustomNavigationPatternAvailable: false
IsSelectionPattern2Available: false
FirstChild: [null]
LastChild: [null]
Next: [null]
Previous: "OK" button
Other Props: Object has no additional properties
Children: Container has no children
Ancestors: "" dialog
 
"New Car  " window
 
"Desktop 1" pane
 
[ No Parent ]


Dialog box (itself):
How found: Selected from tree...
Name: ""
ControlType: UIA_WindowControlTypeId (0xC370)
LocalizedControlType: "dialog"
BoundingRectangle: {l:1621 t:413 r:1764 b:543}
IsEnabled: true
IsKeyboardFocusable: true
HasKeyboardFocus: false
ProcessId: 3036
RuntimeId: [2A.7109FC]
FrameworkId: "Win32"
ClassName: "#32770"
NativeWindowHandle: 0x7109FC
IsControlElement: true
IsContentElement: true
ProviderDescription: "[pid:10424,providerId:0x7109FC Main:Microsoft: Container Proxy (unmanaged:uiautomationcore.dll); Nonclient:Microsoft: Non-Client Proxy (unmanaged:uiautomationcore.dll); Hwnd(parent link):Microsoft: HWND Proxy (unmanaged:uiautomationcore.dll)]"
HeadingLevel: [Not supported]
LegacyIAccessible.ChildId: 0
LegacyIAccessible.Role: dialog (0x12)
LegacyIAccessible.State: focusable (0x100000)
Transform.CanMove: true
Transform.CanResize: false
Transform.CanRotate: false
Window.CanMaximize: false
Window.CanMinimize: false
Window.IsModal: true
Window.IsTopmost: false
Window.WindowInteractionState: ReadyForUserInteraction (2)
Window.WindowVisualState: Normal (0)
IsAnnotationPatternAvailable: false
IsDragPatternAvailable: false
IsDockPatternAvailable: false
IsDropTargetPatternAvailable: false
IsExpandCollapsePatternAvailable: false
IsGridItemPatternAvailable: false
IsGridPatternAvailable: false
IsInvokePatternAvailable: false
IsItemContainerPatternAvailable: false
IsLegacyIAccessiblePatternAvailable: true
IsMultipleViewPatternAvailable: false
IsObjectModelPatternAvailable: false
IsRangeValuePatternAvailable: false
IsScrollItemPatternAvailable: false
IsScrollPatternAvailable: false
IsSelectionItemPatternAvailable: false
IsSelectionPatternAvailable: false
IsSpreadsheetItemPatternAvailable: false
IsSpreadsheetPatternAvailable: false
IsStylesPatternAvailable: false
IsSynchronizedInputPatternAvailable: false
IsTableItemPatternAvailable: false
IsTablePatternAvailable: false
IsTextChildPatternAvailable: false
IsTextEditPatternAvailable: false
IsTextPatternAvailable: false
IsTextPattern2Available: false
IsTogglePatternAvailable: false
IsTransformPatternAvailable: true
IsTransform2PatternAvailable: false
IsValuePatternAvailable: false
IsVirtualizedItemPatternAvailable: false
IsWindowPatternAvailable: true
IsCustomNavigationPatternAvailable: false
IsSelectionPattern2Available: false
FirstChild: "OK" button
LastChild: "No rates returned." text
Next: "Deductible" pane
Previous: [null]
Other Props: Object has no additional properties
Children: "OK" button
 
"No rates returned." text
Ancestors: "New Car  " window
 
"Desktop 1" pane
 
[ No Parent ]


And here is the code I'm using to pull it:
     
  private bool GrabErrorOrExucute()
       
{
           
using (CoreAppXmlConfiguration.Instance.ApplyTemporarySetting(s => s.BusyTimeout = 500))
           
{
               
if (_mainWindow.Exists(SearchCriteria.ByAutomationId("65535")))
               
{
                   
var error = _mainWindow.Get(
                       
SearchCriteria
                           
.ByAutomationId(
                               
"65535"));
                   
MessageBox.Show("Error has been thrown while getting rates, it will be displayed now...",
                       
"Error While Getting Rates", MessageBoxButtons.OK, MessageBoxIcon.Error,
                       
MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly);
                   
MessageBox.Show(error.Name, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error,
                       
MessageBoxDefaultButton.Button1, MessageBoxOptions.DefaultDesktopOnly);


                   
var okbtn = _mainWindow.Get<Button>(SearchCriteria.ByAutomationId("2"));
                    ratestat
.Text = "Error While Getting Rate...";
                   
Stop();
                   
return true;
               
}


               
return false;
           
}
       
}

This can take well over 10-15 minutes minutes because of the table that is in the program. Here's what the table looks like (Which goes from 1 to ~400). How do I make this faster?:

Screenshot_2.png

Mike Hetzer

unread,
Aug 10, 2018, 2:51:37 PM8/10/18
to TestStack.White
Could you post a screenshot as well showing the Dialog's ancestors?
Preferably the inspect tool tree?

Mike Hetzer

unread,
Aug 10, 2018, 3:27:05 PM8/10/18
to TestStack.White
I've had issues with White's slowness in detecting windows/modals/dialogs as well.
Here's a couple of methods I use that utilize System.Windows.Automation or UIAutomationClient.dll libraries, respectively.
Basically I am ignoring the application held by White and using the raw element tree from the root element (desktop) to find the dialog.

                /// <summary>
/// 
/// </summary>
/// <param name="SC"></param>
/// <param name="maxWaitSeconds"></param>
/// <returns>true if the window specified by SC exists, false otherwise</returns>
public static bool checkForWindow(SearchCriteria SC, int maxWaitSeconds = 10, Processes pcs = Processes.Empower)
{
int counter = 0;
bool Found = false;
UIAutomationClient.IUIAutomationElementArray descendantWindows = null;
UIAutomationClient.IUIAutomationCondition windowType = UIA_Extensions.AUTOCLASS.CreatePropertyCondition(UIAutomationClient.UIA_PropertyIds.UIA_LocalizedControlTypePropertyId, "window");
UIAutomationClient.IUIAutomationCondition dialogType = UIA_Extensions.AUTOCLASS.CreatePropertyCondition(UIAutomationClient.UIA_PropertyIds.UIA_LocalizedControlTypePropertyId, "dialog");
UIAutomationClient.IUIAutomationOrCondition windowTypeConditions  = (UIAutomationClient.IUIAutomationOrCondition)UIA_Extensions.AUTOCLASS.CreateOrCondition(windowType, dialogType);
do
{
try
{
descendantWindows = null;
descendantWindows = UIA_Extensions.ROOT.FindAll(UIAutomationClient.TreeScope.TreeScope_Descendants, windowTypeConditions);
if (descendantWindows.Length != 0)
{
for (int i = 0; i < descendantWindows.Length; i++)
{
var window = descendantWindows.GetElement(i);
try
{
if (window.CurrentProcessId == ProcessID)
if (CheckCriteriaMatch(SC, window))
return Found = true;
}
catch (System.Runtime.InteropServices.COMException ex) { }
}
}
}
catch(System.Runtime.InteropServices.COMException com)
{

}
finally
{
Thread.Sleep(1000);
counter++;
}
} while (!Found && counter < maxWaitSeconds);
return Found;
}



public static void CheckForExpectedWindow(SearchCriteria SC, int maxWaitSeconds = 10)
{
bool Found = false;
int counter = 0;
string criteriaMatch1 = SC.ToString().Substring(SC.ToString().IndexOf('=') + 1);
List<Window> windows = new List<Window>();
AutomationElementCollection AEdescendants;
PropertyCondition windowType = new PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "window");
PropertyCondition dialogType = new PropertyCondition(AutomationElement.LocalizedControlTypeProperty, "dialog");
OrCondition windowTypeConditions = new OrCondition(new Condition[] { windowType, dialogType });
do
{
windows.Clear();
windows = Retry.For(() => TestedApplication.GetWindows(), TimeSpan.FromSeconds(10));
if (windows.Count != 0)
{
foreach (Window window in windows)
{
if (CheckCriteriaMatch(SC, window.AutomationElement))
return;
else
{
AEdescendants = Retry.For(() => window.AutomationElement.FindAll(TreeScope.Descendants, windowTypeConditions), TimeSpan.FromSeconds(10));
foreach (AutomationElement desc in AEdescendants)
{
if (CheckCriteriaMatch(SC, desc))
{
return;
}
}
}
}
}
System.Threading.Thread.Sleep(1000);
counter++;
} while (!Found && counter < maxWaitSeconds);
if (!Found)
throw new Exception("The Expected window was not displayed!!!");
}



On Thursday, August 9, 2018 at 1:26:46 PM UTC-4, frankselec...@gmail.com wrote:

frankselec...@gmail.com

unread,
Aug 10, 2018, 3:46:51 PM8/10/18
to TestStack.White

Screenshot_1.png

Mike Hetzer

unread,
Aug 10, 2018, 3:51:59 PM8/10/18
to TestStack.White
Alternatively, you can mess with the Search Depth configurations.
https://teststackwhite.readthedocs.io/en/latest/AdvancedTopics/SearchDepth/

Although remember to turn the configuration back afterwards.

frankselec...@gmail.com

unread,
Aug 10, 2018, 3:52:46 PM8/10/18
to TestStack.White
Can you give me an example on how to use the methods that you just pasted me?

frankselec...@gmail.com

unread,
Aug 10, 2018, 4:06:28 PM8/10/18
to TestStack.White
Also, I have a method that does not exist:

Screenshot_6.png

Mike Hetzer

unread,
Aug 10, 2018, 4:52:06 PM8/10/18
to TestStack.White
Attached the UIA_Extensions class.
UIAutomationClient doesnt really have great documentation, but it is basically an upgrade or the unmanaged code equivalent of System.Windows.Automation (windows automation is what White is built on)

The CheckCriteriaMatch method was built because I am still using White SearchCriteria to locate and define my windows / page objects.
See here:
                public static bool CheckCriteriaMatch(SearchCriteria SC, AutomationElement AE)
{
string searchCriteria, criteriaType, criteriaMatch;
int indexEqualsign;
searchCriteria = SC.ToString();
indexEqualsign = searchCriteria.IndexOf('=');
criteriaType = searchCriteria.Substring(0, indexEqualsign);
criteriaMatch = searchCriteria.Substring(searchCriteria.IndexOf('=') + 1);
switch (criteriaType)
{
case "AutomationId":
if (AE.Current.AutomationId == criteriaMatch)
return true;
break;
case "ClassName":
if (AE.Current.ClassName == criteriaMatch)
return true;
break;
case "Name":
if (AE.Current.Name == criteriaMatch)
return true;
break;
default:
throw new Exception("Search Criteria Type does not match a type inside switch.");
}
return false;
}
UIA_Automation_Extensions.cs

Mike Hetzer

unread,
Aug 10, 2018, 5:12:07 PM8/10/18
to TestStack.White
It's familiar because my main application under test generates a lot of standard windows dialogs which looks like you are dealing with here.
Control type is "dialog" and ClassName is "#32770"

Sample from my page object:
        public class Dialog : BaseScreen
{
public static SearchCriteria scWindow = SearchCriteria.ByClassName("#32770");

If you go this route - I would likely recommend using UIAutomationClient - looking at your inspect sample - the provider description says it is unmanaged uiautomationcore code - System.Windows.Automation may fail


I might have something else useful in this sandbox automation project, idk
https://drive.google.com/drive/folders/0BysJ1o0yuqNGNkVENEhBM3QxMm8?usp=sharing
Reply all
Reply to author
Forward
0 new messages