From 75065b6407fb92eb34dae35dfdd5163718fbb195 Mon Sep 17 00:00:00 2001 From: Daniel Sami Date: Tue, 23 Apr 2019 15:45:27 -0400 Subject: [PATCH] e2e stability backport for 3.5 --- awx/ui/test/e2e/commands/logout.js | 3 +- awx/ui/test/e2e/objects/applications.js | 2 +- awx/ui/test/e2e/tests/test-users-crud.js | 10 +++ awx/ui/test/e2e/tests/test-websockets.js | 6 +- .../e2e/tests/test-workflow-visualizer.js | 88 +++++++++---------- 5 files changed, 57 insertions(+), 52 deletions(-) diff --git a/awx/ui/test/e2e/commands/logout.js b/awx/ui/test/e2e/commands/logout.js index 2eba1f985d..b06e5b5048 100644 --- a/awx/ui/test/e2e/commands/logout.js +++ b/awx/ui/test/e2e/commands/logout.js @@ -5,7 +5,6 @@ exports.command = function logout () { const logoutButton = '.at-Layout-topNav i.fa-power-off'; this - .waitForElementVisible(logoutButton) - .click(logoutButton) + .findThenClick(logoutButton, 'css') .waitForElementPresent('#login-button'); }; diff --git a/awx/ui/test/e2e/objects/applications.js b/awx/ui/test/e2e/objects/applications.js index 6118d8e5e3..2a2a06b95a 100644 --- a/awx/ui/test/e2e/objects/applications.js +++ b/awx/ui/test/e2e/objects/applications.js @@ -62,7 +62,7 @@ module.exports = { this .waitForElementVisible('#alert-modal-msg') .expect.element('#alert-modal-msg').text.contain(application.name); - this.click('#alert_ok_btn'); + this.findThenClick('#alert_ok_btn', 'css'); this.waitForElementNotVisible('#alert-modal-msg'); }, delete (name) { diff --git a/awx/ui/test/e2e/tests/test-users-crud.js b/awx/ui/test/e2e/tests/test-users-crud.js index 26f2bf5a4e..030ed0f7a4 100644 --- a/awx/ui/test/e2e/tests/test-users-crud.js +++ b/awx/ui/test/e2e/tests/test-users-crud.js @@ -20,6 +20,7 @@ const store = { lastName: `last-admin-${testID}`, password: `admin-${testID}`, username: `admin-${testID}`, + usernameDefault: `user-${testID}`, type: 'administrator', }, auditor: { @@ -28,6 +29,7 @@ const store = { lastName: `last-auditor-${testID}`, password: `auditor-${testID}`, username: `auditor-${testID}`, + usernameDefault: `user-${testID}`, type: 'auditor', }, user: { @@ -36,12 +38,20 @@ const store = { lastName: `last-${testID}`, password: `${testID}`, username: `user-${testID}`, + usernameDefault: `user-${testID}`, type: 'normal', }, }; module.exports = { before: (client, done) => { + // generate a unique username on each attempt. + const uniqueUser = uuid().substr(0, 8); + Object.entries(store).forEach(([key]) => { + if ('username' in store[key]) { + store[key].username = `${store[key].usernameDefault}-${uniqueUser}`; + } + }); const resources = [ getOrganization(store.organization.name), getAuditor(store.auditor.username), diff --git a/awx/ui/test/e2e/tests/test-websockets.js b/awx/ui/test/e2e/tests/test-websockets.js index 48e3e654fb..4aa6877ece 100644 --- a/awx/ui/test/e2e/tests/test-websockets.js +++ b/awx/ui/test/e2e/tests/test-websockets.js @@ -1,6 +1,7 @@ /* Websocket tests. These tests verify that items like the sparkline (colored box rows which * display job status) and other status icons update correctly as the jobs progress. */ +import uuid from 'uuid'; import { getInventorySource, @@ -160,13 +161,14 @@ module.exports = { .to.be.present.before(AWX_E2E_TIMEOUT_ASYNC); }, 'Test pending deletion of inventories': client => { - getInventorySource('test-pending-delete'); + const uniqueID = uuid().substr(0, 8); + getInventorySource(`test-pending-delete-${uniqueID}`); client .useCss() .navigateTo(`${AWX_E2E_URL}/#/inventories`, false) .waitForElementVisible('.SmartSearch-input') .clearValue('.SmartSearch-input') - .setValue('.SmartSearch-input', ['test-pending-delete', client.Keys.ENTER]) + .setValue('.SmartSearch-input', [`test-pending-delete-${uniqueID}`, client.Keys.ENTER]) .pause(AWX_E2E_TIMEOUT_SHORT) // helps prevent flake .findThenClick('.fa-trash-o', 'css') .waitForElementVisible('#prompt_action_btn') diff --git a/awx/ui/test/e2e/tests/test-workflow-visualizer.js b/awx/ui/test/e2e/tests/test-workflow-visualizer.js index 1ca625e97a..112b1b8942 100644 --- a/awx/ui/test/e2e/tests/test-workflow-visualizer.js +++ b/awx/ui/test/e2e/tests/test-workflow-visualizer.js @@ -1,3 +1,5 @@ +import uuid from 'uuid'; + import { getInventorySource, getJobTemplate, @@ -7,15 +9,12 @@ import { import { AWX_E2E_URL, - AWX_E2E_TIMEOUT_LONG } from '../settings'; let data; const spinny = "//*[contains(@class, 'spinny')]"; -const workflowSelector = "//a[text()='test-actions-workflow-template']"; const workflowVisualizerBtn = "//button[contains(@id, 'workflow_job_template_workflow_visualizer_btn')]"; const workflowSearchBar = "//input[contains(@class, 'SmartSearch-input')]"; -const workflowText = 'name.iexact:"test-actions-workflow-template"'; const startNodeId = '1'; let initialJobNodeId; @@ -26,10 +25,6 @@ let leafNodeId; const nodeAdd = "//*[contains(@class, 'WorkflowChart-nodeAddIcon')]"; const nodeRemove = "//*[contains(@class, 'WorkflowChart-nodeRemoveIcon')]"; -// one of the jobs or projects or inventories -const testActionsJob = "//div[contains(@class, 'List-tableCell') and contains(text(), 'test-actions-job')]"; -const testActionsJobText = 'name.iexact:"test-actions-job-template"'; - // search bar for visualizer templates const jobSearchBar = "//*[contains(@id, 'workflow-jobs-list')]//input[contains(@class, 'SmartSearch-input')]"; @@ -49,51 +44,50 @@ const deleteConfirmation = "//button[@ng-click='confirmDeleteNode()']"; const xPathNodeById = (id) => `//*[@id='node-${id}']`; const xPathLinkById = (sourceId, targetId) => `//*[@id='link-${sourceId}-${targetId}']//*[contains(@class, 'WorkflowChart-linkPath')]`; +const xPathNodeByName = (name) => `//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "${name}")]/..`; module.exports = { before: (client, done) => { + // Ensure deterministic state on retries + const testID = uuid().substr(0, 8); + const namespace = `test-actions-${testID}`; const resources = [ - getInventorySource('test-actions'), - getJobTemplate('test-actions'), - getProject('test-actions'), - getWorkflowTemplate('test-actions'), + getInventorySource(namespace), + getJobTemplate(namespace), + getProject(namespace), + getWorkflowTemplate(namespace), ]; Promise.all(resources) - .then(([source, template, project, workflow]) => { - data = { source, template, project, workflow }; + .then(([inventory, template, project, workflow]) => { + data = { inventory, template, project, workflow }; + client + .login() + .waitForAngular() + .resizeWindow(1200, 1000) + .navigateTo(`${AWX_E2E_URL}/#/templates`, false) + .useXpath() + .waitForElementVisible(workflowSearchBar) + .setValue(workflowSearchBar, [`name.iexact:"${data.workflow.name}"`]) + .click('//*[contains(@class, "SmartSearch-searchButton")]') + .waitForSpinny(true) + .click(`//a[text()="${namespace}-workflow-template"]`) + .waitForElementVisible(workflowVisualizerBtn) + .click(workflowVisualizerBtn) + .waitForSpinny(true); + client.waitForElementVisible(xPathNodeByName(`${namespace}-job`)); + // Grab the ids of the nodes + client.getAttribute(xPathNodeByName(`${namespace}-job`), 'id', (res) => { + initialJobNodeId = res.value.split('-')[1]; + }); + client.getAttribute(xPathNodeByName(`${namespace}-pro`), 'id', (res) => { + initialProjectNodeId = res.value.split('-')[1]; + }); + client.getAttribute(xPathNodeByName(`${namespace}-inv`), 'id', (res) => { + initialInventoryNodeId = res.value.split('-')[1]; + }); done(); }); - client - .login() - .waitForAngular() - .resizeWindow(1200, 1000) - .navigateTo(`${AWX_E2E_URL}/#/templates`, false) - .useXpath() - .waitForElementVisible(workflowSearchBar) - .setValue(workflowSearchBar, [workflowText]) - .click('//*[contains(@class, "SmartSearch-searchButton")]') - .waitForSpinny(true) - .click('//*[contains(@class, "SmartSearch-clearAll")]') - .waitForSpinny(true) - .setValue(workflowSearchBar, [workflowText]) - .click('//*[contains(@class, "SmartSearch-searchButton")]') - .waitForSpinny(true) - .click(workflowSelector) - .waitForSpinny(true) - .click(workflowVisualizerBtn); - client.waitForElementVisible('//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "test-actions-job")]/..'); - - // Grab the ids of the nodes - client.getAttribute('//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "test-actions-job")]/..', 'id', (res) => { - initialJobNodeId = res.value.split('-')[1]; - }); - client.getAttribute('//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "test-actions-project")]/..', 'id', (res) => { - initialProjectNodeId = res.value.split('-')[1]; - }); - client.getAttribute('//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "test-actions-inventory")]/..', 'id', (res) => { - initialInventoryNodeId = res.value.split('-')[1]; - }); }, 'verify that workflow visualizer new root node can only be set to always': client => { client @@ -143,9 +137,9 @@ module.exports = { client .waitForElementVisible(jobSearchBar) .clearValue(jobSearchBar) - .setValue(jobSearchBar, [testActionsJobText, client.Keys.ENTER]) + .setValue(jobSearchBar, [`name.iexact:"${data.template.name}"`, client.Keys.ENTER]) .pause(1000) - .findThenClick(testActionsJob) + .findThenClick(`//div[contains(@class, "List-tableCell") and contains(text(), "${data.template.name}")]`) .pause(1000) .waitForElementNotVisible(spinny) .findThenClick(edgeTypeDropdownBar) @@ -174,9 +168,9 @@ module.exports = { client .waitForElementVisible(jobSearchBar) .clearValue(jobSearchBar) - .setValue(jobSearchBar, [testActionsJobText, client.Keys.ENTER]) + .setValue(jobSearchBar, [`name.iexact:"${data.template.name}"`, client.Keys.ENTER]) .pause(1000) - .findThenClick(testActionsJob) + .findThenClick(`//div[contains(@class, "List-tableCell") and contains(text(), "${data.template.name}")]`) .pause(1000) .waitForElementNotVisible(spinny) .findThenClick(edgeTypeDropdownBar)