Hi, 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 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 simplest 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 often written.
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 and identifying 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 many attributes that are not found in classic HTML:

Code taken from the page
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. Go!
What is what
First of all, let's see what each component of our stack is for.
Protractor is a WebDriverJS based 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'));
The full list can be viewed on the
manual page.
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 on the website
w3.org .
TypeScript is a programming language created by Microsoft. TypeScript differs from JavaScript by 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 an int, a string is explicitly passed to a 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.
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 in
WebStorm or even
Intellij Idea from JetBrains.
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 quite detailed on the
official site .
Instead of NPM, you can use Yarn, although for a small project it does not 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 pretty 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's fine.
Now you need to create a config for working with TS. It should also be at the root of the project and called
tsconfig.jsonIts contents will be as follows:
{ "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 output_js)
- Enable strict mode
- Specified with what 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
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 check_tsc.ts file with the following contents:
saySomething("Hello, world!"); function saySomething(message: string) { console.log(message); }
And further we will execute the
tsc command (for this it is necessary to be in a 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. 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", }, specs: [ "Tests/*Test.js", ] };
In this file we specified the following:
First, the path to the running Selenium server. It's pretty easy to start, you 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 default. It can be set using the -port parameter or through the config parameter “seleniumArgs” => "-port".
If you want easier and faster: you can download
webdriver-manager npm-package.
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 do not 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. :)
Fourthly - 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 box.
- Enter the text “protractor”.
- Click the submit button (it has a rather complex locator, since there are two buttons and the first 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.
This is the code I got:
import { browser, by, element, protractor } from "protractor"; describe('Search', () => { it('Open google and find a text', async () => {
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.imtqy.comTo 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 for 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 does 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.1" } }
Now, 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 anew and a new one will be 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.
Little about promise
At the end I will talk a little about Promise, async / await and how writing tests for 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 also 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 like that 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, checks the text for 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):
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
Construction of async / await in JavaScript: strengths, pitfalls and features of use .
Homework
As homework I suggest to write tests for the 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 it, 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 nice tutorial with examples in javascript:
https://www.protractortest.org/#/tutorialJasmine has a manual:
https://jasmine.imtqy.com/pages/docs_home.htmlTypeScipt has a good get started:
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.htmlMedium has a good article in English about TypeScript + Protractor + Cucumber:
https://medium.com/@igniteram/e2e-testing-with-protractor-cucumber-using-typescript-564575814e4aAnd 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! :)