📜 ⬆️ ⬇️

About the feasibility of Selenium WebDriverWait

The closer I get to know Selenium WebDriver, the more questions arise for me why this or that functional is executed in one way or another. In his speech, "Zamorochki in Selenium WebDriver" Alexey Barantsev sheds light on the subtleties of the implementation of this automation tool and distinguishes between "bugs" and "features". In the video you will find a lot of interesting things, but still some moments remain (at least for me) in the shadows.

In this article I want to discuss the frequently used tool to wait 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 can it be abandoned?

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

When creating an instance of WebDriverWait, a driver instance is passed to the constructor, which is stored in the internal input field. The Until method assumes a delegate whose input parameter should be an IWebDriver, an instance of which is input.
')
Let's look at the source code for the Until method. The backbone of its logic is an infinite loop with two conditions for exit from it: the occurrence of the desired event or timeout. Additional “buns” are ignoring the predefined exceptions and returning the object, if the booster is not a TResult (more on this later).

The first restriction that I see is that we always need an instance of IWebDriver, although inside the Until method (to be exact, then as an input parameter for the condition) we could easily do ISearchContext. Indeed, in most cases we expect some element or change its properties and use FindElement (s) to find it.

I would venture to say that using the ISearchContext would be even more logical, because the client code (class) is not just a page object, which in the search for child elements is pushed away from the page root. Sometimes it is a class that describes a certain constituent element whose root is a different element of the page, and not the page itself. One such example is SelectElement , which accepts a reference to the parent IWebElement in the constructor.

Let's return to the question of initializing WebDriverWait. This action requires a driver instance. Those. we always, in one way or another, need to forward an instance of IWebDriver to the client code, even if it is a class of a certain composite element (an example about SelectElement) that already accepts the "parent". From my point of view, this is unnecessary.

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

Let's see how the instance passed to the 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 does the condition need a “local” version of the driver if it is always available from the client code? Moreover, it 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 to the method is simply ignored. But it is necessary to transmit it, because the signature of the method requires it!

Why pass the driver inside the condition method? Perhaps to isolate the search inside this method, as implemented in ExpectedConditions ? Look at the implementation of the TextToBePresentInElement method. Or VisibilityOfAllElementsLocatedBy . Or TextToBePresentInElementValue . In them, the transmitted driver is not even used!

So the first thought is that we don’t need a Until method with a delegate parameter that accepts a driver.

Let's now figure out whether the until method needs a return value? If bool is used as a TResult, then no, it is not necessary. After all , if successful, you get true, and in case of failure, you get a TimeoutException. What is the informativeness of this behavior?

And if object acts as TResult? Suppose this 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 are not only waiting for the element to appear, but also use it (if waited), thereby removing one extra reference to the DOM. Good.

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

 try { FindElement } catch (NoSuchElementException) {} 

Those. An exception will be generated each time until the element appears in the DOM. Since the exception generation is a rather expensive event, I would prefer to avoid it, especially in those 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 generate an exception. Wait, will this structure wait for the elements to appear? NOT! Because, if you look at the source code , the execution of an infinite loop terminates as soon as condition returns non-null. And FindElements returns an empty collection in case of failure, but not null. Those. for a list of items, using Until does not make sense.

Well, the list is clear. But still, how to return the found element and not generate 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 list of IWebElement (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 get a null (default value for object) and proceed 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 - the return value of the Until method is not used in most cases.

The transferred value is unnecessary, the return value is not used. What is the utility Until? Only in a cycle and periodicity of a call of a condition method? This approach is already implemented in C # in the SpinWait.SpinUntil method. Its only difference is that it does not generate an timeout exception. This can be corrected as follows:

 public void Wait(Func<bool> 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 results worth the effort?

Update

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

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

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

 public void Wait(Func<bool> 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 got closer to the logic of the Until method.

Source: https://habr.com/ru/post/443754/


All Articles