From b084622c9e22ead3e69d30a1fb8e16ce942ea2fe Mon Sep 17 00:00:00 2001 From: Daniel Sami Date: Thu, 11 Apr 2019 14:21:20 -0400 Subject: [PATCH] prevent flake for user e2e --- awx/ui/test/e2e/commands/waitForSpinny.js | 12 ++++--- awx/ui/test/e2e/fixtures.js | 7 ++-- awx/ui/test/e2e/objects/applications.js | 3 +- awx/ui/test/e2e/settings.js | 2 +- awx/ui/test/e2e/tests/test-applications.js | 22 +++++++------ awx/ui/test/e2e/tests/test-org-permissions.js | 17 ++++------ awx/ui/test/e2e/tests/test-pagination.js | 5 +-- awx/ui/test/e2e/tests/test-users-crud.js | 28 +++++++++++----- awx/ui/test/e2e/tests/test-websockets.js | 8 ++--- .../e2e/tests/test-workflow-visualizer.js | 33 +++++++++++-------- awx/ui/test/e2e/tests/test-xss.js | 33 ++++++++++++++++--- 11 files changed, 106 insertions(+), 64 deletions(-) diff --git a/awx/ui/test/e2e/commands/waitForSpinny.js b/awx/ui/test/e2e/commands/waitForSpinny.js index 8188a79e64..34b8c0c5f5 100644 --- a/awx/ui/test/e2e/commands/waitForSpinny.js +++ b/awx/ui/test/e2e/commands/waitForSpinny.js @@ -1,8 +1,10 @@ /* Utility function to wait for the working spinner to disappear. */ -exports.command = function waitForSpinny () { - const selector = 'div.spinny'; - this - .waitForElementVisible(selector) - .waitForElementNotVisible(selector); +exports.command = function waitForSpinny (useXpath = false) { + let selector = 'div.spinny'; + if (useXpath) { + selector = '//*[contains(@class, "spinny")]'; + } + this.waitForElementVisible(selector); + this.waitForElementNotVisible(selector); return this; }; diff --git a/awx/ui/test/e2e/fixtures.js b/awx/ui/test/e2e/fixtures.js index 5bf4ce04e0..5e3143182c 100644 --- a/awx/ui/test/e2e/fixtures.js +++ b/awx/ui/test/e2e/fixtures.js @@ -246,7 +246,7 @@ const waitForJob = endpoint => { const interval = 2000; const statuses = ['successful', 'failed', 'error', 'canceled']; - let attempts = 20; + let attempts = 30; return new Promise((resolve, reject) => { (function pollStatus () { @@ -437,7 +437,8 @@ const getUser = ( // unique substrings are needed to avoid the edge case // where a user and org both exist, but the user is not in the organization. // this ensures a new user is always created. - username = `user-${uuid().substr(0, 8)}` + username = `user-${uuid().substr(0, 8)}`, + isSuperuser = false ) => getOrganization(namespace) .then(organization => getOrCreate(`/organizations/${organization.id}/users/`, { username: `${username}-${uuid().substr(0, 8)}`, @@ -445,7 +446,7 @@ const getUser = ( first_name: 'firstname', last_name: 'lastname', email: 'null@ansible.com', - is_superuser: false, + is_superuser: `${isSuperuser}`, is_system_auditor: false, password: AWX_E2E_PASSWORD }, ['username'])); diff --git a/awx/ui/test/e2e/objects/applications.js b/awx/ui/test/e2e/objects/applications.js index d36ace9dbc..6118d8e5e3 100644 --- a/awx/ui/test/e2e/objects/applications.js +++ b/awx/ui/test/e2e/objects/applications.js @@ -56,8 +56,9 @@ module.exports = { if (application.redirectUris) { this.section.add.setValue('@redirectUris', application.redirectUris); } + this.section.add.click('@save'); // flake avoidance. triple click ensures it works. + this.section.add.click('@save'); this.section.add.click('@save'); - this.waitForSpinny(); this .waitForElementVisible('#alert-modal-msg') .expect.element('#alert-modal-msg').text.contain(application.name); diff --git a/awx/ui/test/e2e/settings.js b/awx/ui/test/e2e/settings.js index 175334ac25..06a79b6cda 100644 --- a/awx/ui/test/e2e/settings.js +++ b/awx/ui/test/e2e/settings.js @@ -4,7 +4,7 @@ const AWX_E2E_CLUSTER_WORKERS = process.env.AWX_E2E_CLUSTER_WORKERS || 0; const AWX_E2E_PASSWORD = process.env.AWX_E2E_PASSWORD || 'password'; const AWX_E2E_URL = process.env.AWX_E2E_URL || 'https://localhost:8043'; const AWX_E2E_USERNAME = process.env.AWX_E2E_USERNAME || 'awx-e2e'; -const AWX_E2E_TIMEOUT_ASYNC = process.env.AWX_E2E_TIMEOUT_ASYNC || 90000; +const AWX_E2E_TIMEOUT_ASYNC = process.env.AWX_E2E_TIMEOUT_ASYNC || 120000; const AWX_E2E_TIMEOUT_LONG = process.env.AWX_E2E_TIMEOUT_LONG || 10000; const AWX_E2E_TIMEOUT_MEDIUM = process.env.AWX_E2E_TIMEOUT_MEDIUM || 5000; const AWX_E2E_TIMEOUT_SHORT = process.env.AWX_E2E_TIMEOUT_SHORT || 1000; diff --git a/awx/ui/test/e2e/tests/test-applications.js b/awx/ui/test/e2e/tests/test-applications.js index 47a64ed507..9d70eb6067 100644 --- a/awx/ui/test/e2e/tests/test-applications.js +++ b/awx/ui/test/e2e/tests/test-applications.js @@ -1,12 +1,14 @@ /* Tests for applications. */ import uuid from 'uuid'; +import { getOrganization } from '../fixtures'; const row = '.at-List-container .at-Row'; const testID = uuid().substr(0, 8); +const namespace = 'test-applications'; const store = { organization: { - name: `org-${testID}` + name: `${namespace}-organization` }, application: { name: `name-${testID}`, @@ -17,19 +19,19 @@ const store = { }, }; +let data; + module.exports = { before: (client, done) => { + const resources = [getOrganization(namespace)]; + Promise.all(resources) + .then(([org]) => { + data = { org }; + done(); + }); + client.login(); client.waitForAngular(); - - client.inject( - [store, 'OrganizationModel'], - (_store_, Model) => new Model().http.post({ data: _store_.organization }), - ({ data }) => { - store.organization = data; - done(); - } - ); }, 'create an application': client => { const applications = client.page.applications(); diff --git a/awx/ui/test/e2e/tests/test-org-permissions.js b/awx/ui/test/e2e/tests/test-org-permissions.js index 2f37779119..1dcd07211b 100644 --- a/awx/ui/test/e2e/tests/test-org-permissions.js +++ b/awx/ui/test/e2e/tests/test-org-permissions.js @@ -4,6 +4,10 @@ import { getTeam, } from '../fixtures'; +import { + AWX_E2E_URL +} from '../settings'; + const namespace = 'test-org-permissions'; let data; @@ -18,7 +22,6 @@ const modalOrgsSearchBar = '//smart-search[@django-model="organizations"]//input const orgsNavTab = "//at-side-nav-item[contains(@name, 'ORGANIZATIONS')]"; const teamsNavTab = "//at-side-nav-item[contains(@name, 'TEAMS')]"; -const usersNavTab = "//at-side-nav-item[contains(@name, 'USERS')]"; const orgTab = '//div[not(@ng-show="showSection2Container()")]/div[@class="Form-tabHolder"]/div[@ng-click="selectTab(\'organizations\')"]'; const teamsTab = '//*[@id="teams_tab"]'; @@ -40,8 +43,6 @@ const teamsSearchBadgeCount = '//span[contains(@class, "List-titleBadge") and co const teamCheckbox = '//*[@item="team"]//input[@type="checkbox"]'; const addUserToTeam = '//*[@aw-tool-tip="Add User"]'; -const trashButton = '//i[contains(@class, "fa-trash")]'; -const deleteButton = '//*[text()="DELETE"]'; const saveButton = '//*[text()="Save"]'; const addPermission = '//*[@aw-tool-tip="Grant Permission"]'; @@ -109,6 +110,8 @@ module.exports = { .findThenClick(saveButton) // add team-wide permissions to an organization .findThenClick(orgsNavTab) + .navigateTo(`${AWX_E2E_URL}/#/organizations`, false) + .waitForElementVisible(searchBar) .clearValue(searchBar) .setValue(searchBar, [orgsText, client.Keys.ENTER]) .waitForElementNotVisible(spinny) @@ -130,12 +133,6 @@ module.exports = { .waitForElementVisible(verifyTeamPermissions); }, after: client => { - client - .findThenClick(usersNavTab) - .setValue(searchBar, [`username.iexact:${data.user.username}`, client.Keys.ENTER]) - .waitForElementNotVisible(spinny) - .findThenClick(trashButton) - .findThenClick(deleteButton) - .end(); + client.end(); } }; diff --git a/awx/ui/test/e2e/tests/test-pagination.js b/awx/ui/test/e2e/tests/test-pagination.js index 2348292930..f70c4d934e 100644 --- a/awx/ui/test/e2e/tests/test-pagination.js +++ b/awx/ui/test/e2e/tests/test-pagination.js @@ -53,10 +53,7 @@ module.exports = { ); client.useXpath().expect.element('//a[text()="test-pagination-job-template-0"]') .to.be.visible.after(AWX_E2E_TIMEOUT_MEDIUM); - client - .useCss() - .waitForSpinny() - .findThenClick('.Paginate-controls--next', 'css'); + client.useCss().findThenClick('.Paginate-controls--next', 'css'); // Default search sort uses alphanumeric sorting, so template #9 is last client.useXpath().expect.element('//a[text()="test-pagination-job-template-9"]') diff --git a/awx/ui/test/e2e/tests/test-users-crud.js b/awx/ui/test/e2e/tests/test-users-crud.js index 5bb5975a07..26f2bf5a4e 100644 --- a/awx/ui/test/e2e/tests/test-users-crud.js +++ b/awx/ui/test/e2e/tests/test-users-crud.js @@ -1,9 +1,15 @@ /* Tests for the user CRUD operations. */ import uuid from 'uuid'; +import { + getAuditor, + getOrganization, + getUser +} from '../fixtures'; const row = '#users_table .List-tableRow'; const testID = uuid().substr(0, 8); +let data; const store = { organization: { name: `org-${testID}` @@ -36,17 +42,21 @@ const store = { module.exports = { before: (client, done) => { + const resources = [ + getOrganization(store.organization.name), + getAuditor(store.auditor.username), + getUser(store.user.username), + getUser(store.admin.username, true) + ]; + + Promise.all(resources) + .then(([organization, auditor, user, admin]) => { + store.organization.name = `${store.organization.name}-organization`; + data = { organization, auditor, user, admin }; + done(); + }); client.login(); client.waitForAngular(); - - client.inject( - [store, 'OrganizationModel'], - (_store_, Model) => new Model().http.post({ data: _store_.organization }), - ({ data }) => { - store.organization = data; - done(); - } - ); }, 'create a system administrator': (client) => { client.login(); diff --git a/awx/ui/test/e2e/tests/test-websockets.js b/awx/ui/test/e2e/tests/test-websockets.js index 0a1a8fd302..48e3e654fb 100644 --- a/awx/ui/test/e2e/tests/test-websockets.js +++ b/awx/ui/test/e2e/tests/test-websockets.js @@ -39,10 +39,10 @@ module.exports = { getProject('test-websockets', 'https://github.com/ansible/test-playbooks'), getOrganization('test-websockets'), // launch job templates once before running tests so that they appear on the dashboard. - getJob('test-websockets', 'debug.yml', 'test-websockets-successful', done), - getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed', done), + getJob('test-websockets', 'debug.yml', 'test-websockets-successful', true, done), + getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed', true, done), getJobTemplate('test-websockets', 'debug.yml', 'test-ws-split-job-template', true, '2'), - getJob('test-websockets', 'debug.yml', 'test-ws-split-job-template', done) + getJob('test-websockets', 'debug.yml', 'test-ws-split-job-template', false, done) ]; Promise.all(resources) @@ -152,7 +152,7 @@ module.exports = { }, 'Test job slicing sparkline behavior': client => { client.findThenClick('[ui-sref=dashboard]', 'css'); - getJob('test-ws-split-job-template', 'debug.yml', 'test-websockets-failed', false); + getJob('test-websockets', 'debug.yml', 'test-ws-split-job-template', false); client.useXpath().expect.element(`${sparklineIcon}[1]${running}`) .to.be.visible.before(AWX_E2E_TIMEOUT_ASYNC); diff --git a/awx/ui/test/e2e/tests/test-workflow-visualizer.js b/awx/ui/test/e2e/tests/test-workflow-visualizer.js index 03e1556cb8..1ca625e97a 100644 --- a/awx/ui/test/e2e/tests/test-workflow-visualizer.js +++ b/awx/ui/test/e2e/tests/test-workflow-visualizer.js @@ -5,14 +5,17 @@ import { getWorkflowTemplate } from '../fixtures'; +import { + AWX_E2E_URL, + AWX_E2E_TIMEOUT_LONG +} from '../settings'; + let data; const spinny = "//*[contains(@class, 'spinny')]"; -const workflowTemplateNavTab = "//at-side-nav-item[contains(@name, 'TEMPLATES')]"; -const workflowSelector = "//a[contains(text(), 'test-actions-workflow-template')]"; +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 workflowSearchBadgeCount = '//span[contains(@class, "at-Panel-headingTitleBadge") and contains(text(), "1")]'; const startNodeId = '1'; let initialJobNodeId; @@ -65,17 +68,21 @@ module.exports = { .login() .waitForAngular() .resizeWindow(1200, 1000) + .navigateTo(`${AWX_E2E_URL}/#/templates`, false) .useXpath() - .findThenClick(workflowTemplateNavTab) - .pause(1500) - .waitForElementNotVisible(spinny) - .clearValue(workflowSearchBar) - .setValue(workflowSearchBar, [workflowText, client.Keys.ENTER]) - .waitForElementVisible(workflowSearchBadgeCount) - .waitForElementNotVisible(spinny) - .findThenClick(workflowSelector) - .findThenClick(workflowVisualizerBtn) - .waitForElementVisible('//*[contains(@class, "WorkflowChart-nameText") and contains(text(), "test-actions-job")]/..'); + .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) => { diff --git a/awx/ui/test/e2e/tests/test-xss.js b/awx/ui/test/e2e/tests/test-xss.js index 0233cddee1..2f916cdab3 100644 --- a/awx/ui/test/e2e/tests/test-xss.js +++ b/awx/ui/test/e2e/tests/test-xss.js @@ -150,6 +150,9 @@ module.exports = { client.expect.element('[class=xss]').not.present; client.click('#prompt_cancel_btn'); + if (client.isVisible('#prompt_cancel_btn')) { + client.click('#prompt_cancel_btn'); + } client.expect.element('#prompt-header').not.visible; }, @@ -168,10 +171,8 @@ module.exports = { client.expect.element('#permissions_tab').visible; client.expect.element('#permissions_tab').enabled; - client.click('#permissions_tab'); - - client.expect.element('div.spinny').visible; - client.expect.element('div.spinny').not.visible; + client.pause(2000); + client.findThenClick('#permissions_tab', 'css'); client.expect.element('#xss').not.present; client.expect.element('[class=xss]').not.present; @@ -237,6 +238,7 @@ module.exports = { client.sendKeys('div.at-Panel smart-search input', `id:>${data.notification.id - 1} id:<${data.notification.id + 1}`); client.waitForElementNotPresent('div.at-Panel smart-search .SmartSearch-searchButton--disabled'); client.waitForElementNotVisible('.overlay'); + client.pause(2000); client.click('div.at-Panel smart-search .SmartSearch-searchButton'); client.expect.element('div.spinny').visible; @@ -270,6 +272,9 @@ module.exports = { client.expect.element('[class=xss]').not.present; client.click('#prompt_cancel_btn'); + if (client.isVisible('#prompt_cancel_btn')) { + client.click('#prompt_cancel_btn'); + } client.expect.element('#prompt-header').not.visible; }, @@ -322,6 +327,9 @@ module.exports = { client.expect.element('[class=xss]').not.present; client.click('#prompt_cancel_btn'); + if (client.isVisible('#prompt_cancel_btn')) { + client.click('#prompt_cancel_btn'); + } client.expect.element('#prompt-header').not.visible; }, @@ -376,6 +384,9 @@ module.exports = { client.expect.element('[class=xss]').not.present; client.click('#prompt_cancel_btn'); + if (client.isVisible('#prompt_cancel_btn')) { + client.click('#prompt_cancel_btn'); + } client.expect.element('#prompt-header').not.visible; }, @@ -438,6 +449,9 @@ module.exports = { client.expect.element('[class=xss]').not.present; client.click('#prompt_cancel_btn'); + if (client.isVisible('#prompt_cancel_btn')) { + client.click('#prompt_cancel_btn'); + } client.expect.element('#prompt-header').not.visible; }, @@ -455,6 +469,8 @@ module.exports = { client.expect.element('#permissions_tab').visible; client.expect.element('#permissions_tab').enabled; + client.pause(1000); + client.click('#permissions_tab'); client.click('#permissions_tab'); client.expect.element('div.spinny').visible; @@ -563,6 +579,9 @@ module.exports = { client.expect.element('[class=xss]').not.present; client.click('#prompt_cancel_btn'); + if (client.isVisible('#prompt_cancel_btn')) { + client.click('#prompt_cancel_btn'); + } client.expect.element('#prompt-header').not.visible; }, @@ -617,6 +636,9 @@ module.exports = { client.expect.element('[class=xss]').not.present; client.click('#prompt_cancel_btn'); + if (client.isVisible('#prompt_cancel_btn')) { + client.click('#prompt_cancel_btn'); + } client.expect.element('#prompt-header').not.visible; }, @@ -671,6 +693,9 @@ module.exports = { client.expect.element('[class=xss]').not.present; client.click('#prompt_cancel_btn'); + if (client.isVisible('#prompt_cancel_btn')) { + client.click('#prompt_cancel_btn'); + } client.expect.element('#prompt-header').not.visible; },