Fix/refactor UI tests after migration from 9.x
Affected:
* real-plugin/nics-feature
* nigtly/workflows
* nigtly/deployment-history
Closes-Bug: #1669337
Change-Id: Ie4d8a9bb8c6fc9a941477aa69b18fec3fa636265
(cherry picked from commit 5eadafc910
)
This commit is contained in:
parent
0de244cf7c
commit
1befa52126
@ -23,8 +23,6 @@ export REMOTE_SSH_PORT=${REMOTE_SSH_PORT:-22}
|
||||
export REMOTE_PASSWORD=${REMOTE_PASSWORD:-'r00tme'}
|
||||
export REMOTE_DIR=${REMOTE_DIR:-'/root'}
|
||||
|
||||
export FUEL_UI_HOST=${FUEL_UI_HOST:-${REMOTE_HOST}}
|
||||
|
||||
export REMOTE_EXEC="sshpass -p ${REMOTE_PASSWORD}
|
||||
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no
|
||||
-p ${REMOTE_SSH_PORT} ${REMOTE_USER}@${REMOTE_HOST}"
|
||||
@ -64,19 +62,18 @@ function install_prepare_plugin {
|
||||
export plugin_name=$(${REMOTE_EXEC} egrep '^name: ' "${meta}" | cut -d ' ' -f 2)
|
||||
export plugin_version=$(${REMOTE_EXEC} egrep '^version: ' "${meta}" | cut -d ' ' -f 2)
|
||||
|
||||
# Fix package version
|
||||
${REMOTE_EXEC} sed -i '$!s/4.0.0/5.0.0/' ${PLUGIN_PATH}/metadata.yaml
|
||||
# Fix package version and release versions
|
||||
${REMOTE_EXEC} sed -i -e '$!s/4.0.0/5.0.0/' -e '$!s/9.0/10.0/g' -e '$!s/mitaka/newton/' ${meta}
|
||||
${REMOTE_EXEC} fuel plugins --sync
|
||||
|
||||
# Fix components settings
|
||||
${REMOTE_EXEC} sed -i '/requires/,/+$/s/^/#/' ${PLUGIN_PATH}/components.yaml
|
||||
|
||||
${REMOTE_EXEC} fuel plugins --sync
|
||||
|
||||
export INSTALLED_PLUGINS="${INSTALLED_PLUGINS};${plugin_name}==${plugin_version//\'/}"
|
||||
}
|
||||
|
||||
function remove_plugin {
|
||||
function remove_plugins {
|
||||
for plug in $(echo ${INSTALLED_PLUGINS} | tr ";" "\n")
|
||||
do
|
||||
${REMOTE_EXEC} fuel plugins --remove "${plug}" 2>/dev/null && \
|
||||
@ -92,7 +89,7 @@ function remote_scp {
|
||||
-P ${REMOTE_SSH_PORT} $local_file ${REMOTE_USER}@${REMOTE_HOST}:/${REMOTE_DIR}/
|
||||
}
|
||||
|
||||
function run_component_tests {
|
||||
function run_tests {
|
||||
local GULP='./node_modules/.bin/gulp'
|
||||
local TESTS_DIR="static/tests/functional/real_plugin/${TESTS_DIR_NAME}"
|
||||
local TESTS=${TESTS_DIR}/${TEST_PREFIX}.js
|
||||
@ -106,20 +103,29 @@ function run_component_tests {
|
||||
remote_scp ${CONF_PATH}/${conf}_plugin2.yaml
|
||||
${REMOTE_EXEC} cp ${REMOTE_DIR}/${conf}_plugin2.yaml ${PLUGIN_PATH}/${conf}_config.yaml
|
||||
done
|
||||
local plugin1_path=${PLUGIN_PATH}/environment_config.yaml
|
||||
fi
|
||||
|
||||
install_prepare_plugin ${plugin_url} "plugin"
|
||||
|
||||
if [ ${TESTS_DIR_NAME} == 'feature_nics' ]; then
|
||||
${REMOTE_EXEC} cp ${PLUGIN_PATH}/environment_config.yaml ${plugin1_path}
|
||||
${REMOTE_EXEC} sed -i '$!s/fuel.*/dvs/' ${meta}
|
||||
${REMOTE_EXEC} fuel plugins --sync
|
||||
fi
|
||||
|
||||
${GULP} intern:transpile
|
||||
|
||||
for test_case in $TESTS; do
|
||||
echo "INFO: Running test case ${test_case}"
|
||||
|
||||
ARTIFACTS=$ARTIFACTS \
|
||||
${GULP} intern:functional --suites="${test_case}" || result=1
|
||||
${GULP} intern:run --suites="${test_case}" || result=1
|
||||
done
|
||||
|
||||
remove_plugin
|
||||
remove_plugins
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
run_component_tests
|
||||
run_tests
|
||||
|
@ -28,7 +28,7 @@ class HistoryLib {
|
||||
}
|
||||
|
||||
compareViewsData(rowNumber, nodeName) {
|
||||
var taskName, taskStatus, startTime, endTime;
|
||||
var taskName, taskStatus, taskType, startTime, endTime;
|
||||
var historyToolbarSelector = 'div.deployment-history-toolbar ';
|
||||
var timelineViewButton = historyToolbarSelector + 'label.timeline-view';
|
||||
var tableViewButton = historyToolbarSelector + 'label.table-view';
|
||||
@ -39,6 +39,7 @@ class HistoryLib {
|
||||
var taskPopover = 'div.popover.deployment-task-info div.popover-content ';
|
||||
var popoverTaskName = taskPopover + 'div.task_name ';
|
||||
var popoverStatus = taskPopover + 'div.status ';
|
||||
var popoverType = taskPopover + 'div.type ';
|
||||
var popoverStartTime = taskPopover + 'div.time_start ';
|
||||
var popoverEndTime = taskPopover + 'div.time_end ';
|
||||
var taskDetailsAttribure = 'div.deployment-task-details-dialog div.modal-body div.row';
|
||||
@ -57,6 +58,10 @@ class HistoryLib {
|
||||
.getVisibleText()
|
||||
.then((value) => {taskStatus = value;})
|
||||
.end()
|
||||
.findByCssSelector(popoverType + 'span:last-child')
|
||||
.getVisibleText()
|
||||
.then((value) => {taskType = value;})
|
||||
.end()
|
||||
.findByCssSelector(popoverStartTime + 'span:last-child')
|
||||
.getVisibleText()
|
||||
.then((value) => {startTime = value;})
|
||||
@ -79,6 +84,10 @@ class HistoryLib {
|
||||
.getVisibleText()
|
||||
.then((value) => assert.equal(value, taskStatus, 'Task status is the same in the table'))
|
||||
.end()
|
||||
.findByCssSelector(tableBodyRow + ':nth-child(' + rowNumber + ') td:nth-child(4)')
|
||||
.getVisibleText()
|
||||
.then((value) => assert.equal(value, taskType, 'Task type is the same in the table'))
|
||||
.end()
|
||||
.findByCssSelector(tableBodyRow + ':nth-child(' + rowNumber + ') td:nth-child(5)')
|
||||
.getVisibleText()
|
||||
.then((value) => assert.equal(value, startTime, 'Start time is the same in the table'))
|
||||
@ -103,6 +112,10 @@ class HistoryLib {
|
||||
.getVisibleText()
|
||||
.then((value) => assert.equal(value, taskStatus, 'Task status is the same in the dialog'))
|
||||
.end()
|
||||
.findByCssSelector(taskDetailsAttribure + ':nth-child(4) span')
|
||||
.getVisibleText()
|
||||
.then((value) => assert.equal(value, taskType, 'Task type is the same in the dialog'))
|
||||
.end()
|
||||
.findByCssSelector(taskDetailsAttribure + ':nth-child(5) span')
|
||||
.getVisibleText()
|
||||
.then((value) => assert.equal(value, startTime, 'Start time is the same in the dialog'))
|
||||
|
@ -26,15 +26,15 @@ registerSuite(() => {
|
||||
var controllerName = 'Supermicro X9DRW';
|
||||
var computeName = 'Dell Inspiron';
|
||||
var workflowName = 'epicBoost';
|
||||
var modalBodySelector = 'div.modal-body';
|
||||
var modalBody = 'div.modal-body';
|
||||
|
||||
var workflowTabSelector = 'div.workflows-tab ';
|
||||
var toolbarSelector = workflowTabSelector + 'div.deployment-graphs-toolbar ';
|
||||
var filterWorkflowsButton = toolbarSelector + 'button.btn-filters';
|
||||
var uploadNewGraphButton = toolbarSelector + 'button.btn-upload-graph';
|
||||
var workflowTableSelector = workflowTabSelector + 'table.workflows-table ';
|
||||
var tableBodySelector = workflowTableSelector + 'tbody ';
|
||||
var tableRowSelector = tableBodySelector + 'tr';
|
||||
var workflowTab = 'div.workflows-tab ';
|
||||
var toolbar = workflowTab + 'div.deployment-graphs-toolbar ';
|
||||
var filterWorkflowsButton = toolbar + 'button.btn-filters';
|
||||
var uploadNewGraphButton = toolbar + 'button.btn-upload-graph';
|
||||
var workflowTable = workflowTab + 'table.workflows-table ';
|
||||
var tableBody = workflowTable + 'tbody ';
|
||||
var tableRow = tableBody + 'tr';
|
||||
var deleteGraphButton = 'button.btn-remove-graph';
|
||||
|
||||
return {
|
||||
@ -56,179 +56,219 @@ registerSuite(() => {
|
||||
beforeEach() {
|
||||
return this.remote
|
||||
.then(() => clusterPage.goToTab('Workflows'))
|
||||
.assertElementAppears(workflowTabSelector, 5000, '"Workflows" tab appears');
|
||||
.assertElementAppears(workflowTab, 5000, '"Workflows" tab appears');
|
||||
},
|
||||
'Check that default "Workflow" is not avaliable as deployment mode'() {
|
||||
var deployModeMenuSelector = 'ul.dropdown-menu';
|
||||
var deployModeMenu = 'ul.dropdown-menu';
|
||||
|
||||
return this.remote
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => dashboardLib.changeDeploymentMode('Workflow'))
|
||||
.assertElementNotDisplayed(deployModeMenuSelector, 'Deployment Mode menu is not displayed')
|
||||
|
||||
.assertElementNotDisplayed(deployModeMenu, 'Deployment Mode menu is not displayed')
|
||||
.catch((error) => {
|
||||
return this.remote
|
||||
.pressKeys('\uE00C')
|
||||
.assertElementNotDisplayed(deployModeMenuSelector,
|
||||
'Default "Workflow" is not avaliable at Deployment Mode menu. Check error message: ' +
|
||||
error);
|
||||
.assertElementNotDisplayed(deployModeMenu, 'Default "Workflow" is not ' +
|
||||
'avaliable at Deployment Mode menu. Check error message: ' +
|
||||
error);
|
||||
});
|
||||
},
|
||||
'Check that "Workflows" tab is worked'() {
|
||||
var workflowTitleSelector = workflowTabSelector + 'div.title';
|
||||
var tableHeaderSelector = workflowTableSelector + 'thead';
|
||||
var workflowTitle = workflowTab + 'div.title';
|
||||
var tableHeader = workflowTable + 'thead';
|
||||
|
||||
return this.remote
|
||||
.assertElementExists(workflowTitleSelector, '"Workflows" title exists')
|
||||
.assertElementMatchesRegExp(workflowTitleSelector, /Workflows/i,
|
||||
'"Workflows" title has correct description')
|
||||
.assertElementExists(workflowTitle, '"Workflows" title exists')
|
||||
.assertElementMatchesRegExp(workflowTitle, /Workflows/i,
|
||||
'"Workflows" title has correct description')
|
||||
|
||||
.assertElementEnabled(filterWorkflowsButton, '"Filter Workflows" button exists')
|
||||
.assertElementEnabled(uploadNewGraphButton, '"Upload New Workflow" button exists')
|
||||
.assertElementExists(tableHeaderSelector, '"Workflows" table header exists')
|
||||
.assertElementMatchesRegExp(tableHeaderSelector, /Name Level Actions Download/i,
|
||||
'"Workflows" table header has correct description')
|
||||
.assertElementExists(tableBodySelector, '"Workflows" table body exists')
|
||||
.assertElementsExist(tableRowSelector, 3,
|
||||
'Workflows table includes release- and cluster-level default workflows')
|
||||
.assertElementContainsText(tableRowSelector + ':first-child', 'Type "default"',
|
||||
'The first row is default resulting graph for the cluster')
|
||||
.assertElementContainsText(tableRowSelector + ':nth-child(2)', 'Release',
|
||||
'The second row is "Release" level of graph for the cluster')
|
||||
.assertElementContainsText(tableRowSelector + ':nth-child(3)', 'Environment',
|
||||
'The third row is "Environment" level of graph for the cluster')
|
||||
.assertElementNotExists(tableRowSelector + ':first-child ' + deleteGraphButton,
|
||||
'User can not delete resulting graph for the cluster')
|
||||
.assertElementExists(tableRowSelector + ':last-child ' + deleteGraphButton,
|
||||
'User can delete default cluster graph');
|
||||
|
||||
.assertElementExists(tableHeader, '"Workflows" table header exists')
|
||||
.assertElementMatchesRegExp(tableHeader, /Name Level Actions Download/i,
|
||||
'"Workflows" table header has correct description')
|
||||
.assertElementExists(tableBody, '"Workflows" table body exists')
|
||||
.assertElementsExist(tableRow, 3, 'Workflows table includes release- and ' +
|
||||
'cluster-level default workflows')
|
||||
|
||||
.assertElementContainsText(tableRow + ':first-child', 'Type "default"',
|
||||
'The first row is default resulting graph for the cluster')
|
||||
.assertElementContainsText(tableRow + ':nth-child(2)', 'Release',
|
||||
'The second row is "Release" level of graph for the cluster')
|
||||
.assertElementContainsText(tableRow + ':nth-child(3)', 'Environment',
|
||||
'The third row is "Environment" level of graph for the cluster')
|
||||
|
||||
.assertElementNotExists(tableRow + ':first-child ' + deleteGraphButton,
|
||||
'User can not delete resulting graph for the cluster')
|
||||
.assertElementExists(tableRow + ':last-child ' + deleteGraphButton,
|
||||
'User can delete default cluster graph');
|
||||
},
|
||||
'Check "Workflows" tab support filtering'() {
|
||||
var filtersPaneSelector = toolbarSelector + 'div.filters ';
|
||||
var graphTypeButton = filtersPaneSelector + 'div.filter-by-graph_type button';
|
||||
var graphLevelButton = filtersPaneSelector + 'div.filter-by-graph_level button';
|
||||
var filterPopover = toolbarSelector + 'div.popover div.popover-content ';
|
||||
var resetFilter = filtersPaneSelector + 'button.btn-reset-filters';
|
||||
var alertSelector = workflowTabSelector + 'div.alert';
|
||||
var filtersPane = toolbar + 'div.filters ';
|
||||
var graphTypeButton = filtersPane + 'div.filter-by-graph_type button';
|
||||
var graphLevelButton = filtersPane + 'div.filter-by-graph_level button';
|
||||
var filterPopover = toolbar + 'div.popover div.popover-content ';
|
||||
var resetFilter = filtersPane + 'button.btn-reset-filters';
|
||||
var alert = workflowTab + 'div.alert';
|
||||
|
||||
return this.remote
|
||||
.assertElementAppears(filterWorkflowsButton + ':enabled', 1000,
|
||||
'Enabled "Filter Workflows" button appears')
|
||||
'Enabled "Filter Workflows" button appears')
|
||||
|
||||
.clickByCssSelector(filterWorkflowsButton)
|
||||
.assertElementAppears(filtersPaneSelector, 3000, '"Workflows" filter pane appears')
|
||||
.assertElementAppears(filtersPane, 3000, '"Workflows" filter pane appears')
|
||||
.assertElementEnabled(graphTypeButton, 'Filter by "Type" button exists')
|
||||
.assertElementEnabled(graphLevelButton, 'Filter by "Level" button exists')
|
||||
.assertElementsExist(tableRowSelector, 3,
|
||||
'Table includes release- and cluster-level default workflows before filtering')
|
||||
.assertElementsExist(tableRow, 3, 'Table includes release- and cluster-level default ' +
|
||||
'workflows before filtering')
|
||||
|
||||
.clickByCssSelector(graphTypeButton)
|
||||
.assertElementsAppear(filterPopover, 3000, '"Filter popover" by "Type" appears')
|
||||
|
||||
.clickByCssSelector(filterPopover + 'input[name="default"]')
|
||||
.assertElementsExist(tableRowSelector, 3,
|
||||
'Table includes release- and cluster-level default workflows after filtering by "Type"')
|
||||
.assertElementsExist(tableRow, 3, 'Table includes release- and cluster-level default ' +
|
||||
'workflows after filtering by "Type"')
|
||||
|
||||
.clickByCssSelector(graphLevelButton)
|
||||
.assertElementsAppear(filterPopover, 3000, '"Filter popover" by "Level" appears')
|
||||
|
||||
.clickByCssSelector(filterPopover + 'input[name="plugin"]')
|
||||
.assertElementDisappears(workflowTableSelector, 3000,
|
||||
'Workflows table doesn`t have plugin workflows, so workflows table disappears')
|
||||
.assertElementExists(alertSelector, 'Warning message exists')
|
||||
.assertElementMatchesRegExp(alertSelector, /No workflows matched applied filters./i,
|
||||
'Warning message has correct description')
|
||||
.assertElementDisappears(workflowTable, 3000, 'Workflows table doesn`t have plugin ' +
|
||||
'workflows, so workflows table disappears')
|
||||
.assertElementExists(alert, 'Warning message exists')
|
||||
.assertElementMatchesRegExp(alert, /No workflows matched applied filters./i,
|
||||
'Warning message has correct description')
|
||||
|
||||
.clickByCssSelector(filterPopover + 'input[name="release"]')
|
||||
.assertElementsAppear(workflowTableSelector, 3000, 'Workflows table appears')
|
||||
.assertElementsExist(tableRowSelector, 2,
|
||||
'Table includes release-level default workflow after filtering by "Level"')
|
||||
.assertElementsAppear(workflowTable, 3000, 'Workflows table appears')
|
||||
.assertElementsExist(tableRow, 2, 'Table includes release-level default workflow after ' +
|
||||
'filtering by "Level"')
|
||||
.assertElementEnabled(resetFilter, '"Reset Filter" button exists')
|
||||
|
||||
.clickByCssSelector(resetFilter)
|
||||
.assertElementDisappears(filterPopover, 3000, '"Filter popover" by "Level" disappears')
|
||||
.assertElementsExist(tableRowSelector, 3,
|
||||
'Table includes release- and cluster-level default workflows after filter reset');
|
||||
.assertElementsExist(tableRow, 3, 'Table includes release- and cluster-level default ' +
|
||||
'workflows after filter reset');
|
||||
},
|
||||
'Check that user can upload new custom Graph'() {
|
||||
var dialogUploadBody = 'div.upload-graph-form ';
|
||||
var dialogUploadError = dialogUploadBody + 'div.has-error span.help-block';
|
||||
|
||||
return this.remote
|
||||
.assertElementAppears(uploadNewGraphButton + ':enabled', 1000,
|
||||
'Enabled "Upload New Workflow" button appears')
|
||||
'Enabled "Upload New Workflow" button appears')
|
||||
|
||||
.clickByCssSelector(uploadNewGraphButton)
|
||||
.then(() => modal.waitToOpen())
|
||||
.then(() => modal.checkTitle('Upload New Workflow'))
|
||||
.assertElementsExist(dialogUploadBody, 'Upload graph form is exist in the dialog window')
|
||||
|
||||
.then(() => modal.clickFooterButton('Upload'))
|
||||
.assertElementExists(dialogUploadError, 'There is an error due to type field is empty')
|
||||
.assertElementMatchesRegExp(dialogUploadError, /Invalid type/i,
|
||||
'Error message has correct description')
|
||||
'Error message has correct description')
|
||||
|
||||
.setInputValue(dialogUploadBody + 'input[name="type"]', 'default')
|
||||
|
||||
.then(() => modal.clickFooterButton('Upload'))
|
||||
.assertElementExists(dialogUploadError, 'There is an error due to this type already exists')
|
||||
.assertElementMatchesRegExp(dialogUploadError, /Workflow with this type already exists./i,
|
||||
'Error message has correct description')
|
||||
'Error message has correct description')
|
||||
|
||||
.setInputValue(dialogUploadBody + 'input[name=name]', workflowName)
|
||||
.setInputValue(dialogUploadBody + 'input[name=type]', workflowName)
|
||||
|
||||
.assertElementDisappears(dialogUploadError, 1000, 'Error message disappears after set type')
|
||||
|
||||
.then(() => modal.clickFooterButton('Upload'))
|
||||
.then(() => modal.waitToClose())
|
||||
.assertElementsExist(tableRowSelector, 5, 'New graph successfully uploaded')
|
||||
.assertElementContainsText(tableRowSelector + ':nth-child(4)', 'Type "' + workflowName +
|
||||
'"', 'New graph includes resulting graph for just uploaded workflow')
|
||||
.assertElementContainsText(tableRowSelector + ':nth-child(5)', 'Environment',
|
||||
'New graph includes cluster-level for just uploaded workflow');
|
||||
.assertElementsExist(tableRow, 5, 'New graph successfully uploaded')
|
||||
.assertElementContainsText(tableRow + ':nth-child(4)', 'Type "' + workflowName + '"',
|
||||
'New graph includes resulting graph for just uploaded workflow')
|
||||
.assertElementContainsText(tableRow + ':nth-child(5)', 'Environment',
|
||||
'New graph includes cluster-level for just uploaded workflow');
|
||||
},
|
||||
'Check that custom Workflow can be executed'() {
|
||||
this.timeout = 75000;
|
||||
var progressSelector = 'div.dashboard-tab div.progress';
|
||||
var workflowPaneSelector = 'div.actions-panel ';
|
||||
var customGraphSelector = workflowPaneSelector + 'select[name="customGraph"]';
|
||||
var runWorkflowButton = workflowPaneSelector + 'button.btn-run-graph';
|
||||
|
||||
var progress = 'div.dashboard-tab div.progress';
|
||||
var workflowPane = 'div.actions-panel ';
|
||||
var customGraph = workflowPane + 'select[name="customGraph"]';
|
||||
var runWorkflowButton = workflowPane + 'button.btn-run-graph';
|
||||
|
||||
return this.remote
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => dashboardLib.changeDeploymentMode('Workflow'))
|
||||
.assertElementAppears(customGraphSelector, 3000, 'Custom workflow dropdown appears')
|
||||
.assertElementPropertyEquals(customGraphSelector, 'value', workflowName,
|
||||
'Custom workflow dropdown exists and shows just uploaded new graph')
|
||||
.assertElementAppears(customGraph, 3000, 'Custom workflow dropdown appears')
|
||||
.assertElementPropertyEquals(customGraph, 'value', workflowName, 'Custom workflow ' +
|
||||
'dropdown exists and shows just uploaded new graph')
|
||||
|
||||
.clickByCssSelector(runWorkflowButton)
|
||||
.then(() => modal.waitToOpen())
|
||||
.then(() => modal.checkTitle('Run Custom Workflow'))
|
||||
.assertElementContainsText(modalBodySelector,
|
||||
'Click Run Workflow to execute custom deployment tasks on the selected nodes.',
|
||||
'Confirmation message is correct')
|
||||
.assertElementContainsText(modalBody, 'Click Run Workflow to execute custom deployment ' +
|
||||
'tasks on the selected nodes.',
|
||||
'Confirmation message is correct')
|
||||
|
||||
.then(() => modal.clickFooterButton('Run Workflow'))
|
||||
.assertElementDisappears('div.confirmation-question', 5000, 'Confirm dialog disappears')
|
||||
.assertElementAppears(modalBodySelector, 1000, 'Error message appears')
|
||||
.assertElementContainsText(modalBodySelector, 'Deployment tasks not found for',
|
||||
'Workflow can not be started because it contains no deployment tasks')
|
||||
.assertElementAppears(modalBody, 1000, 'Error message appears')
|
||||
.assertElementContainsText(modalBody, 'There are no deployment tasks for graph',
|
||||
'Workflow can not be started because it contains no ' +
|
||||
'deployment tasks')
|
||||
|
||||
.then(() => modal.clickFooterButton('Close'))
|
||||
.then(() => modal.waitToClose())
|
||||
.then(() => dashboardLib.changeDeploymentMode('Deploy'))
|
||||
.then(() => dashboardPage.startDeployment())
|
||||
.assertElementsAppear(progressSelector, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progressSelector, 45000, 'Deployment is finished')
|
||||
.assertElementsAppear(workflowPaneSelector, 5000, 'Workflow panel is shown on Dashboard')
|
||||
.assertElementPropertyEquals(customGraphSelector, 'value', workflowName,
|
||||
'Custom workflow dropdown is shown on the dashboard for the operational cluster')
|
||||
|
||||
.assertElementsAppear(progress, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progress, 45000, 'Deployment is finished')
|
||||
|
||||
.assertElementsAppear(workflowPane, 5000, 'Workflow panel is shown on Dashboard')
|
||||
.assertElementPropertyEquals(customGraph, 'value', workflowName,
|
||||
'Custom workflow dropdown is shown on the dashboard for ' +
|
||||
'the operational cluster')
|
||||
.assertElementContainsText(runWorkflowButton, 'Run Workflow on 2 Nodes',
|
||||
'User can run custom graph for operational cluster');
|
||||
'User can run custom graph for operational cluster');
|
||||
},
|
||||
'Check "Workflows" nodes selection dialog supports Quick Search, Sorting and Filtering'() {
|
||||
this.timeout = 45000;
|
||||
|
||||
var deepCheck = [controllerName, computeName, ['input[name="error"]']];
|
||||
|
||||
return this.remote
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => dashboardLib.selectNodes('Custom Workflow', 1, 1, 1, 0, deepCheck));
|
||||
},
|
||||
'Check that user can delete Workflow'() {
|
||||
var tableRowLast = tableRowSelector + ':last-child ';
|
||||
var tableRowLast = tableRow + ':last-child ';
|
||||
var confirmationForm = 'div.confirmation-form ';
|
||||
var deleteWorkflowSelector = 'div.modal-footer button.remove-graph-btn';
|
||||
var deleteWorkflow = 'div.modal-footer button.remove-graph-btn';
|
||||
|
||||
return this.remote
|
||||
.assertElementAppears(tableRowLast + deleteGraphButton, 1000,
|
||||
'User can delete cluster graph')
|
||||
'User can delete cluster graph')
|
||||
|
||||
.clickByCssSelector(tableRowLast + deleteGraphButton)
|
||||
.then(() => modal.waitToOpen())
|
||||
|
||||
.then(() => modal.checkTitle('Delete Workflow'))
|
||||
.assertElementExists(modalBodySelector, 'Warning message is shown')
|
||||
.assertElementContainsText(modalBodySelector, 'Important' +
|
||||
'Are you sure you want to delete this workflow?', 'Warning message is correct')
|
||||
.assertElementExists(modalBody, 'Warning message is shown')
|
||||
.assertElementContainsText(modalBody, 'Important' + 'Are you sure you want to delete ' +
|
||||
'this workflow?', 'Warning message is correct')
|
||||
|
||||
.then(() => modal.clickFooterButton('Delete'))
|
||||
.assertElementAppears(confirmationForm, 1000, 'Confirmation form for graph removing appers')
|
||||
.assertElementDisabled(deleteWorkflowSelector,
|
||||
'Delete button is disabled, until requested confirmation text will be entered')
|
||||
.assertElementDisabled(deleteWorkflow, 'Delete button is disabled, until requested ' +
|
||||
'confirmation text will be entered')
|
||||
|
||||
.setInputValue(confirmationForm + 'input', workflowName)
|
||||
.assertElementEnabled(deleteWorkflowSelector,
|
||||
'Delete button is enabled after requested confirmation text entered')
|
||||
.assertElementEnabled(deleteWorkflow, 'Delete button is enabled after requested ' +
|
||||
'confirmation text entered')
|
||||
|
||||
.then(() => modal.clickFooterButton('Delete'))
|
||||
.then(() => modal.waitToClose())
|
||||
.assertElementNotContainsText(tableRowLast, workflowName,
|
||||
|
@ -31,53 +31,56 @@ import 'tests/functional/helpers';
|
||||
registerSuite(() => {
|
||||
var common, clusterPage, clustersPage, dashboardPage, nodesLib, genericLib, historyLib, command,
|
||||
clusterName, firstDeployDate, controllerName;
|
||||
var nodeNameSelector = 'div.nodes-group div.node div.name';
|
||||
var progressSelector = 'div.dashboard-tab div.progress';
|
||||
var showDetailsSelector = 'div.toggle-history button';
|
||||
var nodeName = 'div.nodes-group div.node div.name';
|
||||
var progress = 'div.dashboard-tab div.progress';
|
||||
var showDetails = 'div.toggle-history button';
|
||||
|
||||
var historyTabSelector = 'div.history-tab ';
|
||||
var historyTitleSelector = historyTabSelector + 'div.title';
|
||||
var historyAlertSelector = historyTabSelector + 'div.alert';
|
||||
var historyLineSelector = historyTabSelector + 'div.transaction-list ';
|
||||
var historyPointSelector = historyLineSelector + 'a.transaction-link.ready';
|
||||
var historyTab = 'div.history-tab ';
|
||||
var historyTitle = historyTab + 'div.title';
|
||||
var historyAlert = historyTab + 'div.alert';
|
||||
var historyLine = historyTab + 'div.transaction-list ';
|
||||
var historyPoint = historyLine + 'a.transaction-link.ready';
|
||||
var historyPointText = 'Deployment\n';
|
||||
var historyToolbarSelector = 'div.deployment-history-toolbar ';
|
||||
var timelineViewButton = historyToolbarSelector + 'label.timeline-view';
|
||||
var tableViewButton = historyToolbarSelector + 'label.table-view';
|
||||
var filterTasksButton = historyToolbarSelector + 'button.btn-filters';
|
||||
var exportCSVButton = historyToolbarSelector + 'button.btn-export-history-csv';
|
||||
var zoomInButton = historyToolbarSelector + 'button.btn-zoom-in';
|
||||
var zoomOutButton = historyToolbarSelector + 'button.btn-zoom-out';
|
||||
var historyToolbar = 'div.deployment-history-toolbar ';
|
||||
var timelineViewButton = historyToolbar + 'label.timeline-view';
|
||||
var tableViewButton = historyToolbar + 'label.table-view';
|
||||
var filterTasksButton = historyToolbar + 'button.btn-filters';
|
||||
var exportCSVButton = historyToolbar + 'button.btn-export-history-csv';
|
||||
var zoomInButton = historyToolbar + 'button.btn-zoom-in';
|
||||
var zoomOutButton = historyToolbar + 'button.btn-zoom-out';
|
||||
|
||||
var timelinePaneSelector = 'div.deployment-timeline ';
|
||||
var nodeNameLink = timelinePaneSelector + 'div.node-names button.btn-link';
|
||||
var nodeTaskItem = timelinePaneSelector + 'div.timelines div.node-task';
|
||||
var timelinePane = 'div.deployment-timeline ';
|
||||
var nodeNameLink = timelinePane + 'div.node-names button.btn-link';
|
||||
var nodeTaskItem = timelinePane + 'div.timelines div.node-task';
|
||||
var taskPopover = 'div.popover.deployment-task-info div.popover-content ';
|
||||
var popoverTaskName = taskPopover + 'div.task_name ';
|
||||
var popoverStatus = taskPopover + 'div.status ';
|
||||
var popoverStartTime = taskPopover + 'div.time_start ';
|
||||
var popoverEndTime = taskPopover + 'div.time_end ';
|
||||
var tablePaneSelector = 'div.history-table table ';
|
||||
var tableBodyRow = tablePaneSelector + 'tbody tr';
|
||||
var tablePane = 'div.history-table table ';
|
||||
var tableBodyRow = tablePane + 'tbody tr';
|
||||
|
||||
return {
|
||||
name: 'Deployment History',
|
||||
setup() {
|
||||
common = new Common(this.remote);
|
||||
|
||||
clusterPage = new ClusterPage(this.remote);
|
||||
clustersPage = new ClustersPage(this.remote);
|
||||
dashboardPage = new DashboardPage(this.remote);
|
||||
|
||||
nodesLib = new NodesLib(this.remote);
|
||||
genericLib = new GenericLib(this.remote);
|
||||
historyLib = new HistoryLib(this.remote);
|
||||
command = new Command(this.remote);
|
||||
clusterName = common.pickRandomName('Deployment History');
|
||||
|
||||
command = new Command(this.remote);
|
||||
|
||||
clusterName = common.pickRandomName('Deployment History');
|
||||
return this.remote
|
||||
.then(() => common.getIn())
|
||||
.then(() => common.createCluster(clusterName))
|
||||
.then(() => common.addNodesToCluster(1, ['Controller']))
|
||||
.findByCssSelector(nodeNameSelector)
|
||||
.findByCssSelector(nodeName)
|
||||
.getVisibleText()
|
||||
.then((nodeName) => {controllerName = nodeName;})
|
||||
.end();
|
||||
@ -85,93 +88,117 @@ registerSuite(() => {
|
||||
'Check "History" tab before deployment'() {
|
||||
return this.remote
|
||||
.then(() => clusterPage.goToTab('History'))
|
||||
.assertElementAppears(historyTabSelector, 5000, '"History" tab appears')
|
||||
.assertElementExists(historyTitleSelector, '"Deployment History" title exists')
|
||||
.assertElementMatchesRegExp(historyTitleSelector, /Deployment History/i,
|
||||
'"Deployment History" title has correct description')
|
||||
.assertElementExists(historyAlertSelector, 'Alert message exists')
|
||||
.assertElementMatchesRegExp(historyAlertSelector, /No deployment finished yet./i,
|
||||
'Alert message is correct');
|
||||
|
||||
.assertElementAppears(historyTab, 5000, '"History" tab appears')
|
||||
.assertElementExists(historyTitle, '"Deployment History" title exists')
|
||||
|
||||
.assertElementMatchesRegExp(historyTitle, /Deployment History/i,
|
||||
'"Deployment History" title has correct description')
|
||||
|
||||
.assertElementExists(historyAlert, 'Alert message exists')
|
||||
.assertElementMatchesRegExp(historyAlert, /No deployment finished yet./i,
|
||||
'Alert message is correct');
|
||||
},
|
||||
'Check "History" tab after first deployment'() {
|
||||
this.timeout = 70000;
|
||||
|
||||
return this.remote
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
|
||||
.then(() => historyLib.waitForZeroSeconds())
|
||||
.then(() => dashboardPage.startDeployment())
|
||||
.then(() => {firstDeployDate = moment().format('HH:mm DD/MM/YYYY');})
|
||||
|
||||
.then(() => clusterPage.goToTab('History'))
|
||||
.assertElementDisappears(historyAlertSelector, 45000, 'Deployment is successful')
|
||||
.assertElementExists(historyTitleSelector, '"Deployment History" title exists')
|
||||
.assertElementMatchesRegExp(historyTitleSelector, /Deployment History/i,
|
||||
'"Deployment History" title has correct description')
|
||||
.assertElementExists(historyLineSelector, '"History line" pane exists')
|
||||
.assertElementsExist(historyPointSelector + '.active', 1,
|
||||
'Only 1 point of "history line" is observed and selected after deployment')
|
||||
.findByCssSelector(historyPointSelector)
|
||||
|
||||
.assertElementDisappears(historyAlert, 45000, 'Deployment is successful')
|
||||
.assertElementExists(historyTitle, '"Deployment History" title exists')
|
||||
.assertElementMatchesRegExp(historyTitle, /Deployment History/i,
|
||||
'"Deployment History" title has correct description')
|
||||
.assertElementExists(historyLine, '"History line" pane exists')
|
||||
.assertElementsExist(historyPoint + '.active', 1, 'Only 1 point of "history line" is ' +
|
||||
'observed and selected after deployment')
|
||||
|
||||
.findByCssSelector(historyPoint)
|
||||
.getVisibleText()
|
||||
.then((actualText) => assert.equal(actualText, historyPointText + firstDeployDate,
|
||||
'"History point" has correct description, time and date'))
|
||||
.end()
|
||||
|
||||
.assertElementEnabled(timelineViewButton + '.active',
|
||||
'"Timeline View" button exists and active')
|
||||
'"Timeline View" button exists and active')
|
||||
.assertElementEnabled(tableViewButton + ':not(.active)',
|
||||
'"Table View" button exists and not active')
|
||||
'"Table View" button exists and not active')
|
||||
|
||||
.assertElementExists(exportCSVButton + ':enabled', '"Export CSV" button exists and enabled')
|
||||
.assertElementExists(zoomInButton + ':disabled', '"Zoom In" button exists and disabled')
|
||||
.assertElementExists(zoomOutButton + ':disabled', '"Zoom Out" button exists and disabled')
|
||||
.assertElementExists(timelinePaneSelector, '"Timeline pane" exists and opened by default')
|
||||
.assertElementExists(timelinePane, '"Timeline pane" exists and opened by default')
|
||||
|
||||
// Check that user can switch between "Timeline View" and "Table View"
|
||||
.clickByCssSelector(tableViewButton)
|
||||
.assertElementsAppear(tablePaneSelector, 5000, '"Table pane" appears')
|
||||
.assertElementsAppear(tablePane, 5000, '"Table pane" appears')
|
||||
.assertElementEnabled(timelineViewButton + ':not(.active)',
|
||||
'"Timeline View" button exists and not active')
|
||||
'"Timeline View" button exists and not active')
|
||||
.assertElementEnabled(tableViewButton + '.active', '"Table View" button exists and active')
|
||||
|
||||
.assertElementExists(filterTasksButton + ':enabled',
|
||||
'"Filter Tasks" button exists and enabled')
|
||||
'"Filter Tasks" button exists and enabled')
|
||||
.assertElementExists(exportCSVButton + ':enabled', '"Export CSV" button exists and enabled')
|
||||
|
||||
.clickByCssSelector(timelineViewButton)
|
||||
.assertElementsAppear(timelinePaneSelector, 5000, '"Timeline pane" appears');
|
||||
.assertElementsAppear(timelinePane, 5000, '"Timeline pane" appears');
|
||||
},
|
||||
'Check that "History timeline" progress indicator is worked'() {
|
||||
this.timeout = 75000;
|
||||
|
||||
var currentTimeMarkerPosition;
|
||||
var timeMarkerSelector = timelinePaneSelector + 'div.current-time-marker';
|
||||
var timeMarker = timelinePane + 'div.current-time-marker';
|
||||
|
||||
return this.remote
|
||||
.then(() => common.addNodesToCluster(1, ['Controller']))
|
||||
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => dashboardPage.startDeployment())
|
||||
.assertElementAppears(showDetailsSelector, 10000,
|
||||
'Deployment is started and "Show Details" button is shown')
|
||||
.clickByCssSelector(showDetailsSelector)
|
||||
.assertElementsAppear(timelinePaneSelector, 5000, 'Deployment history timeline appears')
|
||||
|
||||
.assertElementAppears(showDetails, 10000,
|
||||
'Deployment is started and "Show Details" button is shown')
|
||||
.clickByCssSelector(showDetails)
|
||||
.assertElementsAppear(timelinePane, 5000, 'Deployment history timeline appears')
|
||||
|
||||
.clickByCssSelector(tableViewButton)
|
||||
.assertElementsAppear(tablePaneSelector, 5000, '"Table pane" appears')
|
||||
.assertElementsAppear(tablePane, 5000, '"Table pane" appears')
|
||||
.clickByCssSelector(timelineViewButton)
|
||||
.assertElementsAppear(timelinePaneSelector, 5000, '"Timeline pane" appears')
|
||||
.assertElementsAppear(timelinePane, 5000, '"Timeline pane" appears')
|
||||
|
||||
.assertElementsExist(nodeNameLink, 2, '2 slave nodes are shown')
|
||||
.assertElementsAppear(nodeTaskItem, 20000, 'Deployment tasks appear on the timeline')
|
||||
.assertElementAppears(timeMarkerSelector, 5000, 'Time marker appears on the timeline')
|
||||
.findByCssSelector(timeMarkerSelector)
|
||||
.assertElementAppears(timeMarker, 5000, 'Time marker appears on the timeline')
|
||||
|
||||
.findByCssSelector(timeMarker)
|
||||
.getComputedStyle('left')
|
||||
.then((value) => {currentTimeMarkerPosition = parseInt(value.split(' ')[0], 10);})
|
||||
.end()
|
||||
|
||||
.then(pollUntil(() =>
|
||||
window.$('.deployment-timeline .timelines .node-task').length >= 6 || null, 60000))
|
||||
.findByCssSelector(timeMarkerSelector)
|
||||
|
||||
.findByCssSelector(timeMarker)
|
||||
.getComputedStyle('left')
|
||||
.then((value) => {
|
||||
return assert.isTrue(parseInt(value.split(' ')[0], 10) > currentTimeMarkerPosition,
|
||||
'Current time marker is moving showing tasks progress');
|
||||
'Current time marker is moving showing tasks progress');
|
||||
})
|
||||
.end()
|
||||
.assertElementDisappears(progressSelector, 30000, 'Deployment is finished');
|
||||
|
||||
.assertElementDisappears(progress, 30000, 'Deployment is finished');
|
||||
},
|
||||
'Check "History" tab after third cluster deployment'() {
|
||||
this.timeout = 80000;
|
||||
|
||||
var thirdDeployDate;
|
||||
var activeHistoryPoint = historyPointSelector + '.active';
|
||||
var inactiveHistoryPoint = historyPointSelector + ':not(.active)';
|
||||
var activeHistoryPoint = historyPoint + '.active';
|
||||
var inactiveHistoryPoint = historyPoint + ':not(.active)';
|
||||
var lgreyColor = 'rgba(220, 220, 220, 1)';
|
||||
var greyColor = 'rgba(200, 200, 200, 1)';
|
||||
var lblueColor = 'rgba(90, 143, 179, 1)';
|
||||
@ -179,109 +206,143 @@ registerSuite(() => {
|
||||
var dblueColor = 'rgba(35, 82, 124, 1)';
|
||||
var lgreenColor = 'rgba(84, 168, 84, 1)';
|
||||
var greenColor = 'rgba(64, 148, 64, 1)';
|
||||
|
||||
return this.remote
|
||||
// Check that "Deployment History" timeline is worked
|
||||
.then(() => common.addNodesToCluster(1, ['Controller']))
|
||||
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => historyLib.waitForZeroSeconds())
|
||||
.then(() => dashboardPage.startDeployment())
|
||||
|
||||
.then(() => {thirdDeployDate = moment().format('HH:mm DD/MM/YYYY');})
|
||||
.assertElementsAppear(progressSelector, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progressSelector, 45000, 'Deployment is finished')
|
||||
.assertElementsAppear(progress, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progress, 45000, 'Deployment is finished')
|
||||
|
||||
.then(() => clusterPage.goToTab('History'))
|
||||
.assertElementsExist(historyPointSelector, 3, '3 points of "history line" are observed')
|
||||
.assertElementsExist(activeHistoryPoint, 1,
|
||||
'First point of "history line" is observed and still selected after third deployment')
|
||||
.assertElementsExist(inactiveHistoryPoint, 2,
|
||||
'2 points of "history line" are observed and not selected after third deployment')
|
||||
|
||||
.assertElementsExist(historyPoint, 3, '3 points of "history line" are observed')
|
||||
.assertElementsExist(activeHistoryPoint, 1, 'First point of "history line" is observed ' +
|
||||
'and still selected after third deployment')
|
||||
.assertElementsExist(inactiveHistoryPoint, 2, '2 points of "history line" are observed ' +
|
||||
'and not selected after third deployment')
|
||||
|
||||
.assertElementTextEquals(activeHistoryPoint, historyPointText + firstDeployDate,
|
||||
'First "History point" has correct description, time and date after third deployment')
|
||||
'First "History point" has correct description, time and date ' +
|
||||
'after third deployment')
|
||||
|
||||
.findByCssSelector(inactiveHistoryPoint + ':last-child')
|
||||
.getVisibleText()
|
||||
.then((actualText) => assert.equal(actualText, historyPointText + thirdDeployDate,
|
||||
'Third "History point" has correct description, time and date after deployment'))
|
||||
'Third "History point" has correct description, ' +
|
||||
'time and date after deployment'))
|
||||
.end()
|
||||
|
||||
.assertElementsExist(nodeNameLink, 1, 'Only 1 node is observed after first deployment')
|
||||
|
||||
.clickByCssSelector(inactiveHistoryPoint + ':last-child')
|
||||
.assertElementsAppear(timelinePaneSelector, 5000, '"Timeline pane" appears')
|
||||
.assertElementsAppear(timelinePane, 5000, '"Timeline pane" appears')
|
||||
.assertElementsAppear(activeHistoryPoint + ':last-child', 5000,
|
||||
'Thirs "History point" is switched to active state')
|
||||
'Thirs "History point" is switched to active state')
|
||||
.assertElementsExist(nodeNameLink, 3, '3 nodes are observed after third deployment')
|
||||
|
||||
// Check "Deployment History" timeline visual design for different states
|
||||
.then(() => genericLib.moveCursorTo(historyTitleSelector))
|
||||
.sleep(500)
|
||||
.then(() => genericLib.moveCursorTo(historyTitle)).sleep(500)
|
||||
.then(() => genericLib.checkSelectorColors('Inactive and unhovered first "History point" ',
|
||||
inactiveHistoryPoint + ':not(:hover)', lgreyColor, lblueColor, lblueColor))
|
||||
.then(() => genericLib.moveCursorTo(inactiveHistoryPoint))
|
||||
inactiveHistoryPoint + ':not(:hover)',
|
||||
lgreyColor, lblueColor, lblueColor))
|
||||
|
||||
.then(() => genericLib.moveCursorTo(inactiveHistoryPoint)).sleep(500)
|
||||
.then(() => genericLib.checkSelectorColors('Inactive and hovered first "History point"',
|
||||
inactiveHistoryPoint + ':hover', greyColor, blueColor, blueColor))
|
||||
.then(() => genericLib.moveCursorTo(historyTitleSelector))
|
||||
.sleep(500)
|
||||
inactiveHistoryPoint + ':hover',
|
||||
greyColor, blueColor, blueColor))
|
||||
|
||||
.then(() => genericLib.moveCursorTo(historyTitle)).sleep(500)
|
||||
.then(() => genericLib.checkSelectorColors('Active and unhovered third "History point"',
|
||||
activeHistoryPoint + ':not(:hover)', lgreenColor, dblueColor, dblueColor))
|
||||
.then(() => genericLib.moveCursorTo(activeHistoryPoint))
|
||||
activeHistoryPoint + ':not(:hover)',
|
||||
lgreenColor, dblueColor, dblueColor))
|
||||
|
||||
.then(() => genericLib.moveCursorTo(activeHistoryPoint)).sleep(500)
|
||||
.then(() => genericLib.checkSelectorColors('Active and hovered third "History point"',
|
||||
activeHistoryPoint + ':hover', greenColor, blueColor, blueColor));
|
||||
activeHistoryPoint + ':hover',
|
||||
greenColor, blueColor, blueColor));
|
||||
},
|
||||
'Check that "Deployment History" results are saved'() {
|
||||
this.timeout = 60000;
|
||||
var activeHistoryPoint = historyPointSelector + '.active';
|
||||
|
||||
var activeHistoryPoint = historyPoint + '.active';
|
||||
var tabNames = ['Dashboard', 'Nodes', 'Networks', 'Settings', 'Logs', 'Workflows',
|
||||
'Health Check'];
|
||||
'Health Check'];
|
||||
|
||||
var chain = this.remote;
|
||||
chain = chain.then(() => clusterPage.goToTab('History'))
|
||||
.assertElementsAppear(timelinePaneSelector, 5000, '"Timeline pane" appears')
|
||||
.clickByCssSelector(historyPointSelector);
|
||||
|
||||
.assertElementsAppear(timelinePane, 5000, '"Timeline pane" appears')
|
||||
.clickByCssSelector(historyPoint);
|
||||
|
||||
// Check that "Deployment History" results saved after refreshing of page
|
||||
chain = chain.then(() => command.refresh())
|
||||
.waitForCssSelector(activeHistoryPoint, 5000)
|
||||
.assertElementTextEquals(activeHistoryPoint, historyPointText + firstDeployDate,
|
||||
'First "History point" still selected after refreshing of page');
|
||||
'First "History point" still selected after refreshing of page');
|
||||
|
||||
// Check that "Deployment History" results saved after switching between cluster tabs
|
||||
for (let i = 0; i < tabNames.length; i++) {
|
||||
chain = chain.findByCssSelector('.cluster-page .tabs')
|
||||
.clickLinkByText(tabNames[i])
|
||||
.end()
|
||||
.then(pollUntil((text) => window.$('.cluster-tab.active').text() === text || null,
|
||||
[tabNames[i]], 3000))
|
||||
[tabNames[i]], 3000))
|
||||
|
||||
.findByCssSelector('.cluster-page .tabs')
|
||||
.clickLinkByText('History')
|
||||
.end()
|
||||
.then(pollUntil((text) => window.$('.cluster-tab.active').text() === text || null,
|
||||
['History'], 3000))
|
||||
['History'], 3000))
|
||||
|
||||
.waitForCssSelector(activeHistoryPoint, 3000)
|
||||
.assertElementTextEquals(activeHistoryPoint, historyPointText + firstDeployDate,
|
||||
'First "History point" still selected after swithing to "' + tabNames[i] + '" tab');
|
||||
'First "History point" still selected after swithing to "' +
|
||||
tabNames[i] + '" tab');
|
||||
}
|
||||
|
||||
// Check that "Deployment History" results are not saved after switching to other page
|
||||
chain = chain.then(() => genericLib.gotoPage('Equipment'))
|
||||
|
||||
.then(() => genericLib.gotoPage('Environments'))
|
||||
.then(() => clustersPage.goToEnvironment(clusterName))
|
||||
|
||||
.then(() => clusterPage.goToTab('History'))
|
||||
.assertElementsAppear(activeHistoryPoint + ':last-child', 5000,
|
||||
'Last "History point" is switched to active state after switching to other page')
|
||||
.assertElementsAppear(activeHistoryPoint + ':last-child', 5000, 'Last "History point" is ' +
|
||||
'switched to active state after switching to other page')
|
||||
.assertElementNotContainsText(activeHistoryPoint, historyPointText + firstDeployDate,
|
||||
'First "History point" is not selected after switching to other page');
|
||||
'First "History point" is not selected after switching to ' +
|
||||
'other page');
|
||||
return chain;
|
||||
},
|
||||
'Check that "Timeline View" is worked'() {
|
||||
this.timeout = 60000;
|
||||
var nodeNameColumn = timelinePaneSelector + 'div.node-names-column ';
|
||||
var timelineColumn = timelinePaneSelector + 'div.timelines-column ';
|
||||
var masterNodeSelector = nodeNameColumn + 'div.node-names div[style^="height"]:first-child';
|
||||
|
||||
var nodeNameColumn = timelinePane + 'div.node-names-column ';
|
||||
var timelineColumn = timelinePane + 'div.timelines-column ';
|
||||
var masterNode = nodeNameColumn + 'div.node-names div[style^="height"]:first-child';
|
||||
|
||||
return this.remote
|
||||
// Check "Timeline View"
|
||||
.then(() => clusterPage.goToTab('History'))
|
||||
.assertElementsAppear(timelinePaneSelector, 5000, '"Timeline pane" appears')
|
||||
.waitForCssSelector(historyPointSelector, 3000)
|
||||
.clickByCssSelector(historyPointSelector)
|
||||
.assertElementsAppear(timelinePane, 5000, '"Timeline pane" appears')
|
||||
.waitForCssSelector(historyPoint, 3000)
|
||||
|
||||
.clickByCssSelector(historyPoint)
|
||||
.assertElementExists(nodeNameColumn, 'Node names column exists')
|
||||
.assertElementsExist(masterNodeSelector, 1, 'Master node exists')
|
||||
.assertElementTextEquals(masterNodeSelector, 'Master node', 'Master node has correct name')
|
||||
.assertElementsExist(masterNode, 1, 'Master node exists')
|
||||
.assertElementTextEquals(masterNode, 'Master node', 'Master node has correct name')
|
||||
|
||||
.assertElementsExist(nodeNameLink, 1, '1 controller node exists')
|
||||
.assertElementTextEquals(nodeNameLink, controllerName, 'Controller has correct name')
|
||||
.assertElementExists(timelineColumn, 'Timeline column exists')
|
||||
.assertElementsExist(nodeTaskItem, 7, '7 node tasks are observed')
|
||||
.assertElementsExist(nodeTaskItem, 8, '8 node tasks are observed')
|
||||
|
||||
// Check popups
|
||||
.then(() => genericLib.moveCursorTo(nodeTaskItem + ':first-child'))
|
||||
.assertElementsAppear(taskPopover, 3000, 'Node task popover appears')
|
||||
@ -289,24 +350,27 @@ registerSuite(() => {
|
||||
.assertElementContainsText(popoverStatus, 'Status', 'Task "Status" field is observed')
|
||||
.assertElementContainsText(popoverStartTime, 'Started', 'Task "Started" field is observed')
|
||||
.assertElementContainsText(popoverEndTime, 'Finished', 'Task "Finished" field is observed')
|
||||
.then(() => genericLib.moveCursorTo(historyPointSelector))
|
||||
|
||||
.then(() => genericLib.moveCursorTo(historyPoint))
|
||||
.assertElementDisappears(taskPopover, 3000, 'Node task popover disappears');
|
||||
},
|
||||
'Check that "Table View" is worked'() {
|
||||
var currentTime = firstDeployDate.split(' ')[0];
|
||||
var currentDate = firstDeployDate.split(' ')[1];
|
||||
var tableHeader = tablePaneSelector + 'thead th';
|
||||
return this.remote
|
||||
var tableHeader = tablePane + 'thead th';
|
||||
|
||||
this.remote
|
||||
// Check that deploy start date/time the same as real
|
||||
.then(() => genericLib.moveCursorTo(nodeTaskItem + ':first-child'))
|
||||
.assertElementsAppear(taskPopover, 3000, 'Node task popover appears')
|
||||
.assertElementContainsText(popoverStartTime + 'span:last-child', currentTime,
|
||||
'Task start time the same as real via "Timeline View"')
|
||||
'Task start time the same as real via "Timeline View"')
|
||||
.assertElementContainsText(popoverStartTime + 'span:last-child', currentDate,
|
||||
'Task start date the same as real via "Timeline View"')
|
||||
'Task start date the same as real via "Timeline View"')
|
||||
|
||||
// Check "Table View"
|
||||
.clickByCssSelector(tableViewButton)
|
||||
.assertElementsAppear(tablePaneSelector, 5000, '"Table pane" appears')
|
||||
.assertElementsAppear(tablePane, 5000, '"Table pane" appears')
|
||||
.assertElementsExist(tableHeader, 7, '7 columns are observed by default')
|
||||
.assertElementTextEquals(tableHeader + ':nth-child(1)', 'Task', 'Column has true name')
|
||||
.assertElementTextEquals(tableHeader + ':nth-child(2)', 'Node', 'Column has true name')
|
||||
@ -315,123 +379,170 @@ registerSuite(() => {
|
||||
.assertElementTextEquals(tableHeader + ':nth-child(5)', 'Started', 'Column has true name')
|
||||
.assertElementTextEquals(tableHeader + ':nth-child(6)', 'Finished', 'Column has true name')
|
||||
.assertElementTextEquals(tableHeader + ':nth-child(7)', '', 'Column has true name')
|
||||
.assertElementsExist(tableBodyRow, 7, '7 node tasks are observed')
|
||||
.assertElementsExist(tableBodyRow + ' td', 49, '49 cells are observed in the table')
|
||||
.assertElementTextEquals(tableBodyRow + ' td:nth-child(2)', 'Master node',
|
||||
'Master node has correct name')
|
||||
.assertElementTextEquals(tableBodyRow + ':nth-child(2) td:nth-child(2)', controllerName,
|
||||
'Controller node has correct name')
|
||||
|
||||
.assertElementsExist(tableBodyRow, 8, '8 node tasks are observed')
|
||||
.assertElementsExist(tableBodyRow + ' td', 56, '56 cells are observed in the table')
|
||||
|
||||
.findAllByCssSelector('table tr td:nth-child(2)')
|
||||
.then((names) => names.reduce(
|
||||
(result, name) => name
|
||||
.getVisibleText()
|
||||
.then((label) => assert(label === controllerName || label === 'Master node',
|
||||
'Invalid node name. Expected "' + label + '" to be "' +
|
||||
controllerName + '" or "Master node"')), false))
|
||||
.end()
|
||||
|
||||
.assertElementContainsText(tableBodyRow + ' td:nth-child(5)', currentTime,
|
||||
'Task start time the same as real via "Table View"')
|
||||
'Task start time the same as real via "Table View"')
|
||||
.assertElementContainsText(tableBodyRow + ' td:nth-child(5)', currentDate,
|
||||
'Task start date the same as real via "Table View"');
|
||||
'Task start date the same as real via "Table View"');
|
||||
},
|
||||
'Check that "Timeline View" data equals to "Table View" data'() {
|
||||
this.timeout = 60000;
|
||||
|
||||
var filterByNode = 'div.filters div.filter-by-node_id button';
|
||||
var node = '.filter-by-node_id .popover-content div:nth-child(2) input';
|
||||
|
||||
return this.remote
|
||||
// Filter to exclude master node
|
||||
.clickByCssSelector('div.deployment-history-toolbar button.btn-filters')
|
||||
.clickByCssSelector(filterByNode)
|
||||
.clickByCssSelector(node)
|
||||
.clickByCssSelector(filterByNode)
|
||||
|
||||
.then(() => historyLib.compareViewsData(2, controllerName))
|
||||
.then(() => historyLib.compareViewsData(3, controllerName))
|
||||
.then(() => historyLib.compareViewsData(4, controllerName))
|
||||
.then(() => historyLib.compareViewsData(5, controllerName))
|
||||
.then(() => historyLib.compareViewsData(6, controllerName))
|
||||
.then(() => historyLib.compareViewsData(7, controllerName));
|
||||
.then(() => historyLib.compareViewsData(7, controllerName))
|
||||
|
||||
.clickByCssSelector('.btn-reset-filters') // .discard-changes-icon
|
||||
.clickByCssSelector('.btn-filters');
|
||||
},
|
||||
'Check "History" tab support filtering'() {
|
||||
var filtersPaneSelector = 'div.filters ';
|
||||
var taskNameButton = filtersPaneSelector + 'div.filter-by-task_name button';
|
||||
var nodeNameButton = filtersPaneSelector + 'div.filter-by-node_id button';
|
||||
var taskStatusButton = filtersPaneSelector + 'div.filter-by-status button';
|
||||
var filtersPane = 'div.filters ';
|
||||
var taskNameButton = filtersPane + 'div.filter-by-task_name button';
|
||||
var nodeNameButton = filtersPane + 'div.filter-by-node_id button';
|
||||
var taskStatusButton = filtersPane + 'div.filter-by-status button';
|
||||
var filterPopover = 'div.popover div.popover-content ';
|
||||
var resetFilter = filtersPaneSelector + 'button.btn-reset-filters';
|
||||
var resetFilter = filtersPane + 'button.btn-reset-filters';
|
||||
|
||||
return this.remote
|
||||
.clickByCssSelector(tableViewButton)
|
||||
.assertElementsAppear(tablePaneSelector, 5000, '"Table pane" appears')
|
||||
.assertElementsExist(tableBodyRow, 7, '7 node tasks are observed before filtering')
|
||||
.assertElementsAppear(tablePane, 5000, '"Table pane" appears')
|
||||
.assertElementsExist(tableBodyRow, 8, '8 node tasks are observed before filtering')
|
||||
|
||||
.clickByCssSelector(filterTasksButton)
|
||||
.assertElementsAppear(filtersPaneSelector, 5000, '"Filter pane" appears')
|
||||
.assertElementsAppear(filtersPane, 5000, '"Filter pane" appears')
|
||||
|
||||
.clickByCssSelector(taskNameButton)
|
||||
.assertElementsAppear(filterPopover, 3000, '"Filter popover" for task name appears')
|
||||
|
||||
.clickByCssSelector(filterPopover + 'input[name="upload_configuration"]')
|
||||
.assertElementsExist(tableBodyRow, 2, '2 node tasks are observed after task name filter')
|
||||
|
||||
.clickByCssSelector(taskStatusButton)
|
||||
.assertElementsAppear(filterPopover, 3000, '"Filter popover" for task status appears')
|
||||
|
||||
.clickByCssSelector(filterPopover + 'input[name="ready"]')
|
||||
.assertElementsExist(tableBodyRow, 2, '2 node tasks are observed after task status filter')
|
||||
|
||||
.clickByCssSelector(nodeNameButton)
|
||||
.assertElementsAppear(filterPopover, 3000, '"Filter popover" for node name appears')
|
||||
|
||||
.clickByCssSelector(filterPopover + 'input[name="master"]')
|
||||
.assertElementsExist(tableBodyRow, 1, '1 node task is observed after node name filter')
|
||||
.assertElementTextEquals(tableBodyRow + ' td:nth-child(1)', 'upload_configuration',
|
||||
'Task name has correct filtered value')
|
||||
'Task name has correct filtered value')
|
||||
.assertElementTextEquals(tableBodyRow + ' td:nth-child(2)', 'Master node',
|
||||
'Node name has correct filtered value')
|
||||
'Node name has correct filtered value')
|
||||
.assertElementTextEquals(tableBodyRow + ' td:nth-child(3)', 'Ready',
|
||||
'Task status has correct filtered value')
|
||||
'Task status has correct filtered value')
|
||||
.assertElementEnabled(resetFilter, '"Reset" button is available')
|
||||
.clickByCssSelector(resetFilter)
|
||||
.sleep(1000)
|
||||
.assertElementsExist(tableBodyRow, 7, '7 node tasks are observed after filter resetting');
|
||||
|
||||
.clickByCssSelector(resetFilter).sleep(1000)
|
||||
.assertElementsExist(tableBodyRow, 8, '8 node tasks are observed after filter resetting');
|
||||
},
|
||||
'Check that "Previous Deployments" dropdown is worked'() {
|
||||
this.timeout = 300000;
|
||||
var lastHistoryPoint = historyPointSelector + ':last-child';
|
||||
var buttonPreviousDeployments = historyTabSelector + 'button.dropdown-toggle';
|
||||
var dropdownMenuSelector = 'ul.dropdown-menu ';
|
||||
var menuItemSelector = dropdownMenuSelector + 'li';
|
||||
var lastItemSelector = menuItemSelector + ':last-child a.ready';
|
||||
|
||||
var lastHistoryPoint = historyPoint + ':last-child';
|
||||
var buttonPreviousDeployments = historyTab + 'button.dropdown-toggle';
|
||||
var dropdownMenu = 'ul.dropdown-menu ';
|
||||
var menuItem = dropdownMenu + 'li';
|
||||
var lastItem = menuItem + ':last-child a.ready';
|
||||
|
||||
return this.remote
|
||||
// Precondition
|
||||
.then(() => common.addNodesToCluster(1, ['Controller']))
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => dashboardPage.startDeployment())
|
||||
.assertElementsAppear(progressSelector, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progressSelector, 45000, 'Deployment is finished')
|
||||
|
||||
.assertElementsAppear(progress, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progress, 45000, 'Deployment is finished')
|
||||
|
||||
.then(() => common.addNodesToCluster(1, ['Controller']))
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => dashboardPage.startDeployment())
|
||||
.assertElementsAppear(progressSelector, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progressSelector, 45000, 'Deployment is finished')
|
||||
|
||||
.assertElementsAppear(progress, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progress, 45000, 'Deployment is finished')
|
||||
|
||||
.then(() => common.addNodesToCluster(1, ['Controller']))
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => dashboardPage.startDeployment())
|
||||
.assertElementsAppear(progressSelector, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progressSelector, 45000, 'Deployment is finished')
|
||||
|
||||
.assertElementsAppear(progress, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progress, 45000, 'Deployment is finished')
|
||||
|
||||
.then(() => nodesLib.removeNodeFromCluster())
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => dashboardPage.startDeployment())
|
||||
.assertElementsAppear(progressSelector, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progressSelector, 45000, 'Deployment is finished')
|
||||
|
||||
.assertElementsAppear(progress, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progress, 45000, 'Deployment is finished')
|
||||
|
||||
.then(() => nodesLib.removeNodeFromCluster())
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => dashboardPage.startDeployment())
|
||||
.assertElementsAppear(progressSelector, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progressSelector, 45000, 'Deployment is finished')
|
||||
|
||||
.assertElementsAppear(progress, 10000, 'Deployment is started')
|
||||
.assertElementDisappears(progress, 45000, 'Deployment is finished')
|
||||
|
||||
.then(() => clusterPage.goToTab('History'))
|
||||
|
||||
// Restore "Previous Deployments" default state
|
||||
.assertElementsAppear(lastHistoryPoint, 3000, 'Last "History point" appears')
|
||||
.clickByCssSelector(lastHistoryPoint)
|
||||
.assertElementsAppear(lastHistoryPoint + '.active', 1000, 'Last "History point" is active')
|
||||
|
||||
// Check "Previous Deployments"
|
||||
.assertElementsAppear(buttonPreviousDeployments, 1000, '"Previous Deployments" btn appears')
|
||||
.assertElementTextEquals(buttonPreviousDeployments, 'Previous Deployments',
|
||||
'"Previous Deployments" button has correct title and dropdown toggle on it')
|
||||
'"Previous Deployments" button has correct title and dropdown ' +
|
||||
'toggle on it')
|
||||
|
||||
.clickByCssSelector(buttonPreviousDeployments)
|
||||
.assertElementsAppear(dropdownMenuSelector, 1000, '"Previous Deployments" menu appears')
|
||||
.assertElementsExist(menuItemSelector, 2, '2 menu items are observed after few deployments')
|
||||
.assertElementTextEquals(lastItemSelector, historyPointText.slice(0, -1) + firstDeployDate,
|
||||
'Menu item has correct description, time and date of first deployment')
|
||||
.clickByCssSelector(lastItemSelector)
|
||||
.waitForElementDeletion(dropdownMenuSelector, 1000)
|
||||
.sleep(500)
|
||||
.assertElementsAppear(dropdownMenu, 1000, '"Previous Deployments" menu appears')
|
||||
.assertElementsExist(menuItem, 2, '2 menu items are observed after few deployments')
|
||||
.assertElementTextEquals(lastItem, historyPointText.slice(0, -1) + firstDeployDate,
|
||||
'Menu item has correct description, time and date of first ' +
|
||||
'deployment')
|
||||
|
||||
.clickByCssSelector(lastItem)
|
||||
.waitForElementDeletion(dropdownMenu, 1000).sleep(500)
|
||||
.assertElementTextEquals(buttonPreviousDeployments + '.ready.active',
|
||||
historyPointText + firstDeployDate,
|
||||
'First "History point" has correct description, time and date after few deployments')
|
||||
historyPointText + firstDeployDate, 'First "History point" has ' +
|
||||
'correct description, time and date after few deployments')
|
||||
|
||||
.waitForCssSelector(nodeNameLink, 5000)
|
||||
.assertElementsExist(nodeNameLink, 1, 'Only 1 node is observed after first deployment')
|
||||
|
||||
.clickByCssSelector(buttonPreviousDeployments)
|
||||
.assertElementsAppear(dropdownMenuSelector, 1000, '"Previous Deployments" menu appears')
|
||||
.assertElementsExist(menuItemSelector, 1,
|
||||
'Only 1 menu item is observed after one of "Previous Deployments" is selected')
|
||||
.assertElementsAppear(dropdownMenu, 1000, '"Previous Deployments" menu appears')
|
||||
.assertElementsExist(menuItem, 1, 'Only 1 menu item is observed after one of ' +
|
||||
'"Previous Deployments" is selected')
|
||||
.pressKeys('\uE00C');
|
||||
}
|
||||
/*
|
||||
@ -440,25 +551,25 @@ registerSuite(() => {
|
||||
|
||||
'Check "History" tab after environment reset'() {
|
||||
this.timeout = 60000;
|
||||
var firstHistoryPoint = historyTabSelector + 'button.dropdown-toggle.ready.active';
|
||||
var firstHistoryPoint = historyTab + 'button.dropdown-toggle.ready.active';
|
||||
return this.remote
|
||||
.assertElementsAppear(firstHistoryPoint, 3000, 'First "History point" appears')
|
||||
.assertElementTextEquals(firstHistoryPoint, historyPointText + firstDeployDate,
|
||||
'First "History point" has correct description, time and date before environment reset')
|
||||
.assertElementsExist(historyPointSelector, 6,
|
||||
.assertElementsExist(historyPoint, 6,
|
||||
'6 "History points" are observed before environment reset')
|
||||
.then(() => clusterPage.goToTab('Dashboard'))
|
||||
.then(() => clusterPage.resetEnvironment(clusterName))
|
||||
.then(() => dashboardPage.discardChanges())
|
||||
.then(() => clusterPage.goToTab('History'))
|
||||
.assertElementAppears(historyTabSelector, 5000, '"History" tab appears')
|
||||
.assertElementExists(historyTitleSelector, '"Deployment History" title exists')
|
||||
.assertElementMatchesRegExp(historyTitleSelector, /Deployment History/i,
|
||||
.assertElementAppears(historyTab, 5000, '"History" tab appears')
|
||||
.assertElementExists(historyTitle, '"Deployment History" title exists')
|
||||
.assertElementMatchesRegExp(historyTitle, /Deployment History/i,
|
||||
'"Deployment History" title has correct description')
|
||||
.assertElementExists(historyAlertSelector, 'Alert message exists')
|
||||
.assertElementMatchesRegExp(historyAlertSelector, /No deployment finished yet./i,
|
||||
.assertElementExists(historyAlert, 'Alert message exists')
|
||||
.assertElementMatchesRegExp(historyAlert, /No deployment finished yet./i,
|
||||
'Alert message is correct.')
|
||||
.assertElementNotExists(historyPointSelector,
|
||||
.assertElementNotExists(historyPoint,
|
||||
'All history was removed after environment reset');
|
||||
}
|
||||
*/
|
||||
|
@ -38,7 +38,7 @@ _.defaults(Command.prototype, {
|
||||
return this.parent
|
||||
.clickByCssSelector('.create-cluster')
|
||||
.then(() => modal.waitToOpen())
|
||||
.setInputValue('[name=name]', 'Temp');
|
||||
.setInputValue('[name=name]', 'Temp' + String(Math.random()).slice(2));
|
||||
});
|
||||
},
|
||||
newClusterWithPlugin(modal) {
|
||||
@ -46,7 +46,7 @@ _.defaults(Command.prototype, {
|
||||
return this.parent
|
||||
.clickByCssSelector('.create-cluster')
|
||||
.then(() => modal.waitToOpen())
|
||||
.setInputValue('[name=name]', 'Temp')
|
||||
.setInputValue('[name=name]', 'Temp' + String(Math.random()).slice(2))
|
||||
|
||||
.pressKeys('\uE007') // go to Compute
|
||||
.pressKeys('\uE007') // Networking
|
||||
@ -83,15 +83,16 @@ _.defaults(Command.prototype, {
|
||||
.clickIfExists('a.dashboard.cluster-tab')
|
||||
|
||||
.clickIfExists('.btn-danger')
|
||||
.then(() => modal.waitToClose())
|
||||
.then(() => modal.waitToClose()).catch(() => true)
|
||||
|
||||
.clickIfExists('button.delete-environment-btn')
|
||||
.then(() => modal.waitToOpen())
|
||||
.then(() => modal.waitToOpen()).catch(() => true)
|
||||
|
||||
.clickIfExists('button.remove-cluster-btn')
|
||||
.then(() => modal.waitToClose())
|
||||
.then(() => modal.waitToClose()).catch(() => true)
|
||||
|
||||
.waitForCssSelector('.create-cluster', 1000);
|
||||
.waitForCssSelector('.create-cluster', 1000)
|
||||
.sleep(1000 * 7);
|
||||
});
|
||||
},
|
||||
clickObjectByIndex(objectsCssSelector, index) {
|
||||
|
Loading…
Reference in New Issue
Block a user