[Intern] Dashboard tests

- also fixed tags on Dashboard tab

Related to blueprint ui-functional-tests-with-intern
Change-Id: If517cb0bd6386812710e2dcbb7f7622e4178c5d2
This commit is contained in:
Alexandra Morozova 2015-09-14 13:36:54 +02:00
parent 6ddaaef18d
commit 8cee51e582
7 changed files with 400 additions and 52 deletions

View File

@ -3985,14 +3985,17 @@ input[type=range] {
height: auto;
}
&.name {
a {
.btn-link {
padding-left: 0;
border-left: 0;
text-align: left;
display: inline-block;
max-width: 220px;
max-width: 180px;
overflow: hidden;
cursor: pointer;
text-overflow: ellipsis;
height: 19px;
line-height: 20px;
height: 27px;
line-height: 19px;
}
.glyphicon {
margin-left: 10px;
@ -4071,6 +4074,10 @@ input[type=range] {
margin-right: 10px;
}
}
.discard-changes {
padding-left: 0;
border-left: 0;
}
.changes-list {
padding: @dashboard-offset @dashboard-offset 0 @dashboard-offset;
a {cursor: pointer;}

View File

@ -79,20 +79,21 @@ define([
);
});
},
checkNodes: function(amount) {
checkNodes: function(amount, status) {
var self = this;
status = status || 'discover';
return this.remote
.then(function() {
return _.range(amount).reduce(
function(result, index) {
return self.remote
.findAllByCssSelector('.node.discover > label')
.then(function(nodes) {
return nodes[index].click();
})
.catch(function() {
throw new Error('Failed to add ' + amount + ' nodes to the cluster');
});
.findAllByCssSelector('.node' + '.' + status + ' > label')
.then(function(nodes) {
return nodes[index].click();
})
.catch(function() {
throw new Error('Failed to add ' + amount + ' nodes to the cluster');
});
},
true);
});

View File

@ -102,7 +102,7 @@ define([
)}, false);
});
},
addNodesToCluster: function(nodesAmount, nodesRoles) {
addNodesToCluster: function(nodesAmount, nodesRoles, nodeStatus) {
var self = this;
return this.remote
.then(function() {
@ -115,7 +115,7 @@ define([
return self.clusterPage.checkNodeRoles(nodesRoles);
})
.then(function() {
return self.clusterPage.checkNodes(nodesAmount);
return self.clusterPage.checkNodes(nodesAmount, nodeStatus);
})
.clickByCssSelector('.btn-apply')
.waitForElementDeletion('.btn-apply', 2000);
@ -126,7 +126,7 @@ define([
.then(function(element) {
return element.getVisibleText()
.then(function(visibleText) {
assert.isTrue(visibleText == searchedText, message);
assert.include(visibleText, searchedText, message);
});
})
.end();

View File

@ -15,14 +15,15 @@
**/
define([
'underscore',
'intern/chai!assert',
'tests/functional/pages/modal',
'../../helpers'
], function(_, ModalWindow) {
], function(assert, ModalWindow) {
'use strict';
function DashboardPage(remote) {
this.remote = remote;
this.modal = new ModalWindow(this.remote);
this.modal = new ModalWindow(remote);
this.deployButtonSelector = 'button.deploy-btn';
}
DashboardPage.prototype = {
@ -30,8 +31,7 @@ define([
startDeployment: function() {
var self = this;
return this.remote
.setFindTimeout(2000)
.clickByCssSelector('.deploy-block button.deploy-btn')
.clickByCssSelector(this.deployButtonSelector)
.then(function() {
return self.modal.waitToOpen();
})
@ -61,6 +61,46 @@ define([
.then(function() {
return self.modal.waitToClose();
});
},
startClusterRenaming: function() {
return this.remote
.clickByCssSelector('.cluster-info-value.name .glyphicon-pencil');
},
setClusterName: function(name) {
var self = this;
return this.remote
.then(function() {
return self.startClusterRenaming();
})
.findByCssSelector('.rename-block input[type=text]')
.clearValue()
.type(name)
// Enter
.type('\uE007')
.end();
},
discardChanges: function() {
var self = this;
return this.remote
.clickByCssSelector('button.discard-changes')
.then(function() {
return self.modal.waitToOpen();
})
.then(function() {
return self.modal.clickFooterButton('Discard');
})
.then(function() {
return self.modal.waitToClose();
});
},
assertIsIntegerContentPositive: function(cssSelector, attributeName) {
return this.remote
.findByCssSelector(cssSelector)
.getVisibleText()
.then(function(text) {
return assert.isTrue(parseInt(text) > 0, attributeName + ' is greater than 0');
})
.end();
}
};
return DashboardPage;

View File

@ -0,0 +1,308 @@
/*
* 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',
'tests/functional/pages/cluster',
'tests/functional/pages/dashboard'
], function(registerSuite, assert, Common, ClusterPage, DashboardPage) {
'use strict';
registerSuite(function() {
var common,
clusterPage,
dashboardPage,
clusterName;
return {
name: 'Dashboard tab',
setup: function() {
common = new Common(this.remote);
clusterPage = new ClusterPage(this.remote);
dashboardPage = new DashboardPage(this.remote);
clusterName = common.pickRandomName('Test Cluster');
return this.remote
.then(function() {
return common.getIn();
})
.then(function() {
return common.createCluster(clusterName);
});
},
beforeEach: function() {
return this.remote
.then(function() {
return clusterPage.goToTab('Dashboard');
});
},
'Renaming cluster works': function() {
var initialName = clusterName,
newName = clusterName + '!!!',
renameInputSelector = '.rename-block input[type=text]',
nameSelector = '.cluster-info-value.name .btn-link';
return this.remote
.then(function() {
return dashboardPage.startClusterRenaming();
})
.findByCssSelector(renameInputSelector)
// Escape
.type('\uE00C')
.end()
.then(function() {
return common.assertElementNotExists(renameInputSelector, 'Rename control disappears');
})
.then(function() {
return common.assertElementTextEqualTo(nameSelector, initialName,
'Switching rename control does not change cluster name');
})
.then(function() {
return dashboardPage.setClusterName(newName);
})
.then(function() {
return common.assertElementTextEqualTo(nameSelector, newName, 'New name is applied');
})
.then(function() {
return dashboardPage.setClusterName(initialName);
});
},
'Provision button availability': function() {
return this.remote
.then(function() {
return common.addNodesToCluster(1, ['Virtual']);
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.then(function() {
return common.assertElementTextEqualTo(dashboardPage.deployButtonSelector, 'Provision VMs',
'After adding Virtual node deploy button has appropriate text');
})
.then(function() {
return dashboardPage.discardChanges();
});
},
'Network validation error warning': function() {
return this.remote
.then(function() {
return common.addNodesToCluster(1, ['Controller']);
})
.then(function() {
return clusterPage.goToTab('Networks');
})
.clickByCssSelector('.verify-networks-btn')
.waitForCssSelector('.verification-box .alert-danger', 200)
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.then(function() {
return common.assertElementContainsText('.warnings-block',
'Networks verification', 'Network verification warning is shown');
})
.then(function() {
return dashboardPage.discardChanges();
});
},
'No controller warning': function() {
return this.remote
.then(function() {
// Adding single compute
return common.addNodesToCluster(1, ['Compute']);
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.then(function() {
return common.assertElementNotExists(dashboardPage.deployButtonSelector, 'No deployment should be possible without controller nodes added');
})
.findByCssSelector('div.instruction.invalid')
// Invalid configuration message is shown
.end()
.then(function() {
return common.assertElementContainsText(
'div.validation-result ul.danger li',
'At least 1 Controller nodes are required (0 selected currently).',
'No controllers added warning should be shown'
);
})
.then(function() {
return dashboardPage.discardChanges();
});
},
'Capacity table tests': function() {
return this.remote
.then(function() {
return common.addNodesToCluster(1, ['Controller', 'Storage - Cinder']);
})
.then(function() {
return common.addNodesToCluster(2, ['Compute']);
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.then(function() {
return dashboardPage.assertIsIntegerContentPositive('.capacity-items .cpu .capacity-value', 'CPU');
})
.then(function() {
return dashboardPage.assertIsIntegerContentPositive('.capacity-items .hdd .capacity-value', 'HDD');
})
.then(function() {
return dashboardPage.assertIsIntegerContentPositive('.capacity-items .ram .capacity-value', 'RAM');
})
.then(function() {
return dashboardPage.discardChanges();
});
},
'Test statistics update': function() {
this.timeout = 90000;
var controllerNodes = 3,
storageCinderNodes = 1,
computeNodes = 2,
operatingSystemNodes = 1,
virtualNodes = 1,
valueSelector = '.statistics-block .cluster-info-value',
total = controllerNodes + storageCinderNodes + computeNodes + operatingSystemNodes + virtualNodes;
return this.remote
.then(function() {
return common.addNodesToCluster(controllerNodes, ['Controller']);
})
.then(function() {
return common.addNodesToCluster(storageCinderNodes, ['Storage - Cinder']);
})
.then(function() {
return common.addNodesToCluster(computeNodes, ['Compute']);
})
.then(function() {
return common.addNodesToCluster(operatingSystemNodes, ['Operating System'], 'error');
})
.then(function() {
return common.addNodesToCluster(virtualNodes, ['Virtual'], 'offline');
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.then(function() {
return common.assertElementTextEqualTo(valueSelector + '.total',
total,
'The number of Total nodes in statistics is updated according to added nodes');
})
.then(function() {
return common.assertElementTextEqualTo(valueSelector + '.controller',
controllerNodes,
'The number of controllerNodes nodes in statistics is updated according to added nodes');
})
.then(function() {
return common.assertElementTextEqualTo(valueSelector + '.compute',
computeNodes,
'The number of Compute nodes in statistics is updated according to added nodes');
})
.then(function() {
return common.assertElementTextEqualTo(valueSelector + '.base-os',
operatingSystemNodes,
'The number of Operating Systems nodes in statistics is updated according to added nodes');
})
.then(function() {
return common.assertElementTextEqualTo(valueSelector + '.virt',
virtualNodes,
'The number of Virtual nodes in statistics is updated according to added nodes');
})
.then(function() {
return common.assertElementTextEqualTo(valueSelector + '.offline',
1,
'The number of Offline nodes in statistics is updated according to added nodes');
})
.then(function() {
return common.assertElementTextEqualTo(valueSelector + '.error',
1,
'The number of Error nodes in statistics is updated according to added nodes');
})
.then(function() {
return common.assertElementTextEqualTo(valueSelector + '.pending_addition',
total,
'The number of Pending Addition nodes in statistics is updated according to added nodes');
})
.then(function() {
return dashboardPage.discardChanges();
});
},
'Testing error nodes in cluster deploy': function() {
this.timeout = 60000;
return this.remote
.then(function() {
return common.addNodesToCluster(1, ['Controller'], 'error');
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.then(function() {
return common.assertElementTextEqualTo('.statistics-block .cluster-info-value.error',
1, 'Error node is reflected in Statistics block');
})
.then(function() {
return dashboardPage.startDeployment();
})
.waitForElementDeletion('.deploy-block .progress', 10000)
.waitForCssSelector('.dashboard-tab .alert strong', 1000)
.then(function() {
return common.assertElementTextEqualTo('.dashboard-tab .alert strong', 'Error',
'Deployment failed in case of adding offline nodes');
})
.then(function() {
return clusterPage.resetEnvironment(clusterName);
})
.then(function() {
return dashboardPage.discardChanges();
});
},
'VCenter warning appears': function() {
var vCenterClusterName = clusterName + 'VCenter test';
return this.remote
.clickLinkByText('Environments')
// needed here to wait for transition
.waitForCssSelector('a.clusterbox', 2000)
.then(function() {
return common.createCluster(
vCenterClusterName,
{
Compute: function() {
// Selecting VCenter
return this.remote
.clickByCssSelector('.custom-tumbler input[name=vcenter]');
},
'Networking Setup': function() {
// Selecting Nova Network
return this.remote
.clickByCssSelector('.custom-tumbler input[value=nova-network]');
}
}
);
})
.then(function() {
return common.addNodesToCluster(1, ['Controller']);
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.then(function() {
return common.assertElementContainsText('.warnings-block',
'VMware settings are invalid', 'VMware warning is shown');
});
}
};
});
});

View File

@ -65,30 +65,7 @@ define([
'No deployment button when there are no nodes added': function() {
return this.remote
.then(function() {
return common.assertElementNotExists('button.deploy-btn', 'No deployment should be possible without nodes added')
});
},
'No controller warning': function() {
return this.remote
.then(function() {
// Adding single compute
return common.addNodesToCluster(1, ['Compute']);
})
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.then(function() {
return common.assertElementNotExists('button.deploy-btn', 'No deployment should be possible without controller nodes added')
})
.findByCssSelector('div.instruction.invalid')
// Invalid configuration message is shown
.end()
.then(function() {
return common.assertElementContainsText(
'div.validation-result ul.danger li',
'At least 1 Controller nodes are required (0 selected currently).',
'No controllers added warning should be shown'
);
return common.assertElementNotExists(dashboardPage.deployButtonSelector, 'No deployment should be possible without nodes added')
});
},
'Discard changes': function() {
@ -100,7 +77,7 @@ define([
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.clickLinkByText('Discard Changes')
.clickByCssSelector('.discard-changes')
.then(function() {
return modal.waitToOpen();
})
@ -118,6 +95,7 @@ define([
.waitForCssSelector('div.deploy-readiness a.btn-add-nodes', 2000);
},
'Start/stop deployment': function() {
this.timeout = 60000;
return this.remote
.then(function() {
return common.addNodesToCluster(3, ['Controller']);
@ -125,7 +103,7 @@ define([
.then(function() {
return clusterPage.goToTab('Dashboard');
})
.waitForCssSelector('.dashboard-tab', 2000)
.waitForCssSelector('.dashboard-tab', 200)
.then(function() {
return dashboardPage.startDeployment();
})
@ -135,10 +113,14 @@ define([
})
.waitForElementDeletion('div.deploy-process div.progress', 5000)
// Deployment button available
.waitForCssSelector('div.deploy-block button.deploy-btn', 1000)
.waitForCssSelector(dashboardPage.deployButtonSelector, 1000)
.then(function() {
return common.assertElementContainsText('div.alert-warning strong', 'Success', 'Deployment successfully stopped alert is expected');
})
//@todo: uncomment this after bug fix https://bugs.launchpad.net/fuel/+bug/1493291
//.then(function() {
// return common.assertElementNotExists('.go-to-healthcheck', 'Healthcheck link is not visible after stopped deploy');
//})
// Reset environment button is available
.then(function() {
return clusterPage.resetEnvironment(clusterName);
@ -171,6 +153,15 @@ define([
})
// Deployment competed
.waitForCssSelector('div.horizon', 50000)
.then(function() {
return common.assertElementExists('.go-to-healthcheck', 'Healthcheck link is visible after deploy');
})
.findByCssSelector('div.horizon a.btn-success')
.getAttribute('href')
.then(function(value) {
return assert.isTrue(_.startsWith(value, 'http'), 'Link to Horizon is formed');
})
.end()
.then(function() {
return clusterPage.isTabLocked('Networks');
})

View File

@ -500,12 +500,13 @@ function(_, i18n, $, React, utils, models, dispatcher, dialogs, componentMixins,
)
}
{nodes.hasChanges() &&
<a
<button
className='btn-link discard-changes'
key='discard-changes'
onClick={_.partial(this.showDialog, dialogs.DiscardNodeChangesDialog)}
>
{i18n('cluster_page.discard_changes')}
</a>
</button>
}
</div>
}
@ -712,7 +713,7 @@ function(_, i18n, $, React, utils, models, dispatcher, dialogs, componentMixins,
</div>
</div>
<div className='col-xs-2'>
<div className='cluster-info-value pull-right'>
<div className={'cluster-info-value pull-right ' + field}>
{numberOfNodes}
</div>
</div>
@ -788,9 +789,9 @@ function(_, i18n, $, React, utils, models, dispatcher, dialogs, componentMixins,
/>
:
<div className='cluster-info-value name' onClick={this.startClusterRenaming}>
<a>
<button className='btn-link cluster-name'>
{cluster.get('name')}
</a>
</button>
<i className='glyphicon glyphicon-pencil'></i>
</div>
}