There are many options when it comes to putting your program to sleep (or insert delays in the program). When performing Selenium automation testing, the Sleep function will cause the execution of your code to halt for a specified number of seconds. However, using Sleep is not considered a good Selenium testing best practice, due to which QA engineers use other forms of wait in the source code.
Selenium has bindings for a wide range of programming languages which includes JavaScript. Selenium-supported languages like Java support different waits in Selenium, but JavaScript does not have that native function for inserting waits in the code. Hence, we need to use alternatives for realizing JavaScript wait. For example, you can use the combination of Async/Await, setTimeout(), and Promises to implement the JavaScript wait function that will work as you would expect it should.
In the meantime, if you’d like to run your JavaScript test scripts over a Selenium Grid online then leverage LambdaTest for your test automation.👇
This Selenium JavaScript tutorial will dive deep into the Async and Await in JavaScript, followed by practical examples. Preceding that, we will also discuss the synchronous and asynchronous nature of JavaScript. By the end of this tutorial, you will be able to use the JavaScript wait function in your Selenium WebDriver tests.
Let’s get started!
Synchronous Function In JavaScript
JavaScript, at its core, is a synchronous, blocking, and single-threaded programming language. It means that only one task can be in progress at a given time, and the code gets executed in the order of its appearance. For example, consider the following code:
console.log(message)
let message = "Hello world!"
// Throws error as we cannot call 'message' before initialization
Synchronous code is also called “blocking” because it blocks code execution until all the resources are available. It is easier and simpler to write code synchronously as we don’t have to worry about concurrency issues.
However, when it comes to performance, synchronous code can make a programmer’s life difficult. When performing long operations such as database requests, the main thread will be blocked when the server processes the request, and the program execution will be halted. In addition, it can create a “freezing” effect on the screen, equating to an unresponsive user experience. Now we don’t want that! Do we? This is where asynchronous JavaScript comes into the picture.
Asynchronous Function In JavaScript
Asynchronous code does not wait for I/O operations to complete. It allows the main execution thread to continue while waiting asynchronously for the completion of the costly I/O operations. Here the main thread is not blocked while the asynchronous requests wait for a request to respond or a timer to finish. In the meanwhile, execution proceeds with the rest of the program. Once the requests are processed, they are handled elegantly.
JavaScript provides you the ability to manipulate it to behave asynchronously. Here are some of the most common ways to implement asynchronous code in JavaScript:
Callbacks
Promises
Async/Await
Generators
Let us explore the journey from Callbacks to Async/Await in JavaScript.
Callbacks In JavaScript
One of the earliest and most straightforward solutions for synchronous blocking is the callback mechanism. Here, a callback function is passed into another function as an argument, which is then invoked or “called-back” later inside the outer function to complete some kind of routine (or action).
Consider the example of a database request used for fetching some data. While the server processes the request and waits for a response, the main thread can execute the rest of the code. Once the request is completed, the results are sent to the queue and then processed through the event loop, i.e., the callback functions get executed.
A simple implementation of the callback function using setTimeout() is given below. However, you can go through our complete guide on Handling Timeouts With Selenium.
console.log('one ')
setTimeout(function waitTwoSeconds() {
console.log('two')
}, 2000)
console.log('three')
/**
* OUTPUT
*
* one
* three
* two
*
However, the callback functions come with a major drawback. We need to nest all the dependent functions since we cannot exactly estimate when a request gets resolved. Otherwise, the code quickly gets messy and unmanageable, leading to infamously known as the callback hell.
Now that is undesirable, isn’t it? Hence to manage callbacks, the Promise mechanism was developed as a substitute.
Promises In JavaScript
A Promise is an object used to handle asynchronous operations in JavaScript. They help you to write callback code in a more readable manner. Also, it provides a better error handling mechanism in comparison to callbacks or events. JavaScript Promises offer a much better flow of control definition in asynchronous logic.
In real life, a promise is made for the unpredictable future. Thus, it has only two possible outcomes — it will be either kept when the time comes or won’t. Similarly, the Promise object represents an asynchronous operation’s eventual completion (or failure) and its result of execution.
A promise in JavaScript has three states:
Pending — It is the initial state, i.e., neither fulfilled nor rejected
Fulfilled (Resolved) — It means that the operation was completed successfully
Rejected — It means that the operation failed
When the pending promise gets resolved successfully, the code within the .then() block gets executed. In similar terms, when a promise is rejected with a reason (error), the code within the .catch() block gets triggered. Lastly, the .finally() gets called only when the promise is settled. Thus, it will always get executed no matter whether the promise is fulfilled or rejected. However, finally() is an optional handler.
A simple implementation of Promise is given below. In this example, we are trying to mimic a scenario for comparing the actual page title with the expected page title and seeing whether they match. Based on the status, resolve() or reject() gets invoked, and an appropriate message gets logged.
function getActualPageTitle(){
//ideally returned by program
console.log('Fetching page title..')
let actualPageTitle = "Page not found"
return actualPageTitle;
}
function verifyPage(){
return new Promise(function(resolve, reject) {
let expectedPageTitle = "Selenium Playground"
let actualPageTitle = getActualPageTitle()
console.log('Verifying page title..')
if(expectedPageTitle === actualPageTitle){
resolve('Page title is matching!');
}
else {
reject('ERROR! Page title is not matching!');
}
})
}
//Using promise then/catch
let promise = verifyPage();
promise
.then(res => {console.log(res)})
.catch(err => {console.log(err)})
.finally(() => console.log('You have reached the end of execution!'))
Console output:
Chaining is another best feature of Promises in JavaScript. Instead of bundling all dependencies into a single code block, we can separate them using multiple linked .then() and .catch() handlers, as shown below:
Promise.resolve()
.then(() => console.log('then#1'))
.then(() => console.log('then#2'))
.catch(() => console.error)
.then(() => console.log('then#3'));
NoSuchShadowRootException is thrown by the selenium webdriver when the script is trying to access an element's shadowRoot and not found any shadowRoot attached to the element.
The Promise objects became a part of the ES2015 language standard. As an advanced way of handling Promises, the concept of Async/Await in JavaScript was introduced in ES2017. The next section will cover the Async/Await clause in detail.
Async and Await Functions In JavaScript
The promise mechanism offered a better way of handling callbacks. Still, it was pretty obfuscated. Consequently, the async-await in JavaScript was introduced, which acts as syntactic sugar on top of Promise. But how? Let us understand.
The async/await features enable the programmers to write promise-based code synchronously without blocking the main thread. In addition, they offer a way to inform JavaScript to wait on that Promise and then continue execution from that location once the promise resolves and has a value.
The async/await in JavaScript allows you to:
Continue using the promises but in a more comfortable fashion
Write asynchronous code that looks and feels like synchronous codes
Clean up the syntax and make the code more readable
Async Function In JavaScript
The async keyword is used to mark a function as an asynchronous function. An asynchronous function operates in a different order than the rest of the code through the event loop and always returns a Promise. But the syntax and structure of the async function code will only look like a standard synchronous function, as shown below:
async function myFunction() {
return "Hello world";
}
// IS SAME AS
async function myFunction() {
return Promise.resolve("Hello world");
}
If you are wondering how we work with the Promise.reject() scenario, just utilize a try-catch block for it. We will see an example implementation later.
Await Function In JavaScript
The advantage of an async function becomes only apparent when it is combined with the await keyword. The keyword await makes the function wait for a Promise. When you await a Promise, the function is paused in a non-blocking way until the promise settles. If the Promise fulfills, a value is returned. If the Promise rejects, the rejected value is thrown.
However, the await keyword works only inside async functions, as shown in the below sample code:
async function displayMessage() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("Hello World!"), 3000)
});
let result = await promise; // wait until the promise resolves (*)
alert(result); // "Hello World!"
}
displayMessage();
Let us now see how we can rewrite the example code given for Promise using async/await in JavaScript.
function getActualPageTitle(){
//ideally returned by program
console.log('Fetching page title..')
let actualPageTitle = "Page not found"
return actualPageTitle;
}
function verifyPage(){
return new Promise(function(resolve, reject) {
let expectedPageTitle = "Selenium Playground"
let actualPageTitle = getActualPageTitle()
console.log('Verifying page title..')
if(expectedPageTitle === actualPageTitle){
resolve('Page title is matching!');
}
else {
reject('ERROR! Page title is not matching!');
}
})
}
//Using async/await
async function promise(){
try{
const response = await verifyPage();
console.log(response);
}
catch (err){
console.log(err);
}
finally{
console.log('You have reached the end of execution!')
}
}
promise();
Now, this pattern looks more familiar and neat, right? The next section will see how to write Selenium WebDriver automation tests using Async/Await in JavaScript.
Writing Tests In Selenium WebDriver For Automation Testing
In the previous sections, we looked at the asynchronous way of writing codes in JavaScript. Now it’s time to write and run an end-to-end test scenario!
We will be using only a basic Selenium JavaScript project set-up for the demonstration. In case you are looking out for JavaScript frameworks for automation testing, make sure to check out our blog on the best JavaScript frameworks. Also, we would be utilizing the Online Selenium Grid provided by LambdaTest to run our tests. LambdaTest Selenium Automation Grid is a cloud-based, scalable cross-browser testing platform that enables you to run your automation scripts on 2000+ different browsers and operating systems online.
It is similar to NoSuchFrameException, when browser has more then one window and script need to verify some elements on seperate windows so script need to switch the window first to access the element, if the window does not exists then it throws NoSuchWindowException.
Pre-requisites
First and foremost, make sure that your system is equipped with the following:
- NodeJS and NPM — Verify if NodeJS and NPM are already installed in your system by running the commands node -v and npm -v on the terminal, as shown below:
If Node JS (or Node.js) is not present on your machine, you can download and install the latest LTS version. You need not have a separate installation, as NPM will be installed along with Node.js.
IDE of your choice — For implementation, we will be using Visual Studio Code as IDE. However, you can choose any IDE of your choice.
Project Setup For Selenium WebDriver With JavaScript
With the prerequisites checked, let’s move on to the project setup. Here are the step-by-step instructions.
Step 1: Create a folder for the project in the desired location and open it in your IDE.
Step 2: Initialize the project in this folder and give the following command in the IDE terminal.
npm init-y
The project gets initialized, and the “package.json” file will be created.
Step 3: Install project dependencies. We need to install Selenium WebDriver as we are running scripts on remote Selenium Grid; however, no need to install browser drivers. We can declare the capabilities object for the browser configuration later.
npm install --save selenium-webdriver
Step 4: We will be running our tests on the remote cloud-based Selenium Grid provided by LambdaTest. So log in or create a free account with LambdaTest and obtain your unique username and access key from the LambdaTest profile section.
The project setup is completed!
Writing Our First Test Script In JavaScript
We will be writing code for the following simple test scenario.
Launch the browser.
Go to Selenium Playground (https://www.lambdatest.com/selenium-playground/).
Select ‘Simple Form Demo.’
Enter input message and submit the form.
Verify the displayed message equals the input message.
Before we demonstrate how to write the first test script in JavaScript, it’s important to have the basic setup ready for Selenium test automation. You have already seen in our earlier blogs titled Automation Testing With Selenium JavaScript to set up an environment for writing tests in Selenium with JavaScript bindings.
However, you can also visit the LambdaTest YouTube channel for videos that will guide you through the Selenium JavaScript journey.
Implementation:
Step 1: Create a folder named “tests” in the project root. Inside this folder, we will create our first test file named “test.js,” as shown below:
Step 2: Inside test.js, pull all the required functions from node_modules.
const {By,Key,Builder} = require("selenium-webdriver");
var assert = require('assert');
Step 3: Configure the test script to run in a remote Selenium Grid hosted by LamdaTest. Inside test.js, provide your unique username, access key, and Grid host details.
const USERNAME ='YOUR_USERNAME';//replace with your email address
const KEY = 'YOUR_ACCESSKEY'; //replace with your authkey
const GRID_HOST = 'hub.lambdatest.com/wd/hub';
Step 4: Fetch capabilities by generating the desired browser capabilities using LambdaTest Capabilities Generator.
For demonstration, I am choosing Chrome as the browser, and the capabilities object will look like this:
var capabilities = {
"build" : "JavaScript Wait Function",
"name" : "Google search",
"platform" : "Windows 10",
"browserName" : "Chrome",
"version" : "91.0",
"selenium_version" : "3.13.0",
"chrome.driver" : "91.0"
}
Step 5: Write the test function using Async/Await feature. Here we are defining an async function called example(), and await is given at the beginning of every step.
Also, declare the capabilities and Grid URL inside the async function. The function call is given at the end.
async function example(){
var capabilities = {
"build" : "JavaScript Wait Function",
"name" : "Form Submission",
"platform" : "Windows 10",
"browserName" : "Chrome",
"version" : "91.0",
"selenium_version" : "3.13.0",
"chrome.driver" : "91.0"
}
const gridUrl = '[https://'](https://') + USERNAME + ':' + KEY + '@' + GRI
//To wait for browser to build and launch properly
let driver = await new Builder()
.usingServer(gridUrl)
.withCapabilities(capabilities)
.build();
var message = "Hello World";
//To wait for browser to build and launch properly
let driver = await new Builder().forBrowser("chrome").build();
await driver.get("[https://www.lambdatest.com/selenium-playground/](https://www.lambdatest.com/selenium-playground/)");
await driver.findElement(By.partialLinkText("Got it")).click()
await driver.findElement(By.partialLinkText("Simple Form Demo")).click()
await driver.findElement(By.id("user-message")).sendKeys(message)
await driver.findElement(By.id("showInput")).click()
var displayedMessage = await driver.findElement(By.id("message")).getText()
//To verify both input message and displayed message are matching
assert.strictEqual(displayedMessage, message);
console.log('Message match status:',displayedMessage === message);
//It is always a safe practice to quit the browser after execution
await driver.quit();
}
example()
Step 6: And finally, it is time to run the test! Give the following command in the terminal.
node test.js
The test starts running and gets executed successfully!
The live running status, along with the detailed reports and logs, can be viewed from the LambdaTest Automation dashboard.
Conclusion
Though synchronous at its core, JavaScript can be manipulated to behave in a non-blocking asynchronous way to improve performance. Callbacks, Promise, Async/Awaits are the popular features that let you asynchronously code JavaScript.
In this Selenium JavaScript tutorial, we did an in-depth exploration of the JavaScript wait function. We also looked into async and await in JavaScript with examples. And finally, we learned how to write Selenium WebDriver tests using async and await. I hope this tutorial turned out to be beneficial!
Happy Testing!
Top comments (0)