JavaScript for Testers - Part 1 (Basics)
Tags: javascript, beginners, tutorial, testing
Published: 2023-09-22 19:19:01

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](https://dev.to/asheeshmisra/javascript-for-testers-part-2-callbacks-promises-and-async-await-15b1))

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](https://nodejs.org/en/)** (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](https://code.visualstudio.com/Download)** 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](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/First_steps/What_is_JavaScript)
>“[JavaScript](https://developer.mozilla.org/en-US/docs/Glossary/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](https://developer.mozilla.org/en-US/docs/Web/API), 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. 

```bash
mkdir JSBasics
cd JSBasics
```

Then we will do
```bash
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)
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mp7kgemmiubfrh1yccr.jpg)

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 <u>to declare variables in JS</u>. `let` and `const` keywords got introduced in ES6.

`const` variables cannot be reassigned. For example,

```javascript
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.
```

notice the **TypeError** line.

`let` variables can be reassigned. For example,

```javascript
for(let i=0;i<5;i++){
   console.log("JS is good but TS is awesome");
}
```

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

```javascript
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
```

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,

```javascript
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
```

## 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

```javascript
// 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'));
```

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.

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

// its usage
console.log(heroIdentity('Batman','Bruce Wayne'));
```

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

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

## Template Literals
According to [MDN Docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals),

> 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

```javascript
// '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'
}
```

This example shows yet another application of template literals. _‘tagged templates’_ are beyond the scope of our context, however, interested reader may visit [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates).


## Rest Parameters (…)

We can add two numbers like so

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

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,

```javascript
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
```

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

```javascript
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"
}
```

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

```javascript
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
```

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

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

```javascript
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' ]
```

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.

```javascript
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' ]
```

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

```javascript
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'
]
```

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

```javascript
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
```

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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment),

> 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

```javascript
const [var1, var2, ...] = arrayName;
```

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.

```javascript
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
```

Similarly, we can unpack an object also. 

```javascript
const dcChar={
 name: "Desaad",
 allegiance: "Darkseid"
}
```

We can unpack this object in to two variables like this

```javascript
const {name, allegiance}=dcChar
```

This is same as writing

```javascript
const name = dcChar.name;
const allegiance = dcChar.allegiance;
```

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

```javascript
const {name: charName, allegiance: charAllegiance} = dcChar;
```

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

```javascript
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' }
]
```

## 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` 

```javascript
 //index.js
 import { getRandomHero } from "./dcChars.js";
 console.log(getRandomHero());
```

```javascript
 //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];
 }
```

#### 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

```javascript
//index.js
import { getRandomHero, getRandomVillain } from "./dcChars.js";
console.log(`DC Hero: ${getRandomHero()}`);
console.log(`DC Villain: ${getRandomVillain()}`);
```

```javascript
//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];
}
```

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.

```javascript
//index.js
import getRandomHero, { getRandomVillain } from "./dcChars.js";
...
```

```javascript
//dcChars.js
...
...
export default function getRandomHero(){
   let heroIndex = getRandomIntFromRange(0, dcHeroes.length-1);
   return dcHeroes[heroIndex];
}
...
```

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

```javascript
import getRandomHero, { getRandomVillain } from "./dcChars.js";
...
console.log(getRandomHero());
```

if we wrote this

```javascript
import getRandomDCHero, { getRandomVillain } from "./dcChars.js";
...
console.log(getRandomDCHero());
```

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.