About the appropriateness of Selenium WebDriverWait

The closer I get to know Selenium WebDriver, the more I have questions why this or that functionality is implemented this way and not otherwise. In his speech, “Troubles in Selenium WebDriver,” Alexey Barantsev sheds light on the subtleties of implementing this automation tool and distinguishes between “bugs” and “features”. You’ll find a lot of interesting things in the video, but still some points remain (at least for me) in the shade.

In this article I want to discuss the frequently used tool for waiting for an event on a page, implemented using the WebDriverWait class and its main Until method . I wonder if WebDriverWait is needed at all, and is it possible to refuse it?

Thoughts will be presented in the context of C #, although I do not think that the implementation logic of this class will be any different for other programming languages.

When creating a WebDriverWait instance, a driver instance is passed to the constructor, which is stored in the input input field . The Until method assumes a delegate whose input parameter should be IWebDriver, of which input is an instance.

Let's look at the source code of the Until method . The backbone of his logic is an endless cycle with two conditions for exiting it: the onset of the desired event or timeout. Additional “goodies” are ignoring the predefined exceptions and returning the object if the bes does not act as TResult (more on that later).

The first limitation that I see is that we always need exactly an instance of IWebDriver, although inside the Until method (to be precise, as an input parameter for condition) we could completely manage ISearchContext. Indeed, in most cases, we expect some element or change in its property and use FindElement (s) to search for it.

I would venture to say that using ISearchContext would be even more logical, because client code (class) is not only a page object, which, in the search for children, is repelled from the root of the page. Sometimes this is a class that describes a composite element whose root is another element of the page, and not the page itself. An example of this is SelectElement , which accepts a reference to the parent IWebElement in the constructor.

Let's get back to the issue of initializing WebDriverWait. This action requires an instance of the driver. Those. we always, one way or another, need to throw an instance of IWebDriver from outside the client code, even if it is a class of some composite element (an example about SelectElement), which already accepts a "parent". From my point of view, this is unnecessary.

Of course, we can declare a class by analogy
SearchContextWait : DefaultWait
But let's not rush. We do not need him.

Let's see how the driver instance passed to condition is used. Usually it looks something like this:

var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10));
wait.Until( d => d.FindElements(By.XPath("locator")).Count > 0 );

The question arises, why is a “local” version of the driver necessary if condition is always available from client code? Moreover, this is the same instance passed earlier through the constructor. Those. the code might look something like this:

var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10));
wait.Until( d => Driver.FindElements(By.XPath("locator")).Count > 0 );

Even Simon Stewart uses this approach in his speech .

image

He does not write “d -> d.”, But writes “d -> driver.”, I.e. the driver instance passed into the method is simply ignored. But it is necessary to transmit it, for this is required by the method signature!

Why pass the driver inside the condition of the method? It is possible to isolate the search inside this method, as implemented in ExpectedConditions ? Take a look at the implementation of the TextToBePresentInElement method . Or VisibilityOfAllElementsLocatedBy . Or TextToBePresentInElementValue . The transferred driver is not even used in them!

So, the first thought is that we do not need the Until method with the delegate parameter that the driver accepts.

Let's now figure out if the Until method needs a return value? If bool acts as TResult, then no, it is not necessary. Indeed, in case of success, you will get true, and in case of failure, you will get a TimeoutException. What is the information content of such behavior?

But what if the object is TResult? Assume the following construction:

var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10));
wait.IgnoreExceptionTypes(typeof(NoSuchElementException));
var element = wait.Until(d => d.FindElement(By.XPath("locator")));

Those. we not only wait for the appearance of the element, but also use it (if we have waited), thereby removing one unnecessary call to the DOM. Good.

Let's take a closer look at these three lines of code. Inside the implementation of the Until method, it boils down to a kind of similarity (conditional code)

try { FindElement } catch (NoSuchElementException) {}

Those. an exception will be thrown every time until the element appears in the DOM. Since the generation of exception is a rather expensive event, I would prefer to avoid it, especially in places where it is not difficult. We can rewrite the code as follows:

var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10));
var elements = wait.Until(d => d.FindElements(By.XPath("locator")));

Those. we use FindElements, which does not throw an exception. Wait, will this design wait for the appearance of the elements? NOT! Because, if you look at the source code , an infinite loop completes immediately, as soon as condition returns non-null. And FindElements in case of failure returns an empty collection, but not null in any way. Those. For a list of items, using Until makes no sense.

Ok, the list is clear. But still, how to return the found element and not throw an exception? The code might look like this:

var wait = new WebDriverWait(Driver, TimeSpan.FromSeconds(10));
var element = wait.Until(d => d.FindElements(By.XPath("locator")).FirstOrDefault());

In this case, at each iteration of the loop, we will not just get the IWebElement list (which may be empty), but also try to extract the first element from it. If the elements are still not displayed on the page, we will get null (default value for object) and move on to the next iteration of the loop. If the element is found, we will exit the method and the element variable will be initialized with the return value.

And yet, the second thought is that the return value of the Until method is not used in most cases.

The value passed is unnecessary, the return value is not used. What is the usefulness of Until? Only in the cycle and frequency of calling the condition method? This approach is already implemented in C # in the SpinWait.SpinUntil method. Its only difference is that it does not throw a timeout exception. This can be fixed as follows:

public void Wait(Func condition, TimeSpan timeout)
{
	var waited = SpinWait.SpinUntil(condition, timeout);
	if (!waited)
	{
		throw new TimeoutException();
	}
}

Those. these few lines of code in most cases replace the logic of the whole WebDriverWait class. Are the efforts worth the result?

Update

In the comments to the article, the KSA user made a sensible remark about the difference between SpinUntil and Until in terms of the frequency of execution of the condition. For WebDriverWait, this value is adjustable and defaults to 500 milliseconds. Those. the Until method has a delay between loop iterations. While for SpinUntil the logic is slightly complicated and often the wait does not exceed 1 millisecond.

In practice, this results in a situation where, while waiting for an element that appears within 2 seconds, the Unitl method performs 4 iterations, and the SpinUntil method takes about 200 or more.

Let's discard SpinUntil and rewrite the Wait method as follows.

public void Wait(Func condition, TimeSpan timeout, int evaluatedInterval = 500)
{
	Stopwatch sw = Stopwatch.StartNew();
	while (sw.Elapsed < timeout)
	{
		if (condition())
		{
			return;
		}
		Thread.Sleep(evaluatedInterval);
	}
	throw new TimeoutException();
}


We added a few lines of code, and at the same time we got closer to the logic of the Until method.

Also popular now: