Merge pull request #3035 from ansible/e2e-websockets

E2E tests for websockets (and other things)

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
This commit is contained in:
softwarefactory-project-zuul[bot]
2019-01-23 02:34:19 +00:00
committed by GitHub
3 changed files with 134 additions and 47 deletions

View File

@@ -16,7 +16,7 @@ const store = {};
* *
* @param endpoint - The REST API url suffix. * @param endpoint - The REST API url suffix.
* @param data - Attributes used to create a new endpoint. * @param data - Attributes used to create a new endpoint.
* @param [unique=['name']] - An array of keys used to uniquely identify previously * @param [unique] - An array of keys used to uniquely identify previously
* created resources from the endpoint. * created resources from the endpoint.
* *
*/ */
@@ -52,7 +52,7 @@ const getOrCreate = (endpoint, data, unique = ['name']) => {
/* Retrieves an organization, and creates it if it does not exist. /* Retrieves an organization, and creates it if it does not exist.
* *
* @param [namespace=session] - A unique name prefix for the organization. * @param [namespace] - A unique name prefix for the organization.
* *
*/ */
const getOrganization = (namespace = session) => getOrCreate('/organizations/', { const getOrganization = (namespace = session) => getOrCreate('/organizations/', {
@@ -63,7 +63,7 @@ const getOrganization = (namespace = session) => getOrCreate('/organizations/',
/* Retrieves an inventory, and creates it if it does not exist. /* Retrieves an inventory, and creates it if it does not exist.
* Also creates an organization with the same name prefix if needed. * Also creates an organization with the same name prefix if needed.
* *
* @param [namespace=session] - A unique name prefix for the inventory. * @param [namespace] - A unique name prefix for the inventory.
* *
*/ */
const getInventory = (namespace = session) => getOrganization(namespace) const getInventory = (namespace = session) => getOrganization(namespace)
@@ -81,7 +81,7 @@ const getInventory = (namespace = session) => getOrganization(namespace)
/* Identical to getInventory except it provides a unique suffix, /* Identical to getInventory except it provides a unique suffix,
* "*-inventory-nosource". * "*-inventory-nosource".
* *
* @param[namespace=session] - A unique name prefix for the inventory. * @param[namespace] - A unique name prefix for the inventory.
*/ */
const getInventoryNoSource = (namespace = session) => getOrganization(namespace) const getInventoryNoSource = (namespace = session) => getOrganization(namespace)
.then(organization => getOrCreate('/inventories/', { .then(organization => getOrCreate('/inventories/', {
@@ -98,7 +98,7 @@ const getInventoryNoSource = (namespace = session) => getOrganization(namespace)
/* Retrieves a host with the given name prefix, and creates it if it does not exist. /* Retrieves a host with the given name prefix, and creates it if it does not exist.
* If an inventory does not exist with the same prefix, it is created as well. * If an inventory does not exist with the same prefix, it is created as well.
* *
* @param[namespace=session] - A unique name prefix for the host. * @param[namespace] - A unique name prefix for the host.
*/ */
const getHost = (namespace = session) => getInventory(namespace) const getHost = (namespace = session) => getInventory(namespace)
.then(inventory => getOrCreate('/hosts/', { .then(inventory => getOrCreate('/hosts/', {
@@ -112,7 +112,7 @@ const getHost = (namespace = session) => getInventory(namespace)
* does not exist. If an organization does not exist with the same prefix, it is * does not exist. If an organization does not exist with the same prefix, it is
* created as well. * created as well.
* *
* @param[namespace=session] - A unique name prefix for the host. * @param[namespace] - A unique name prefix for the host.
*/ */
const getInventoryScript = (namespace = session) => getOrganization(namespace) const getInventoryScript = (namespace = session) => getOrganization(namespace)
.then(organization => getOrCreate('/inventory_scripts/', { .then(organization => getOrCreate('/inventory_scripts/', {
@@ -126,7 +126,7 @@ const getInventoryScript = (namespace = session) => getOrganization(namespace)
* required dependent inventory and inventory script do not exist, they are also * required dependent inventory and inventory script do not exist, they are also
* created. * created.
* *
* @param[namespace=session] - A unique name prefix for the inventory source. * @param[namespace] - A unique name prefix for the inventory source.
*/ */
const getInventorySource = (namespace = session) => { const getInventorySource = (namespace = session) => {
const promises = [ const promises = [
@@ -146,7 +146,7 @@ const getInventorySource = (namespace = session) => {
/* Retrieves an AWS credential, and creates it if it does not exist. /* Retrieves an AWS credential, and creates it if it does not exist.
* *
* @param[namespace=session] - A unique name prefix for the AWS credential. * @param[namespace] - A unique name prefix for the AWS credential.
*/ */
const getAdminAWSCredential = (namespace = session) => { const getAdminAWSCredential = (namespace = session) => {
const promises = [ const promises = [
@@ -176,7 +176,7 @@ const getAdminAWSCredential = (namespace = session) => {
/* Retrieves a machine credential, and creates it if it does not exist. /* Retrieves a machine credential, and creates it if it does not exist.
* *
* @param[namespace=session] - A unique name prefix for the machine credential. * @param[namespace] - A unique name prefix for the machine credential.
*/ */
const getAdminMachineCredential = (namespace = session) => { const getAdminMachineCredential = (namespace = session) => {
const promises = [ const promises = [
@@ -200,7 +200,7 @@ const getAdminMachineCredential = (namespace = session) => {
* If an organization does not exist with the same prefix, it is * If an organization does not exist with the same prefix, it is
* created as well. * created as well.
* *
* @param[namespace=session] - A unique name prefix for the team. * @param[namespace] - A unique name prefix for the team.
*/ */
const getTeam = (namespace = session) => getOrganization(namespace) const getTeam = (namespace = session) => getOrganization(namespace)
.then(organization => getOrCreate(`/organizations/${organization.id}/teams/`, { .then(organization => getOrCreate(`/organizations/${organization.id}/teams/`, {
@@ -213,7 +213,7 @@ const getTeam = (namespace = session) => getOrganization(namespace)
* name prefix. If an organization does not exist with the same prefix, it is * name prefix. If an organization does not exist with the same prefix, it is
* created as well. * created as well.
* *
* @param[namespace=session] - A unique name prefix for the smart inventory. * @param[namespace] - A unique name prefix for the smart inventory.
*/ */
const getSmartInventory = (namespace = session) => getOrganization(namespace) const getSmartInventory = (namespace = session) => getOrganization(namespace)
.then(organization => getOrCreate('/inventories/', { .then(organization => getOrCreate('/inventories/', {
@@ -228,7 +228,7 @@ const getSmartInventory = (namespace = session) => getOrganization(namespace)
* name prefix. If an organization does not exist with the same prefix, it is * name prefix. If an organization does not exist with the same prefix, it is
* created as well. * created as well.
* *
* @param[namespace=session] - A unique name prefix for the notification template. * @param[namespace] - A unique name prefix for the notification template.
*/ */
const getNotificationTemplate = (namespace = session) => getOrganization(namespace) const getNotificationTemplate = (namespace = session) => getOrganization(namespace)
.then(organization => getOrCreate(`/organizations/${organization.id}/notification_templates/`, { .then(organization => getOrCreate(`/organizations/${organization.id}/notification_templates/`, {
@@ -246,15 +246,21 @@ const getNotificationTemplate = (namespace = session) => getOrganization(namespa
* name prefix. If an organization does not exist with the same prefix, it is * name prefix. If an organization does not exist with the same prefix, it is
* created as well. * created as well.
* *
* @param[namespace=session] - A unique name prefix for the host. * @param[namespace] - A unique name prefix for the host.
* @param[scmUrl] - The url of the repository.
* @param[scmType] - The type of scm (git, etc.)
*/ */
const getProject = (namespace = session) => getOrganization(namespace) const getProject = (
namespace = session,
scmUrl = 'https://github.com/ansible/ansible-tower-samples',
scmType = 'git'
) => getOrganization(namespace)
.then(organization => getOrCreate(`/organizations/${organization.id}/projects/`, { .then(organization => getOrCreate(`/organizations/${organization.id}/projects/`, {
name: `${namespace}-project`, name: `${namespace}-project`,
description: namespace, description: namespace,
organization: organization.id, organization: organization.id,
scm_url: 'https://github.com/ansible/ansible-tower-samples', scm_url: `${scmUrl}`,
scm_type: 'git' scm_type: `${scmType}`
})); }));
const waitForJob = endpoint => { const waitForJob = endpoint => {
@@ -297,9 +303,15 @@ const getUpdatedProject = (namespace = session) => getProject(namespace)
* name prefix. This function also runs getOrCreate for an inventory, * name prefix. This function also runs getOrCreate for an inventory,
* credential, and project with the same prefix. * credential, and project with the same prefix.
* *
* @param[namespace=session] - A unique name prefix for the job template. * @param [namespace] - Name prefix for associated dependencies.
*/ * @param [playbook] - Playbook for the job template.
const getJobTemplate = (namespace = session) => { * @param [name] - Unique name prefix for the job template.
* */
const getJobTemplate = (
namespace = session,
playbook = 'hello_world.yml',
name = `${namespace}-job-template`
) => {
const promises = [ const promises = [
getInventory(namespace), getInventory(namespace),
getAdminMachineCredential(namespace), getAdminMachineCredential(namespace),
@@ -308,20 +320,26 @@ const getJobTemplate = (namespace = session) => {
return Promise.all(promises) return Promise.all(promises)
.then(([inventory, credential, project]) => getOrCreate('/job_templates/', { .then(([inventory, credential, project]) => getOrCreate('/job_templates/', {
name: `${namespace}-job-template`, name: `${name}`,
description: namespace, description: namespace,
inventory: inventory.id, inventory: inventory.id,
credential: credential.id, credential: credential.id,
project: project.id, project: project.id,
playbook: 'hello_world.yml', playbook: `${playbook}`,
})); }));
}; };
/* Similar to getJobTemplate, except that it also launches the job. /* Similar to getJobTemplate, except that it also launches the job.
* *
* @param[namespace=session] - A unique name prefix for the host. * @param[namespace] - A unique name prefix for the job and its dependencies.
* @param[playbook] - The playbook file to be run by the job template.
* @param[name] - A unique name for the job template.
*/ */
const getJob = (namespace = session) => getJobTemplate(namespace) const getJob = (
namespace = session,
playbook = 'hello_world.yml',
name = `${namespace}-job-template`
) => getJobTemplate(namespace, playbook, name)
.then(template => { .then(template => {
const launchURL = template.related.launch; const launchURL = template.related.launch;
return post(launchURL, {}).then(response => { return post(launchURL, {}).then(response => {
@@ -334,7 +352,7 @@ const getJob = (namespace = session) => getJobTemplate(namespace)
* name prefix. If an organization does not exist with the same prefix, it is * name prefix. If an organization does not exist with the same prefix, it is
* created as well. A basic workflow node setup is also created. * created as well. A basic workflow node setup is also created.
* *
* @param[namespace=session] - A unique name prefix for the workflow template. * @param[namespace] - A unique name prefix for the workflow template.
*/ */
const getWorkflowTemplate = (namespace = session) => { const getWorkflowTemplate = (namespace = session) => {
const workflowTemplatePromise = getOrganization(namespace) const workflowTemplatePromise = getOrganization(namespace)
@@ -380,7 +398,7 @@ const getWorkflowTemplate = (namespace = session) => {
* name prefix. If an organization does not exist with the same prefix, * name prefix. If an organization does not exist with the same prefix,
* it is also created. * it is also created.
* *
* @param[namespace=session] - A unique name prefix for the auditor. * @param[namespace] - A unique name prefix for the auditor.
*/ */
const getAuditor = (namespace = session) => getOrganization(namespace) const getAuditor = (namespace = session) => getOrganization(namespace)
.then(organization => getOrCreate(`/organizations/${organization.id}/users/`, { .then(organization => getOrCreate(`/organizations/${organization.id}/users/`, {
@@ -398,23 +416,15 @@ const getAuditor = (namespace = session) => getOrganization(namespace)
* name prefix. If an organization does not exist with the same prefix, * name prefix. If an organization does not exist with the same prefix,
* it is also created. * it is also created.
* *
* @param[namespace=session] - A unique name prefix for the user. * @param[namespace] - A unique name prefix for the user's organization.
* @param[username] - A unique name for the user.
*/ */
const getUser = (namespace = session) => getOrganization(namespace) const getUser = (
namespace = session,
username = `user-${uuid().substr(0, 8)}`
) => getOrganization(namespace)
.then(organization => getOrCreate(`/organizations/${organization.id}/users/`, { .then(organization => getOrCreate(`/organizations/${organization.id}/users/`, {
username: `user-${uuid().substr(0, 8)}`, username: `${username}`,
organization: organization.id,
first_name: 'firstname',
last_name: 'lastname',
email: 'null@ansible.com',
is_superuser: false,
is_system_auditor: false,
password: AWX_E2E_PASSWORD
}, ['username']));
const getUserExact = (namespace = session, name) => getOrganization(namespace)
.then(organization => getOrCreate(`/organizations/${organization.id}/users/`, {
username: `${name}`,
organization: organization.id, organization: organization.id,
first_name: 'firstname', first_name: 'firstname',
last_name: 'lastname', last_name: 'lastname',
@@ -428,7 +438,7 @@ const getUserExact = (namespace = session, name) => getOrganization(namespace)
* If a job template or organization does not exist with the same * If a job template or organization does not exist with the same
* prefix, they are also created. * prefix, they are also created.
* *
* @param[namespace=session] - A unique name prefix for the template admin. * @param[namespace] - A unique name prefix for the template admin.
*/ */
const getJobTemplateAdmin = (namespace = session) => { const getJobTemplateAdmin = (namespace = session) => {
const rolePromise = getJobTemplate(namespace) const rolePromise = getJobTemplate(namespace)
@@ -457,7 +467,7 @@ const getJobTemplateAdmin = (namespace = session) => {
* If a job template or organization does not exist with the same * If a job template or organization does not exist with the same
* prefix, they are also created. * prefix, they are also created.
* *
* @param[namespace=session] - A unique name prefix for the project admin. * @param[namespace] - A unique name prefix for the project admin.
*/ */
const getProjectAdmin = (namespace = session) => { const getProjectAdmin = (namespace = session) => {
const rolePromise = getUpdatedProject(namespace) const rolePromise = getUpdatedProject(namespace)
@@ -485,7 +495,7 @@ const getProjectAdmin = (namespace = session) => {
/* Retrieves a inventory source schedule, and creates it if it does not exist. /* Retrieves a inventory source schedule, and creates it if it does not exist.
* If an inventory source does not exist with the same prefix, it is also created. * If an inventory source does not exist with the same prefix, it is also created.
* *
* @param[namespace=session] - A unique name prefix for the schedule. * @param[namespace] - A unique name prefix for the schedule.
*/ */
const getInventorySourceSchedule = (namespace = session) => getInventorySource(namespace) const getInventorySourceSchedule = (namespace = session) => getInventorySource(namespace)
.then(source => getOrCreate(source.related.schedules, { .then(source => getOrCreate(source.related.schedules, {
@@ -497,7 +507,7 @@ const getInventorySourceSchedule = (namespace = session) => getInventorySource(n
/* Retrieves a job template schedule, and creates it if it does not exist. /* Retrieves a job template schedule, and creates it if it does not exist.
* If an job template does not exist with the same prefix, it is also created. * If an job template does not exist with the same prefix, it is also created.
* *
* @param[namespace=session] - A unique name prefix for the schedule. * @param[namespace] - A unique name prefix for the schedule.
*/ */
const getJobTemplateSchedule = (namespace = session) => getJobTemplate(namespace) const getJobTemplateSchedule = (namespace = session) => getJobTemplate(namespace)
.then(template => getOrCreate(template.related.schedules, { .then(template => getOrCreate(template.related.schedules, {
@@ -529,6 +539,5 @@ module.exports = {
getTeam, getTeam,
getUpdatedProject, getUpdatedProject,
getUser, getUser,
getUserExact,
getWorkflowTemplate, getWorkflowTemplate,
}; };

View File

@@ -1,6 +1,6 @@
import { import {
getOrganization, getOrganization,
getUserExact, getUser,
getTeam, getTeam,
} from '../fixtures'; } from '../fixtures';
@@ -53,7 +53,7 @@ const readOrgPermissionResults = `//*[@id="permissions_table"]//*[text()="${name
module.exports = { module.exports = {
before: (client, done) => { before: (client, done) => {
const resources = [ const resources = [
getUserExact(namespace, `${namespace}-user`), getUser(namespace, `${namespace}-user`),
getOrganization(namespace), getOrganization(namespace),
getTeam(namespace), getTeam(namespace),
]; ];

View File

@@ -0,0 +1,78 @@
/* Websocket tests. These tests verify that the sparkline (colored box rows which
* display job status) update correctly as the jobs progress.
*/
import {
getInventorySource,
getProject,
getJob
} from '../fixtures';
let data;
const spinny = '//*[contains(@class, "spinny")]';
const dashboard = '//at-side-nav-item[contains(@name, "DASHBOARD")]';
// UI elements for recently run job templates on the dashboard.
const successfulJt = '//a[contains(text(), "test-websockets-successful")]/../..';
const failedJt = '//a[contains(text(), "test-websockets-failed")]/../..';
const sparklineIcon = '//div[contains(@class, "SmartStatus-iconContainer")]';
// Sparkline icon statuses.
// Running is blinking green, successful is green, fail/error/cancellation is red.
const running = '//div[@ng-show="job.status === \'running\'"]';
const success = '//div[contains(@class, "SmartStatus-iconIndicator--success")]';
const fail = '//div[contains(@class, "SmartStatus-iconIndicator--failed")]';
module.exports = {
before: (client, done) => {
// Jobs only display on the dashboard if they have been run at least once.
const resources = [
getInventorySource('test-websockets'),
getProject('test-websockets', 'https://github.com/ansible/test-playbooks'),
// launch job templates once before running tests.
getJob('test-websockets', 'debug.yml', 'test-websockets-successful', done),
getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed', done)
];
Promise.all(resources)
.then(([inventory, project, jt1, jt2]) => {
data = { inventory, project, jt1, jt2 };
done();
});
client
.login()
.waitForAngular()
.resizeWindow(1200, 1000);
},
'Test job template status updates for a successful job on dashboard': client => {
client.useXpath().findThenClick(dashboard);
getJob('test-websockets', 'debug.yml', 'test-websockets-successful');
client.expect.element(spinny).to.not.be.visible.before(5000);
client.expect.element(`${sparklineIcon}[1]${running}`)
.to.be.visible.before(5000);
// Allow a maximum amount of 30 seconds for the job to complete.
client.expect.element(`${successfulJt}${sparklineIcon}[1]${success}`)
.to.be.present.after(30000);
},
'Test job template status updates for a failed job on dashboard': client => {
client.useXpath().findThenClick(dashboard);
getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed');
client.expect.element(spinny).to.not.be.visible.before(5000);
client.expect.element(`${sparklineIcon}[1]${running}`)
.to.be.visible.before(5000);
// Allow a maximum amount of 30 seconds for the job to complete.
client.expect.element(`${failedJt}${sparklineIcon}[1]${fail}`)
.to.be.present.after(30000);
},
after: client => {
client.end();
}
};