Hello,
I recently discovered the WinAppDriver and have been attempting to create a GUI stress test using Appium that randomly clicks all elements in my application. I've hit a lot of snags along the way which I've worked around but this latest one I can't seem to conquer.
I have successfully written code that recursively does the following:
etc.
The problem is that querying to see if an element has children is extremely slow. I manually construct my own xPath so that I know which element to search underneath (since there's no way to get the xPath from an element otherwise) but even with the narrowed scope, it takes super long to find all children. We're talking about 24 seconds.
My question is:
Is there a faster way to find all the children of an element? Or perhaps, any tips on achieving a GUI stress test another way? The app is a rather complex Windows Forms app that also has hosted WPF controls.
I've pasted the test code below for reference.
using TestApp.API;
using NUnit.Framework;
using System.Collections.Generic;
using OpenQA.Selenium.Appium;
namespace GuiAutomationTest
{
[TestFixture]
public class Program
{
DesktopSession desktopSession;
TestApp app;
public Program()
{
app = new TestApp();
app.Visible = true;
desktopSession = new DesktopSession();
}
[Test]
public void StartStress()
{
app.CreateNewProject();
string startingxPath = $"//";
var startingElement = desktopSession.DesktopSessionElement.FindElementByAccessibilityId("MainForm");
var window = desktopSession.DesktopSessionElement.CurrentWindowHandle;
RecursivelyClickAllElements(window, startingElement,startingxPath);
}
public void RecursivelyClickAllElements(string window, AppiumWebElement e, string xPath)
{
xPath += ConstructXPathFromElement(e);
System.Diagnostics.Debug.WriteLine($"Control: {e.GetAttribute("AutomationId")}; Displayed: {e.Displayed}");
//Check if the element is displayed (clickable) to avoid errors when clicking. An error occurs, for example, when attempting to click the selected item of a list box instead of the listbox itself.
if (e.Displayed)
e.Click();
//Grab the current window in case clicking the control changed the active window by popping up a dialog
string currentWindow = desktopSession.DesktopSessionElement.CurrentWindowHandle;
if (currentWindow != window)
{
//Need to write code to handle new dialog
}
//Find all immediate children under the current element
var childElements = desktopSession.DesktopSessionElement.FindElementsByXPath($"{xPath}*");
//If there are no children, abort and return back to parent element (if it exists)
if (childElements == null)
return;
else if (childElements.Count == 0)
return;
//If there are children, continue recursively down the chain
foreach (var element in childElements)
{
RecursivelyClickAllElements(currentWindow, element, xPath);
}
}
//Create an XPath based off of an elements attributes.
public string ConstructXPathFromElement(AppiumWebElement e)
{
string className = e.GetAttribute("LocalizedControlType");
className = (char.ToUpper(className[0]) + className.Substring(1));
string automationId = e.GetAttribute("AutomationId");
string name = e.GetAttribute("Name");
string xPath = $"{className}[@AutomationId=\"{automationId}\"][@Name=\"{name}\"]/";
return xPath;
}
}
I would suggest using XPath count function to get the number of child elements. That may speed up your code.
Hello Karel. Thanks for the quick reply! but I’m afraid I’m not following. How could getting the count of elements speed up performance?
From: Karel Frajtak notifications@github.com
Sent: Sunday, April 14, 2019 12:09:36 AM
To: Microsoft/WinAppDriver
Cc: bkilada; Author
Subject: Re: [Microsoft/WinAppDriver] Quickly finding all child elements (#669)
I would suggest using XPath count function to get the number of child elements. That may speed up your code.
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHubhttps://github.com/Microsoft/WinAppDriver/issues/669#issuecomment-482925770, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AWl8D5i7tBUSUw9NsM8dKmYvFUirYWfaks5vgtQwgaJpZM4cudC2.
Now your code counts the elements on "client side", you run the query to find the elements you are interested in, transfer them to "clent side" and count them.
But if you run query like count(//element/Element1[namespace-uri()='mynamespace']) (found here) then it may speed up the opearaton - the counting is done on "server side".
But I am not 100% sure if you can run query like that in Selenium.
@kfrajtak the counting operations (for example, "else if (childElements.Count == 0)") are not what's consuming large amounts of time. It's pretty much instant, since I already have the array of child elements acquired using FindElementsByXPath. I'm afraid that won't help.
Anyway, there's no reason I can see why searching for elements should take so long. There are only 6 child elements under the root element, and even though I am only searching for immediate children using "/*" it still takes ~20 seconds to find those 6 elements. I'd think it would be fairly instantaneous. I think it might be searching other programs control trees as well, because I initially received a 60 second HTTP time out request and had to close all other open applications before the test ran successfully.
Hi @bkilada, @kfrajtak,
These are valid observations and analysis on the current Windows Application Driver release. There is a known performance bottleneck on querying element(s) using XPath locator due to the existing implementation. It is fairly understood where the performance issue comes from and it is something that will be improved in the future. I am marking this is enhancement to keep it tracked and prioritized.
@timotiusmargo,
Thanks for the reply. Well I guess I'm out of luck with my current approach, then. Perhaps I'll re-work it to get all elements at the start, the problem is that the availability of elements can change based on previous clicks; that's why I preferred the dynamic approach. Anyway, looking forward to a fix!
@timotiusmargo ,
When can we expect the enhancement? I am seeing the same issue with our application. It will be really helpful if it is quicker
Regards,
M. Ganesh
We use the XPath "*/*" to get the children of an element, e.g.
e.findElements(By.xpath("*/*"))
The expected XPath "*" returns the element again, looks like a bug to me...
@joerg1985 Thanks confirming that it works to find children using
e.FindElementsByXPath("*/*")
This works to expensively find itself
e.FindElementsByXPath("/*")
I think you should be able to use
e.FindElementsByXPath(".//child::*")
Most helpful comment
@timotiusmargo ,
When can we expect the enhancement? I am seeing the same issue with our application. It will be really helpful if it is quicker
Regards,
M. Ganesh