1
0
mirror of https://github.com/ansible/awx.git synced 2024-10-31 06:51:10 +03:00

prevent flake for user e2e

This commit is contained in:
Daniel Sami 2019-04-11 14:21:20 -04:00
parent fbc7d1a9f2
commit b084622c9e
11 changed files with 106 additions and 64 deletions

View File

@ -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;
};

View File

@ -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']));

View File

@ -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);

View File

@ -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;

View File

@ -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();

View File

@ -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();
}
};

View File

@ -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"]')

View File

@ -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();

View File

@ -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);

View File

@ -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) => {

View File

@ -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;
},