diff --git a/awx/ui/test/e2e/README.md b/awx/ui/test/e2e/README.md index f7a9a2e65b..b0ac5ba1a0 100644 --- a/awx/ui/test/e2e/README.md +++ b/awx/ui/test/e2e/README.md @@ -43,3 +43,14 @@ npm --prefix awx/ui run e2e -- --filter="test-credentials*" **Note:** - Use `npm --prefix awx/ui run e2e -- --help` to see additional usage information for the test runner. - All example commands in this document assume that you are working from the root directory of the awx project. + +#### File Overview +All nightwatch.js tests are present in the `tests` directory. When writing +these tests, you may import needed functions from [fixtures.js](fixtures.js), which provides a convenient way to create resources needed for tests +via API, which might include organizations, users, and job templates. + +The `commands` directory provides extra functions for the client object in +nightwatch.js tests. These functions are automatically made available for use by the +client object. For more information on these functions and how to +create your own, refer to the [nightwatch.js documentation on custom commands] +(http://nightwatchjs.org/guide/#writing-custom-commands). diff --git a/awx/ui/test/e2e/commands/findThenClick.js b/awx/ui/test/e2e/commands/findThenClick.js index fd8581623e..558ec26944 100644 --- a/awx/ui/test/e2e/commands/findThenClick.js +++ b/awx/ui/test/e2e/commands/findThenClick.js @@ -1,5 +1,9 @@ const spinny = "//*[contains(@class, 'spinny')]"; +/* Utility function for clicking elements; attempts to scroll to + * the element if necessary, and waits for the page to finish loading. + * + * @param selector - xpath of the element to click. */ exports.command = function findThenClick (selector) { this.waitForElementPresent(selector, () => { this.moveToElement(selector, 0, 0, () => { diff --git a/awx/ui/test/e2e/commands/waitForAngular.js b/awx/ui/test/e2e/commands/waitForAngular.js index bad16b09cf..4daf737dea 100644 --- a/awx/ui/test/e2e/commands/waitForAngular.js +++ b/awx/ui/test/e2e/commands/waitForAngular.js @@ -1,5 +1,6 @@ import { AWX_E2E_TIMEOUT_ASYNC } from '../settings'; +/* Post-login utility function that waits for the application to fully load. */ exports.command = function waitForAngular (callback) { this.timeoutsAsyncScript(AWX_E2E_TIMEOUT_ASYNC, () => { this.executeAsync(done => { diff --git a/awx/ui/test/e2e/fixtures.js b/awx/ui/test/e2e/fixtures.js index 56c012fb28..8130a32133 100644 --- a/awx/ui/test/e2e/fixtures.js +++ b/awx/ui/test/e2e/fixtures.js @@ -10,6 +10,16 @@ import { const session = `e2e-${uuid().substr(0, 8)}`; const store = {}; +/* Utility function for accessing awx resources. This includes resources like + * users, organizations, and job templates. Retrieves the end point, and creates + * it if it does not exist. + * + * @param endpoint - The REST API url suffix. + * @param data - Attributes used to create a new endpoint. + * @param [unique=['name']] - An array of keys used to uniquely identify previously + * created resources from the endpoint. + * + */ const getOrCreate = (endpoint, data, unique = ['name']) => { const identifiers = Object.keys(data).filter(key => unique.indexOf(key) > -1); @@ -40,11 +50,22 @@ const getOrCreate = (endpoint, data, unique = ['name']) => { return store[lookup].then(created => created.data); }; +/* Retrieves an organization, and creates it if it does not exist. + * + * @param [namespace=session] - A unique name prefix for the organization. + * + */ const getOrganization = (namespace = session) => getOrCreate('/organizations/', { name: `${namespace}-organization`, description: namespace }); +/* Retrieves an inventory, and creates it if it does not exist. + * Also creates an organization with the same name prefix if needed. + * + * @param [namespace=session] - A unique name prefix for the inventory. + * + */ const getInventory = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/inventories/', { name: `${namespace}-inventory`, @@ -57,6 +78,11 @@ const getInventory = (namespace = session) => getOrganization(namespace) variables: JSON.stringify({ ansible_connection: 'local' }), }, ['name', 'inventory']).then(() => inventory))); +/* Identical to getInventory except it provides a unique suffix, + * "*-inventory-nosource". + * + * @param[namespace=session] - A unique name prefix for the inventory. +*/ const getInventoryNoSource = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/inventories/', { name: `${namespace}-inventory-nosource`, @@ -69,6 +95,11 @@ const getInventoryNoSource = (namespace = session) => getOrganization(namespace) variables: JSON.stringify({ ansible_connection: 'local' }), }, ['name', 'inventory']).then(() => inventory))); +/* 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. + * + * @param[namespace=session] - A unique name prefix for the host. + */ const getHost = (namespace = session) => getInventory(namespace) .then(inventory => getOrCreate('/hosts/', { name: `${namespace}-host`, @@ -77,6 +108,12 @@ const getHost = (namespace = session) => getInventory(namespace) variables: JSON.stringify({ ansible_connection: 'local' }), }, ['name', 'inventory'])); +/* Retrieves an inventory script with the given name prefix, and creates it if it + * does not exist. If an organization does not exist with the same prefix, it is + * created as well. + * + * @param[namespace=session] - A unique name prefix for the host. + */ const getInventoryScript = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/inventory_scripts/', { name: `${namespace}-inventory-script`, @@ -85,6 +122,12 @@ const getInventoryScript = (namespace = session) => getOrganization(namespace) script: '#!/usr/bin/env python' })); +/* Retrieves an inventory source, and creates it if it does not exist. If the + * required dependent inventory and inventory script do not exist, they are also + * created. + * + * @param[namespace=session] - A unique name prefix for the inventory source. + */ const getInventorySource = (namespace = session) => { const promises = [ getInventory(namespace), @@ -101,6 +144,10 @@ const getInventorySource = (namespace = session) => { })); }; +/* Retrieves an AWS credential, and creates it if it does not exist. + * + * @param[namespace=session] - A unique name prefix for the AWS credential. + */ const getAdminAWSCredential = (namespace = session) => { const promises = [ get('/me/'), @@ -127,6 +174,10 @@ const getAdminAWSCredential = (namespace = session) => { }); }; +/* Retrieves a machine credential, and creates it if it does not exist. + * + * @param[namespace=session] - A unique name prefix for the machine credential. + */ const getAdminMachineCredential = (namespace = session) => { const promises = [ get('/me/'), @@ -145,6 +196,12 @@ const getAdminMachineCredential = (namespace = session) => { }); }; +/* Retrieves a team, and creates it if it does not exist. + * If an organization does not exist with the same prefix, it is + * created as well. + * + * @param[namespace=session] - A unique name prefix for the team. + */ const getTeam = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate(`/organizations/${organization.id}/teams/`, { name: `${namespace}-team`, @@ -152,6 +209,12 @@ const getTeam = (namespace = session) => getOrganization(namespace) organization: organization.id, })); +/* Retrieves a smart inventory, and creates it if it does not exist. + * name prefix. If an organization does not exist with the same prefix, it is + * created as well. + * + * @param[namespace=session] - A unique name prefix for the smart inventory. + */ const getSmartInventory = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/inventories/', { name: `${namespace}-smart-inventory`, @@ -161,6 +224,12 @@ const getSmartInventory = (namespace = session) => getOrganization(namespace) kind: 'smart' })); +/* Retrieves a notification template, and creates it if it does not exist. + * name prefix. If an organization does not exist with the same prefix, it is + * created as well. + * + * @param[namespace=session] - A unique name prefix for the notification template. + */ const getNotificationTemplate = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate(`/organizations/${organization.id}/notification_templates/`, { name: `${namespace}-notification-template`, @@ -173,6 +242,12 @@ const getNotificationTemplate = (namespace = session) => getOrganization(namespa } })); +/* Retrieves a project, and creates it if it does not exist. + * name prefix. If an organization does not exist with the same prefix, it is + * created as well. + * + * @param[namespace=session] - A unique name prefix for the host. + */ const getProject = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate(`/organizations/${organization.id}/projects/`, { name: `${namespace}-project`, @@ -218,6 +293,12 @@ const getUpdatedProject = (namespace = session) => getProject(namespace) return project; }); +/* Retrieves a job template, and creates it if it does not exist. + * name prefix. This function also runs getOrCreate for an inventory, + * credential, and project with the same prefix. + * + * @param[namespace=session] - A unique name prefix for the job template. + */ const getJobTemplate = (namespace = session) => { const promises = [ getInventory(namespace), @@ -236,6 +317,10 @@ const getJobTemplate = (namespace = session) => { })); }; +/* Similar to getJobTemplate, except that it also launches the job. + * + * @param[namespace=session] - A unique name prefix for the host. + */ const getJob = (namespace = session) => getJobTemplate(namespace) .then(template => { const launchURL = template.related.launch; @@ -245,6 +330,12 @@ const getJob = (namespace = session) => getJobTemplate(namespace) }); }); +/* Retrieves a workflow template, and creates it if it does not exist. + * 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. + * + * @param[namespace=session] - A unique name prefix for the workflow template. + */ const getWorkflowTemplate = (namespace = session) => { const workflowTemplatePromise = getOrganization(namespace) .then(organization => getOrCreate(`/organizations/${organization.id}/workflow_job_templates/`, { @@ -285,6 +376,12 @@ const getWorkflowTemplate = (namespace = session) => { .then(([workflowTemplate, nodes]) => workflowTemplate); }; +/* Retrieves a auditor user, and creates it if it does not exist. + * name prefix. If an organization does not exist with the same prefix, + * it is also created. + * + * @param[namespace=session] - A unique name prefix for the auditor. + */ const getAuditor = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate(`/organizations/${organization.id}/users/`, { username: `auditor-${uuid().substr(0, 8)}`, @@ -297,6 +394,12 @@ const getAuditor = (namespace = session) => getOrganization(namespace) password: AWX_E2E_PASSWORD }, ['username'])); +/* Retrieves a user, and creates it if it does not exist. + * name prefix. If an organization does not exist with the same prefix, + * it is also created. + * + * @param[namespace=session] - A unique name prefix for the user. + */ const getUser = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate(`/organizations/${organization.id}/users/`, { username: `user-${uuid().substr(0, 8)}`, @@ -321,6 +424,12 @@ const getUserExact = (namespace = session, name) => getOrganization(namespace) password: AWX_E2E_PASSWORD }, ['username'])); +/* Retrieves a job template admin, and creates it if it does not exist. + * If a job template or organization does not exist with the same + * prefix, they are also created. + * + * @param[namespace=session] - A unique name prefix for the template admin. + */ const getJobTemplateAdmin = (namespace = session) => { const rolePromise = getJobTemplate(namespace) .then(obj => obj.summary_fields.object_roles.admin_role); @@ -344,6 +453,12 @@ const getJobTemplateAdmin = (namespace = session) => { .then(([user, assignment]) => user); }; +/* Retrieves a project admin, and creates it if it does not exist. + * If a job template or organization does not exist with the same + * prefix, they are also created. + * + * @param[namespace=session] - A unique name prefix for the project admin. + */ const getProjectAdmin = (namespace = session) => { const rolePromise = getUpdatedProject(namespace) .then(obj => obj.summary_fields.object_roles.admin_role); @@ -367,6 +482,11 @@ const getProjectAdmin = (namespace = session) => { .then(([user, assignment]) => user); }; +/* 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. + * + * @param[namespace=session] - A unique name prefix for the schedule. + */ const getInventorySourceSchedule = (namespace = session) => getInventorySource(namespace) .then(source => getOrCreate(source.related.schedules, { name: `${source.name}-schedule`, @@ -374,6 +494,11 @@ const getInventorySourceSchedule = (namespace = session) => getInventorySource(n rrule: 'DTSTART:20171104T040000Z RRULE:FREQ=DAILY;INTERVAL=1;COUNT=1' })); +/* 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. + * + * @param[namespace=session] - A unique name prefix for the schedule. + */ const getJobTemplateSchedule = (namespace = session) => getJobTemplate(namespace) .then(template => getOrCreate(template.related.schedules, { name: `${template.name}-schedule`,