diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..ce955f30db851ffd61cf9861113bda511842297a
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,47 @@
+# .gitlab-ci.yml
+
+image: node:8
+
+stages:
+  - test
+  - build
+
+test:
+  stage: test
+  before_script:
+    # Add Google Chrome to aptitude's (package manager) sources
+    - echo "deb http://dl.google.com/linux/chrome/deb/ stable main" | tee -a /etc/apt/sources.list
+    # Fetch Chrome's PGP keys for secure installation
+    - wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add -
+    # Update aptitude's package sources
+    - apt-get -qq update -y
+    # Install latest Chrome stable, Xvfb packages
+    - apt-get -qq install -y google-chrome-stable xvfb gtk2-engines-pixbuf xfonts-cyrillic xfonts-100dpi xfonts-75dpi xfonts-base xfonts-scalable imagemagick x11-apps default-jre
+    # Launch Xvfb
+    - Xvfb :0 -ac -screen 0 1024x768x24 &
+    # Export display for Chrome
+    - export DISPLAY=:99
+    # Install AngularJS CLI exclusively
+    # Add --unsafe-perm to resolve problems with node-gyp infinite loop on Docker
+    - npm install --silent --unsafe-perm -g @angular/cli@1.1.2
+    # Install remaining project dependencies
+    - npm install --silent
+    # Download Selenium server JAR, drivers for Chrome
+    - node ./node_modules/.bin/webdriver-manager update
+  script:
+    - ng test --single-run --progress false
+    - ng e2e --progress false
+
+build:
+  stage: build
+  before_script:
+    - npm install --silent --unsafe-perm -g @angular/cli@1.1.2
+    - npm install --silent
+  script:
+    - ng build --prod --progress false
+  artifacts:
+    paths:
+      - dist/
+  only:
+    - master
+    - develop
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 3abc2808f5c306b956ca8ece7f676ff797e8cffa..36da3f2cb50e656acd28f071305ac9be1be12cdc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -196,6 +196,12 @@
       "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
       "dev": true
     },
+    "arrify": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+      "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+      "dev": true
+    },
     "babel-eslint": {
       "version": "10.0.1",
       "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.1.tgz",
@@ -238,6 +244,12 @@
         "concat-map": "0.0.1"
       }
     },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "dev": true
+    },
     "caller-path": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz",
@@ -305,6 +317,12 @@
       "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
       "dev": true
     },
+    "colors": {
+      "version": "1.1.2",
+      "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+      "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
+      "dev": true
+    },
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -353,6 +371,12 @@
         "rimraf": "^2.2.8"
       }
     },
+    "diff": {
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+      "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+      "dev": true
+    },
     "doctrine": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -740,6 +764,15 @@
       "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
       "dev": true
     },
+    "jasmine-spec-reporter": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz",
+      "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==",
+      "dev": true,
+      "requires": {
+        "colors": "1.1.2"
+      }
+    },
     "js-tokens": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -806,6 +839,12 @@
       "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
       "dev": true
     },
+    "make-error": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
+      "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
+      "dev": true
+    },
     "mimic-fn": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
@@ -1088,6 +1127,24 @@
       "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
       "dev": true
     },
+    "source-map-support": {
+      "version": "0.5.9",
+      "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
+      "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
+      "dev": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "source-map": "^0.6.0"
+      },
+      "dependencies": {
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+          "dev": true
+        }
+      }
+    },
     "sprintf-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -1173,6 +1230,47 @@
       "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
       "dev": true
     },
+    "ts-node": {
+      "version": "7.0.1",
+      "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz",
+      "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==",
+      "dev": true,
+      "requires": {
+        "arrify": "^1.0.0",
+        "buffer-from": "^1.1.0",
+        "diff": "^3.1.0",
+        "make-error": "^1.1.1",
+        "minimist": "^1.2.0",
+        "mkdirp": "^0.5.1",
+        "source-map-support": "^0.5.6",
+        "yn": "^2.0.0"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+          "dev": true
+        },
+        "mkdirp": {
+          "version": "0.5.1",
+          "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+          "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+          "dev": true,
+          "requires": {
+            "minimist": "0.0.8"
+          },
+          "dependencies": {
+            "minimist": {
+              "version": "0.0.8",
+              "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+              "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+              "dev": true
+            }
+          }
+        }
+      }
+    },
     "tslib": {
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
@@ -1188,6 +1286,12 @@
         "prelude-ls": "~1.1.2"
       }
     },
+    "typescript": {
+      "version": "3.1.6",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz",
+      "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==",
+      "dev": true
+    },
     "underscore": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz",
@@ -1247,6 +1351,12 @@
           }
         }
       }
+    },
+    "yn": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+      "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
+      "dev": true
     }
   }
 }
diff --git a/package.json b/package.json
index fcce9ee9479a2ce767ef6509f05ef1c9ae76bea2..3d688a7427743f72435c57e3ab43b14a86565a93 100644
--- a/package.json
+++ b/package.json
@@ -10,7 +10,10 @@
   "devDependencies": {
     "babel-eslint": "^10.0.1",
     "eslint": "^5.9.0",
-    "eslint-config-google": "^0.11.0"
+    "eslint-config-google": "^0.11.0",
+    "jasmine-spec-reporter": "^4.2.1",
+    "ts-node": "^7.0.1",
+    "typescript": "^3.1.6"
   },
   "scripts": {
     "test": "protractor protractor.conf.js"
diff --git a/protractor.conf.js b/protractor.conf.js
index bdb68571cc5e100d17144311d59f080adc70fc2e..b5894780288ea5128dcd15257d4b10bd2fba850f 100644
--- a/protractor.conf.js
+++ b/protractor.conf.js
@@ -1,7 +1,9 @@
 const HtmlReporter = require('protractor-beautiful-reporter');
+const {SpecReporter} = require('jasmine-spec-reporter');
 const path = require('path');
 
 exports.config = {
+    allScriptsTimeout: 11000,
     seleniumAddress: 'http://localhost:4444/wd/hub',
 
     // The port to start the selenium server on, or null if the server should
@@ -23,6 +25,7 @@ exports.config = {
     // A base URL for your application under test. Calls to protractor.get()
     // with relative paths will be prepended with this.
     baseUrl: 'http://localhost:9999',
+    directConnect: true,
 
     framework: 'jasmine',
 
@@ -60,9 +63,11 @@ exports.config = {
     },
 
     jasmineNodeOpts: {
-        // If true, print colors to the terminal.
         showColors: true,
-        // Default time to wait in ms before a test fails.
         defaultTimeoutInterval: 30000,
+        print: function() {}
+    },
+    onPrepare: function() {
+        jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
     },
 };