mirror of
https://github.com/ansible/awx.git
synced 2026-05-07 09:27:36 -02:30
Merge pull request #257 from mabashian/auditor-read-only-e2e
Added e2e tests for auditor read-only forms
This commit is contained in:
@@ -17,7 +17,7 @@ docker-compose \
|
|||||||
up --scale chrome=2 --scale firefox=0
|
up --scale chrome=2 --scale firefox=0
|
||||||
|
|
||||||
# run headlessly with multiple workers on the cluster
|
# run headlessly with multiple workers on the cluster
|
||||||
AWX_E2E_URL='https://awx:8043' AWX_E2E_WORKERS=2 npm --prefix awx/ui run e2e
|
AWX_E2E_LAUNCH_URL='https://awx:8043' AWX_E2E_WORKERS=2 npm --prefix awx/ui run e2e
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** Unless overridden in [settings](settings.js), tests will run against `localhost:8043`.
|
**Note:** Unless overridden in [settings](settings.js), tests will run against `localhost:8043`.
|
||||||
|
|||||||
104
awx/ui/client/test/e2e/api.js
Normal file
104
awx/ui/client/test/e2e/api.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
import https from 'https';
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
import {
|
||||||
|
awxURL,
|
||||||
|
awxUsername,
|
||||||
|
awxPassword
|
||||||
|
} from './settings.js';
|
||||||
|
|
||||||
|
|
||||||
|
let authenticated;
|
||||||
|
|
||||||
|
const session = axios.create({
|
||||||
|
baseURL: awxURL,
|
||||||
|
xsrfHeaderName: 'X-CSRFToken',
|
||||||
|
xsrfCookieName: 'csrftoken',
|
||||||
|
httpsAgent: new https.Agent({
|
||||||
|
rejectUnauthorized: false
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const endpoint = function(location) {
|
||||||
|
|
||||||
|
if (location.indexOf('/api/v') === 0) {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location.indexOf('://') > 0) {
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${awxURL}/api/v2${location}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const authenticate = function() {
|
||||||
|
if (authenticated) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
let uri = endpoint('/authtoken/');
|
||||||
|
|
||||||
|
let credentials = {
|
||||||
|
username: awxUsername,
|
||||||
|
password: awxPassword
|
||||||
|
};
|
||||||
|
|
||||||
|
return session.post(uri, credentials).then(res => {
|
||||||
|
session.defaults.headers.Authorization = `Token ${res.data.token}`;
|
||||||
|
authenticated = true;
|
||||||
|
return res
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const request = function(method, location, data) {
|
||||||
|
let uri = endpoint(location);
|
||||||
|
let action = session[method.toLowerCase()];
|
||||||
|
|
||||||
|
return authenticate().then(() => action(uri, data)).then(res => {
|
||||||
|
console.log([
|
||||||
|
res.config.method.toUpperCase(),
|
||||||
|
uri,
|
||||||
|
res.status,
|
||||||
|
res.statusText
|
||||||
|
].join(' '));
|
||||||
|
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const get = function(endpoint, data) {
|
||||||
|
return request('GET', endpoint, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const options = function(endpoint) {
|
||||||
|
return request('OPTIONS', endpoint);
|
||||||
|
};
|
||||||
|
|
||||||
|
const post = function(endpoint, data) {
|
||||||
|
return request('POST', endpoint, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const patch = function(endpoint, data) {
|
||||||
|
return request('PATCH', endpoint, data)
|
||||||
|
};
|
||||||
|
|
||||||
|
const put = function(endpoint, data) {
|
||||||
|
return request('PUT', endpoint, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
get,
|
||||||
|
options,
|
||||||
|
post,
|
||||||
|
patch,
|
||||||
|
put,
|
||||||
|
all: axios.all,
|
||||||
|
spread: axios.spread
|
||||||
|
};
|
||||||
@@ -26,7 +26,7 @@ Login.prototype.command = function(username, password) {
|
|||||||
.waitForElementVisible('div.spinny')
|
.waitForElementVisible('div.spinny')
|
||||||
.waitForElementNotVisible('div.spinny');
|
.waitForElementNotVisible('div.spinny');
|
||||||
|
|
||||||
// tempoary hack while login issue is resolved
|
// temporary hack while login issue is resolved
|
||||||
this.api.elements('css selector', '.LoginModal-alert', result => {
|
this.api.elements('css selector', '.LoginModal-alert', result => {
|
||||||
let alertVisible = false;
|
let alertVisible = false;
|
||||||
result.value.map(i => i.ELEMENT).forEach(id => {
|
result.value.map(i => i.ELEMENT).forEach(id => {
|
||||||
|
|||||||
263
awx/ui/client/test/e2e/fixtures.js
Normal file
263
awx/ui/client/test/e2e/fixtures.js
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
import uuid from 'uuid';
|
||||||
|
|
||||||
|
import {
|
||||||
|
all,
|
||||||
|
get,
|
||||||
|
post,
|
||||||
|
spread
|
||||||
|
} from './api.js';
|
||||||
|
|
||||||
|
|
||||||
|
const sid = uuid().substr(0,8);
|
||||||
|
|
||||||
|
let store = {};
|
||||||
|
|
||||||
|
|
||||||
|
const getOrCreate = function(endpoint, data) {
|
||||||
|
let identifier = Object.keys(data).find(key => ['name', 'username'].includes(key));
|
||||||
|
|
||||||
|
if (identifier === undefined) {
|
||||||
|
throw new Error('A unique key value must be provided.');
|
||||||
|
}
|
||||||
|
|
||||||
|
let identity = data[identifier];
|
||||||
|
|
||||||
|
if (store[endpoint] && store[endpoint][identity]) {
|
||||||
|
return store[endpoint][identity].then(created => created.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!store[endpoint]) {
|
||||||
|
store[endpoint] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = { params: { [identifier]: identity } };
|
||||||
|
|
||||||
|
store[endpoint][identity] = get(endpoint, query).then(res => {
|
||||||
|
|
||||||
|
if (res.data.results.length > 1) {
|
||||||
|
return Promise.reject(new Error('More than one matching result.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.data.results.length === 1) {
|
||||||
|
return get(res.data.results[0].url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res.data.results.length === 0) {
|
||||||
|
return post(endpoint, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(new Error(`unexpected response: ${res}`));
|
||||||
|
});
|
||||||
|
|
||||||
|
return store[endpoint][identity].then(created => created.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getOrganization = function() {
|
||||||
|
return getOrCreate('/organizations/', {
|
||||||
|
name: `e2e-organization-${sid}`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getInventory = function() {
|
||||||
|
return getOrganization().then(organization => {
|
||||||
|
return getOrCreate('/inventories/', {
|
||||||
|
name: `e2e-inventory-${sid}`,
|
||||||
|
organization: organization.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getInventoryScript = function() {
|
||||||
|
return getOrganization().then(organization => {
|
||||||
|
return getOrCreate('/inventory_scripts/', {
|
||||||
|
name: `e2e-inventory-script-${sid}`,
|
||||||
|
organization: organization.id,
|
||||||
|
script: '#!/usr/bin/env python'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getAdminAWSCredential = function() {
|
||||||
|
return all([
|
||||||
|
get('/me/'),
|
||||||
|
getOrCreate('/credential_types/', {
|
||||||
|
name: "Amazon Web Services"
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.then(spread((me, credentialType) => {
|
||||||
|
let admin = me.data.results[0];
|
||||||
|
return getOrCreate('/credentials/', {
|
||||||
|
name: `e2e-aws-credential-${sid}`,
|
||||||
|
credential_type: credentialType.id,
|
||||||
|
user: admin.id,
|
||||||
|
inputs: {
|
||||||
|
username: 'admin',
|
||||||
|
password: 'password',
|
||||||
|
security_token: 'AAAAAAAAAAAAAAAA'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getAdminMachineCredential = function() {
|
||||||
|
return all([
|
||||||
|
get('/me/'),
|
||||||
|
getOrCreate('/credential_types/', { name: "Machine" })
|
||||||
|
])
|
||||||
|
.then(spread((me, credentialType) => {
|
||||||
|
let admin = me.data.results[0];
|
||||||
|
return getOrCreate('/credentials/', {
|
||||||
|
name: `e2e-machine-credential-${sid}`,
|
||||||
|
credential_type: credentialType.id,
|
||||||
|
user: admin.id
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getTeam = function() {
|
||||||
|
return getOrganization().then(organization => {
|
||||||
|
return getOrCreate('/teams/', {
|
||||||
|
name: `e2e-team-${sid}`,
|
||||||
|
organization: organization.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getSmartInventory = function() {
|
||||||
|
return getOrganization().then(organization => {
|
||||||
|
return getOrCreate('/inventories/', {
|
||||||
|
name: `e2e-smart-inventory-${sid}`,
|
||||||
|
organization: organization.id,
|
||||||
|
host_filter: 'search=localhost',
|
||||||
|
kind: 'smart'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getNotificationTemplate = function() {
|
||||||
|
return getOrganization().then(organization => {
|
||||||
|
return getOrCreate('/notification_templates/', {
|
||||||
|
name: `e2e-notification-template-${sid}`,
|
||||||
|
organization: organization.id,
|
||||||
|
notification_type: 'slack',
|
||||||
|
notification_configuration: {
|
||||||
|
token: '54321GFEDCBAABCDEFG12345',
|
||||||
|
channels: ['awx-e2e']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getProject = function() {
|
||||||
|
return getOrganization().then(organization => {
|
||||||
|
return getOrCreate('/projects/', {
|
||||||
|
name: `e2e-project-${sid}`,
|
||||||
|
organization: organization.id,
|
||||||
|
scm_url: 'https://github.com/ansible/ansible-tower-samples',
|
||||||
|
scm_type: 'git'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const waitForJob = function(endpoint) {
|
||||||
|
const interval = 2000;
|
||||||
|
const statuses = ['successful', 'failed', 'error', 'canceled'];
|
||||||
|
|
||||||
|
let attempts = 20;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
(function pollStatus() {
|
||||||
|
get(endpoint).then(update => {
|
||||||
|
let completed = statuses.indexOf(update.data.status) > -1;
|
||||||
|
if (completed) return resolve();
|
||||||
|
if (--attempts <= 0) return reject('Retry limit exceeded.');
|
||||||
|
setTimeout(pollStatus, interval);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getUpdatedProject = function() {
|
||||||
|
return getProject().then(project => {
|
||||||
|
let updateURL = project.related.current_update;
|
||||||
|
if (updateURL) {
|
||||||
|
return waitForJob(updateURL).then(() => project);
|
||||||
|
}
|
||||||
|
return project;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getJobTemplate = function() {
|
||||||
|
return all([
|
||||||
|
getInventory(),
|
||||||
|
getAdminMachineCredential(),
|
||||||
|
getUpdatedProject()
|
||||||
|
])
|
||||||
|
.then(spread((inventory, credential, project) => {
|
||||||
|
return getOrCreate('/job_templates', {
|
||||||
|
name: `e2e-job-template-${sid}`,
|
||||||
|
inventory: inventory.id,
|
||||||
|
credential: credential.id,
|
||||||
|
project: project.id,
|
||||||
|
playbook: 'hello_world.yml'
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getAuditor = function() {
|
||||||
|
return getOrganization().then(organization => {
|
||||||
|
return getOrCreate('/users/', {
|
||||||
|
organization: organization.id,
|
||||||
|
username: `e2e-auditor-${sid}`,
|
||||||
|
first_name: 'auditor',
|
||||||
|
last_name: 'last',
|
||||||
|
email: 'null@ansible.com',
|
||||||
|
is_superuser: false,
|
||||||
|
is_system_auditor: true,
|
||||||
|
password: 'password'
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const getUser = function() {
|
||||||
|
return getOrCreate('/users/', {
|
||||||
|
username: `e2e-user-${sid}`,
|
||||||
|
first_name: `user-${sid}-first`,
|
||||||
|
last_name: `user-${sid}-last`,
|
||||||
|
email: `null-${sid}@ansible.com`,
|
||||||
|
is_superuser: false,
|
||||||
|
is_system_auditor: false,
|
||||||
|
password: 'password'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAdminAWSCredential,
|
||||||
|
getAdminMachineCredential,
|
||||||
|
getAuditor,
|
||||||
|
getInventory,
|
||||||
|
getInventoryScript,
|
||||||
|
getJobTemplate,
|
||||||
|
getNotificationTemplate,
|
||||||
|
getOrCreate,
|
||||||
|
getOrganization,
|
||||||
|
getSmartInventory,
|
||||||
|
getTeam,
|
||||||
|
getUpdatedProject,
|
||||||
|
getUser
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
url() {
|
url() {
|
||||||
return `${this.api.globals.awxURL}/#/activity_stream`
|
return `${this.api.globals.launch_url}/#/activity_stream`
|
||||||
},
|
},
|
||||||
elements: {
|
elements: {
|
||||||
title: '.List-titleText',
|
title: '.List-titleText',
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const listPanel = {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
url() {
|
url() {
|
||||||
return `${this.api.globals.awxURL}/#/credential_types`
|
return `${this.api.globals.launch_url}/#/credential_types`
|
||||||
},
|
},
|
||||||
sections: {
|
sections: {
|
||||||
header,
|
header,
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ const details = _.merge({}, common, {
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
url() {
|
url() {
|
||||||
return `${this.api.globals.awxURL}/#/credentials`
|
return `${this.api.globals.launch_url}/#/credentials`
|
||||||
},
|
},
|
||||||
sections: {
|
sections: {
|
||||||
header,
|
header,
|
||||||
|
|||||||
113
awx/ui/client/test/e2e/objects/inventories.js
Normal file
113
awx/ui/client/test/e2e/objects/inventories.js
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import actions from './sections/actions.js';
|
||||||
|
import breadcrumb from './sections/breadcrumb.js';
|
||||||
|
import createFormSection from './sections/createFormSection.js';
|
||||||
|
import createTableSection from './sections/createTableSection.js';
|
||||||
|
import header from './sections/header.js';
|
||||||
|
import lookupModal from './sections/lookupModal.js';
|
||||||
|
import navigation from './sections/navigation.js';
|
||||||
|
import pagination from './sections/pagination.js';
|
||||||
|
import permissions from './sections/permissions.js';
|
||||||
|
import search from './sections/search.js';
|
||||||
|
|
||||||
|
const standardInvDetails = createFormSection({
|
||||||
|
selector: 'form',
|
||||||
|
props: {
|
||||||
|
formElementSelectors: [
|
||||||
|
'#inventory_form .Form-textInput',
|
||||||
|
'#inventory_form select.Form-dropDown',
|
||||||
|
'#inventory_form .Form-textArea',
|
||||||
|
'#inventory_form input[type="checkbox"]',
|
||||||
|
'#inventory_form .ui-spinner-input',
|
||||||
|
'#inventory_form .ScheduleToggle-switch'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const smartInvDetails = createFormSection({
|
||||||
|
selector: 'form',
|
||||||
|
props: {
|
||||||
|
formElementSelectors: [
|
||||||
|
'#smartinventory_form input.Form-textInput',
|
||||||
|
'#smartinventory_form textarea.Form-textArea',
|
||||||
|
'#smartinventory_form .Form-lookupButton',
|
||||||
|
'#smartinventory_form #InstanceGroups'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
url() {
|
||||||
|
return `${this.api.globals.launch_url}/#/inventories`;
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
header,
|
||||||
|
navigation,
|
||||||
|
breadcrumb,
|
||||||
|
lookupModal,
|
||||||
|
addStandardInventory: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
standardInvDetails
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editStandardInventory: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
standardInvDetails,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addSmartInventory: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
smartInvDetails
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editSmartInventory: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
smartInvDetails,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
selector: 'div[ui-view="list"]',
|
||||||
|
elements: {
|
||||||
|
badge: 'span[class~="badge"]',
|
||||||
|
title: 'div[class="List-titleText"]',
|
||||||
|
add: 'button[class~="List-dropdownButton"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
search,
|
||||||
|
pagination,
|
||||||
|
table: createTableSection({
|
||||||
|
elements: {
|
||||||
|
status: 'td[class~="status-column"]',
|
||||||
|
name: 'td[class~="name-column"]',
|
||||||
|
kind: 'td[class~="kind-column"]',
|
||||||
|
organization: 'td[class~="organization-column"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
cancel: 'button[class*="Form-cancelButton"]',
|
||||||
|
save: 'button[class*="Form-saveButton"]'
|
||||||
|
}
|
||||||
|
};
|
||||||
77
awx/ui/client/test/e2e/objects/inventoryScripts.js
Normal file
77
awx/ui/client/test/e2e/objects/inventoryScripts.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import actions from './sections/actions.js';
|
||||||
|
import breadcrumb from './sections/breadcrumb.js';
|
||||||
|
import createFormSection from './sections/createFormSection.js';
|
||||||
|
import createTableSection from './sections/createTableSection.js';
|
||||||
|
import header from './sections/header.js';
|
||||||
|
import lookupModal from './sections/lookupModal.js';
|
||||||
|
import navigation from './sections/navigation.js';
|
||||||
|
import pagination from './sections/pagination.js';
|
||||||
|
import permissions from './sections/permissions.js';
|
||||||
|
import search from './sections/search.js';
|
||||||
|
|
||||||
|
const details = createFormSection({
|
||||||
|
selector: 'form',
|
||||||
|
props: {
|
||||||
|
formElementSelectors: [
|
||||||
|
'#inventory_script_form .Form-textInput',
|
||||||
|
'#inventory_script_form .Form-textArea',
|
||||||
|
'#inventory_script_form .Form-lookupButton'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
url() {
|
||||||
|
return `${this.api.globals.launch_url}/#/inventory_scripts`;
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
header,
|
||||||
|
navigation,
|
||||||
|
breadcrumb,
|
||||||
|
lookupModal,
|
||||||
|
add: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
selector: 'div[ui-view="list"]',
|
||||||
|
elements: {
|
||||||
|
badge: 'span[class~="badge"]',
|
||||||
|
title: 'div[class="List-titleText"]',
|
||||||
|
add: 'button[class~="List-buttonSubmit"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
search,
|
||||||
|
pagination,
|
||||||
|
table: createTableSection({
|
||||||
|
elements: {
|
||||||
|
name: 'td[class~="name-column"]',
|
||||||
|
organization: 'td[class~="organization-column"]',
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
cancel: 'button[class*="Form-cancelButton"]',
|
||||||
|
save: 'button[class*="Form-saveButton"]'
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
url() {
|
url() {
|
||||||
return `${this.api.globals.awxURL}/#/login`
|
return `${this.api.globals.launch_url}/#/login`
|
||||||
},
|
},
|
||||||
elements: {
|
elements: {
|
||||||
username: '#login-username',
|
username: '#login-username',
|
||||||
|
|||||||
82
awx/ui/client/test/e2e/objects/notificationTemplates.js
Normal file
82
awx/ui/client/test/e2e/objects/notificationTemplates.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import actions from './sections/actions.js';
|
||||||
|
import breadcrumb from './sections/breadcrumb.js';
|
||||||
|
import createFormSection from './sections/createFormSection.js';
|
||||||
|
import createTableSection from './sections/createTableSection.js';
|
||||||
|
import header from './sections/header.js';
|
||||||
|
import lookupModal from './sections/lookupModal.js';
|
||||||
|
import navigation from './sections/navigation.js';
|
||||||
|
import pagination from './sections/pagination.js';
|
||||||
|
import permissions from './sections/permissions.js';
|
||||||
|
import search from './sections/search.js';
|
||||||
|
|
||||||
|
const details = createFormSection({
|
||||||
|
selector: 'form',
|
||||||
|
props: {
|
||||||
|
formElementSelectors: [
|
||||||
|
'#notification_template_form .Form-textInput',
|
||||||
|
'#notification_template_form select.Form-dropDown',
|
||||||
|
'#notification_template_form input[type="checkbox"]',
|
||||||
|
'#notification_template_form input[type="radio"]',
|
||||||
|
'#notification_template_form .ui-spinner-input',
|
||||||
|
'#notification_template_form .Form-textArea',
|
||||||
|
'#notification_template_form .ScheduleToggle-switch',
|
||||||
|
'#notification_template_form .Form-lookupButton'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
url() {
|
||||||
|
return `${this.api.globals.launch_url}/#/notification_templates`;
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
header,
|
||||||
|
navigation,
|
||||||
|
breadcrumb,
|
||||||
|
lookupModal,
|
||||||
|
add: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
selector: 'div[ui-view="list"]',
|
||||||
|
elements: {
|
||||||
|
badge: 'span[class~="badge"]',
|
||||||
|
title: 'div[class="List-titleText"]',
|
||||||
|
add: 'button[class~="List-buttonSubmit"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
search,
|
||||||
|
pagination,
|
||||||
|
table: createTableSection({
|
||||||
|
elements: {
|
||||||
|
name: 'td[class~="name-column"]',
|
||||||
|
organization: 'td[class~="organization-column"]',
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
cancel: 'button[class*="Form-cancelButton"]',
|
||||||
|
save: 'button[class*="Form-saveButton"]'
|
||||||
|
}
|
||||||
|
};
|
||||||
66
awx/ui/client/test/e2e/objects/organizations.js
Normal file
66
awx/ui/client/test/e2e/objects/organizations.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import breadcrumb from './sections/breadcrumb.js';
|
||||||
|
import createFormSection from './sections/createFormSection.js';
|
||||||
|
import header from './sections/header.js';
|
||||||
|
import lookupModal from './sections/lookupModal.js';
|
||||||
|
import navigation from './sections/navigation.js';
|
||||||
|
import pagination from './sections/pagination.js';
|
||||||
|
import permissions from './sections/permissions.js';
|
||||||
|
import search from './sections/search.js';
|
||||||
|
|
||||||
|
const details = createFormSection({
|
||||||
|
selector: 'form',
|
||||||
|
props: {
|
||||||
|
formElementSelectors: [
|
||||||
|
'#organization_form input.Form-textInput',
|
||||||
|
'#organization_form .Form-lookupButton',
|
||||||
|
'#organization_form #InstanceGroups'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
url() {
|
||||||
|
return `${this.api.globals.launch_url}/#/organizations`;
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
header,
|
||||||
|
navigation,
|
||||||
|
breadcrumb,
|
||||||
|
lookupModal,
|
||||||
|
add: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
selector: '#organizations',
|
||||||
|
elements: {
|
||||||
|
badge: 'span[class~="badge"]',
|
||||||
|
title: 'div[class="List-titleText"]',
|
||||||
|
add: 'button[class~="List-buttonSubmit"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
search,
|
||||||
|
pagination
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
cancel: 'button[class*="Form-cancelButton"]',
|
||||||
|
save: 'button[class*="Form-saveButton"]'
|
||||||
|
}
|
||||||
|
};
|
||||||
80
awx/ui/client/test/e2e/objects/projects.js
Normal file
80
awx/ui/client/test/e2e/objects/projects.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import actions from './sections/actions.js';
|
||||||
|
import breadcrumb from './sections/breadcrumb.js';
|
||||||
|
import createFormSection from './sections/createFormSection.js';
|
||||||
|
import createTableSection from './sections/createTableSection.js';
|
||||||
|
import header from './sections/header.js';
|
||||||
|
import lookupModal from './sections/lookupModal.js';
|
||||||
|
import navigation from './sections/navigation.js';
|
||||||
|
import pagination from './sections/pagination.js';
|
||||||
|
import permissions from './sections/permissions.js';
|
||||||
|
import search from './sections/search.js';
|
||||||
|
|
||||||
|
const details = createFormSection({
|
||||||
|
selector: 'form',
|
||||||
|
props: {
|
||||||
|
formElementSelectors: [
|
||||||
|
'#project_form .Form-textInput',
|
||||||
|
'#project_form select.Form-dropDown',
|
||||||
|
'#project_form input[type="checkbox"]',
|
||||||
|
'#project_form .ui-spinner-input',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
url() {
|
||||||
|
return `${this.api.globals.launch_url}/#/projects`;
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
header,
|
||||||
|
navigation,
|
||||||
|
breadcrumb,
|
||||||
|
lookupModal,
|
||||||
|
add: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
selector: 'div[ui-view="list"]',
|
||||||
|
elements: {
|
||||||
|
badge: 'span[class~="badge"]',
|
||||||
|
title: 'div[class="List-titleText"]',
|
||||||
|
add: 'button[class~="List-buttonSubmit"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
search,
|
||||||
|
pagination,
|
||||||
|
table: createTableSection({
|
||||||
|
elements: {
|
||||||
|
status: 'td[class~="status-column"]',
|
||||||
|
name: 'td[class~="name-column"]',
|
||||||
|
scm_type: 'td[class~="scm_type-column"]',
|
||||||
|
last_updated: 'td[class~="last_updated-column"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
cancel: 'button[class*="Form-cancelButton"]',
|
||||||
|
save: 'button[class*="Form-saveButton"]'
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -73,19 +73,42 @@ const generateInputSelectors = function(label, containerElements) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const checkAllFieldsDisabled = function() {
|
||||||
|
let client = this.client.api;
|
||||||
|
|
||||||
|
let selectors = this.props.formElementSelectors ? this.props.formElementSelectors : [
|
||||||
|
'.at-Input'
|
||||||
|
];
|
||||||
|
|
||||||
|
selectors.forEach(function(selector) {
|
||||||
|
client.elements('css selector', selector, inputs => {
|
||||||
|
inputs.value.map(o => o.ELEMENT).forEach(id => {
|
||||||
|
client.elementIdAttribute(id, 'disabled', ({ value }) => {
|
||||||
|
client.assert.equal(value, 'true');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
const generatorOptions = {
|
const generatorOptions = {
|
||||||
default: inputContainerElements,
|
default: inputContainerElements,
|
||||||
legacy: legacyContainerElements
|
legacy: legacyContainerElements
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const createFormSection = function({ selector, labels, strategy }) {
|
const createFormSection = function({ selector, labels, strategy, props }) {
|
||||||
let options = generatorOptions[strategy || 'default'];
|
let options = generatorOptions[strategy || 'default'];
|
||||||
|
|
||||||
let formSection = {
|
let formSection = {
|
||||||
selector,
|
selector,
|
||||||
sections: {},
|
sections: {},
|
||||||
elements: {}
|
elements: {},
|
||||||
|
commands: [{
|
||||||
|
checkAllFieldsDisabled: checkAllFieldsDisabled
|
||||||
|
}],
|
||||||
|
props: props
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let key in labels) {
|
for (let key in labels) {
|
||||||
@@ -95,7 +118,7 @@ const createFormSection = function({ selector, labels, strategy }) {
|
|||||||
|
|
||||||
formSection.elements[key] = inputElement;
|
formSection.elements[key] = inputElement;
|
||||||
formSection.sections[key] = inputContainer;
|
formSection.sections[key] = inputContainer;
|
||||||
};
|
}
|
||||||
|
|
||||||
return formSection;
|
return formSection;
|
||||||
};
|
};
|
||||||
|
|||||||
76
awx/ui/client/test/e2e/objects/teams.js
Normal file
76
awx/ui/client/test/e2e/objects/teams.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import actions from './sections/actions.js';
|
||||||
|
import breadcrumb from './sections/breadcrumb.js';
|
||||||
|
import createFormSection from './sections/createFormSection.js';
|
||||||
|
import createTableSection from './sections/createTableSection.js';
|
||||||
|
import header from './sections/header.js';
|
||||||
|
import lookupModal from './sections/lookupModal.js';
|
||||||
|
import navigation from './sections/navigation.js';
|
||||||
|
import pagination from './sections/pagination.js';
|
||||||
|
import permissions from './sections/permissions.js';
|
||||||
|
import search from './sections/search.js';
|
||||||
|
|
||||||
|
const details = createFormSection({
|
||||||
|
selector: 'form',
|
||||||
|
props: {
|
||||||
|
formElementSelectors: [
|
||||||
|
'#team_form input.Form-textInput',
|
||||||
|
'#team_form .Form-lookupButton'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
url() {
|
||||||
|
return `${this.api.globals.launch_url}/#/teams`;
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
header,
|
||||||
|
navigation,
|
||||||
|
breadcrumb,
|
||||||
|
lookupModal,
|
||||||
|
add: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
selector: 'div[ui-view="list"]',
|
||||||
|
elements: {
|
||||||
|
badge: 'span[class~="badge"]',
|
||||||
|
title: 'div[class="List-titleText"]',
|
||||||
|
add: 'button[class~="List-buttonSubmit"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
search,
|
||||||
|
pagination,
|
||||||
|
table: createTableSection({
|
||||||
|
elements: {
|
||||||
|
name: 'td[class~="name-column"]',
|
||||||
|
organization: 'td[class~="organization-column"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
cancel: 'button[class*="Form-cancelButton"]',
|
||||||
|
save: 'button[class*="Form-saveButton"]'
|
||||||
|
}
|
||||||
|
};
|
||||||
99
awx/ui/client/test/e2e/objects/templates.js
Normal file
99
awx/ui/client/test/e2e/objects/templates.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import actions from './sections/actions.js';
|
||||||
|
import breadcrumb from './sections/breadcrumb.js';
|
||||||
|
import createFormSection from './sections/createFormSection.js';
|
||||||
|
import createTableSection from './sections/createTableSection.js';
|
||||||
|
import header from './sections/header.js';
|
||||||
|
import lookupModal from './sections/lookupModal.js';
|
||||||
|
import navigation from './sections/navigation.js';
|
||||||
|
import pagination from './sections/pagination.js';
|
||||||
|
import permissions from './sections/permissions.js';
|
||||||
|
import search from './sections/search.js';
|
||||||
|
|
||||||
|
const details = createFormSection({
|
||||||
|
selector: 'form',
|
||||||
|
props: {
|
||||||
|
formElementSelectors: [
|
||||||
|
'#job_template_form .Form-textInput',
|
||||||
|
'#job_template_form select.Form-dropDown',
|
||||||
|
'#job_template_form .Form-textArea',
|
||||||
|
'#job_template_form input[type="checkbox"]',
|
||||||
|
'#job_template_form .ui-spinner-input',
|
||||||
|
'#job_template_form .ScheduleToggle-switch'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
url() {
|
||||||
|
return `${this.api.globals.launch_url}/#/templates`;
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
header,
|
||||||
|
navigation,
|
||||||
|
breadcrumb,
|
||||||
|
lookupModal,
|
||||||
|
addJobTemplate: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editJobTemplate: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addWorkflowJobTemplate: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editWorkflowJobTemplate: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
selector: 'div[ui-view="list"]',
|
||||||
|
elements: {
|
||||||
|
badge: 'span[class~="badge"]',
|
||||||
|
title: 'div[class="List-titleText"]',
|
||||||
|
add: 'button[class~="List-buttonSubmit"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
search,
|
||||||
|
pagination,
|
||||||
|
table: createTableSection({
|
||||||
|
elements: {
|
||||||
|
name: 'td[class~="name-column"]',
|
||||||
|
kind: 'td[class~="type-column"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
cancel: 'button[class*="Form-cancelButton"]',
|
||||||
|
save: 'button[class*="Form-saveButton"]'
|
||||||
|
}
|
||||||
|
};
|
||||||
77
awx/ui/client/test/e2e/objects/users.js
Normal file
77
awx/ui/client/test/e2e/objects/users.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import actions from './sections/actions.js';
|
||||||
|
import breadcrumb from './sections/breadcrumb.js';
|
||||||
|
import createFormSection from './sections/createFormSection.js';
|
||||||
|
import createTableSection from './sections/createTableSection.js';
|
||||||
|
import header from './sections/header.js';
|
||||||
|
import lookupModal from './sections/lookupModal.js';
|
||||||
|
import navigation from './sections/navigation.js';
|
||||||
|
import pagination from './sections/pagination.js';
|
||||||
|
import permissions from './sections/permissions.js';
|
||||||
|
import search from './sections/search.js';
|
||||||
|
|
||||||
|
const details = createFormSection({
|
||||||
|
selector: 'form',
|
||||||
|
props: {
|
||||||
|
formElementSelectors: [
|
||||||
|
'#user_form .Form-textInput',
|
||||||
|
'#user_form select.Form-dropDown'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
url() {
|
||||||
|
return `${this.api.globals.launch_url}/#/users`;
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
header,
|
||||||
|
navigation,
|
||||||
|
breadcrumb,
|
||||||
|
lookupModal,
|
||||||
|
add: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
edit: {
|
||||||
|
selector: 'div[ui-view="form"]',
|
||||||
|
sections: {
|
||||||
|
details,
|
||||||
|
permissions
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
title: 'div[class^="Form-title"]'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
selector: 'div[ui-view="list"]',
|
||||||
|
elements: {
|
||||||
|
badge: 'span[class~="badge"]',
|
||||||
|
title: 'div[class="List-titleText"]',
|
||||||
|
add: 'button[class~="List-buttonSubmit"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
search,
|
||||||
|
pagination,
|
||||||
|
table: createTableSection({
|
||||||
|
elements: {
|
||||||
|
username: 'td[class~="username-column"]',
|
||||||
|
first_name: 'td[class~="first_name-column"]',
|
||||||
|
last_name: 'td[class~="last_name-column"]'
|
||||||
|
},
|
||||||
|
sections: {
|
||||||
|
actions
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
cancel: 'button[class*="Form-cancelButton"]',
|
||||||
|
save: 'button[class*="Form-saveButton"]'
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,6 +3,7 @@ const AWX_E2E_USERNAME = process.env.AWX_E2E_USERNAME || 'awx-e2e';
|
|||||||
const AWX_E2E_PASSWORD = process.env.AWX_E2E_PASSWORD || 'password';
|
const AWX_E2E_PASSWORD = process.env.AWX_E2E_PASSWORD || 'password';
|
||||||
const AWX_E2E_SELENIUM_HOST = process.env.AWX_E2E_SELENIUM_HOST || 'localhost';
|
const AWX_E2E_SELENIUM_HOST = process.env.AWX_E2E_SELENIUM_HOST || 'localhost';
|
||||||
const AWX_E2E_SELENIUM_PORT = process.env.AWX_E2E_SELENIUM_PORT || 4444;
|
const AWX_E2E_SELENIUM_PORT = process.env.AWX_E2E_SELENIUM_PORT || 4444;
|
||||||
|
const AWX_E2E_LAUNCH_URL = process.env.AWX_E2E_LAUNCH_URL || AWX_E2E_URL;
|
||||||
const AWX_E2E_TIMEOUT_SHORT = process.env.AWX_E2E_TIMEOUT_SHORT || 1000;
|
const AWX_E2E_TIMEOUT_SHORT = process.env.AWX_E2E_TIMEOUT_SHORT || 1000;
|
||||||
const AWX_E2E_TIMEOUT_MEDIUM = process.env.AWX_E2E_TIMEOUT_MEDIUM || 5000;
|
const AWX_E2E_TIMEOUT_MEDIUM = process.env.AWX_E2E_TIMEOUT_MEDIUM || 5000;
|
||||||
const AWX_E2E_TIMEOUT_LONG = process.env.AWX_E2E_TIMEOUT_LONG || 10000;
|
const AWX_E2E_TIMEOUT_LONG = process.env.AWX_E2E_TIMEOUT_LONG || 10000;
|
||||||
@@ -20,6 +21,7 @@ module.exports = {
|
|||||||
retryAssertionTimeout: AWX_E2E_TIMEOUT_MEDIUM,
|
retryAssertionTimeout: AWX_E2E_TIMEOUT_MEDIUM,
|
||||||
selenium_host: AWX_E2E_SELENIUM_HOST,
|
selenium_host: AWX_E2E_SELENIUM_HOST,
|
||||||
selenium_port: AWX_E2E_SELENIUM_PORT,
|
selenium_port: AWX_E2E_SELENIUM_PORT,
|
||||||
|
launch_url: AWX_E2E_LAUNCH_URL,
|
||||||
shortTimeout: AWX_E2E_TIMEOUT_SHORT,
|
shortTimeout: AWX_E2E_TIMEOUT_SHORT,
|
||||||
waitForConditionTimeout: AWX_E2E_TIMEOUT_MEDIUM,
|
waitForConditionTimeout: AWX_E2E_TIMEOUT_MEDIUM,
|
||||||
test_workers: {
|
test_workers: {
|
||||||
|
|||||||
186
awx/ui/client/test/e2e/tests/test-auditor-read-only-forms.js
Normal file
186
awx/ui/client/test/e2e/tests/test-auditor-read-only-forms.js
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
import { all } from '../api.js';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getAdminAWSCredential,
|
||||||
|
getAdminMachineCredential,
|
||||||
|
getAuditor,
|
||||||
|
getInventory,
|
||||||
|
getInventoryScript,
|
||||||
|
getNotificationTemplate,
|
||||||
|
getOrCreate,
|
||||||
|
getOrganization,
|
||||||
|
getSmartInventory,
|
||||||
|
getTeam,
|
||||||
|
getUpdatedProject,
|
||||||
|
getUser
|
||||||
|
} from '../fixtures.js';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
let data = {};
|
||||||
|
|
||||||
|
let credentials,
|
||||||
|
inventoryScripts,
|
||||||
|
templates,
|
||||||
|
notificationTemplates,
|
||||||
|
organizations,
|
||||||
|
projects,
|
||||||
|
users,
|
||||||
|
inventories,
|
||||||
|
teams;
|
||||||
|
|
||||||
|
|
||||||
|
function navigateAndWaitForSpinner(client, url) {
|
||||||
|
client
|
||||||
|
.url(url)
|
||||||
|
.waitForElementVisible('div.spinny')
|
||||||
|
.waitForElementNotVisible('div.spinny');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
before: function (client, done) {
|
||||||
|
all([
|
||||||
|
getAuditor().then(obj => data.auditor = obj),
|
||||||
|
getOrganization().then(obj => data.organization = obj),
|
||||||
|
getInventory().then(obj => data.inventory = obj),
|
||||||
|
getInventoryScript().then(obj => data.inventoryScript = obj),
|
||||||
|
getAdminAWSCredential().then(obj => data.adminAWSCredential = obj),
|
||||||
|
getAdminMachineCredential().then(obj => data.adminMachineCredential = obj),
|
||||||
|
getSmartInventory().then(obj => data.smartInventory = obj),
|
||||||
|
getTeam().then(obj => data.team = obj),
|
||||||
|
getUser().then(obj => data.user = obj),
|
||||||
|
getNotificationTemplate().then(obj => data.notificationTemplate = obj),
|
||||||
|
getUpdatedProject().then(obj => data.project = obj)
|
||||||
|
])
|
||||||
|
.then(() => {
|
||||||
|
client.useCss();
|
||||||
|
|
||||||
|
credentials = client.page.credentials();
|
||||||
|
inventoryScripts = client.page.inventoryScripts();
|
||||||
|
templates = client.page.templates();
|
||||||
|
notificationTemplates = client.page.notificationTemplates();
|
||||||
|
organizations = client.page.organizations();
|
||||||
|
projects = client.page.projects();
|
||||||
|
users = client.page.users();
|
||||||
|
inventories = client.page.inventories();
|
||||||
|
teams = client.page.teams();
|
||||||
|
|
||||||
|
client.login(data.auditor.username, data.auditor.password);
|
||||||
|
client.waitForAngular();
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
'verify an auditor\'s credentials inputs are read-only': function (client) {
|
||||||
|
navigateAndWaitForSpinner(client, `${credentials.url()}/${data.adminAWSCredential.id}/`);
|
||||||
|
|
||||||
|
credentials.section.edit
|
||||||
|
.expect.element('@title').text.contain(data.adminAWSCredential.name);
|
||||||
|
|
||||||
|
credentials.section.edit.section.details.checkAllFieldsDisabled();
|
||||||
|
},
|
||||||
|
'verify an auditor\'s inventory scripts inputs are read-only': function (client) {
|
||||||
|
navigateAndWaitForSpinner(client, `${inventoryScripts.url()}/${data.inventoryScript.id}/`);
|
||||||
|
|
||||||
|
inventoryScripts.section.edit
|
||||||
|
.expect.element('@title').text.contain(data.inventoryScript.name);
|
||||||
|
|
||||||
|
inventoryScripts.section.edit.section.details.checkAllFieldsDisabled();
|
||||||
|
},
|
||||||
|
'verify save button hidden from auditor on inventory scripts form': function () {
|
||||||
|
inventoryScripts.expect.element('@save').to.not.be.visible;
|
||||||
|
},
|
||||||
|
// TODO: re-enable these tests when JT edit has been re-factored to reliably show/remove the loading spinner
|
||||||
|
// only one time. Without this, we can't tell when all the requisite data is available.
|
||||||
|
// 'verify an auditor\'s job template inputs are read-only': function (client) {
|
||||||
|
// navigateAndWaitForSpinner(client, `${templates.url()}/job_template/${data.jobTemplate.id}/`);
|
||||||
|
//
|
||||||
|
// templates.section.editJobTemplate
|
||||||
|
// .expect.element('@title').text.contain(data.jobTemplate.name);
|
||||||
|
//
|
||||||
|
// templates.section.edit.section.details.checkAllFieldsDisabled();
|
||||||
|
// },
|
||||||
|
// 'verify save button hidden from auditor on job templates form': function () {
|
||||||
|
// templates.expect.element('@save').to.not.be.visible;
|
||||||
|
// },
|
||||||
|
'verify an auditor\'s notification templates inputs are read-only': function (client) {
|
||||||
|
navigateAndWaitForSpinner(client, `${notificationTemplates.url()}/${data.notificationTemplate.id}/`);
|
||||||
|
|
||||||
|
notificationTemplates.section.edit
|
||||||
|
.expect.element('@title').text.contain(data.notificationTemplate.name);
|
||||||
|
|
||||||
|
notificationTemplates.section.edit.section.details.checkAllFieldsDisabled();
|
||||||
|
},
|
||||||
|
'verify save button hidden from auditor on notification templates page': function () {
|
||||||
|
notificationTemplates.expect.element('@save').to.not.be.visible;
|
||||||
|
},
|
||||||
|
'verify an auditor\'s organizations inputs are read-only': function (client) {
|
||||||
|
navigateAndWaitForSpinner(client, `${organizations.url()}/${data.organization.id}/`);
|
||||||
|
|
||||||
|
organizations.section.edit
|
||||||
|
.expect.element('@title').text.contain(data.organization.name);
|
||||||
|
|
||||||
|
organizations.section.edit.section.details.checkAllFieldsDisabled();
|
||||||
|
},
|
||||||
|
'verify save button hidden from auditor on organizations form': function () {
|
||||||
|
organizations.expect.element('@save').to.not.be.visible;
|
||||||
|
},
|
||||||
|
'verify an auditor\'s smart inventory inputs are read-only': function (client) {
|
||||||
|
navigateAndWaitForSpinner(client, `${inventories.url()}/smart/${data.smartInventory.id}/`);
|
||||||
|
|
||||||
|
inventories.section.editSmartInventory
|
||||||
|
.expect.element('@title').text.contain(data.smartInventory.name);
|
||||||
|
|
||||||
|
inventories.section.editSmartInventory.section.smartInvDetails.checkAllFieldsDisabled();
|
||||||
|
},
|
||||||
|
'verify save button hidden from auditor on smart inventories form': function () {
|
||||||
|
inventories.expect.element('@save').to.not.be.visible;
|
||||||
|
},
|
||||||
|
'verify an auditor\'s project inputs are read-only': function (client) {
|
||||||
|
navigateAndWaitForSpinner(client, `${projects.url()}/${data.project.id}/`);
|
||||||
|
|
||||||
|
projects.section.edit
|
||||||
|
.expect.element('@title').text.contain(data.project.name);
|
||||||
|
|
||||||
|
projects.section.edit.section.details.checkAllFieldsDisabled();
|
||||||
|
},
|
||||||
|
'verify save button hidden from auditor on projects form': function () {
|
||||||
|
projects.expect.element('@save').to.not.be.visible;
|
||||||
|
},
|
||||||
|
'verify an auditor\'s standard inventory inputs are read-only': function (client) {
|
||||||
|
navigateAndWaitForSpinner(client, `${inventories.url()}/inventory/${data.inventory.id}/`);
|
||||||
|
|
||||||
|
inventories.section.editStandardInventory
|
||||||
|
.expect.element('@title').text.contain(data.inventory.name);
|
||||||
|
|
||||||
|
inventories.section.editStandardInventory.section.standardInvDetails.checkAllFieldsDisabled();
|
||||||
|
},
|
||||||
|
'verify save button hidden from auditor on standard inventory form': function () {
|
||||||
|
inventories.expect.element('@save').to.not.be.visible;
|
||||||
|
},
|
||||||
|
'verify an auditor\'s teams inputs are read-only': function (client) {
|
||||||
|
navigateAndWaitForSpinner(client, `${teams.url()}/${data.team.id}/`);
|
||||||
|
|
||||||
|
teams.section.edit
|
||||||
|
.expect.element('@title').text.contain(data.team.name);
|
||||||
|
|
||||||
|
teams.section.edit.section.details.checkAllFieldsDisabled();
|
||||||
|
},
|
||||||
|
'verify save button hidden from auditor on teams form': function () {
|
||||||
|
teams.expect.element('@save').to.not.be.visible;
|
||||||
|
},
|
||||||
|
'verify an auditor\'s user inputs are read-only': function (client) {
|
||||||
|
navigateAndWaitForSpinner(client, `${users.url()}/${data.user.id}/`);
|
||||||
|
|
||||||
|
users.section.edit
|
||||||
|
.expect.element('@title').text.contain(data.user.username);
|
||||||
|
|
||||||
|
users.section.edit.section.details.checkAllFieldsDisabled();
|
||||||
|
},
|
||||||
|
'verify save button hidden from auditor on users form': function (client) {
|
||||||
|
users.expect.element('@save').to.not.be.visible;
|
||||||
|
|
||||||
|
client.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
import uuid from 'uuid';
|
|
||||||
|
|
||||||
|
|
||||||
let testID = uuid().substr(0,8);
|
|
||||||
|
|
||||||
|
|
||||||
let store = {
|
|
||||||
auditor: {
|
|
||||||
username: `auditor-${testID}`,
|
|
||||||
first_name: 'auditor',
|
|
||||||
last_name: 'last',
|
|
||||||
email: 'null@ansible.com',
|
|
||||||
is_superuser: false,
|
|
||||||
is_system_auditor: true,
|
|
||||||
password: 'password'
|
|
||||||
},
|
|
||||||
adminCredential: {
|
|
||||||
name: `adminCredential-${testID}`,
|
|
||||||
description: `adminCredential-description-${testID}`,
|
|
||||||
inputs: {
|
|
||||||
username: 'username',
|
|
||||||
password: 'password',
|
|
||||||
security_token: 'AAAAAAAAAAAAAAAAAAAAAAAAAA'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
before: function (client, done) {
|
|
||||||
const credentials = client.page.credentials();
|
|
||||||
|
|
||||||
client.login();
|
|
||||||
client.waitForAngular();
|
|
||||||
|
|
||||||
client.inject([store, '$http'], (store, $http) => {
|
|
||||||
|
|
||||||
let { adminCredential, auditor } = store;
|
|
||||||
|
|
||||||
return $http.get('/api/v2/me')
|
|
||||||
.then(({ data }) => {
|
|
||||||
let resource = 'Amazon%20Web%20Services+cloud';
|
|
||||||
adminCredential.user = data.results[0].id;
|
|
||||||
|
|
||||||
return $http.get(`/api/v2/credential_types/${resource}`);
|
|
||||||
})
|
|
||||||
.then(({ data }) => {
|
|
||||||
adminCredential.credential_type = data.id;
|
|
||||||
|
|
||||||
return $http.post('/api/v2/credentials/', adminCredential);
|
|
||||||
})
|
|
||||||
.then(({ data }) => {
|
|
||||||
adminCredential = data;
|
|
||||||
|
|
||||||
return $http.post('/api/v2/users/', auditor);
|
|
||||||
})
|
|
||||||
.then(({ data }) => {
|
|
||||||
auditor = data;
|
|
||||||
|
|
||||||
return { adminCredential, auditor };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
({ adminCredential, auditor }) => {
|
|
||||||
store.created = { adminCredential, auditor };
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
},
|
|
||||||
beforeEach: function (client) {
|
|
||||||
const credentials = client.useCss().page.credentials();
|
|
||||||
|
|
||||||
credentials
|
|
||||||
.login(store.auditor.username, store.auditor.password)
|
|
||||||
.navigate(`${credentials.url()}/${store.created.adminCredential.id}/`)
|
|
||||||
.waitForElementVisible('div.spinny')
|
|
||||||
.waitForElementNotVisible('div.spinny');
|
|
||||||
},
|
|
||||||
'verify an auditor\'s inputs are read-only': function (client) {
|
|
||||||
const credentials = client.useCss().page.credentials()
|
|
||||||
const details = credentials.section.edit.section.details;
|
|
||||||
|
|
||||||
let expected = store.created.adminCredential.name;
|
|
||||||
|
|
||||||
credentials.section.edit
|
|
||||||
.expect.element('@title').text.contain(expected);
|
|
||||||
|
|
||||||
client.elements('css selector', '.at-Input', inputs => {
|
|
||||||
inputs.value.map(o => o.ELEMENT).forEach(id => {
|
|
||||||
client.elementIdAttribute(id, 'disabled', ({ value }) => {
|
|
||||||
client.assert.equal(value, 'true');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
client.end();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -33,6 +33,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"angular-mocks": "~1.4.14",
|
"angular-mocks": "~1.4.14",
|
||||||
|
"axios": "^0.16.2",
|
||||||
"babel-core": "^6.26.0",
|
"babel-core": "^6.26.0",
|
||||||
"babel-istanbul": "^0.12.2",
|
"babel-istanbul": "^0.12.2",
|
||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user