Proper usage of LoadableComponent to eliminate waits in Selenium

  • Posted on November 5, 2019
  • Estimated reading time 5 minutes
LoadableComponent in Selenium

This article was originally written by Avanade alum Pavlo Mashurenko

What does it mean “Selenium without waits?” When saying "waits" I’m referring to multiple usages of explicit and fluent waits with all these possible expected conditions, or other hacks (including hardcoded waits) in multiple places across your test automation. The sole purpose is to ensure that we'll work with the element, instead of getting various exceptions that we usually don't really care about.

It is important to understand that there are some scenarios when it will be inevitable to write some specific code that will be waiting for some event to happen or condition to be met. But such cases are outside the scope of this article and what most important implementation of proposed approach will greatly reduce amount of such cases.

One of the main fundamental problems with Selenium is that this is a tool for browser automation and not a specific test automation tool. Being created as a test automation tool it won't be throwing all these NoSuchElementException and StaleElementReferenceException, because we don't really care about them (and if you do, you shouldn't be). What we should care about instead are exceptions that we want to throw when AUT (Application Under Test) isn't working as intended. This brings us to a question - what should we do with Selenium to drastically decrease the amount of Selenium generated exceptions, and create our own exceptions that will be meaningful in context of AUT?

First, we need to decompose any page to (ideally) the smallest possible isolated pieces of functionality and move them out to separate classes. This will highly reduce the complexity of main page objects and make them much cleaner and easier to maintain.

Second, with an approach like this, we will be able to use LoadableComponent from Selenium - a great class that enforces implementation of logic that should define when page or its parts are done loading. The main advantage of this approach is that the decision about whether some part of page is loaded or not will be isolated inside class that will be responsible for implementation of the mentioned functionality. Now all what we need to do is just to instantiate class with isolated functionality, and LoadableComponent will wait for this functionality to be loaded.

Here is C# source code of LoadableComponent. Since the code is pretty simple and straightforward, I’ve removed all comments just to save a bit of space.

LoadableComponent in Selenium

From the code above it is pretty clear that by implementing methods like EvaluateLoadedStatus(), ExecuteLoad() and HandleLoadError() as desired, we can verify whether the page or some part of the page is loaded within the expected time and is ready for further interaction. In our projects we are using custom overload of LoadableComponent which implements ExecuteLoad and HandleLoadError internally. Only the implementation of EvaluateLoadedStatus is needed in target class, that should know which conditions should be met to state that this page or component is loaded.

Th second part of the solution is to change mindset and code to a more functional approach. We should not store state of webelements, but rather get them on demand with state that they have at that particular moment. Storing states and keeping instances of web elements is a direct path to dealing with StaleElementReferenceException. A simple way to implement this is to use expression-bodied properties in C#, or lambdas in Java and Python in conjunction with properties for the sake of code readability. So, element instantiation should happen only when corresponding property is used, and not any earlier.

Another important point to note is that the instance of webelement should not be passed as a parameter because it increases the risk of StaleElementReferenceException to happen. Instead of a WebElements instance, a function that will return this WebElement on demand should be passed as a parameter. This approach will eliminate the majority of StaleElementReferenceException exceptions, apart from a few specific situations when code doesn't take into account how DOM (Document Object Model) updates are happening in AUT.

Also, it makes sense to create some wrappers for Selenium methods and properties like FindElement(), Exists or Displayed that will return proper results instead of exceptions when the element is not present or was modified.

Here is a pretty typical code in our projects:

LoadableComponent in Selenium

This example EvaluateLoadedStatus is implemented using class specific logic. Loadable constructor will be calling internally EvaluateLoadedStatus until true is returned or load timeout is reached. The instance of class doesn’t store its own state, but serves as an interface to get the current state of AUT.

Also, it’s worth mentioning that we do not expose IWebElements outside of pages/components classes. All IWebElements that we work with can have private or protected access level only. In rare cases when we need to get access to element-specific information outside class, elements locator is made internal/public so external class can search for this element on its own. Find is implemented in a way that if the element/elements exists, the result will be returned. Otherwise, null will be returned. Displayed() method as well as Exists() method are implemented in a way that they will never throw an exception and only true or false will be returned from these methods.

Applying these recommendations will help you make your code cleaner, reduce code complexity in your project, and will improve maintainability by isolating areas of responsibility in classes/modules that will be responsible for reflecting of some particular functionality from AUT.

Techs and Specs Newsletter

Stay up to date with our latest news.

Contact Avanade

Next steps

Talk to us about how we can bring the power of digital innovation to your business.

Share this page