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

  • Tutorial
Hello, Habr!

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

I managed to find the most useful and sensible manuals only in English. I decided that in Russian I also needed to do this. I’ll only tell you the basics: why such a stack, what you need to configure, and what the simplest test looks like.

I must say right away that I rarely work with NodeJS, npm, and with server-side JavaScript in general (especially with TypeScript). If you find a mistake in the terminology somewhere or some of my decisions 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: “We deploy automation in a couple of hours: PHPUnit, Selenium, Composer” .



Task


First of all, let's figure out what problem we are solving. We have a web application written using AngularJS. This is a JavaScript framework based on which web projects are often written.

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

A rather important aspect of testing automation is working with page elements, which happens with the help of locators. A locator is a line composed according to certain rules and identifying a UI element: one or more.

For the web, CSS and Xpath are most commonly used. Sometimes, if there is an element with a unique ID on the page, 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 some AngularJS project, we will see a lot of attributes for elements that are not in classical HTML:



The code is taken from the protractor-demo page .

All attributes starting with ng- * are used by AngularJS to work with the UI. A fairly typical situation is when elements other than these control attributes have no others, which complicates the process of compiling quality locators.

Those who have done a lot of automation know about the value of such UIs for which locators can be easily built. After all, this is rare for large projects. :)

Actually, for such a project, we also need to configure test automation. Go!

What is what


First of all, let's figure out why each component of our stack is needed.

Protractor is a test framework based on WebDriverJS. It will be he who will launch our browsers, make them open the necessary pages and interact with the necessary elements.

This framework is specifically tailored for AngularJS projects. It provides additional ways to specify locators:

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

A complete list can be found on the manual page .

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

TypeScript is a programming language created by Microsoft. TypeScript differs from JavaScript in its 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 translated into JS code, which is already executing. During this transformation, the code is checked for compliance. For example, it does not “compile” if, instead of int, a string is explicitly passed to the function somewhere.

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.

Assembly and project setup


Well, we decided on a set of frameworks, now let's put this whole thing together.

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

I have already installed NodeJS (v11.6.0) and NPM (6.9.0). If you don’t have it, this is not a problem; installing them will not be difficult. Everything is described in sufficient detail on the official website .

Yarn can be used instead of NPM, although this is not important for a small project.

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

You can create it using the npm init command. Or you can simply 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 that, we run the npm install command to install all the necessary modules and their dependencies (well, you remember the very picture that is heavier than a black hole ...)

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

TypeScript and its config


To install TypeScript, we need npm:

npm install -g typescript

Make sure that it is installed:

$  tsc -v
Version 3.4.1

Everything seems to be in order.

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

Its contents will be like this:

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

In short, we specified the following in this config:

  • In which directory to put the final JS-code (in our case it is output_js)
  • Enable strict mode
  • Indicated with which frameworks we are working
  • Excluded node_modules from compilation

TS has a huge variety of settings. These are enough for our project. You can learn more at typescriptlang.org .

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

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

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

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

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

This file can already be launched using the node command:

$  node output_js/check_tsc.js
Hello, world!

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

Protractor config


For Protractor we also need a config. But it will no longer be in the form of json, but in the form of a ts-file. Let's 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:

Firstly, the path to the running Selenium server. It is quite simple to run, you just need to download the Standalone Server jar file and the necessary drivers (for example, the chrome driver for the 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 defaulted. It can be set using the -port parameter or through the config parameter "seleniumArgs" => "-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 isn’t much difference, it's just that I’m more used to working with a jar file. :)

Secondly - we indicated that we do not want to use the Promise manager. More about this later.

Thirdly , we specified capabilities for our browser. I commented out a part, for example, that we can quite easily launch the browser in headless mode. This is a cool feature, but it will not allow you to visually observe our tests. In the meantime, we are just learning - I would like to. :)

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

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

  • Disables checking that this is an Angular page. Without this, we get this error message: Error while running testForAngular. Of course, for Angular-pages this check is not necessary to turn off.
  • Goes to the Google page.
  • Check that there is a text input field.
  • Enter the text “protractor”.
  • Click on the submit button (it has a rather complicated locator, since there are two buttons and the first one is invisible).
  • It will be expected that the URL will contain the word “protractor” - this means that we did everything right and the search began.

Here is the code I got:

import { browser, by, element, protractor } from "protractor";
describe('Search', () => {
    it('Open google and find a text', async () => {
        // Создаем объект для работы с ожиданиями
        let EC = protractor.ExpectedConditions;
        // выключаем проверку на AngularJS
        await browser.waitForAngularEnabled(false);
        // открываем страницу Google
        await browser.get('https://www.google.com/');
        // создаем элемент по css = input[role='combobox']
        let input_button = element(by.css("input[role='combobox']"));
        // ждем появление этого элемента (события presenceOf)
        await browser.wait(EC.presenceOf(input_button), 5000);
        // пишем в элемент текст “protractor”
        await input_button.sendKeys("protractor");
        // создаем элемент кнопки сабмита по css
        let submit_button = element(by.css(".FPdoLc input[type='submit'][name='btnK']"));
        // дожидаемся его появления на странице (не обязательно, ведь мы уже дождались input-элемента, значит страница загрузилась)
        await browser.wait(EC.presenceOf(submit_button), 5000);
        // кликаем по кнопке сабмита
        await submit_button.click();
        // ждем, когда URL будет содержать текст '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, there may be functions beforeAll () and beforeEach () to perform any manipulations before all tests and before each test. As many functions as it () are, in fact, our tests. In the end, if defined, afterAll () and afterEach () will be executed for manipulations after each test and all tests.

I won’t talk about all the features 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 JS code, and then run it:

$  tsc
$  protractor output_js/config.js

Our test started - we are great. :)



If the test did not start, it is worth checking:

  • That the code is written correctly. In general, if there are critical errors in the code, we will catch them during the tsc command.
  • That Selenium Server is running. To do this, you can open the URL http: //127.0.0.1-00-00444/wd/hub - there should be an interface for Selenium-sessions.
  • That Chrome starts normally with the downloaded version of chrome-driver. To do this, on the wd / hub / page, click Create Session and select Chrome. If it does not start, then you need to either update Chrome, or download another version of chrome-driver.
  • If all this fails, 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. It most often helps. :)

NPM scripts


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

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.1"
  }
}

Now entering the npm test command the following will happen: the output_js directory with the old code will be deleted, it will be created anew and a new JS code will be written to it. After which the tests will start immediately.

Instead of this set of commands, you can specify any other that you personally need to work. For example, you can start and quench a selenium server between test runs. Although this, of course, is easier to control inside the test code itself.

A bit about Promise


In the end, I’ll talk a bit about Promise, async / await and how the writing of tests in 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 more about them at learn.javascript.ru .

In fact, these are objects that make one code dependent on the execution of another, thereby guaranteeing a certain order. Protractor works very actively with these objects.

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

driver.findElement().getText();

In Java, we expect us to return an object of type String. In Protractor, this is not quite so, we will return a Promise object. And just like that, printing it with a goal of debug will not work.

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

Similar methods in Protractor also accept Promise objects as input, so there is no problem. But, if you still want to see the value, then () will come in handy.

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

// создаем элемент
let submit_button = element(by.css(".FPdoLc input[type='submit'][name='btnK']"));
// дожидаемся его появления
await browser.wait(EC.presenceOf(submit_button), 5000);
// при помощи then() получаем текст
await submit_button.getAttribute("value").then((text) => {
    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 the promise hell, which was previously formed in the code due to the large number of nesting. Nevertheless, you won’t be able to completely get rid of Promise and you need to be able to work with them. This is understandable and detailed can be found in the article Design async / await in JavaScript: strengths, pitfalls and features of use .

Homework


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

Do not forget to remove the 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 particular magic in this, but it is quite convenient.

Total


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

Along the way, we discussed a bit about working with JavaScript auto-tests. It seems good for a couple of hours. :)

What to read, where to look


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

On the Medium there is 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 larger projects on this stack.

Thanks for attention! :)

Also popular now: