JavaScript for Testers - Part 1 (Basics)

asheeshmisra

Asheesh Misra

Posted on September 22, 2023

JavaScript for Testers - Part 1 (Basics)

Namaskaar 🙏 🙂

This is a two part tutorial on getting up and running with JavaScript, for the testers, so that they can leverage this knowledge while writing test automation scripts.

The way this tutorial has been planned is

  • We begin with covering JavaScript Basics, especially ES6 (that’s this tutorial)
  • Then we deal with Asynchronous JS (that’s part 2)

Having laid the itinerary of what we are going to do and learn in this tutorial, there WILL be a lot of other things that we will do and learn as we progress through the tutorial.

This is a beginner friendly tutorial for the testers by a tester. Let’s start

Disclaimer

I have used DC Universe characters heavily in this multi part tutorial and purely for educational purposes 👼 . They are all proprietary to DC Comics.

Intro

Many of you may ask why should I even care about JavaScript, or why learn JavaScript before starting on an Automation Tool (Cypress, Playwright, Selenium)?

I would humbly say that if you are looking to really understand what every sentence of the automation script written by someone else means, if you want to be able to write one by yourself from scratch, if you want to write maintainable, reusable automation scripts, this is the way to go.

What do we need (aka Setup)

Node JS (install the LTS version that conforms to your OS and check for its successful installation by typing in your terminal node -v and npm -v. If you get corresponding version numbers back for both commands then Node JS has been installed successfully.

VS Code and then optionally install Prettier Extension for VS Code through VS Code extensions. Search for Prettier - Code formatter

That’s it 😄

JavaScript Basics

JavaScript (hereafter JS) has been the language of the web for a long long time and has evolved to meet the challenges of ever changing requirements of the users on the Internet.

According to MDN Web Docs

JavaScript is a scripting language that enables you to create dynamically updating content, control multimedia, animate images, and pretty much everything else.”

The umbrella term “JavaScript” as understood in a web browser context contains several very different elements. One of them is the core language (ECMAScript), another is the collection of the Web APIs, including the DOM (Document Object Model).

In this part of the tutorial, we will be learning about ES6 (ECMAScript 2015 and its successors) features which will be very helpful later when we start writing test automation scripts.

How and where shall we run the JS Code?

To answer this question, let’s start with creating a new NodeJS project, to store our JS files. We will open terminal (command prompt in Windows) and cd to the folder/ directory where we want/ can create a folder to keep the .js files we will be using for our learning.

mkdir JSBasics
cd JSBasics
Enter fullscreen mode Exit fullscreen mode

Then we will do

npm init

This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (jsbasics)
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to /Users/asheesh/Desktop/JSBasics/package.json:

{
 "name": "jsbasics",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
   "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC"
}


Is this OK? (yes)
Enter fullscreen mode Exit fullscreen mode

On being asked about package name, version, test command, etc., simply press ‘Enter’ to accept the default value (or to leave it blank). Since all of this information being asked, is going to be stored in a file called package.json (created by NodeJS in the current working directory), we can always edit it later, if needed, for our purpose.

Creating a new node project with default options

The command code . loads the VS Code with our current working directory as the ‘open folder’.

We will create a couple of folders inside our JSBasics folder, namely js, css and then within _js folder create a .js file (I have named it concepts101.js, you may use name of your choice). To run the JS file we will open a terminal in VS Code itself by pressing Ctrl + Shift + BackTick (press the tilde(~) key) . In the terminal, type

node js/concepts101.js

(you will obviously be using the name of your .js file instead of concepts.js) and observe the output.

const, let and var

All of these keywords are used to declare variables in JS. let and const keywords got introduced in ES6.

const variables cannot be reassigned. For example,

const name="Hal Jordan";
console.log(name);
name = "Green Lantern";    // will give an error


-------------------------------------------------------------
Output:
asheesh@Asheeshs-MacBook-Pro concepts % node js/concepts101.js
Hal Jordan
/Users/asheesh/D/D/JSWorkspace/concepts/js/concepts101.js:7
name = "Green Lantern";
    ^
TypeError: Assignment to constant variable.
Enter fullscreen mode Exit fullscreen mode

notice the TypeError line.

let variables can be reassigned. For example,

for(let i=0;i<5;i++){
   console.log("JS is good but TS is awesome");
}
Enter fullscreen mode Exit fullscreen mode

but if we update the above example as following, the last line will error out

for(let i=0;i<5;i++){
     console.log("JS is good but TS is awesome");
}
console.log("Current value of i is: ", i); //will give an error

//-----------------------------------------------------------
Output:
asheesh@Asheeshs-MacBook-Pro concepts % node js/concepts101.js
JS is good but TS is awesome
JS is good but TS is awesome
JS is good but TS is awesome
JS is good but TS is awesome
JS is good but TS is awesome
/Users/asheesh/D/D/JSWorkspace/concepts/js/concepts101.js:16
console.log("Current value of i is: ", i);
                                      ^

ReferenceError: i is not defined
Enter fullscreen mode Exit fullscreen mode

this is so because both let and const variables are ‘block scoped’. They do not exist outside the block in which they have been declared.

var variables can be reassigned and are not block scoped. This keyword was the only way to declare variables in JS before 2015. Consider the following example,

for(var i=0;i<5;i++){
console.log("JS is good and learning it make me feel awesome");
}
console.log("Current value of i is: ", i);

//-----------------------------------------------------------
Output:
JS is good and learning it make me feel  awesome
JS is good and learning it make me feel  awesome
JS is good and learning it make me feel  awesome
JS is good and learning it make me feel  awesome
JS is good and learning it make me feel  awesome
Current value of i is:  5
Enter fullscreen mode Exit fullscreen mode

Arrow Functions

In ES6, very simple and concise syntax for creating functions was introduced. To be precise they are arrow function expressions and are much cleaner, shorter than functions in classic JS. Let’s see an example

// Normal JS Function
function heroIdentity(heroName, realName){
   return `Name: ${heroName}, Age: ${realName}`;
}
console.log(heroIdentity('Batman','Bruce Wayne'));

// Arrow Function
const heroIdentity = (heroName, realName) =>{
   return `Name: ${heroName}, Age: ${realName}`;
}

console.log(heroIdentity('Batman','Bruce Wayne'));
Enter fullscreen mode Exit fullscreen mode

The normal JS function takes in two parameters and returns their values in a formatted manner. Observe the _single ‘tick’ _used in the return statement. This is called a ‘template literal’. We will talk about it in a bit. For now just understand it helps us print text plus value of the variables specified in it.

The arrow function as you would observe is anonymous (i.e. without any name) and is assigned to a const variable. That big fat arrow (=>) indicates it is an arrow function. It also takes in two parameters and returns their values in a formatted manner.

We can write the same function as below (since it has only one line of code in it). As you would observe that we removed the braces and put the function in a single line.

// arrow function
const heroIdentity = (name, age) => `Name: ${name}, Age: ${age}`;

// its usage
console.log(heroIdentity('Batman','Bruce Wayne'));
Enter fullscreen mode Exit fullscreen mode

If only one parameter is being passed to the arrow function, we can remove the parentheses also. For example

// arrow function
const heroIdentity = name => `Name: ${name}`;
// its usage
console.log(heroIdentity("Bruce Wayne"));
Enter fullscreen mode Exit fullscreen mode

Template Literals

According to MDN Docs,

Template literals are literals delimited with backtick characters, allowing for multi-line strings, string interpolation with embedded expressions, and special constructs called tagged templates.

We have already used template literals in our previous examples. Let’s see one more example

// 'api.nationalize.io' is a free API which tries to predict the nationality of the input name.
const baseUrl ="https://api.nationalize.io"

fetch(`${baseUrl}?name=Asheesh`) // => "https://api.nationalize.io?name=Asheesh"
.then(res => res.json())
.then(json => console.log(json));

fetch(`${baseUrl}/?name=Ronaldo`)
.then(res => res.json())
.then(json => console.log(json));

fetch(`${baseUrl}/?name=Ratnasinghe`)
.then(res => res.json())
.then(json => console.log(json));


//-----------------------------------------------------------
Output
(node:66694) ExperimentalWarning: The Fetch API is an experimental feature. This feature could change at any time
(Use `node --trace-warnings ...` to show where the warning was created)

{
 country: [ { country_id: 'LK', probability: 1 } ],
 name: 'Ratnasinghe'
}
{
 country: [
   { country_id: 'BR', probability: 0.277 },
   { country_id: 'PH', probability: 0.09 },
   { country_id: 'GT', probability: 0.03 },
   { country_id: 'PE', probability: 0.028 },
   { country_id: 'JP', probability: 0.023 }
 ],
 name: 'Ronaldo'
}
{
 country: [
   { country_id: 'IN', probability: 0.554 },
   { country_id: 'AE', probability: 0.092 },
   { country_id: 'SQ', probability: 0.075 },
   { country_id: 'KE', probability: 0.029 },
   { country_id: 'AU', probability: 0.026 }
 ],
 name: 'Asheesh'
}
Enter fullscreen mode Exit fullscreen mode

This example shows yet another application of template literals. ‘tagged templates’ are beyond the scope of our context, however, interested reader may visit here.

Rest Parameters (…)

We can add two numbers like so

function addThemAll(a, b){
   return a + b;
}
console.log(`Sum is: ${addThemAll(5,6)}`);
Enter fullscreen mode Exit fullscreen mode

but what if we had to add N numbers?

ES6 introduced an effective way to handle function parameters (…). Rest parameters are used to create functions that accept any number of arguments. By specifying …<param_name> in a function we can force that function to accepts any number of parameters. For this to happen the rest parameter MUST be the last parameter in the function definition. Also, rest parameters are of array type. For example,

function addThemAll(a, b, ...numbers){
   console.log(numbers);
   let sum = a+b;
   numbers.forEach(number => sum +=number);
   return sum;
}
console.log(`Sum is: ${addThemAll(3,4,-5,6,7,-1)}`);

//-----------------------------------------------------------
Output:
[ -5, 6, 7, -1 ]
Sum is: 14
Enter fullscreen mode Exit fullscreen mode

In the example above, numbers is the rest parameter and accepts all parameters after second parameter in the function call. Also notice that when we try to console log numbers, it prints a list/ array.

Spread Operator

The spread (…) syntax allows an iterable, such as an array or string, to be expanded in places where zero or more arguments (for function calls) or elements (for array literals) are expected.

Spread syntax looks exactly like rest syntax. In a way, spread syntax is the opposite of rest syntax. Spread syntax “expands” an array into its elements, while rest syntax collects multiple elements and “condenses” them into a single element.

Consider the following examples showing application of Spread operator

let charName={name: "Batman"};
let charPowers ={
   skills: "Trained in various martial arts and combat techniques",
   weapon: "Batrangs, Smoke Bomb",
   gadgets: "Kevlar Suite, Utility Belt"
}
let charAssociations={
   family: "Robin, Nightwing, Batgirl, Alfred",
   friends: "SuperMan, Wonder Woman, Green Arrow",
   enemies: "Joker, Penguin, Poison Ivy, Dr. Freeze, R'as Al Ghul, Deathstroke"
}

const myFavoriteChar={
   ...charName,
   ...charPowers,
   ...charAssociations
}

console.log(myFavoriteChar);

//-----------------------------------------------------------
Output:
{
 name: 'Batman',
 skills: 'Trained in various martial arts and combat techniques',
 weapon: 'Batrangs, Smoke Bomb',
 gadgets: 'Kevlar Suite, Utility Belt',
 family: 'Robin, Nightwing, Batgirl, Alfred',
 friends: 'SuperMan, Wonder Woman, Green Arrow',
 enemies: "Joker, Penguin, Poison Ivy, Dr. Freeze, R'as Al Ghul, Deathstroke"
}
Enter fullscreen mode Exit fullscreen mode

In the above example, we used spread operator to combine multiple objects in to one bigger object.

let heroes= ["Batman", "Superman","Wonder Woman"];
let sidekicks=["Robin", "SuperBoy", "Wondergirl"];
let dcChars = [...heroes,...sidekicks];
console.log(`Heroes: ${heroes} \nSidekicks: ${sidekicks} \nDC Chars:${dcChars}`);

//-----------------------------------------------------------
Output:
Heroes: Batman,Superman,Wonder Woman
Sidekicks: Robin,SuperBoy,Wondergirl
DC Chars:Batman,Superman,Wonder Woman,Robin,SuperBoy,Wondergirl
Enter fullscreen mode Exit fullscreen mode

In the above example, we used spread operator to concatenate multiple arrays.

Now, consider the example below. We are copying arrays without Spread operator

let justiceLeague=["Batman","Superman", "Wonder Woman", "Green Lantern"];
let justiceLeagueOfAmerica = justiceLeague;

console.log("Justice League: ", justiceLeague);

justiceLeagueOfAmerica.push("Shazam");
console.log("Justice League: ", justiceLeague);
console.log("Justice League of America: ",justiceLeagueOfAmerica);

/*
Output:
Justice League:  [ 'Batman', 'Superman', 'Wonder Woman', 'Green Lantern' ]
Justice League:  [ 'Batman', 'Superman', 'Wonder Woman', 'Green Lantern', 'Shazam' ]
Justice League of America:  [ 'Batman', 'Superman', 'Wonder Woman', 'Green Lantern', 'Shazam' ]
Enter fullscreen mode Exit fullscreen mode

We first assigned justiceLeague to justiceLeagueOfAmerica and then added a new hero ‘Sahazam’ to justiceLeagueOfAmerica. However, when we printed both the arrays, we found that ‘Sahazam’ got added to both arrays. This happened so because they way we assigned the arrays, both of them pointed to the same memory location. Therefore, when we added a new element to one array, it started showing up in the other array too.

We can get rid of this problem if we use Spread Operator to copy arrays, like so.

let justiceLeague=["Batman","Superman", "Wonder Woman", "Green Lantern"];
let justiceLeagueOfAmerica = [...justiceLeague];

console.log("Justice League: ", justiceLeague);

justiceLeagueOfAmerica.push("Shazam");
console.log("Justice League: ", justiceLeague);
console.log("Justice League of America: ",justiceLeagueOfAmerica);

//-----------------------------------------------------------
Output:
Justice League:  [ 'Batman', 'Superman', 'Wonder Woman', 'Green Lantern' ]
Justice League:  [ 'Batman', 'Superman', 'Wonder Woman', 'Green Lantern' ]
Justice League of America:  [ 'Batman', 'Superman', 'Wonder Woman', 'Green Lantern', 'Shazam' ]
Enter fullscreen mode Exit fullscreen mode

As we can see in the above example, the original array was not impacted.

let dcHeroes=["Martian Manhunter","Flash","Atom", "Tornado"];
// let moreDcHeroes = [dcHeroes, "Dr. Fate", "Zatana", "Swamp Thing"];
// console.log(moreDcHeroes);

let multipleDcHeroes = [...dcHeroes, "Starfire", "Spectre", "Constantine", "Etrigan"];
console.log(multipleDcHeroes);

//-----------------------------------------------------------
Output:
[
 'Martian Manhunter',
 'Flash',
 'Atom',
 'Tornado',
 'Starfire',
 'Spectre',
 'Constantine',
 'Etrigan'
]
Enter fullscreen mode Exit fullscreen mode

The above example shows another application of Spread Operator. We expanded the array using the spread operator.

console.log(Math.min(1,6,-7,-1,0));

//Now consider
let arr =[1,6,-7,-1,0];
//console.log(Math.min(arr)); //this would fail
console.log(Math.min(...arr));

//-----------------------------------------------------------
Output:
-7
-7
Enter fullscreen mode Exit fullscreen mode

Observe that in the above example, the first line console logs the minimum number from the list of number supplied to to the Math.min() method of ‘Math’ object. However, if we were to find the minimum number from an array, this function won’t help. However, usage of spread operator helps us pass the array as a list of numbers to the function and we get the minimum number.

Destructuring

According to MDN docs,

The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.

To unpack an array

const [var1, var2, ...] = arrayName;
Enter fullscreen mode Exit fullscreen mode

Consider the example below. We are unpacking elements of an array to different variables and then logging them to the console. Observe that only those many elements of the array were unpacked that we specified.

const greenLanternCorps=["Hal Jordan", "AbinSur", "Mogo", "Medphyll", "John Stewart", "Kilowog"];
let [human, mentor, planet, broccolli]= greenLanternCorps
console.log(`${human}, ${mentor}, ${planet}, ${broccolli}`);

//-----------------------------------------------------------
Output:
Hal Jordan, AbinSur, Mogo, Medphyll
Enter fullscreen mode Exit fullscreen mode

Similarly, we can unpack an object also.

const dcChar={
 name: "Desaad",
 allegiance: "Darkseid"
}
Enter fullscreen mode Exit fullscreen mode

We can unpack this object in to two variables like this

const {name, allegiance}=dcChar
Enter fullscreen mode Exit fullscreen mode

This is same as writing

const name = dcChar.name;
const allegiance = dcChar.allegiance;
Enter fullscreen mode Exit fullscreen mode

Now, consider that we do not want the variables to be of the same name as those of object properties. In such a case we would need to do something like this

const {name: charName, allegiance: charAllegiance} = dcChar;
Enter fullscreen mode Exit fullscreen mode

We can use rest parameters while destructuring. Consider we have an array which lists the prime villain and his minions. We can destructure such an array like

const apokolipsVillians=[
{name: "Darkseid"},
{name: "De Saad"},
{name: "SteppenWolf"},
{name: "Granny Goodness"}
]

const [leader, ...minions]=apokolipsVillians;
console.log(leader);
console.log(minions);

//-----------------------------------------------------------
Output:
{ name: 'Darkseid' }
[
 { name: 'De Saad' },
 { name: 'SteppenWolf' },
 { name: 'Granny Goodness' }
]
Enter fullscreen mode Exit fullscreen mode

Export

In ES 5, we could export a module and then require in some other file to make use of it. We can do the same in ES 6 but in a more neater and easier way. For example, in the code fragment below, we export a function written in some module and then import it in our index.js

 //index.js
 import { getRandomHero } from "./dcChars.js";
 console.log(getRandomHero());
Enter fullscreen mode Exit fullscreen mode
 //dcChars.js
 const dcHeroes=["Superman", "Batman", "Wonder Woman", "Flash", "Green Lantern", "Shazam"];

 const getRandomIntFromRange = (min, max) =>{
   return Math.floor(Math.random() * (max - min + 1) + min);
 }

 export function getRandomHero(){
   let heroIndex = getRandomIntFromRange(0, dcHeroes.length-1);
   return dcHeroes[heroIndex];
 }
Enter fullscreen mode Exit fullscreen mode

Note: both index.js and dcChars.js are in the same folder. We could have exported getRandomIntFromRange function and the dcHeroes array too by prepending export to them.

Let’s add one more function to dcChars.js, export it and then import it in our index.js

//index.js
import { getRandomHero, getRandomVillain } from "./dcChars.js";
console.log(`DC Hero: ${getRandomHero()}`);
console.log(`DC Villain: ${getRandomVillain()}`);
Enter fullscreen mode Exit fullscreen mode
//dcChars.js
const dcHeroes=["Superman", "Batman", "Wonder Woman", "Flash", "Green Lantern", "Shazam"];
const dcVillains = ["Darkseid", "Joker", "Cheetah", "Captain Cold", "Sinestro", "Black Adam"];
const getRandomIntFromRange = (min, max) =>{
   return Math.floor(Math.random() * (max - min + 1) + min);
}

export function getRandomHero(){
   let heroIndex = getRandomIntFromRange(0, dcHeroes.length-1);
   return dcHeroes[heroIndex];

}

export function getRandomVillain(){
   let villainIndex = getRandomIntFromRange(0, dcVillains.length-1);
   return dcVillains[villainIndex];
}
Enter fullscreen mode Exit fullscreen mode

We see that we can specify multiple items to be imported from a module in curly braces. If we had specified default along with export for any one of the function above, then we could have imported it without specifying it between curly braces.

//index.js
import getRandomHero, { getRandomVillain } from "./dcChars.js";
...
Enter fullscreen mode Exit fullscreen mode
//dcChars.js
...
...
export default function getRandomHero(){
   let heroIndex = getRandomIntFromRange(0, dcHeroes.length-1);
   return dcHeroes[heroIndex];
}
...
Enter fullscreen mode Exit fullscreen mode

Another thing with default export is that we can export it with any name and it would still work. This implies that instead of

import getRandomHero, { getRandomVillain } from "./dcChars.js";
...
console.log(getRandomHero());
Enter fullscreen mode Exit fullscreen mode

if we wrote this

import getRandomDCHero, { getRandomVillain } from "./dcChars.js";
...
console.log(getRandomDCHero());
Enter fullscreen mode Exit fullscreen mode

it would still work.

Remember that we can have only one default export.

Note: With the import keyword all imported modules are pre parsed. This implies that they get executed before the code where they have been imported. With exports keyword (ES 5) the exported module(s) get executed only when it is invoked.

This is the different between require (ES 5)and import(ES 6). Using require we can load the dependency ‘on demand’.

Conclusion

We breezed through some of the most important concepts of modern JavaScript. Our ultimate objective is to have a better understanding of JavaScript, so that later on we can easily read and write test automation script(s) easily.

Please do share your thoughts and feedback on this tutorial. I will surely make amends where ever necessary and it will help me improve.

💖 💪 🙅 🚩
asheeshmisra
Asheesh Misra

Posted on September 22, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

JavaScript for Testers - Part 1 (Basics)
javascript JavaScript for Testers - Part 1 (Basics)

September 22, 2023