ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From akuznet...@apache.org
Subject ignite git commit: IGNITE-7064 Web Console: Implemented basic E2E tests.
Date Thu, 25 Jan 2018 08:49:41 GMT
Repository: ignite
Updated Branches:
  refs/heads/master 7b29f1dbc -> ce96e4f3e


IGNITE-7064 Web Console: Implemented basic E2E tests.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/ce96e4f3
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/ce96e4f3
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/ce96e4f3

Branch: refs/heads/master
Commit: ce96e4f3ed7c151b7f3e845981d7470d707c4050
Parents: 7b29f1d
Author: alexdel <verbalab@yandex.ru>
Authored: Thu Jan 25 15:49:28 2018 +0700
Committer: Alexey Kuznetsov <akuznetsov@apache.org>
Committed: Thu Jan 25 15:49:28 2018 +0700

----------------------------------------------------------------------
 modules/web-console/.dockerignore               |   4 +
 modules/web-console/DEVNOTES.txt                |  33 ++++
 .../docker/standalone/docker-compose.yml        |  36 ----
 modules/web-console/e2e/docker-compose.yml      |  41 ++++
 modules/web-console/e2e/testcafe/Dockerfile     |  32 ++++
 modules/web-console/e2e/testcafe/envtools.js    | 186 +++++++++++++++++++
 .../e2e/testcafe/fixtures/admin-panel.js        |  60 ++++++
 .../web-console/e2e/testcafe/fixtures/auth.js   | 183 ++++++++++++++++++
 .../e2e/testcafe/fixtures/menu-smoke.js         |  50 +++++
 .../e2e/testcafe/fixtures/user-profile.js       | 113 +++++++++++
 modules/web-console/e2e/testcafe/helpers.js     |  28 +++
 modules/web-console/e2e/testcafe/package.json   |  47 +++++
 modules/web-console/e2e/testcafe/roles.js       |  59 ++++++
 modules/web-console/e2e/testcafe/testcafe.js    |  86 +++++++++
 modules/web-console/e2e/testenv/Dockerfile      |  84 +++++++++
 modules/web-console/e2e/testenv/entrypoint.sh   |  21 +++
 .../web-console/e2e/testenv/nginx/nginx.conf    |  55 ++++++
 .../e2e/testenv/nginx/web-console.conf          |  62 +++++++
 modules/web-console/frontend/package.json       |  15 +-
 .../frontend/public/stylesheets/style.scss      |   4 +
 .../frontend/test/e2e/exampe.test.js            |  42 -----
 .../frontend/views/settings/profile.tpl.pug     |   2 +-
 .../web-console/frontend/views/signin.tpl.pug   |   4 +-
 .../views/templates/validation-error.tpl.pug    |   2 +-
 parent/pom.xml                                  |   1 +
 25 files changed, 1166 insertions(+), 84 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/.dockerignore
----------------------------------------------------------------------
diff --git a/modules/web-console/.dockerignore b/modules/web-console/.dockerignore
new file mode 100644
index 0000000..bcac98f
--- /dev/null
+++ b/modules/web-console/.dockerignore
@@ -0,0 +1,4 @@
+.git
+*Dockerfile*
+*docker-compose*
+*/node_modules*
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/DEVNOTES.txt
----------------------------------------------------------------------
diff --git a/modules/web-console/DEVNOTES.txt b/modules/web-console/DEVNOTES.txt
index dfb017e..f720611 100644
--- a/modules/web-console/DEVNOTES.txt
+++ b/modules/web-console/DEVNOTES.txt
@@ -38,3 +38,36 @@ To build direct-install archive from sources run following command in Ignite pro
 "mvn clean package -pl :ignite-web-agent,:ignite-web-console -am -P web-console -DskipTests=true -DskipClientDocs -Dmaven.javadoc.skip=true"
 
 Assembled archive can be found here: `/modules/web-console/target/ignite-web-console-direct-install-*.zip`.
+
+
+End-to-end tests
+================
+E2E tests are performed with TestCafe framework - https://testcafe.devexpress.com/.
+
+To launch tests on your local machine you will need:
+1. Install and launch MongoDB.
+2. Optionally install Chromium (https://www.chromium.org/getting-involved/download-chromium or https://chromium.woolyss.com).
+   You may use any other browser, just set 'BROWSERS' constant in 'modules\web-console\e2e\testcafe.js'.
+3. In new terminal change directory to 'modules/web-console/e2e/testcafe' folder and execute: "npm install".
+4. To start test environment and tests execute: "npm run test".
+
+During developing tests you may need to run some particular tests without running all suites.
+For this case you need to run environment and test separately.
+To perform it do the following:
+1. Ensure that MongoDB is up and running and all dependencies for backend and frontend are installed.
+2. Open directory "modules/web-console/e2e/testcafe" in terminal. Install dependencies for E2E testing with "npm install" command.
+3. Execute command "npm run env". This will start backend and frontend environment.
+4. Open another terminal window and run command "node testcafe.js" in the same directory. This will run only tests without launching environment.
+
+Please refer to TestCafe documentation at https://devexpress.github.io/testcafe/documentation/test-api/test-code-structure.html#skipping-tests
+ upon how to specify which particular test should be run or skipped.
+
+You can modify the following params with environment variables:
+- DB_URL - connection string to test MongoDB. Default: mongodb://localhost/console-e2e
+- APP_URL - URL for test environment applications. Default: http://localhost:9001
+- TEAMCITY - Whether to use TeamCity reporter. Default: false (native Testcafe "spec" reporter is used)
+
+You can run tests in docker:
+1. Install docker and docker-compose.
+2. Execute in terminal: "docker-compose up --abort-on-container-exit" in directory "modules/web-console/e2e".
+3. If you need to cleanup docker container then execute "docker-compose down".

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/docker/standalone/docker-compose.yml
----------------------------------------------------------------------
diff --git a/modules/web-console/docker/standalone/docker-compose.yml b/modules/web-console/docker/standalone/docker-compose.yml
deleted file mode 100644
index c6b73d9..0000000
--- a/modules/web-console/docker/standalone/docker-compose.yml
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License.  You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-#
-
-webconsole:
-  image: apacheignite/web-console-standalone
-  ports:
-    - 80:80
-  restart: always
-  environment:
-    # Port for serving frontend API
-    - server_port=3000
-    # Cookie session secret
-    - server_sessionSecret="CHANGE ME"
-    # URL for mongodb connection
-    - mongodb_url=mongodb://127.0.0.1/console
-    # Mail connection settings. Leave empty if no needed. See also settings, https://github.com/nodemailer/nodemailer
-    - mail_service=""
-    - mail_sign=""
-    - mail_greeting=""
-    - mail_from=""
-    - mail_auth_user=""
-    - mail_auth_pass=""

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/docker-compose.yml
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/docker-compose.yml b/modules/web-console/e2e/docker-compose.yml
new file mode 100644
index 0000000..c265237
--- /dev/null
+++ b/modules/web-console/e2e/docker-compose.yml
@@ -0,0 +1,41 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+version: '2'
+services:
+  mongodb:
+    image: 'mongo:latest'
+    container_name: 'mongodb'
+
+  testenv:
+    build:
+      context: '../'
+      dockerfile: './e2e/testenv/Dockerfile'
+    environment:
+      - mongodb_url=mongodb://mongodb:27017/console-e2e
+    depends_on:
+      - mongodb
+
+  e2e:
+    build: './testcafe'
+    environment:
+      - DB_URL=mongodb://mongodb:27017/console-e2e
+      - APP_URL=http://testenv:9001/
+      - TEAMCITY=true
+    depends_on:
+      - mongodb
+      - testenv
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/Dockerfile
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/Dockerfile b/modules/web-console/e2e/testcafe/Dockerfile
new file mode 100644
index 0000000..e58cbef
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/Dockerfile
@@ -0,0 +1,32 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM testcafe/testcafe:latest
+
+USER 0
+
+RUN mkdir -p /opt/testcafe/tests
+
+WORKDIR /opt/testcafe/tests
+
+COPY . /opt/testcafe/tests
+
+RUN npm install --production && \
+ npm cache verify --force && \
+ rm -rf /tmp/*
+
+ENTRYPOINT ["node", "./testcafe.js"]
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/envtools.js
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/envtools.js b/modules/web-console/e2e/testcafe/envtools.js
new file mode 100644
index 0000000..846a33a
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/envtools.js
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const MongoClient = require('mongodb').MongoClient;
+const objectid = require('objectid');
+const { spawn } = require('child_process');
+const url = require('url');
+
+const argv = require('minimist')(process.argv.slice(2));
+const start = argv._.includes('start');
+const stop = argv._.includes('stop');
+
+const mongoUrl = process.env.DB_URL || 'mongodb://localhost/console-e2e';
+
+const insertTestUser = ({userId = '000000000000000000000001', token = 'ppw4tPI3JUOGHva8CODO' } = options = {}) => {
+    return new Promise((res, rej) => {
+        MongoClient
+            .connect(mongoUrl, function(err, db) {
+                if (err) {
+                    rej();
+                    throw err;
+                }
+
+                // add user
+                const user = {
+                    _id: objectid(userId),
+                    salt: 'ca8b49c2eacd498a0973de30c0873c166ed99fa0605981726aedcc85bee17832',
+                    hash: 'c052c87e454cd0875332719e1ce085ccd92bedb73c8f939ba45d387f724da97128280643ad4f841d929d48de802f48f4a27b909d2dc806d957d38a1a4049468ce817490038f00ac1416aaf9f8f5a5c476730b46ea22d678421cd269869d4ba9d194f73906e5d5a4fec5229459e20ebda997fb95298067126f6c15346d886d44b67def03bf3ffe484b2e4fa449985de33a0c12e4e1da4c7d71fe7af5d138433f703d8c7eeebbb3d57f1a89659010a1f1d3cd4fbc524abab07860daabb08f08a28b8bfc64ecde2ea3c103030d0d54fc24d9c02f92ee6b3aa1bcd5c70113ab9a8045faea7dd2dc59ec4f9f69fcf634232721e9fb44012f0e8c8fdf7c6bf642db6867ef8e7877123e1bc78af7604fee2e34ad0191f8b97613ea458e0fca024226b7055e08a4bdb256fabf0a203a1e5b6a6c298fb0c60308569cefba779ce1e41fb971e5d1745959caf524ab0bedafce67157922f9c505cea033f6ed28204791470d9d08d31ce7e8003df8a3a05282d4d60bfe6e2f7de06f4b18377dac0fe764ed683c9b2553e75f8280c748aa166fef6f89190b1c6d369ab86422032171e6f9686de42ac65708e63bf018a043601d85bc5c820c7ad1d51ded32e59cdaa629a3f7ae325bbc931f9f21d90c9204effdbd53721a60c8b180dd8c236133e287a47ccc9e5072eb65937
 71e435e4d5196d50d6ddb32c226651c6503387895c5ad025f69fd3',
+                    password: 'a',
+                    email: 'a@a',
+                    firstName: 'John',
+                    lastName: 'Doe',
+                    company: 'TestCompany',
+                    country: 'Canada',
+                    admin: true,
+                    token,
+                    attempts: 0,
+                    lastLogin: '2016-06-28T10:41:07.463Z',
+                    resetPasswordToken: '892rnLbEnVp1FP75Jgpi'
+                };
+                db.collection('accounts').insert(user);
+
+                // add spaces
+
+                const spaces = [
+                    {
+                        _id: objectid('000000000000000000000001'),
+                        name: 'Personal space',
+                        owner: objectid(userId),
+                        demo: false
+                    },
+                    {
+                        _id: objectid('000000000000000000000002'),
+                        name: 'Demo space',
+                        owner: objectid(userId),
+                        demo: true
+                    }
+                ];
+                db.collection('spaces').insertMany(spaces);
+
+                db.close();
+                res();
+
+            });
+    });
+};
+
+const removeData = () => {
+    return new Promise((resolve, reject) => {
+        MongoClient.connect(mongoUrl, async(err, db) => {
+            if (err)
+                return reject(err);
+
+            db.dropDatabase((err) => {
+                if (err)
+                    return reject(err);
+
+                resolve();
+            });
+        });
+    });
+};
+
+
+/**
+ * Spawns a new process using the given command.
+ * @param command {String} The command to run.
+ * @param onResolveString {String} Await string in output.
+ * @param cwd {String} Current working directory of the child process.
+ * @param env {Object} Environment key-value pairs.
+ * @return {Promise<ChildProcess>}
+ */
+const exec = (command, onResolveString, cwd, env) => {
+    return new Promise((resolve) => {
+        env = Object.assign({}, process.env, env, { FORCE_COLOR: true });
+
+        const [cmd, ...args] = command.split(' ');
+
+        const detached = process.platform !== 'win32';
+
+        const child = spawn(cmd, args, {cwd, env, detached});
+
+        if (detached) {
+            // do something when app is closing
+            process.on('exit', () => process.kill(-child.pid));
+
+            // catches ctrl+c event
+            process.on('SIGINT', () => process.kill(-child.pid));
+
+            // catches "kill pid" (for example: nodemon restart)
+            process.on('SIGUSR1', () => process.kill(-child.pid));
+            process.on('SIGUSR2', () => process.kill(-child.pid));
+
+            // catches uncaught exceptions
+            process.on('uncaughtException', () => process.kill(-child.pid));
+        }
+
+        // Pipe error messages to stdout.
+        child.stderr.on('data', (data) => {
+            process.stdout.write(data.toString());
+        });
+
+        child.stdout.on('data', (data) => {
+            process.stdout.write(data.toString());
+
+            if (data.includes(onResolveString))
+                resolve(child);
+        });
+    });
+};
+
+const startEnv = () => {
+    return new Promise(async(resolve) => {
+        const command = `${process.platform === 'win32' ? 'npm.cmd' : 'npm'} start`;
+
+        let port = 9001;
+        if (process.env.APP_URL) {
+            port = parseInt(url.parse(process.env.APP_URL).port) || 80;
+        }
+
+        const backendInstanceLaunch = exec(command, 'Start listening', '../../backend', {server_port: 3001, mongodb_url: mongoUrl});
+        const frontendInstanceLaunch = exec(command, 'Compiled successfully', '../../frontend', {BACKEND_PORT: 3001, PORT: port});
+
+        console.log('Building backend in progress...');
+        await backendInstanceLaunch;
+        console.log('Building backend done!');
+
+        console.log('Building frontend in progress...');
+        await frontendInstanceLaunch;
+        console.log('Building frontend done!');
+
+        resolve();
+    });
+};
+
+if (start) {
+    startEnv();
+
+    process.on('SIGINT', async() => {
+        await removeData();
+
+        process.exit(0);
+    });
+}
+
+if (stop) {
+    removeData();
+
+    console.log('Cleaning done...');
+}
+
+module.exports = { startEnv, removeData, insertTestUser };

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/fixtures/admin-panel.js
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/fixtures/admin-panel.js b/modules/web-console/e2e/testcafe/fixtures/admin-panel.js
new file mode 100644
index 0000000..39c596e
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/fixtures/admin-panel.js
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const { Selector } = require('testcafe');
+const { removeData, insertTestUser } = require('../envtools');
+const { signIn } = require('../roles');
+
+fixture('Checking admin panel')
+    .page `${process.env.APP_URL || 'http://localhost:9001/'}settings/admin`
+    .beforeEach(async(t) => {
+        await t.setNativeDialogHandler(() => true);
+        await removeData();
+        await insertTestUser();
+        await signIn(t);
+
+        await t.navigateTo(`${process.env.APP_URL || 'http://localhost:9001/'}settings/admin`);
+    })
+    .after(async() => {
+        await removeData();
+    });
+
+test('Testing setting notifications', async(t) => {
+    await t.click(Selector('button').withAttribute('ng-click', 'ctrl.changeUserNotifications()'));
+
+    await t
+        .expect(Selector('h4').withText(/.*Set user notifications.*/).exists)
+        .ok()
+        .click('.ace_content')
+        .pressKey('t e s t space m e s s a g e')
+        .click('#btn-submit');
+
+    await t
+        .expect(Selector('div').withText('test message').exists)
+        .ok();
+
+    await t.click(Selector('button').withAttribute('ng-click', 'ctrl.changeUserNotifications()'));
+
+    await t
+        .click('.ace_content')
+        .pressKey('ctrl+a delete')
+        .click('#btn-submit');
+
+    await t
+        .expect(Selector('div').withText('test message').exists)
+        .notOk();
+});

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/fixtures/auth.js
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/fixtures/auth.js b/modules/web-console/e2e/testcafe/fixtures/auth.js
new file mode 100644
index 0000000..d6d0226
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/fixtures/auth.js
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const { Selector, Role } = require('testcafe');
+const { signUp } = require('../roles');
+const { AngularJSSelector } = require('testcafe-angular-selectors');
+const { removeData, insertTestUser } = require('../envtools');
+
+fixture('Checking Ignite auth screen')
+    .page `${process.env.APP_URL || 'http://localhost:9001/'}`
+    .beforeEach(async(t) => {
+        await removeData();
+
+        await t.setNativeDialogHandler(() => true);
+        await t.useRole(Role.anonymous());
+    })
+    .after(async() => {
+        await removeData();
+    });
+
+test('Testing Ignite signup validation and signup success', async(t) => {
+    async function checkBtnDisabled() {
+        const btnDisabled = await t.expect(Selector('#signup').getAttribute('disabled')).ok();
+
+        const btnNotWorks =  await t
+            .click('#signup')
+            .expect(Selector('title').innerText).eql('Apache Ignite - Management Tool and Configuration Wizard – Apache Ignite Web Console');
+
+        return btnDisabled && btnNotWorks;
+    }
+
+    await t.click(Selector('a').withText('Sign Up'));
+
+    await t
+        .click(Selector('#signup_email'))
+        .typeText(Selector('#signup_email'), 'test@test.com');
+    await checkBtnDisabled();
+
+    await t
+        .typeText(AngularJSSelector.byModel('ui.password'), 'qwerty')
+        .typeText(AngularJSSelector.byModel('ui_exclude.confirm'), 'qwerty');
+    await checkBtnDisabled();
+
+    await t
+        .typeText(AngularJSSelector.byModel('ui.firstName'), 'John')
+        .typeText(AngularJSSelector.byModel('ui.lastName'), 'Doe');
+    await checkBtnDisabled();
+
+    await t
+        .typeText(AngularJSSelector.byModel('ui.company'), 'DevNull LTD');
+    await checkBtnDisabled();
+
+    await t
+        .click('#country')
+        .click(Selector('span').withText('Brazil'));
+
+    // checking passwords confirm dismatch
+    await t
+        .click(AngularJSSelector.byModel('ui_exclude.confirm'))
+        .pressKey('ctrl+a delete')
+        .typeText(AngularJSSelector.byModel('ui_exclude.confirm'), 'ytrewq');
+    await checkBtnDisabled();
+    await t
+        .click(AngularJSSelector.byModel('ui_exclude.confirm'))
+        .pressKey('ctrl+a delete')
+        .typeText(AngularJSSelector.byModel('ui_exclude.confirm'), 'qwerty');
+
+    await t.click('#signup')
+        .expect(Selector('title').innerText).eql('Basic Configuration – Apache Ignite Web Console');
+
+});
+
+test('Testing Ignite validation and successful sign in of existing user', async(t) => {
+    async function checkSignInBtnDisabled() {
+        const btnDisabled = await t.expect(await Selector('#login').getAttribute('disabled')).ok();
+        const btnNotWorks =  await t
+            .click('#login')
+            .expect(Selector('title').innerText).eql('Apache Ignite - Management Tool and Configuration Wizard – Apache Ignite Web Console');
+
+        return btnDisabled && btnNotWorks;
+    }
+
+    await insertTestUser();
+
+    // checking signin validation
+    await t
+        .typeText(AngularJSSelector.byModel('ui.email'), 'test@test.com');
+    await checkSignInBtnDisabled();
+
+    await t
+        .typeText(AngularJSSelector.byModel('ui.password'), 'b')
+        .click('#login');
+    await t.expect(Selector('#popover-validation-message').withText('Invalid email or password').exists).ok();
+
+    await t
+        .click(AngularJSSelector.byModel('ui.email'))
+        .pressKey('ctrl+a delete')
+        .typeText(AngularJSSelector.byModel('ui.email'), 'testtest.com');
+    await checkSignInBtnDisabled();
+
+    // checking regular sigin in
+    await t
+        .click(AngularJSSelector.byModel('ui.email'))
+        .pressKey('ctrl+a delete')
+        .typeText(AngularJSSelector.byModel('ui.email'), 'a@a')
+        .click(AngularJSSelector.byModel('ui.password'))
+        .pressKey('ctrl+a delete')
+        .typeText(AngularJSSelector.byModel('ui.password'), 'a')
+        .click('#login')
+        .expect(Selector('title').innerText).eql('Basic Configuration – Apache Ignite Web Console');
+
+});
+
+test('Forbid Ignite signing up of already existing user', async(t) => {
+    await insertTestUser();
+
+    await t.click(Selector('a').withText('Sign Up'));
+
+    await t
+        .click(Selector('#signup_email'))
+        .typeText(Selector('#signup_email'), 'a@a')
+        .typeText(AngularJSSelector.byModel('ui.password'), 'a')
+        .typeText(AngularJSSelector.byModel('ui_exclude.confirm'), 'a')
+        .typeText(AngularJSSelector.byModel('ui.firstName'), 'John')
+        .typeText(AngularJSSelector.byModel('ui.lastName'), 'Doe')
+        .typeText(AngularJSSelector.byModel('ui.company'), 'DevNull LTD')
+        .click('#country')
+        .click(Selector('span').withText('Brazil'))
+        .click('#signup');
+
+    await t.expect(Selector('#popover-validation-message').withText('A user with the given username is already registered.').exists).ok();
+
+});
+
+test('Test Ignite password reset', async(t) => {
+    await t.click(Selector('#password-forgot-signin'));
+
+    // testing incorrect email
+    await t
+        .typeText('#forgot_email', 'testtest')
+        .expect(await Selector('button').withText('Send it to me').getAttribute('disabled')).ok();
+
+    // testing handling unknown email password reset
+    await t
+        .click(Selector('#forgot_email'))
+        .pressKey('ctrl+a delete')
+        .typeText(Selector('#forgot_email'), 'nonexisting@mail.com')
+        .click(Selector('button').withText('Send it to me'));
+
+    await t.expect(Selector('#popover-validation-message').withText('Account with that email address does not exists!').exists).ok();
+
+    // testing regular password reset
+    await t
+        .click(Selector('#forgot_email'))
+        .pressKey('ctrl+a delete')
+        .typeText(Selector('#forgot_email'), 'a@a')
+        .click(Selector('button').withText('Send it to me'));
+
+    await t.expect(Selector('#popover-validation-message').withText('Account with that email address does not exists!').exists).notOk();
+});
+
+test('Testing Ignite loguout', async(t) => {
+    await signUp(t);
+
+    await t.click(Selector('div').withAttribute('bs-dropdown', 'userbar.items'));
+    await t
+        .click(Selector('a').withAttribute('ui-sref', 'logout'))
+        .expect(Selector('title').innerText).eql('Apache Ignite - Management Tool and Configuration Wizard – Apache Ignite Web Console');
+});

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/fixtures/menu-smoke.js
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/fixtures/menu-smoke.js b/modules/web-console/e2e/testcafe/fixtures/menu-smoke.js
new file mode 100644
index 0000000..9ae79b4
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/fixtures/menu-smoke.js
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const { Selector } = require('testcafe');
+const { removeData } = require('../envtools');
+const { signUp } = require('../roles');
+
+fixture('Checking Ingite main menu')
+    .page `${process.env.APP_URL || 'http://localhost:9001/'}`
+    .beforeEach(async(t) => {
+        await t.setNativeDialogHandler(() => true);
+        await removeData();
+        await signUp(t);
+    })
+    .after(async() => {
+        await removeData();
+    });
+
+test('Ingite main menu smoke test', async(t) => {
+
+    await t
+        .click(Selector('a').withAttribute('ui-sref', 'base.configuration.tabs'))
+        .expect(Selector('title').innerText)
+        .eql('Basic Configuration – Apache Ignite Web Console');
+
+    await t
+        .click(Selector('a').withText('Queries'))
+        .expect(Selector('h4').withText('New query notebook').exists)
+        .ok()
+        .typeText('#create-notebook', 'Test query')
+        .click('#copy-btn-confirm');
+
+    await t
+        .expect(Selector('span').withText('Connection to Ignite Web Agent is not established').exists)
+        .ok();
+});

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/fixtures/user-profile.js
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/fixtures/user-profile.js b/modules/web-console/e2e/testcafe/fixtures/user-profile.js
new file mode 100644
index 0000000..133b37e
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/fixtures/user-profile.js
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const { Selector } = require('testcafe');
+const { removeData, insertTestUser } = require('../envtools');
+const { signIn, signUp } = require('../roles');
+
+fixture('Checking user profile')
+    .page `${process.env.APP_URL || 'http://localhost:9001/'}settings/profile`
+    .beforeEach(async(t) => {
+        await t.setNativeDialogHandler(() => true);
+        await removeData();
+        await insertTestUser();
+        await signIn(t);
+
+        await t.navigateTo(`${process.env.APP_URL || 'http://localhost:9001/'}settings/profile`);
+    })
+    .after(async() => {
+        await removeData();
+    });
+
+test('Testing user data change', async(t) => {
+
+    const newUserData = {
+        firstName: {
+            selector: '#profile-firstname',
+            value: 'Richard'
+        },
+        lastName: {
+            selector: '#profile-lastname',
+            value: 'Roe'
+        },
+        email: {
+            selector: '#profile-email',
+            value: 'r.roe@mail.com'
+        },
+        company: {
+            selector: '#profile-company',
+            value: 'New Company'
+        },
+        country: {
+            selector: '#profile-country',
+            value: 'Israel'
+        }
+    };
+
+    ['firstName', 'lastName', 'email', 'company'].forEach(async(item) => {
+        await t
+           .click(newUserData[item].selector)
+           .pressKey('ctrl+a delete')
+           .typeText(newUserData[item].selector, newUserData[item].value);
+    });
+
+    await t
+        .click(newUserData.country.selector)
+        .click(Selector('span').withText(newUserData.country.value))
+        .click(Selector('a').withText('Save'));
+
+    await t.navigateTo(`${process.env.APP_URL || 'http://localhost:9001/'}settings/profile`);
+
+    ['firstName', 'lastName', 'email', 'company'].forEach(async(item) => {
+        await t
+            .expect(await Selector(newUserData[item].selector).getAttribute('value'))
+            .eql(newUserData[item].value);
+    });
+
+    await t
+        .expect(Selector(newUserData.country.selector).innerText)
+        .eql(newUserData.country.value);
+});
+
+test('Testing secure token change', async(t) => {
+    await t.click(Selector('a').withAttribute('ng-click', 'toggleToken()'));
+
+    const currentToken = await Selector('#current-security-token').innerText;
+
+    await t
+        .click(Selector('i').withAttribute('ng-click', 'generateToken()'))
+        .expect(Selector('p').withText('Are you sure you want to change security token?').exists)
+        .ok()
+        .click('#confirm-btn-ok', {timeout: 5000});
+
+    await t
+        .expect(await Selector('#current-security-token').innerText)
+        .notEql(currentToken);
+});
+
+test('Testing password change', async(t) => {
+    await t.click(Selector('a').withAttribute('ng-click', 'togglePassword()'));
+
+    await t
+        .typeText('#profile_password', 'newPass')
+        .typeText('#profile_confirm', 'newPass')
+        .click(Selector('a').withText('Save'));
+
+    await t
+        .expect(Selector('span').withText('Profile saved.').exists)
+        .ok();
+});

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/helpers.js
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/helpers.js b/modules/web-console/e2e/testcafe/helpers.js
new file mode 100644
index 0000000..8ab4d47
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/helpers.js
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const { ClientFunction } = require('testcafe');
+
+const mouseenterTrigger = ClientFunction((selector = '') => {
+    return new Promise((resolve) => {
+        window.jQuery(selector).mouseenter();
+
+        resolve();
+    });
+});
+
+module.exports = { mouseenterTrigger };

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/package.json b/modules/web-console/e2e/testcafe/package.json
new file mode 100644
index 0000000..0c8ea90
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/package.json
@@ -0,0 +1,47 @@
+{
+  "name": "ignite-web-console-e2e-tests",
+  "version": "1.0.0",
+  "description": "E2E tests for Apache Ignite Web console",
+  "private": true,
+  "scripts": {
+    "env": "node envtools.js start",
+    "test": "node testcafe.js --env=true"
+  },
+  "author": "",
+  "contributors": [
+    {
+      "name": "",
+      "email": ""
+    }
+  ],
+  "license": "Apache-2.0",
+  "keywords": [
+    "Apache Ignite Web console"
+  ],
+  "homepage": "https://ignite.apache.org/",
+  "engines": {
+    "npm": "3.x.x",
+    "node": "4.x.x"
+  },
+  "os": [
+    "darwin",
+    "linux",
+    "win32"
+  ],
+  "dependencies": {
+    "app-module-path": "2.2.0",
+    "cross-env": "5.1.1",
+    "glob": "7.1.2",
+    "minimist": "1.2.0",
+    "mongodb": "2.2.33",
+    "node-cmd": "3.0.0",
+    "objectid": "3.2.1",
+    "path": "0.12.7",
+    "sinon": "2.3.8",
+    "testcafe": "0.18.5",
+    "testcafe-angular-selectors": "0.3.0",
+    "testcafe-reporter-teamcity": "1.0.9",
+    "type-detect": "4.0.3",
+    "util": "0.10.3"
+  }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/roles.js
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/roles.js b/modules/web-console/e2e/testcafe/roles.js
new file mode 100644
index 0000000..bfa9f13
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/roles.js
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const path = require('path');
+const { Selector } = require('testcafe');
+const { AngularJSSelector } = require('testcafe-angular-selectors');
+
+const igniteSignUp = async(t) => {
+    await t.navigateTo(`${process.env.APP_URL || 'http://localhost:9001/'}`);
+
+    await t.click(Selector('a').withText('Sign Up'));
+
+    await t
+        .click(Selector('#signup_email'))
+        .typeText(Selector('#signup_email'), 'a@a')
+        .typeText(AngularJSSelector.byModel('ui.password'), 'a')
+        .typeText(AngularJSSelector.byModel('ui_exclude.confirm'), 'a')
+        .typeText(AngularJSSelector.byModel('ui.firstName'), 'John')
+        .typeText(AngularJSSelector.byModel('ui.lastName'), 'Doe')
+        .typeText(AngularJSSelector.byModel('ui.company'), 'DevNull LTD')
+        .click('#country')
+        .click(Selector('span').withText('Brazil'))
+        .click('#signup');
+
+    // close modal window
+    await t.click('.modal-header button.close');
+};
+
+
+const igniteSignIn = async(t) => {
+    await t.navigateTo(`${process.env.APP_URL || 'http://localhost:9001/'}`);
+
+    await t
+        .typeText(AngularJSSelector.byModel('ui.email'), 'a@a')
+        .typeText(AngularJSSelector.byModel('ui.password'), 'a')
+        .click('#login');
+
+    // close modal window
+    await t.click('.modal-header button.close');
+};
+
+const signIn = process.env.IGNITE_MODULES ? require(path.join(process.env.IGNITE_MODULES, 'e2e/testcafe/roles.js')).igniteSignIn : igniteSignIn;
+const signUp = process.env.IGNITE_MODULES ? require(path.join(process.env.IGNITE_MODULES, 'e2e/testcafe/roles.js')).igniteSignUp : igniteSignUp;
+
+module.exports = { signUp, signIn };

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testcafe/testcafe.js
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testcafe/testcafe.js b/modules/web-console/e2e/testcafe/testcafe.js
new file mode 100644
index 0000000..597d29a
--- /dev/null
+++ b/modules/web-console/e2e/testcafe/testcafe.js
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+const glob = require('glob');
+const path = require('path');
+
+require('app-module-path').addPath(path.join(__dirname, 'node_modules'));
+require('app-module-path').addPath(__dirname);
+
+const argv = require('minimist')(process.argv.slice(2));
+const envEnabled = argv.env;
+
+const { startEnv, removeData } = require('./envtools');
+
+const createTestCafe = require('testcafe');
+
+// See all supported browsers at http://devexpress.github.io/testcafe/documentation/using-testcafe/common-concepts/browsers/browser-support.html#locally-installed-browsers
+const BROWSERS = ['chromium:headless --no-sandbox']; // For example: ['chrome', 'firefox'];
+
+let testcafe = null;
+
+const resolveFixturesPaths = () => {
+    let fixturesPaths = glob.sync('./fixtures/*.js');
+
+    if (process.env.IGNITE_MODULES) {
+        const igniteModulesTestcafe = path.join(process.env.IGNITE_MODULES, 'e2e/testcafe');
+        const additionalFixturesPaths = glob.sync(path.join(igniteModulesTestcafe, 'fixtures', '*.js'));
+        const relativePaths = new Set(additionalFixturesPaths.map((fixturePath) => path.relative(igniteModulesTestcafe, fixturePath)));
+
+        fixturesPaths = fixturesPaths.filter((fixturePath) => !relativePaths.has(path.relative(process.cwd(), fixturePath))).concat(additionalFixturesPaths);
+    }
+
+    return fixturesPaths;
+};
+
+createTestCafe('localhost', 1337, 1338)
+    .then(async(tc) => {
+        try {
+            if (envEnabled)
+                await startEnv();
+
+            await removeData();
+
+            testcafe = tc;
+
+            const runner = testcafe.createRunner();
+            const reporter = process.env.TEAMCITY ? 'teamcity' : 'spec';
+
+            console.log('Start E2E testing!');
+
+            return runner
+                .src(resolveFixturesPaths())
+                .browsers(BROWSERS)
+                .reporter(reporter)
+                .run({ skipJsErrors: true });
+        } catch (err) {
+            console.log(err);
+
+            process.exit(1);
+        }
+    })
+    .then(async(failedCount) => {
+        console.log('Cleaning after tests...');
+
+        testcafe.close();
+
+        if (envEnabled)
+            await removeData();
+
+        console.log('Tests failed: ' + failedCount);
+
+        process.exit(0);
+    });

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testenv/Dockerfile
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testenv/Dockerfile b/modules/web-console/e2e/testenv/Dockerfile
new file mode 100644
index 0000000..5a8f24b
--- /dev/null
+++ b/modules/web-console/e2e/testenv/Dockerfile
@@ -0,0 +1,84 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM ubuntu:14.04
+
+ENV NPM_CONFIG_LOGLEVEL info
+ENV NODE_VERSION 8.6.0
+
+# Before package list update.
+RUN set -ex  && \
+      for key in \
+        9554F04D7259F04124DE6B476D5A82AC7E37093B \
+        94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \
+        FD3A5288F042B6850C66B31F09FE44734EB7990E \
+        71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \
+        DD8F2338BAE7501E3DD5AC78C273792F7D83545D \
+        B9AE9905FFD7803F25714661B63B535A4C206CA9 \
+        C4F0DFFF4E8C1A8236409D08E73BC641CC11F4C8 \
+        56730D5401028683275BD23C23EFEFE93C4CFFFE \
+      ; do \
+        gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key" || \
+        gpg --keyserver pgp.mit.edu --recv-keys "$key" || \
+        gpg --keyserver keyserver.pgp.com --recv-keys "$key" ; \
+    done
+
+# Update package list & install.
+RUN apt-get update && \
+    apt-get install -y nginx-light curl xz-utils git dos2unix
+
+# Install Node JS.
+RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz"  && \
+  curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" && \
+  gpg --batch --decrypt --output SHASUMS256.txt SHASUMS256.txt.asc && \
+  grep " node-v$NODE_VERSION-linux-x64.tar.xz\$" SHASUMS256.txt | sha256sum -c - && \
+  tar -xJf "node-v$NODE_VERSION-linux-x64.tar.xz" -C /usr/local --strip-components=1 && \
+  rm "node-v$NODE_VERSION-linux-x64.tar.xz" SHASUMS256.txt.asc SHASUMS256.txt
+
+# Install global node packages.
+RUN npm install -g pm2
+
+# Install frontend & backend apps.
+RUN mkdir -p /opt/web-console
+
+# Copy source.
+WORKDIR /opt/web-console
+COPY frontend ./frontend
+COPY backend ./backend
+
+
+# Install node modules.
+RUN cd /opt/web-console/frontend && npm install --no-optional --prod && npm run build
+RUN cd /opt/web-console/backend && npm install --only=production --no-optional
+
+# Returns to base path.
+WORKDIR /opt/web-console
+
+# Copy nginx config.
+COPY ./e2e/testenv/nginx/nginx.conf /etc/nginx/nginx.conf
+COPY ./e2e/testenv/nginx/web-console.conf /etc/nginx/web-console.conf
+
+# Setup entrypoint.
+COPY ./e2e/testenv/entrypoint.sh .
+RUN chmod 755 /opt/web-console/entrypoint.sh && dos2unix /opt/web-console/entrypoint.sh
+
+# Clean up.
+RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
+
+EXPOSE 9001
+
+ENTRYPOINT ["/opt/web-console/entrypoint.sh"]

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testenv/entrypoint.sh
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testenv/entrypoint.sh b/modules/web-console/e2e/testenv/entrypoint.sh
new file mode 100644
index 0000000..f6107a4
--- /dev/null
+++ b/modules/web-console/e2e/testenv/entrypoint.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+service nginx start
+
+cd /opt/web-console/backend && pm2 start ./index.js --no-daemon

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testenv/nginx/nginx.conf
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testenv/nginx/nginx.conf b/modules/web-console/e2e/testenv/nginx/nginx.conf
new file mode 100644
index 0000000..dbc79d7
--- /dev/null
+++ b/modules/web-console/e2e/testenv/nginx/nginx.conf
@@ -0,0 +1,55 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+user  www-data;
+worker_processes  1;
+
+error_log  /var/log/nginx/error.log  warn;
+pid        /var/run/nginx.pid;
+
+events {
+    worker_connections  128;
+}
+
+http {
+    server_tokens off;
+    sendfile            on;
+    tcp_nopush          on;
+
+    keepalive_timeout   60;
+    tcp_nodelay         on;
+
+    client_max_body_size 100m;
+
+    #access log
+    log_format main '$http_host $remote_addr - $remote_user [$time_local] '
+    '"$request" $status $bytes_sent '
+    '"$http_referer" "$http_user_agent" '
+    '"$gzip_ratio"';
+
+    include /etc/nginx/mime.types;
+    default_type  application/octet-stream;
+    gzip on;
+    gzip_disable "msie6";
+    gzip_types text/plain text/css text/xml text/javascript application/json application/x-javascript application/xml application/xml+rss application/javascript;
+    gzip_vary on;
+    gzip_comp_level 5;
+
+    access_log  /var/log/nginx/access.log  main;
+    #conf.d
+    include web-console.conf ;
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/e2e/testenv/nginx/web-console.conf
----------------------------------------------------------------------
diff --git a/modules/web-console/e2e/testenv/nginx/web-console.conf b/modules/web-console/e2e/testenv/nginx/web-console.conf
new file mode 100644
index 0000000..c57c0d4
--- /dev/null
+++ b/modules/web-console/e2e/testenv/nginx/web-console.conf
@@ -0,0 +1,62 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+upstream backend-api {
+  server localhost:3000;
+}
+
+server {
+  listen 9001;
+  server_name _;
+
+  set $ignite_console_dir /opt/web-console/frontend/build;
+
+  root $ignite_console_dir;
+
+  error_page 500 502 503 504 /50x.html;
+
+  location / {
+    try_files $uri /index.html = 404;
+  }
+
+  location /api/v1 {
+    proxy_set_header Host $http_host;
+    proxy_pass http://backend-api;
+  }
+
+  location /socket.io {
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection "upgrade";
+    proxy_http_version 1.1;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header Host $host;
+    proxy_pass http://backend-api;
+  }
+
+  location /agents {
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection "upgrade";
+    proxy_http_version 1.1;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header Host $host;
+    proxy_pass http://backend-api;
+  }
+
+  location = /50x.html {
+    root $ignite_console_dir/error_page;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/frontend/package.json
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/package.json b/modules/web-console/frontend/package.json
index 7f7671a..c30f80c 100644
--- a/modules/web-console/frontend/package.json
+++ b/modules/web-console/frontend/package.json
@@ -8,6 +8,8 @@
     "dev": "npm start",
     "build": "webpack --config ./webpack/webpack.prod.babel.js",
     "test": "karma start ./test/karma.conf.js",
+    "enve2e": "node ./test/e2e/envtools.js start",
+    "teste2e": "node ./test/e2e/testcafe.js --env=true",
     "eslint": "eslint --format node_modules/eslint-friendly-formatter app/ controllers/ ignite_modules/ -- --eff-by-issue"
   },
   "author": "",
@@ -74,7 +76,6 @@
     "file-loader": "0.11.2",
     "file-saver": "1.3.3",
     "font-awesome": "4.7.0",
-    "glob": "7.1.2",
     "html-loader": "0.4.5",
     "html-webpack-plugin": "2.29.0",
     "jquery": "3.2.1",
@@ -104,6 +105,9 @@
   },
   "devDependencies": {
     "chai": "4.1.0",
+    "copy-dir": "^0.3.0",
+    "fs-extra": "^4.0.2",
+    "glob": "^7.1.2",
     "jasmine-core": "2.6.4",
     "karma": "1.7.0",
     "karma-babel-preprocessor": "6.0.1",
@@ -113,10 +117,17 @@
     "karma-phantomjs-launcher": "1.0.4",
     "karma-teamcity-reporter": "1.0.0",
     "karma-webpack": "2.0.4",
+    "minimist": "^1.2.0",
     "mocha": "3.4.2",
     "mocha-teamcity-reporter": "1.1.1",
+    "mongodb": "2.2.33",
+    "node-cmd": "3.0.0",
+    "objectid": "3.2.1",
     "phantomjs-prebuilt": "2.1.14",
     "sinon": "2.3.8",
-    "type-detect": "4.0.3"
+    "testcafe": "0.18.5",
+    "testcafe-angular-selectors": "0.3.0",
+    "type-detect": "4.0.3",
+    "util": "0.10.3"
   }
 }

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/frontend/public/stylesheets/style.scss
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/public/stylesheets/style.scss b/modules/web-console/frontend/public/stylesheets/style.scss
index baa49f8..d94f398 100644
--- a/modules/web-console/frontend/public/stylesheets/style.scss
+++ b/modules/web-console/frontend/public/stylesheets/style.scss
@@ -1064,6 +1064,10 @@ button.form-control {
 
 .popover-content {
     padding: 5px;
+
+    button {
+        margin-left: 5px;
+    }
 }
 
 .popover:focus {

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/frontend/test/e2e/exampe.test.js
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/test/e2e/exampe.test.js b/modules/web-console/frontend/test/e2e/exampe.test.js
deleted file mode 100644
index 00788bb..0000000
--- a/modules/web-console/frontend/test/e2e/exampe.test.js
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { suite, test, setup } from 'mocha';
-
-suite('ExampleTestSuite', () => {
-    setup(() => {
-        // browser.get('http://localhost:9000/');
-    });
-
-    test('initially has a greeting', (done) => {
-        done();
-
-        // element(by.model('ui.email')).sendKeys('jhon@doe.com');
-    });
-
-    test('initially has a greeting', (done) => {
-        done();
-
-        // element(by.model('ui.email')).sendKeys('jhon@doe.com');
-    });
-
-    test('initially has a greeting', (done) => {
-        done();
-
-        // element(by.model('ui.email')).sendKeys('jhon@doe.com');
-    });
-});

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/frontend/views/settings/profile.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/settings/profile.tpl.pug b/modules/web-console/frontend/views/settings/profile.tpl.pug
index 0dc35e4..8f19b16 100644
--- a/modules/web-console/frontend/views/settings/profile.tpl.pug
+++ b/modules/web-console/frontend/views/settings/profile.tpl.pug
@@ -60,7 +60,7 @@ mixin lbl-not-required(txt)
                             a(ng-click='toggleToken()') {{expandedToken ? 'Cancel security token changing...' : 'Show security token...'}}
                         div(ng-if='expandedToken')
                             +lbl('Security token:')
-                            label {{user.token || 'No security token. Regenerate please.'}}
+                            label#current-security-token {{user.token || 'No security token. Regenerate please.'}}
                             i.tipLabel.fa.fa-refresh(ng-click='generateToken()' bs-tooltip='' data-title='Generate random security token')
                             i.tipLabel.fa.fa-clipboard(ignite-copy-to-clipboard='{{user.token}}' bs-tooltip='' data-title='Copy security token to clipboard')
                             i.tipLabel.icon-help(ng-if=lines bs-tooltip='' data-title='The security token is used for authorization of web agent')

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/frontend/views/signin.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/signin.tpl.pug b/modules/web-console/frontend/views/signin.tpl.pug
index a053b86..29da8b1 100644
--- a/modules/web-console/frontend/views/signin.tpl.pug
+++ b/modules/web-console/frontend/views/signin.tpl.pug
@@ -80,7 +80,7 @@ web-console-header
                                         | I agree to the #[a(ui-sref='{{::terms.termsState}}' target='_blank') terms and conditions]
                         .col-xs-12.col-md-11
                             .login-footer(ng-show='action == "signup"')
-                                a.labelField(ng-click='action = "password/forgot"' ignite-on-click-focus='signin_email') Forgot password?
+                                a#password-forgot-signup.labelField(ng-click='action = "password/forgot"' ignite-on-click-focus='signin_email') Forgot password?
                                 a.labelLogin(ng-click='action = "signin"' ignite-on-click-focus='signin_email') Sign In
                                 button#signup.btn.btn-primary(ng-click='auth(action, ui)' ng-disabled='form.$invalid') Sign Up
                         .col-xs-12.col-md-11
@@ -89,7 +89,7 @@ web-console-header
                                 button#forgot.btn.btn-primary(ng-click='forgotPassword(ui)' ng-disabled='form.$invalid') Send it to me
                         .col-xs-12.col-md-11
                             .login-footer(ng-show='action == "signin"')
-                                a.labelField(ng-click='action = "password/forgot"' ignite-on-click-focus='signin_email') Forgot password?
+                                a#password-forgot-signin.labelField(ng-click='action = "password/forgot"' ignite-on-click-focus='signin_email') Forgot password?
                                 a.labelLogin(ng-click='action = "signup"' ignite-on-click-focus='first_name') Sign Up
                                 button#login.btn.btn-primary(ng-click='auth(action, ui)' ng-disabled='form.$invalid') Sign In
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/modules/web-console/frontend/views/templates/validation-error.tpl.pug
----------------------------------------------------------------------
diff --git a/modules/web-console/frontend/views/templates/validation-error.tpl.pug b/modules/web-console/frontend/views/templates/validation-error.tpl.pug
index 13deb9b..2e0e423 100644
--- a/modules/web-console/frontend/views/templates/validation-error.tpl.pug
+++ b/modules/web-console/frontend/views/templates/validation-error.tpl.pug
@@ -20,6 +20,6 @@
         table
             tr
                 td
-                    label {{content}}&nbsp&nbsp
+                    label#popover-validation-message {{content}}
                 td
                     button.close(id='popover-btn-close' ng-click='$hide()') &times;

http://git-wip-us.apache.org/repos/asf/ignite/blob/ce96e4f3/parent/pom.xml
----------------------------------------------------------------------
diff --git a/parent/pom.xml b/parent/pom.xml
index 001ce64..cbfe879 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -920,6 +920,7 @@
                                         <exclude>**/.dockerignore</exclude>
                                         <exclude>**/backend/config/settings.json.sample</exclude>
                                         <exclude>**/backend/node_modules/**</exclude>
+                                        <exclude>**/e2e/testcafe/node_modules/**</exclude>
                                         <exclude>**/frontend/build/**</exclude>
                                         <exclude>**/frontend/public/images/**/*.png</exclude>
                                         <exclude>**/frontend/public/images/**/*.svg</exclude>


Mime
View raw message