September 30, 2019 // By Paul Grizzaffi
In 1972, Deep Purple released their arguably most popular album Machine Head. Most people are familiar with the song Smoke on the Water (though, I’m a bigger fan of Highway Star and Space Truckin’). Also appearing on that album is the song Lazy. Likely considered a deep cut by today’s standards, Deep Purple has performed Lazy as recently as 2018. As with many other songs, Lazy makes me think of automation and testing. When it comes to automation software development, being lazy can be a good thing when applied appropriately.
Lazy loading is a common software development technique that delay’s the initialization of data structures until that initialization is needed, such when interrogating a specific instance of a data structure. If used judiciously, this technique can improve the performance of an application.
When applied to UI automation, lazy loading translates into a deferred finding of elements. In other words, locating a specific UI element, such as an HTML element, is deferred until later in the execution flow; specifically, it’s deferred until that element is accessed by attempting to click it, type into it, or another similar action. By using this delayed approach, we can emit more understandable logs and reports from our automated test scripts and associated code. One of the key points about understandable logs is making them domain-friendly, i.e. using vocabulary and jargon that’s common in our organization.
How does lazy loading help with domain-friendliness?
When using the page object model, accessing an HTML element associated with the login link might look like the following:
LoginLink() method is often implemented by calling Selenium’s
FindElement() method, then returning the located
IWebElement instance, similar to this:
This simple, straight-forward approach does fulfill our basic needs: it returns the located element if it finds the specified element, otherwise it throws an exception like the one below:
Certainly, this message, along with the stack trace, provides enough information to determine what could not be located and where the issue occurred in our execution flow. This approach does, however, require us to look at that stack trace to determine the domain-friendly name of the element we were looking for. Not everyone is trained on reading stack traces and we’re not always great at naming things. Also, using the stack trace is super convenient when working in an IDE; just click the method name in the stack, and we are transported right to the call site. This is less convenient when running outside of an IDE, such as when running in a CI, CD, or CT environment.
A more helpful way to deliver the error information is to include a domain-friendly message in the exception being thrown. One possible implementation is to catch the exception thrown by
FindElement(), add our own error message, and then rethrow the exception. This approach allows us to provide the domain-friendly name without losing the other details in the exception. But we certainly don’t need lazy loading to accomplish this.
So, so why bother with lazy loading?
One thing that can be useful, but that the preceding approach does not provide, is logging the action we were going to perform had our
FindElement() succeeded. In our example above, the action we’d like to log is
Click(), but we don’t know that we were going to call
Click() when we fail to locate the expected element; programmatically, we don’t even find out about
Click() unless the
Enter, lazy loading. With lazy loading, we wait until the action is
Click() is called before attempting the
FindElement(); this way, if the
FindElement() fails, we have determined that we were going to call
Click() and we can include that information in our error log. Unfortunately, Selenium doesn’t provide lazy loading in this fashion, but fortunately, Selenium is a flexible toolkit that allows us great latitude in how we use it.
One implementation, the one used by Magenic’s MAQS framework and stack, is to create a helper class called
LazyElement. Instead of calling
FindElement() in the LoginLink method, we create an instance of
LazyElement similar to the following:
LazyElement stores the By locator and the domain-friendly name string (“
LoginLink”). Later, when
Click() is called on the
FindElement() is called. If the find is not successful, the exception can be caught, and additional information can then be added to it before we rethrow it. The resulting log message could look like this:
Again, consider the situation where this script is not executed from an IDE. The log above and a corresponding screenshot of the browser are often enough to start and possibly complete debugging.
As with most implementation approaches, there are advantages and disadvantages to lazy loading. Additionally, the approach should be evolved to be appropriate for each organization’s needs. I will be going into more detail in a talk about lazy loading and
LazyElement in 2020; keep an eye on my upcoming events page to find out when and where!