2

I have an IWebElement (a div) that contains a child element half of the time. I want to see if it contains the child element and if so, I want to capture it. I do something like this:

IWebElement firstChild;
try 
{
    firstChild = divElement.FindElement(By.XPath("*"));
}
catch (NoSuchElementException e)
{
    // catch and handling...
}

But this is slow on three parts:
1. The use of By.XPath("*")
2. FindElement that is slow in case an element does not exist
3. try/catch is a slower mechanism, I'd rather want a returned boolean on an existing of an item

How can I speed up this way of detecting child elements?

EDIT:
To clarify: The test I am performing runs on grids with divs, a typical grid is 4 x 16, so 64 fields. I want to transform this grid into a DataTable to compare it to the expected outcome. The capturing of these fields to the datatable runs in 22 seconds in total. It is not performing very bad, but I'd like to shave off those precious seconds.

UPDATE:
I managed to do it by capturing the grid with the HTML Agility Pack. Unfortunately (there is always something), the values for the input elements could not be captured because they are dynamically set. As a solution I'd let the HAP return the Ids of the input elements and capture the values with FindElement(By.Id(inputId)) which is blazingly fast compared to other Selenium selection methods.

To keep a long story short: I managed to reduce the capture time from around 22 seconds to less than 3 seconds, a more than 600% performance improvement.

AutomatedChaos
  • 7,267
  • 2
  • 27
  • 47

2 Answers2

1

You need to add a . in front of your XPath to search among the descendants:

var elems = divElement.FindElements(By.XPath("(.//*)[1]"));
IWebElement firstChild = elems.Count > 0 ? elems[0] : null;

As an alternative you can use a CSS selector. It might be slightly faster:

var elems = divElement.FindElements(By.CssSelector("*"));
IWebElement firstChild = elems.Count > 0 ? elems[0] : null;
Florent B.
  • 41,537
  • 7
  • 86
  • 101
  • I like the strict XPath that always returns one element. Unfortunately it didn't help. Maybe I just hit the limit. – AutomatedChaos Nov 02 '16 at 13:06
  • Most of the cost is in sending the command to the driver. So if you wish to significantly reduce the time, you should avoid calling `FindElements` in a loop. Instead, try to locate all the children from all the `divElement` with a single `FindElements`. Another way would be to use a JavaScript call to count the children: `((IJavaScriptExecutor)driver).ExecuteScript("return [].map.call(arguments, function(e){return e.children.length})", elements)`. – Florent B. Nov 02 '16 at 13:25
  • That is actually a good idea: Do a broad find on all the elements with `FindElements` once, and then traversing the elements while ignoring the ones that I don't need. Going to test that. – AutomatedChaos Nov 02 '16 at 14:11
  • Added an update on a different approach that solved the slowness. I came up with this solution thanks to your remark that repeated calls to `FindElements` is slow due to repeated calls to the driver. – AutomatedChaos Nov 03 '16 at 10:46
1

Create an extension method on ISearchContext like this:

public static IWebElement FindElementInstant(this ISearchContext context, By by)
{
    Driver.Instance.Manage().Timeouts().ImplicitlyWait(TimeSpan.Zero);

    var matchingElement = context.FindElements(by).FirstOrDefault();

    Driver.Instance.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(/* whatever your implicit wait is normally */));

    return matchingElement;
}

Used like:

var firstChild = divElement.FindElementInstant(By.XPath("*"));

If you don't need to access the matching element, you can just return bool out of FindElementInstant.

By temporarily disabling your implicit waiting, FindElements will return an empty collection instantly if no matching elements exist and by using FindElements instead of FindElement within a try-catch you avoid the extra time that comes from exceptions.

FLSH
  • 343
  • 1
  • 5
  • 15
  • My ImplicitWait was already set to zero. The FindElements().FirstOrDefault is a good trick combined with the strict xpath given by Florent B. Unfortunately it didn't make the finding faster. – AutomatedChaos Nov 02 '16 at 13:05
  • Maybe I don't have a good understanding of the DOM you are traversing but 22 seconds is a very long time.. are you certain there aren't any implicit/explicit waits happening in the background? Or maybe you have an event handler? – FLSH Nov 02 '16 at 13:58