JavaScript Asynchrony and async/await in Selenium WebDriver Tests

Advanced Topics — Published December 18, 2017

Selenium is a wonderful library. It supports all major browsers, has all the features we will probably need and is currently the de-facto standard in browser tests today, and rightfully so.

(For those that don’t know, browser tests are tests that run a browser, automate the browser to interact with your frontend application, and test it that way.)

Selenium has bindings for lots of languages — Java, C#, Python, Ruby, and others. This is great — you can use your favorite language to write your tests in.

If you’re a frontend developer, which language would you choose? Well, the language that you are probably most comfortable in: JavaScript. But JavaScript’s asynchronous nature poses special challenges for Selenium. In this post, I will discuss those challenges, and how JavaScript (and Selenium) overcome these challenges.

But first, I want to discuss Asynchrony in JavaScript in general. 

Asynchrony in JavaScript

JavaScript is an asynchronous language. All I/O in it will (almost) always be asynchronous. Meaning that a callback will be needed to notify the program when the I/O operation is done. But this is not true in most other languages. In Java, for example, doing I/O is a synchronous operation. A file copy looks like this:

First, we read a file, then we copy it. Simple, no? But in JavaScript, it ain’t that simple:

Notice how there’s no code after the fs.readFile. The “real” code happens in the callback that is passed tofs.readFile. This is why callbacks in NodeJS are everywhere!

Why? Because JavaScript code has only one thread. If you block that thread, no other parallel code can run. This means you must never block your thread with I/O, which means that you need to supply a callback which will be called when that I/O operation finishes, and which has the code for the next part of your program.

Unfortunately, this makes for some really ugly and obfuscated code. Imagine needing to loop over a set of files and copying them. With callbacks, you can’t use for-loops for that — you actually need some sort of recursion to do that. Yes, Asynchronous code is not just like synchronous code with callbacks — it’s a different way of writing code.

And it’s not pretty.

To compensate for this ugliness, ES2015 addedPromise objects. Using promises, one can write callback code in a much nicer way:

This is much nicer but is still a bit obfuscated. And it still has callbacks in it (the “then” handlers). Because, in the end, NodeJS has only one thread, and you’re not allowed to block it with I/O.

What if there was a way to tell JavaScript to wait on that promise, and then continue from that location once the promise resolves and has a value? Well, there is! It’s called async/await:

Yay! async/await handles all the ugly stuff and translates the above code to code that uses promises. We don’t care — it just works. It will never block on I/O. And we can use for-loops, and while loops, and break, and continue, and try/catch, as if we were programming in synchronous mode. Just don’t forget thoseawait-s on functions that return a promise, or strange concurrent things start to happen.

Selenium WebDriver Code

Given all the above, imagine my surprise when I saw
this Selenium WebDriver code:

This is, in JavaScript land, impossible code. Why? See thedriver.get(…) line? That’s code that does I/O — it communicates via the network to another process. There is no possibility in NodeJS to run this code synchronously.

And yet, there is nary a promise, async/await, or callback in site! How does this work? Well, it turns out that it’s all a scam. You can find the sordid details
here. But what is happening really is that when you call driver.get it doesn’t really navigate to the URL given, but rather just schedules it for later execution. And when the code callsdriver.findElement, then it doesn’t really execute that, but rather schedules it for later, after thedriver.get. It’s all scheduled to run asynchronously and not really executing synchronously.

This is wonderfully complex programming and a technological feat in itself! But why did they do it? They did it to make testers that are used to Java/C#/Python synchronous programming, not be flummoxed by asynchronous programming. So they hacked it to make it look synchronous, but execute asynchronously.

But why do I call it a hack? Looks good, no? Well, no. Unfortunately, this abstraction leaks like hell. If you’re debugging the code, and reach thedriver.get line, you expect it execute in the browser, but it doesn’t. And if you want to do an if based on the value of an element, then you can’t, because you don’t really have the value. It will get the value later.

Fortunately, they left a backdoor that allows us to work the “JavaScript” way. All the functions in Selenium WebDriver return aPromise, so the above could be written thus:

This is better, as now our code has control on when things happen, but it’s still Promise code — not the nicest way to write. But at least it’s not disguising itself as synchronous—it’s running asynchronously and things work as expected.

Fortunately, we now have async/await to help us. Let’s write the above code in async/await style:

That’s it — Selenium WebDriver code that looks synchronous code, but that uses Promises underneath, and has no hack code underneath that does crazy things.

The Future

It seems that the Selenium community understands that the scam that is this synchronous/asynchronous mode should be temporary:

The promise manager contained in this module is in the process of being phased out in favor of native JavaScript promises. This will be a long process and will not be completed until there have been two major LTS Node releases (approx. Node v10.0) that support async functions.

So start getting used to using async/await in your Selenium WebDriver tests—it’ll shortly be the only way in town.
And that’s a good thing.

What Now?

Want to learn more about browser tests? You can learn more in previous posts of mine — here and here.

To read more about Applitools’ visual UI testing and Application Visual Management (AVM) solutions, check out the resources section on the Applitools website. To get started with Applitools, request a demo or sign up for a free Applitools account.

About the Author:

30 years of experience have not dulled the fascination Gil Tayar has with software development. From the olden days of DOS, to the contemporary world of Software Testing, Gil was, is, and always will be, a software developer. He has in the past co-founded WebCollage, survived the bubble collapse of 2000, and worked on various big cloudy projects at Wix.

His current passion is figuring out how to test software, a passion which he has turned into his main job as Evangelist and Senior Architect at Applitools. He has religiously tested all his software, from the early days as a junior software developer to the current days at Applitools, where he develops tests for software that tests software, which is almost one meta layer too many for him.

In his private life, he is a dad to two lovely kids (and a cat), an avid reader of Science Fiction, (he counts Samuel Delany, Robert Silverberg, and Robert Heinlein as favorites) and a passionate film buff. (Stanley Kubrick, Lars Von Trier, David Cronenberg, anybody?)

Unfortunately for him, he hasn’t really answered the big question of his life – he still doesn’t know whether static languages or dynamic languages are best.

Are you ready?

Get started Schedule a demo