Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Quickly finding all child elements #669

Open
bkilada opened this issue Apr 14, 2019 · 12 comments
Open

Quickly finding all child elements #669

bkilada opened this issue Apr 14, 2019 · 12 comments

Comments

@bkilada
Copy link

bkilada commented Apr 14, 2019

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:

  1. Click highest level element "e" and find all immediate children.
    -----2. Click first child of "e" and check if the child also has children.
    ----------3. If yes, continue recursively until no more children.
    ----------4. If no, return.
    -----5. Click second child of of element "e".

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;
        }
    }
@kfrajtak
Copy link

I would suggest using XPath count function to get the number of child elements. That may speed up your code.

@bkilada
Copy link
Author

bkilada commented Apr 14, 2019 via email

@kfrajtak
Copy link

kfrajtak commented Apr 14, 2019

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.

@bkilada
Copy link
Author

bkilada commented Apr 15, 2019

@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.

@timotiusmargo
Copy link
Contributor

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.

@bkilada
Copy link
Author

bkilada commented Apr 15, 2019

@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!

@GaneshMucherla
Copy link

@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

@joerg1985
Copy link

joerg1985 commented Aug 14, 2019

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...

@liljohnak
Copy link

liljohnak commented Dec 4, 2019

@joerg1985 Thanks confirming that it works to find children using

e.FindElementsByXPath("*/*")

This works to expensively find itself

e.FindElementsByXPath("/*")

@andreea-qa
Copy link

andreea-qa commented Aug 24, 2020

I think you should be able to use
e.FindElementsByXPath(".//child::*")

@0lks
Copy link

0lks commented Oct 7, 2021

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...

Thank you, this simple prefix helped me as well. I agree that this looks like a bug... I was calling WindowsElement.FindElementByXPath("child::*") and it made no sense to get back the same element.

@huster-songtao
Copy link

C# code
It work well, but not fast.

        public static ReadOnlyCollection<AppiumWebElement> GetChildren(this WindowsElement element)
        {
            try
            {
                return element.FindElementsByXPath("*/*");
            }
            catch (Exception)
            {
                return null;
            }
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants