We develop automation in a couple of hours: TypeScript, Protractor, Jasmine

We develop automation in a couple of hours: TypeScript, Protractor, Jasmine


Hello, Habr!

My name is Vitaly Kotov, I do a lot of testing automation and I like it. Recently, I participated in a project to set up automation from scratch on a TypeScript + Protractor + Jasmine stack. For me, this stack was new and the necessary information I was looking for on the Internet.

The most useful and intelligent manuals I managed to find only in English. I decided that in Russian, too, need to do this. I'll tell you only the basics: why exactly this stack, what needs to be set up and what the easiest test looks like.

At once I will make a reservation that I rarely work with NodeJS, npm and in general with server-side JavaScript (especially with TypeScript). If you find a mistake in terminology somewhere or some of my solutions can be improved, I will be glad to know about it in the comments from more experienced guys.

By the way, I already had a similar article: “Deploy automation in a couple of hours: PHPUnit, Selenium, Composer” .



Task


First of all, let's understand the problem we are solving. We have a web application written using AngularJS. This is a JavaScript framework, on the basis of which web projects are written quite often.

In this article we will not consider the pros and cons of AngularJS-projects. Just a couple of words about the features of such projects in terms of writing e2e tests for them.

A rather important aspect of test automation is the work with page elements that occurs with the help of locators. A locator is a string composed according to certain rules that identifies a UI element: one or more.

For the web, CSS and Xpath are most commonly used. Sometimes, if the page has an element with a unique ID, you can search by it. However, it seems to me that WebDriver still turns this ID into a CSS locator in the end and is already working with it.

If we look at the HTML code of an AngularJS project, we will see elements with a lot of attributes that are not found in classic HTML:



Code taken from protractor-demo .

All attributes starting with ng- * are used by AngularJS for working with the UI. A rather typical situation is when elements other than these control attributes have no other, which somewhat complicates the process of drawing up qualitative locators.

Those who have been involved in automation a lot know the value of such UIs for which locators can easily be compiled. After all, this is a rarity for large projects.:)

Actually, for such a project, we need to set up a test automation. Let's go!

What is what


First of all, let's see what each component of our stack is for.

Protractor is a WebDriverJS test framework. He will launch our browsers, force them to open the necessary pages and interact with the necessary elements.

This framework is specially sharpened for AngularJS projects. It provides additional ways to define locators:

  element (by.model ('first'));
 element (by.binding ('latest'));
 element (by.repeater ('some'));
  

For a complete list, please visit the manual .

These methods simplify the creation and maintenance of locators on a project. However, we must understand that "under the hood" all this in any case is converted into css.The fact is that the W3C protocol, based on which interaction takes place in WebDriver, can work only with a finite set of locators. This list can be viewed at w3.org .

TypeScript is a programming language created by Microsoft. TypeScript differs from JavaScript in the ability to type variables, support for the use of full-fledged classes and the ability to connect modules.
Written in TS-code to work with the V8 engine is transported to JS-code, which is already executed. In the course of this transformation, the code is checked for its compliance. For example, it is not “compiled” if, instead of the int, a string is somewhere passed to the function.

Jasmine is a framework for testing JavaScript code. In fact, it is thanks to him that our JS code turns into what we used to call a test. He manages these tests.

Below we look at its capabilities.

Build and configure the project


Well, with a set of frameworks, we decided, now let's collect the whole thing.

To work with the code, I chose Visual Studio Code from Microsoft. Although many people write to WebStorm or even Intellij JetBrains Idea .

I already had NodeJS (v11.6.0) and NPM (6.9.0) installed. If you don’t have it, it’s not a problem, it’s easy to install. Everything is described in some detail on the official website .

You can use Yarn instead of NPM, although for a small project it doesn't matter.

In our IDE, we are creating a new project. At the root of the project we create package.json - in it we will describe all those packages that we need for the project.

You can create it using the npm init command. And you can just copy the contents to a file.

Initially, package.json looks like this:

  {
  "name": "protractor",
  "dependencies": {
  "@ types/node": "^ 10.5.2",
  "@ types/jasmine": "^ 3.3.12",
  "protractor": "^ 5.4.2",
  "typescript": "^ 3.4.1"
  }
 }
  

After we execute the npm install command to install all the necessary modules and their dependencies (well, you remember that very picture about the fact that it is heavier than a black hole ...)

As a result, we should have a node_modules directory. If she appeared, then everything goes according to plan. If not, it is worth looking into the result of the command, there is usually quite detailed.

TypeScript and its config


To install TypeScript we need npm:

  npm install -g typescript
  

Make sure it is established:

  $ tsc -v
 Version 3.4.1
  

Looks like everything is in order.

Now you need to create a config for working with TS. It should also be at the root of the project and called tsconfig.json

Its contents will be:

  {
  "compilerOptions": {
  "lib": ["es6"],
  "strict": true
  "outDir": "output_js",
  "types": ["jasmine", "node"]
  },
  "exclude": [
  "node_modules/*"
  ]
 }
  

In short, we have indicated the following in this config:

  • In which directory to put the final JS code (in our case, this is output_js)
  • Enable strict mode
  • Indicated with which frameworks we work
  • Exclude node_modules from compilation

TS has a lot of options. For our project, these are enough for now. You can learn more about on the typescriptlang.org website .

Now let's see how the tsc command works, which will turn our TS code into JS code. To do this, create a simple file check_tsc.ts with the following content:

  saySomething ("Hello, world!");

 function saySomething (message: string) {
  console.log (message);
 }
  

And then execute the command tsc (for this you need to be inside the directory with the project).

We will see that we have a directory output_js and inside we have a similar js-file with the following contents:

  "use strict";
 saySomething ("Hello, world!");
 function saySomething (message) {
  console.log (message);
 }
  

This file can already be run using the node command:

  $ node output_js/check_tsc.js
 Hello, world!
  

So, we wrote our first program on TypeScipt, my congratulations. Let's write tests now.:)

Protractor config


For Protractor, we also need a config. But it will not be in the form of json, but in the form of a ts-file. Call it config.ts and write the following code there:

  import {Config} from "protractor";

 export const config: Config = {
  seleniumAddress: "http://127.0.0.1:4444/wd/hub",
  SELENIUM_PROMISE_MANAGER: false,
  capabilities: {
  browserName: "chrome",
/* chromeOptions: {
  args: ["--headless", "--window-size = 800,600"]
  } */
  },
  specs: [
  "Tests/* Test.js",
  ]
 };
  

In this file we specified the following:

First, the path to the running Selenium server. It is quite easy to run, you need to download the Standalone Server jar-file and the necessary drivers (for example, the chrome driver for Chrome browser ). Next, write the following command:

  java -jar -Dwebdriver.chrome.driver =/path/to/chromedriver/path/to/selenium-server-standalone.jar
  

As a result, we should see the following conclusion:

  23: 52: 41.691 INFO [GridLauncherV3.launch] - Selenium build info: version: '3.11.0', revision: 'e59cfb3'
 23: 52: 41.693 INFO [GridLauncherV3 $ 1.launch] - Launching a standalone Selenium Server on port 4444
 2019-05-02 23: 52: 41.860: INFO :: main: Logging initialized @ 555ms to org.seleniumhq.jetty9.util.log.StdErrLog
 23: 52: 42.149 INFO [SeleniumServer.boot] - Welcome to Selenium for Workgroups ....
 23: 52: 42.149 INFO [SeleniumServer.boot] - Selenium Server is up and running on port 4444
  

Port 4444 is default. It can be set using the -port parameter or through the configuration parameter “seleniumArgs” = & gt; "-port".

If you want easier and faster: you can download the npm-package webdriver-manager .

And then manage the server using the start, shutdown and so on commands. There is no particular difference, I just get used to working with the jar file.:)

Secondly - we indicated that we didn’t want to use a Promise Manager. More on this later.

Third - we specified the capabilities for our browser. I commented out the part, for example, that we can run the browser in headless mode fairly easily. This is a cool feature, but it will not allow you to visually monitor our tests. For now we only study - it would be desirable.:)

Fourth - we specified a mask for specs (tests). Everything that lies in the Tests folder and ends in Test.js. Why on js and not on ts? That's because, as a result, Node will work with JS-files, and not with TS-files. This is important not to get confused, especially at the beginning of work.

Now we create the Tests folder and write the first test. He will do the following:

  • Turns off the check that this is an Angular page. Without this, we get the following error message: Error while running testForAngular. Of course, for Angular-pages this check is not necessary to turn off.
  • Go to the Google page.
  • Verify that there is a text entry field.
  • Enter the text “protractor”.
  • Click the submit button (it has a rather complex locator, since there are two buttons and one is invisible).
  • It will wait for the URL to contain the word “protractor” - this means that we have done everything correctly and the search has begun.

Here is the code I got:

  import {browser, by, element, protractor} from "protractor";

 describe ('Search', () = & gt; {
  it ('Open google and find a text', async () = & gt; {
//Create an object to work with expectations
  let EC = protractor.ExpectedConditions;

//turn off the check for AngularJS
  await browser.waitForAngularEnabled (false);

//open Google page
  await browser.get ('https://www.google.com/');
 
//create an item by css = input [role = 'combobox']
  let input_button = element (by.css ("input [role = 'combobox']"));

//wait for this element to appear (presenceOf events)
  await browser.wait (EC.presenceOf (input_button), 5000);

//write the text “protractor” to the element
  await input_button.sendKeys ("protractor");

//create a submit button for css
  let submit_button = element (by.css (". FPdoLc input [type = 'submit'] [name = 'btnK']"));

//wait for it to appear on the page (not necessary, because we have already waited for the input element, which means the page has loaded)
  await browser.wait (EC.presenceOf (submit_button), 5000);

//click on the submission button
  await submit_button.click ();

//wait for the URL to contain the text 'protractor'
  await browser.wait (EC.urlContains ('protractor'), 5000);
  });
 });
  

In the code, we see that everything starts with the describe () function. She came to us from the Jasmine framework. This is a wrapper for our script. Inside it can be the beforeAll () and beforeEach () functions to perform any manipulations before all tests and before each test. Any number of it () functions are, in fact, our tests. At the end, if defined, afterAll () and afterEach () will be executed for manipulations after each test and all tests.

I will not talk about all the possibilities of Jasmine, you can read about them on the website jasmine.github.io

To run our test, you first need to turn the TS code into a JS code, and then run it:

  $ tsc
 $ protractor output_js/config.js
  

Our test started - we are great.:)



If the test does not start, you should check:

  • That the code is written correctly. In general, if there are critical errors in the code, we will catch them during the execution of the tsc command.
  • That Selenium Server is running. To do this, you can open the URL http://127.0.0.1:4444/wd/hub - there should be an interface of Selenium-sessions .
  • That Chrome starts up fine with the downloaded version of chrome-driver. To do this, on the wd/hub/page, you must click on Create Session and select Chrome. If it does not start, then you must either update Chrome or download another version of chrome-driver.
  • If all this did not help, you can verify that the npm install command has completed successfully.
  • If everything is written correctly, but still nothing starts - try to google the error. This most often helps.:)

NPM scripts


In order to make life easier, you can make a part of the commands in npm aliases. For example, I would like before each test run to first delete the directory with the previous JS-files and recreate it with new ones.

To do this, add the “scripts” item to package.json:

  {
  "name": "protractor",
  "scripts": {
  "test": "rm -rf output_js/; tsc; protractor output_js/config.js"
  },
  "dependencies": {
  "@ types/node": "^ 10.5.2",
  "@ types/jasmine": "^ 3.3.12",
  "protractor": "^ 5.4.2",
  "typescript": "^ 3.4.one"
  }
 }
  

Now, by entering the npm test command, the following will happen: the output_js directory with the old code will be deleted, the new JS code will be created again and written into it. After that, the tests will start immediately.

Instead of this command set, you can specify any other one that is necessary for you to work personally. For example, you can run and extinguish the selenium server between test runs. Although it is, of course, easier to control inside the test code itself.

A little about Promise


At the end I will talk a little about Promise, async/await and how writing tests on NodeJS differs from the same Java or Python.

JavaScript is an asynchronous language. This means that the code is not always executed in the order in which it is written. This includes HTTP requests, and we remember that the code communicates with Selenium Server via HTTP.

Promise (usually called “promises”) provide a convenient way to organize asynchronous code. You can read about them in some detail at learn.javascript.ru .

In essence, these are objects that allow one code to be made dependent on the execution of another, thereby guaranteeing a certain order. Protractor is very actively working with these objects.

Let's look at an example. Suppose we execute this code:

  driver.findElement (). getText ();
  

In Java, we expect an object of type String to be returned to us. In Protractor, this is not quite the case; the Promise object will return to us. And just to print it for debag will not work.

Usually we do not need to print the resulting value. We need to transfer it to some other method that will already work with this value. For example, check the text for compliance with the expected.

Similar methods in Protractor accept Promise objects as input, so there are no problems. But, if you still want to see the value, we need the then () function.

This is how we can print the button text on the Google page (note that since this is a button, the text is inside the value attribute):

 //create element
 let submit_button = element (by.css (". FPdoLc input [type = 'submit'] [name = 'btnK']"));
//wait for it to appear
 await browser.wait (EC.presenceOf (submit_button), 5000);
 //with then () get the text
 await submit_button.getAttribute ("value"). then ((text) = & gt; {
  console.log (text);
 });
  

As for the async/await keywords, this is a slightly newer approach to working with asynchronous code. It allows you to avoid promise hell, which was previously formed in the code due to the large number of nestings. Nevertheless, it will not be possible to get rid of Promise completely and you need to be able to work with them. You can read about this clearly and in detail in the article The construction of async/await in JavaScript: strengths, pitfalls and features of use .

Homework


As a homework, I suggest writing tests for a page written on AngularJS: protractor-demo .

Do not forget to remove a line from the code about turning off page checking on AngularJS. And be sure to work with locators specifically designed for AngularJS. There is no special magic in this, but it is quite convenient.

Total


Let's take stock. We managed to write tests that work on a TypeScript + Protractor + Jasmine bundle. We learned how to build such a project, create the necessary configs and wrote the first test.

Along the way, we discussed a little about how to work with autotests on JavaScript. It feels good for a couple of hours.:)

What to read, where to look


Protractor has a pretty good javascript example manual: https://www.protractortest.org/#/tutorial
Jasmine has a manual: https://jasmine.github.io/pages/docs_home.html
TypeScipt has a good get started: https://www. typescriptlang.org/docs/handbook/typescript-in-5-minutes.html

Medium has a good article in English about TypeScript + Protractor + Cucumber: https://medium.com/@igniteram/e2e-testing-with-protractor-cucumber-using-typescript-564575814e4a

And in my repository, I posted the final code of what we discussed in this article: https://github.com/KotovVitaliy/HarbProtractorJasmineJasmine.

On the Internet you can find examples of more complex and large projects on this stack.

Thanks for attention!:)

Source text: We develop automation in a couple of hours: TypeScript, Protractor, Jasmine