Photo Credit: WikiImages

Accelerate Web Test Automation, Part 2

In Accelerate Web Test Automation, Part 1, we’ve talked about how we improved the performance of our web test automation by 31.7% via making a asynchronous call to check if the element is operable.

In this article, we’re going to cover some other performance improvements that we’ve made to reduce the number of the expensive method calls to inject the JavaScript.

Throwback

In Part 1, we’ve introduced JavaScript injection as the core concept of the library we’ve built (https://github.com/TestArmada/nightwatch-extra). jQuery was chosen to be the first JavaScript to be injected because of its perfect browser compatibility. A common issue of creating your own JavaScript element detection library is the cost to verify its browser compatibility. Sometimes it’s just too complicated (or impossible to some degree) to make it work for all the browsers you need. jQuery solves this question really well for us.

Thanks to jQuery, we’re worry free from browser compatibility. More and more easy-to-use and user-friendly commands and assertions are created based on the JavaScript injection mechanism. Our customers are quite happy with the library. We’re very excited to see the library be adopted by more and more users, too.

The Imperfection

We have been enjoying our life by using jQuery for quite a long time. Unfortunately, every coin has two sides. Functionality-wise, jQuery is perfect with no doubt. But we are kind of concerned about the size of a compressed jQuery file (84KB, version 3.1.0).

This 84KB size isn’t really an issue for the desktop browser. Selenium doesn’t have any constraint on the size of the injected JavaScript so everything stays perfect. However, same rule doesn’t apply to the mobile browser. There is a size limit, which is 65KB, for Selendroid’s string argument. To make the same library work for mobile browser, we have to split one JavaScript injection call into two calls so that the size of the string is within the 65KB limit every time.

So, in order to make our library compatible with mobile automation, the number of the JavaScript injection request is doubled. It’s time to tune some thing up now.

The Alternative

Before making any conclusion on how to reduce the number of the JavaScript injection calls back to one, we need to know what jQuery really does in our library.

jQuery is a rich JavaScript library, however we only use like 1% of it. All we need is the jQuery pseudos that enrich the CSS3 selectors like :visible:eq(n), etc. Well, you should’ve known that jQuery had already peeled of its CSS selectors and placed it into a separate library, Sizzlejs (https://sizzlejs.com/).

Sizzlejs is neat. It’s only 19KB (compressed version 2.3.1). If we can prove that Sizzlejs is good enough for our case, the 84KB-to-19KB change doesn’t only reduce the number of JavaScript injection calls, but also it can speed up the injection speed. The speed of JavaScript injection call does relate to the injected string size, and it can be exacerbated if user has a slow network connection.

Code Inspection

Now it’s time to go through our codes. We want to check two things here:

  1. If all the jQuery pseudos we use are valid in Sizzlejs.
  2. If we use any jQuery pseudos to operate the html element, like click on or move the cursor to an element.

And the results are quite encouraging

  1. There are two jQuery pseudos, :visible and :hidden, that are not supported by Sizzlejs. However they could be easily ported into Sizzlejs by customized pseudos.
  2. There isn’t any jQuery pseudos in use to operate the html element. We rely on the Selenium methods to do the element operation.

Code change

It turns out the only change we need to do is to port the implementation of :visible and :hidden from jQuery into Sizzlejs. Code sample:

sizzleRef.selectors.pseudos.visible =
function (elem) {
return elem.offsetWidth > 0
|| elem.offsetHeight > 0
|| elem.getClientRects().length > 0;
};
sizzleRef.selectors.pseudos.hidden =
function (elem) {
return !sizzle.selectors.pseudos.visible(elem);
};

More improvement

Improvement one

Now, we are ready to migrate from jQuery to Sizzlejs. The number of the http calls has been successfully got back to one. User won’t feel anything different since the migration is seamlessly.

But, can we improve more?

We used to evaluate the injected jQuery every time to make sure the jQuery is always there before being used by executing eval().

var executeSizzlejs = function(jQuerySource){
// jQuerySource is the string containing compressed jQuery.min.js
eval(jQuerySource);
}

But, eval() is kind of slow. The more eval() we called, the slower the js-injection would be.

Since Sizzlejs is used as selector in jQuery, does it mean there is no need to inject it at all if the target page has jQuery built in? The answer is YES. Check this code out:

https://github.com/jquery/jquery/blob/master/src/selector-sizzle.js#L8

Having said this, we can even avoid running eval() at all. Code sample:

if (window.jQuery) {
sizzleRef = window.jQuery.find;
}

Improvement Two

Most of our web test automation commands and assertions are executed on the same page. Similar to the Improvement One above, if Sizzlejs is detected on the page, there is no need to inject or evaluate it again as the page only reloads when refreshes.

Complete The Improvement

if (window.Sizzle) {
// if Sizzlejs is found on page
sizzleRef = window.Sizzle;
} else if (window.jQuery) {
// if jQuery is found on page
sizzleRef = window.jQuery.find;
} else {
// if none is found on page
eval(pSizzleSource);
sizzleRef = Sizzle;
sizzleRef.noConflict();
sizzleRef.selectors.pseudos.visible =
function (elem) {
return elem.offsetWidth > 0
|| elem.offsetHeight > 0
|| elem.getClientRects().length > 0;
};
sizzleRef.selectors.pseudos.hidden =
function (elem) {
return !sizzle.selectors.pseudos.visible(elem);
};
window.Sizzle = sizzleRef;
}

The complete code can be found here:

https://github.com/TestArmada/nightwatch-extra/blob/master/lib/injections/js-injection.js

Conclusion

With the performance improvements in this article, we’ve successfully removed some of the unnecessary http calls, and the expensive javascript eval() on the page as much as possible for the javascript injection. Please leave me a message in the comments to share your experience. Any comment on the series is welcome too. If you’d like me to write anything specific to this article, please let me know.

We’re still learning and growing. Stay with us.

To be continued…