From 3577169e209596a8e4a95d1c41d2dde099a3945f Mon Sep 17 00:00:00 2001 From: Nick Bogdanov Date: Wed, 8 Jul 2015 15:46:09 +0300 Subject: [PATCH] Intern-based functional tests This commit makes it possible to execute UI functional tests either based on Casper or on Intern/Selenium Change-Id: I21d342093f142a1bbe7b8ae4f2c96fad1c125a0b --- nailgun/gulpfile.js | 12 +- nailgun/package.json | 2 +- .../static/tests/functional/pages/cluster.js | 103 ++++++++++++++ .../static/tests/functional/pages/clusters.js | 78 +++++++++++ .../static/tests/functional/pages/common.js | 132 ++++++++++++++++++ .../static/tests/functional/pages/login.js | 84 +++++++++++ .../static/tests/functional/pages/welcome.js | 47 +++++++ .../tests/functional/test_cluster_page.js | 129 +++++++++++++++++ .../tests/functional/test_clusters_page.js | 60 ++++++++ .../tests/functional/test_login_logout.js | 59 ++++++++ .../tests/functional/test_welcome_page.js | 49 +++++++ nailgun/static/tests/helpers.js | 47 +++++++ nailgun/static/tests/intern.js | 5 +- .../test_cluster_stop_reset_deployment.js | 4 +- run_tests.sh | 75 +++++++++- 15 files changed, 877 insertions(+), 9 deletions(-) create mode 100644 nailgun/static/tests/functional/pages/cluster.js create mode 100644 nailgun/static/tests/functional/pages/clusters.js create mode 100644 nailgun/static/tests/functional/pages/common.js create mode 100644 nailgun/static/tests/functional/pages/login.js create mode 100644 nailgun/static/tests/functional/pages/welcome.js create mode 100644 nailgun/static/tests/functional/test_cluster_page.js create mode 100644 nailgun/static/tests/functional/test_clusters_page.js create mode 100644 nailgun/static/tests/functional/test_login_logout.js create mode 100644 nailgun/static/tests/functional/test_welcome_page.js create mode 100644 nailgun/static/tests/helpers.js diff --git a/nailgun/gulpfile.js b/nailgun/gulpfile.js index 64c259e416..9ccacf5708 100644 --- a/nailgun/gulpfile.js +++ b/nailgun/gulpfile.js @@ -158,7 +158,7 @@ function runIntern(params) { return function() { var baseDir = 'static'; var runner = './node_modules/.bin/intern-runner'; - var browser = params.browser || argv.browser || 'phantomjs'; + var browser = params.browser || argv.browser || 'firefox'; var options = [['config', 'tests/intern-' + browser + '.js']]; var suiteOptions = []; ['suites', 'functionalSuites'].forEach(function(suiteType) { @@ -181,7 +181,8 @@ function runIntern(params) { }; } -gulp.task('intern:unit', runIntern({suites: argv.suites || 'static/tests/unit/**/*.js'})); +gulp.task('intern:unit', runIntern({suites: argv.suites || 'static/tests/unit/**/*.js', browser: 'phantomjs'})); +gulp.task('intern:functional', runIntern({functionalSuites: argv.suites || 'static/tests/functional/**/test_*.js'})); gulp.task('unit-tests', function(cb) { runSequence('selenium', 'intern:unit', function(err) { @@ -190,6 +191,13 @@ gulp.task('unit-tests', function(cb) { }); }); +gulp.task('functional-tests', function(cb) { + runSequence('selenium', 'intern:functional', function(err) { + shutdownSelenium(); + cb(err); + }); +}); + gulp.task('jison', function() { return gulp.src('static/expression/parser.jison') .pipe(jison({moduleType: 'js'})) diff --git a/nailgun/package.json b/nailgun/package.json index cedc6621f3..a91ee4c782 100644 --- a/nailgun/package.json +++ b/nailgun/package.json @@ -35,6 +35,6 @@ "rimraf": "~2.2.8", "run-sequence": "~1.0.2", "selenium-standalone": "~4.4.0", - "uglify-js": "~2.4.16" + "uglify-js": "~2.4.21" } } diff --git a/nailgun/static/tests/functional/pages/cluster.js b/nailgun/static/tests/functional/pages/cluster.js new file mode 100644 index 0000000000..4852024852 --- /dev/null +++ b/nailgun/static/tests/functional/pages/cluster.js @@ -0,0 +1,103 @@ +/* + * Copyright 2015 Mirantis, Inc. + * + * Licensed 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. + **/ + +define(['underscore', + '../../helpers'], function(_, Helpers) { + 'use strict'; + function ClusterPage(remote) { + this.remote = remote; + } + + ClusterPage.prototype = { + constructor: ClusterPage, + goToTab: function(tabName) { + var that = this; + return this.remote + .then(function() { + return Helpers.clickLinkByText( + that.remote, + '.tabs-box .tabs a', + tabName); + }); + }, + removeCluster: function() { + var that = this; + return this.remote + .then( + function() { + return this.parent + .setFindTimeout(2000) + .then(function() { + return that.goToTab('Actions'); + }) + .findByCssSelector('button.delete-environment-btn') + .click() + .end() + .setFindTimeout(2000) + .findByCssSelector('div.modal-content') + .findByCssSelector('button.remove-cluster-btn') + .click() + .end() + .setFindTimeout(2000) + .waitForDeletedByCssSelector('div.modal-content'); + } + ); + }, + checkNodeRoles: function(assignRoles) { + return this.remote + .setFindTimeout(2000) + .findAllByCssSelector('div.role-panel label') + .then(function(roles) { + return roles.reduce( + function(result, role) { + return role + .getVisibleText() + .then(function(label) { + var index = assignRoles.indexOf(label.substr(1)); + if (index >= 0) { + role.click(); + assignRoles.splice(index, 1); + return assignRoles.length == 0; + } + }); + }, + false + ); + }); + }, + checkNodes: function(amount) { + var that = this; + return this.remote + .setFindTimeout(2000) + .then(function() { + return _.range(amount).reduce( + function(result, index) { + return that.remote + .setFindTimeout(1000) + .findAllByCssSelector('.node.discover > label') + .then(function(nodes) { + return nodes[index].click(); + }) + .catch(function() { + throw new Error('Failed to add ' + amount + ' nodes to the cluster'); + }); + }, + true); + }); + } + }; + return ClusterPage; +}); diff --git a/nailgun/static/tests/functional/pages/clusters.js b/nailgun/static/tests/functional/pages/clusters.js new file mode 100644 index 0000000000..ec7408a354 --- /dev/null +++ b/nailgun/static/tests/functional/pages/clusters.js @@ -0,0 +1,78 @@ +/* + * Copyright 2015 Mirantis, Inc. + * + * Licensed 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. + **/ + +define([], function() { + 'use strict'; + function ClustersPage(remote) { + this.remote = remote; + } + + ClustersPage.prototype = { + constructor: ClustersPage, + createCluster: function(clusterName) { + return this.remote + .setFindTimeout(1000) + .findByClassName('create-cluster') + .click() + .end() + .setFindTimeout(2000) + .findByCssSelector('div.modal-content') + .findByName('name') + .clearValue() + .type(clusterName) + .pressKeys('\uE007') + .pressKeys('\uE007') + .pressKeys('\uE007') + .pressKeys('\uE007') + .pressKeys('\uE007') + .pressKeys('\uE007') + .pressKeys('\uE007') + .end() + .setFindTimeout(4000) + .waitForDeletedByCssSelector('div.modal-content') + .end(); + }, + clusterSelector: '.clusterbox div.name', + goToEnvironment: function(clusterName) { + var that = this; + return this.remote + .setFindTimeout(5000) + .findAllByCssSelector(that.clusterSelector) + .then(function(divs) { + return divs.reduce( + function(matchFound, element) { + return element.getVisibleText().then( + function(name) { + if (name === clusterName) { + element.click(); + return true; + } + return matchFound; + } + )}, + false + ); + }) + .then(function(result) { + if (!result) { + throw new Error('Cluster ' + clusterName + ' not found'); + } + return true; + }); + } + }; + return ClustersPage; +}); diff --git a/nailgun/static/tests/functional/pages/common.js b/nailgun/static/tests/functional/pages/common.js new file mode 100644 index 0000000000..50ea89df28 --- /dev/null +++ b/nailgun/static/tests/functional/pages/common.js @@ -0,0 +1,132 @@ +define([ + 'intern/node_modules/dojo/node!fs', + '../../helpers', + 'tests/functional/pages/login', + 'tests/functional/pages/welcome', + 'tests/functional/pages/cluster', + 'tests/functional/pages/clusters' +], + function(fs, Helpers, LoginPage, WelcomePage, ClusterPage, ClustersPage) { + 'use strict'; + function CommonMethods(remote) { + this.remote = remote; + this.loginPage = new LoginPage(remote); + this.welcomePage = new WelcomePage(remote); + this.clusterPage = new ClusterPage(remote); + this.clustersPage = new ClustersPage(remote); + } + + CommonMethods.prototype = { + constructor: CommonMethods, + takeScreenshot: function(filename, error) { + return this.remote + .takeScreenshot() + .then(function(buffer) { + if (!filename) { + filename = new Date().toTimeString(); + } + fs.writeFileSync('/tmp/' + filename + '.png', buffer); + if (error) { + throw error; + } + }); + }, + getOut: function() { + var that = this; + return this.remote + .then(function() { + return that.welcomePage.skip(); + }) + .then(function() { + return that.loginPage.logout(); + }); + }, + getIn: function() { + var that = this; + return this.remote + .then(function() { + return that.loginPage.logout(); + }) + .then(function() { + return that.loginPage.login(); + }) + .waitForDeletedByClassName('login-btn') + .then(function() { + return that.welcomePage.skip(); + }); + }, + clickLink: function(text) { + return this.remote + .setFindTimeout(1000) + .findByLinkText(text) + .click() + .end(); + }, + waitForModal: function() { + return this.remote + .setTimeout(2000) + .findByCssSelector('div.modal-content') + .end(); + }, + waitForModalToClose: function() { + return this.remote + .setTimeout(2000) + .waitForDeletedByCssSelector('div.modal-content') + .end(); + }, + goToEnvironment: function(clusterName) { + var that = this; + return this.remote + .then(function() { + return that.clickLink('Environments'); + }) + .then(function() { + return that.clustersPage.goToEnvironment(clusterName); + }); + }, + createCluster: function(clusterName) { + var that = this; + return this.remote + .then(function() { + return that.clickLink('Environments'); + }) + .then(function() { + return that.clustersPage.createCluster(clusterName); + }); + }, + removeCluster: function(clusterName, suppressErrors) { + var that = this; + return this.remote + .then(function() { + return that.clickLink('Environments'); + }) + .then(function() { + return that.clustersPage.goToEnvironment(clusterName); + }) + .then(function() { + return that.clusterPage.removeCluster(); + }) + .catch(function() { + if (!suppressErrors) throw new Error('Unable to delete cluster ' + clusterName); + }); + }, + doesClusterExist: function(clusterName) { + var that = this; + return this.remote + .setFindTimeout(2000) + .then(function() { + return that.clickLink('Environments'); + }) + .findAllByCssSelector(that.clustersPage.clusterSelector) + .then(function(divs) { + return divs.reduce(function(matchFound, element) { + return element.getVisibleText().then( + function(name) { + return (name === clusterName) || matchFound; + } + )}, false); + }); + } + }; + return CommonMethods; +}); diff --git a/nailgun/static/tests/functional/pages/login.js b/nailgun/static/tests/functional/pages/login.js new file mode 100644 index 0000000000..10dff72415 --- /dev/null +++ b/nailgun/static/tests/functional/pages/login.js @@ -0,0 +1,84 @@ +/* + * Copyright 2015 Mirantis, Inc. + * + * Licensed 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. + **/ + +define([ + '../../helpers' +], function(Helpers) { + 'use strict'; + + function LoginPage(remote) { + this.remote = remote; + } + + LoginPage.prototype = { + constructor: LoginPage, + login: function(username, password) { + username = username || Helpers.username; + password = password || Helpers.password; + var that = this; + + return this.remote + .setWindowSize(1280, 1024) + .setTimeout('page load', 20000) + .getCurrentUrl() + .then(function(url) { + if (url !== Helpers.serverUrl + '/#login') { + return that.logout(); + } + }) + .setFindTimeout(10000) + .findByName('username') + .clearValue() + .type(username) + .end() + .findByName('password') + .clearValue() + .type(password) + .end() + .findByClassName('login-btn') + .click() + .end(); + }, + logout: function() { + return this.remote + .getCurrentUrl() + .then(function(url) { + if (url.indexOf(Helpers.serverUrl) !== 0) { + return this.parent + .get(Helpers.serverUrl + '/#logout') + .setFindTimeout(10000) + .findByClassName('login-btn') + .then(function() { + return true; + }); + } + }) + .setFindTimeout(1000) + .findByCssSelector('li.user-icon') + .click() + .end() + .findByCssSelector('.user-popover button.btn-logout') + .click() + .end() + .findByCssSelector('.login-btn') + .then( + function() {return true}, + function() {return true} + ); + } + }; + return LoginPage; +}); diff --git a/nailgun/static/tests/functional/pages/welcome.js b/nailgun/static/tests/functional/pages/welcome.js new file mode 100644 index 0000000000..5fd249396b --- /dev/null +++ b/nailgun/static/tests/functional/pages/welcome.js @@ -0,0 +1,47 @@ +/* + * Copyright 2015 Mirantis, Inc. + * + * Licensed 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. + **/ + +define(['../../helpers'], function(Helpers) { + 'use strict'; + function WelcomePage(remote) { + this.remote = remote; + } + + WelcomePage.prototype = { + constructor: WelcomePage, + skip: function(strictCheck) { + return this.remote + .getCurrentUrl() + .then(function(url) { + if (url == Helpers.serverUrl + '/#welcome') { + return this.parent + .setFindTimeout(2000) + .findByCssSelector('.welcome-button-box button') + .click() + .end() + .waitForDeletedByCssSelector('.welcome-button-box button') + .then( + function() {return true}, + function() {return !strictCheck} + ); + } else { + return true; + } + }); + } + }; + return WelcomePage; +}); diff --git a/nailgun/static/tests/functional/test_cluster_page.js b/nailgun/static/tests/functional/test_cluster_page.js new file mode 100644 index 0000000000..cb5b8346de --- /dev/null +++ b/nailgun/static/tests/functional/test_cluster_page.js @@ -0,0 +1,129 @@ +/* + * Copyright 2015 Mirantis, Inc. + * + * Licensed 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. + **/ + +define([ + 'underscore', + 'intern!object', + 'intern/chai!assert', + 'tests/functional/pages/common', + 'tests/functional/pages/cluster' +], function(_, registerSuite, assert, Common, ClusterPage) { + 'use strict'; + + registerSuite(function() { + var common, + clusterPage, + clusterName; + + return { + name: 'Clusters page', + setup: function() { + common = new Common(this.remote); + clusterPage = new ClusterPage(this.remote); + clusterName = 'Test Cluster #' + Math.round(99999 * Math.random()); + }, + beforeEach: function() { + return this.remote + .then(function() { + return common.getIn(); + }) + .then(function() { + return common.createCluster(clusterName); + }); + }, + afterEach: function() { + return this.remote + .then(function() { + return common.removeCluster(clusterName, true); + }); + }, + 'Remove Cluster': function() { + return this.remote + .then(function() { + return common.doesClusterExist(clusterName); + }) + .then(function(result) { + assert.ok(result, 'Cluster exists'); + }) + .then(function() { + return common.removeCluster(clusterName); + }) + .then(function() { + return common.doesClusterExist(clusterName); + }) + .then(function(result) { + assert.notOk(result, 'Cluster removed successfully'); + }); + }, + 'Add Cluster Nodes': function() { + var nodesAmount = 3, + that = this, + applyButton; + return this.remote + .then(function() { + return common.goToEnvironment(clusterName); + }) + .setFindTimeout(5000) + .findByCssSelector('button.btn-add-nodes') + .click() + .end() + .findByCssSelector('button.btn-apply') + .then(function(button) { + applyButton = button; + return applyButton.isEnabled().then(function(isEnabled) { + assert.isFalse(isEnabled, 'Apply button is disabled until both roles and nodes chosen'); + return true; + }); + }) + .end() + .findByCssSelector('div.role-panel') + .end() + .then(function() { + return clusterPage.checkNodeRoles(['Controller', 'Storage - Cinder']); + }) + .then(function() { + return applyButton.isEnabled().then(function(isEnabled) { + assert.isFalse(isEnabled, 'Apply button is disabled until both roles and nodes chosen'); + return true; + }); + }) + .then(function() { + return clusterPage.checkNodes(nodesAmount); + }) + .then(function() { + applyButton.click(); + }) + .setFindTimeout(2000) + .findByCssSelector('button.btn-add-nodes') + .end() + + .then(function() { + return _.range(1, 1 + nodesAmount).reduce( + function(nodesFound, index) { + return that.remote + .setFindTimeout(1000) + .findByCssSelector('div.node:nth-child(' + index + ')') + .catch(function() { + throw new Error('Unable to find ' + index + ' node in cluster'); + }); + }, + 0 + ); + }); + } + }; + }); +}); diff --git a/nailgun/static/tests/functional/test_clusters_page.js b/nailgun/static/tests/functional/test_clusters_page.js new file mode 100644 index 0000000000..8d32f44642 --- /dev/null +++ b/nailgun/static/tests/functional/test_clusters_page.js @@ -0,0 +1,60 @@ +/* + * Copyright 2015 Mirantis, Inc. + * + * Licensed 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. + **/ + +define([ + 'intern!object', + 'intern/chai!assert', + 'tests/functional/pages/common' +], function(registerSuite, assert, Common) { + 'use strict'; + + registerSuite(function() { + var common, + clusterName; + + return { + name: 'Clusters page', + setup: function() { + common = new Common(this.remote); + clusterName = 'Test Cluster #' + Math.round(99999 * Math.random()); + }, + beforeEach: function() { + return this.remote + .then(function() { + return common.getIn(); + }) + .then(function() { + return common.createCluster(clusterName); + }); + }, + afterEach: function() { + return this.remote + .then(function() { + return common.removeCluster(clusterName, true); + }); + }, + 'Create Cluster': function() { + return this.remote + .then(function() { + return common.doesClusterExist(clusterName); + }) + .then(function(result) { + assert.ok(result, 'Newly created cluster name found in the list'); + }); + } + }; + }); +}); diff --git a/nailgun/static/tests/functional/test_login_logout.js b/nailgun/static/tests/functional/test_login_logout.js new file mode 100644 index 0000000000..607bcbd410 --- /dev/null +++ b/nailgun/static/tests/functional/test_login_logout.js @@ -0,0 +1,59 @@ +/* + * Copyright 2014 Mirantis, Inc. + * + * Licensed 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. + **/ + +define([ + 'intern!object', + 'intern/chai!assert', + 'tests/functional/pages/login', + 'tests/functional/pages/common' +], function(registerSuite, assert, LoginPage, Common) { + 'use strict'; + + registerSuite(function() { + var loginPage, common; + return { + name: 'Login page', + setup: function() { + loginPage = new LoginPage(this.remote); + common = new Common(this.remote); + }, + beforeEach: function() { + this.remote + .then(function() { + return common.getOut(); + }); + }, + 'Login with incorrect credentials': function() { + return this.remote + .then(function() { + return loginPage.login('login', '*****'); + }) + .findByCssSelector('div.login-fields-box p.text-danger') + .isDisplayed() + .then(function(errorShown) { + assert.ok(errorShown, 'Error message is expected to be displayed'); + }); + }, + 'Login with proper credentials': function() { + return this.remote + .then(function() { + return loginPage.login(); + }) + .waitForDeletedByClassName('login-btn'); + } + }; + }); +}); diff --git a/nailgun/static/tests/functional/test_welcome_page.js b/nailgun/static/tests/functional/test_welcome_page.js new file mode 100644 index 0000000000..582f2976a4 --- /dev/null +++ b/nailgun/static/tests/functional/test_welcome_page.js @@ -0,0 +1,49 @@ +/* + * Copyright 2015 Mirantis, Inc. + * + * Licensed 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. + **/ + +define([ + 'intern!object', + 'intern/chai!assert', + 'tests/functional/pages/login', + 'tests/functional/pages/welcome' +], function(registerSuite, assert, LoginPage, WelcomePage) { + 'use strict'; + + registerSuite(function() { + var loginPage, + welcomePage; + + return { + name: 'Welcome page', + setup: function() { + loginPage = new LoginPage(this.remote); + welcomePage = new WelcomePage(this.remote); + }, + 'Skip welcome page': function() { + return this.remote + .then(function() { + return loginPage.login(); + }) + .then(function() { + return welcomePage.skip(true); + }) + .then(function(result) { + assert.ok(result, 'Start using fuel button is present'); + }); + } + }; + }); +}); diff --git a/nailgun/static/tests/helpers.js b/nailgun/static/tests/helpers.js new file mode 100644 index 0000000000..1e426feb24 --- /dev/null +++ b/nailgun/static/tests/helpers.js @@ -0,0 +1,47 @@ +/* + * Copyright 2014 Mirantis, Inc. + * + * Licensed 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. + **/ + +define(['underscore'], function(_) { + 'use strict'; + + var serverHost = '127.0.0.1', + serverPort = process.env.SERVER_PORT || 5544, + serverUrl = 'http://' + serverHost + ':' + serverPort, + username = 'admin', + password = 'admin'; + + return { + username: username, + password: password, + serverUrl: serverUrl, + clickLinkByText: function(remote, cssSelector, linkText) { + return remote + .setFindTimeout(1000) + .findAllByCssSelector(cssSelector) + .then(function(links) { + return links.reduce(function(matchFound, link) { + return link.getVisibleText().then(function(text) { + if (_.trim(text) == linkText) { + link.click(); + return true; + } + return matchFound; + }); + }, false); + }); + } + }; +}); diff --git a/nailgun/static/tests/intern.js b/nailgun/static/tests/intern.js index fec6d28e33..c0a3092996 100644 --- a/nailgun/static/tests/intern.js +++ b/nailgun/static/tests/intern.js @@ -31,8 +31,9 @@ define(['config'], function(config) { 'host-node': 'requirejs', 'host-browser': '/vendor/bower/requirejs/require.js' }, - // A regular expression matching URLs to files that should not be included in code coverage analysis + grep: /^/, excludeInstrumentation: /^/, - loader: config + loader: config, + reporters: ['console'] }; }); diff --git a/nailgun/ui_tests/test_cluster_stop_reset_deployment.js b/nailgun/ui_tests/test_cluster_stop_reset_deployment.js index bedd7efbad..dac587cbc1 100644 --- a/nailgun/ui_tests/test_cluster_stop_reset_deployment.js +++ b/nailgun/ui_tests/test_cluster_stop_reset_deployment.js @@ -38,7 +38,7 @@ casper.then(function() { this.click('.btn-add-nodes'); this.test.assertSelectorAppears('.node', 'Add Nodes screen appeared and unallocated nodes loaded'); this.then(function() { - this.test.assertEvalEquals(function() {return $('.node').length}, 1, 'Number of unallocated nodes is correct'); + this.test.assertEvalEquals(function() {return $('.node').length}, 9, 'Number of unallocated nodes is correct'); this.click('input[name=controller]'); this.click('.node input[type=checkbox]'); this.test.assertSelectorAppears('.node .role-list > ul', 'Controller role is applied to the node'); @@ -187,4 +187,4 @@ casper.then(function() { casper.run(function() { this.test.done(); -}); \ No newline at end of file +}); diff --git a/run_tests.sh b/run_tests.sh index 7bdea4bc5a..d337e8ab9c 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -43,6 +43,7 @@ function usage { echo " --no-ui-unit Don't run UI unit tests" echo " --ui-func Run UI functional tests" echo " --no-ui-func Don't run UI functional tests" + echo " --ui-selenium Run UI functional selenium tests" echo "" echo "Note: with no options specified, the script will try to run all available" echo " tests with all available checks." @@ -74,6 +75,7 @@ function process_options { --ui-unit) ui_unit_tests=1;; --no-ui-unit) no_ui_unit_tests=1;; --ui-func) ui_func_tests=1;; + --ui-selenium) ui_func_selenium_tests=1;; --no-ui-func) no_ui_func_tests=1;; -t|--tests) certain_tests=1;; -*) testropts="$testropts $arg";; @@ -110,7 +112,7 @@ ARTIFACTS=${ARTIFACTS:-`pwd`/test_run} TEST_WORKERS=${TEST_WORKERS:-0} mkdir -p $ARTIFACTS -# disabled/enabled flags that are setted from the cli. +# disabled/enabled flags that are set from the cli. # used for manipulating run logic. agent_tests=0 no_agent_tests=0 @@ -132,7 +134,7 @@ no_ui_func_tests=0 certain_tests=0 tasklib_tests=0 no_tasklib_tests=0 - +ui_func_selenium_tests=0 function run_tests { run_cleanup @@ -163,6 +165,7 @@ function run_tests { $ui_lint_checks -eq 0 && \ $ui_unit_tests -eq 0 && \ $ui_func_tests -eq 0 && \ + $ui_func_selenium_tests -eq 0 && \ $upgrade_system -eq 0 && \ $shotgun_tests -eq 0 && \ $flake8_checks -eq 0 ]]; then @@ -214,6 +217,11 @@ function run_tests { run_ui_func_tests || errors+=" ui_func_tests" fi + if [ $ui_func_selenium_tests -eq 1 ]; then + echo "Starting UI functional selenium tests..." + run_ui_func_selenium_tests || errors+=" ui_func_selenium_tests" + fi + if [ $upgrade_system -eq 1 ]; then echo "Starting upgrade system tests..." run_upgrade_system_tests || errors+=" upgrade_system_tests" @@ -363,6 +371,66 @@ function run_ui_func_tests { return $result } +# Run UI functional tests. +# +# Arguments: +# +# $@ -- tests to be run; with no arguments all tests will be run +function run_ui_func_selenium_tests { + local SERVER_PORT=$UI_SERVER_PORT + local TESTS_DIR=$ROOT/nailgun/static/tests/functional + local TESTS=$TESTS_DIR/test_*.js + local artifacts=$ARTIFACTS/ui_func + local config=$artifacts/test.yaml + prepare_artifacts $artifacts $config + local COMPRESSED_STATIC_DIR=$artifacts/static_compressed + + if [ $# -ne 0 ]; then + TESTS=$@ + fi + + pushd $ROOT/nailgun >> /dev/null + + # test compression + echo -n "Compressing UI... " + local output=$(${GULP} build --static-dir=$COMPRESSED_STATIC_DIR 2>&1) + if [ $? -ne 0 ]; then + echo "$output" + popd >> /dev/null + exit 1 + fi + echo "done" + + # run js testcases + local server_log=`mktemp /tmp/test_nailgun_ui_server.XXXX` + local result=0 + local pid + + for testcase in $TESTS; do + + dropdb $config + syncdb $config true + + run_server $SERVER_PORT $server_log $config || \ + { echo 'Failed to start Nailgun'; return 1; } + + SERVER_PORT=$SERVER_PORT \ + ${GULP} functional-tests --suites=$testcase + if [ $? -ne 0 ]; then + result=1 + break + fi + + kill_server $SERVER_PORT + + done + + rm $server_log + popd >> /dev/null + + return $result +} + # Run tests for fuel upgrade system # @@ -484,6 +552,7 @@ function syncdb { if [[ $# -ne 0 && $defaults = true ]]; then NAILGUN_CONFIG=$config tox -evenv -- python manage.py loaddefault > /dev/null + NAILGUN_CONFIG=$config tox -evenv -- python manage.py loaddata nailgun/fixtures/sample_environment.json > /dev/null fi popd >> /dev/null @@ -626,6 +695,8 @@ EOL function guess_test_run { if [[ $1 == *ui_tests* && $1 == *.js ]]; then run_ui_func_tests $1 || echo "ERROR: $1" + elif [[ $1 == *functional* && $1 == *.js ]]; then + run_ui_func_selenium_tests $1 || echo "ERROR: $1" elif [[ $1 == *fuel_upgrade_system* ]]; then run_upgrade_system_tests $1 || echo "ERROR: $1" elif [[ $1 == *shotgun* ]]; then