mirror of
https://github.com/ansible/awx.git
synced 2026-01-11 10:00:01 -03:30
add e2e tests
This commit is contained in:
parent
05b4b875e2
commit
19f96b1f6f
22
awx/ui/client/test/e2e/README.md
Normal file
22
awx/ui/client/test/e2e/README.md
Normal file
@ -0,0 +1,22 @@
|
||||
## AWX E2E
|
||||
```shell
|
||||
# setup
|
||||
docker exec -i tools_awx_1 sh <<-EOSH
|
||||
awx-manage createsuperuser --noinput --username=awx-e2e --email=null@ansible.com
|
||||
awx-manage update_password --username=awx-e2e --password=password
|
||||
EOSH
|
||||
|
||||
# run with with a live browser
|
||||
npm --prefix awx/ui run e2e -- --env=debug
|
||||
|
||||
# setup a local webdriver cluster for test development
|
||||
docker-compose \
|
||||
-f awx/ui/client/test/e2e/cluster/docker-compose.yml \
|
||||
-f awx/ui/client/test/e2e/cluster/devel-override.yml \
|
||||
up --scale chrome=2 --scale firefox=0
|
||||
|
||||
# run headlessly with multiple workers on the cluster
|
||||
AWX_E2E_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`.
|
||||
13
awx/ui/client/test/e2e/cluster/devel-override.yml
Normal file
13
awx/ui/client/test/e2e/cluster/devel-override.yml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
version: '2'
|
||||
networks:
|
||||
default:
|
||||
external:
|
||||
name: tools_default
|
||||
services:
|
||||
chrome:
|
||||
external_links:
|
||||
- 'tools_awx_1:awx'
|
||||
firefox:
|
||||
external_links:
|
||||
- 'tools_awx_1:awx'
|
||||
23
awx/ui/client/test/e2e/cluster/docker-compose.yml
Normal file
23
awx/ui/client/test/e2e/cluster/docker-compose.yml
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
version: '2'
|
||||
services:
|
||||
hub:
|
||||
image: selenium/hub
|
||||
ports:
|
||||
- '4444:4444'
|
||||
chrome:
|
||||
image: selenium/node-chrome
|
||||
links:
|
||||
- hub
|
||||
volumes:
|
||||
- /dev/shm:/dev/shm
|
||||
environment:
|
||||
HUB_PORT_4444_TCP_ADDR: hub
|
||||
HUB_PORT_4444_TCP_PORT: 4444
|
||||
firefox:
|
||||
image: selenium/node-firefox
|
||||
links:
|
||||
- hub
|
||||
environment:
|
||||
HUB_PORT_4444_TCP_ADDR: hub
|
||||
HUB_PORT_4444_TCP_PORT: 4444
|
||||
21
awx/ui/client/test/e2e/commands/inject.js
Normal file
21
awx/ui/client/test/e2e/commands/inject.js
Normal file
@ -0,0 +1,21 @@
|
||||
exports.command = function(deps, script, callback) {
|
||||
this.executeAsync(`let args = Array.prototype.slice.call(arguments,0);
|
||||
return function(deps, done) {
|
||||
let injector = angular.element('body').injector();
|
||||
let loaded = deps.map(d => {
|
||||
if (typeof(d) === "string") {
|
||||
return injector.get(d);
|
||||
} else {
|
||||
return d;
|
||||
}
|
||||
});
|
||||
(${script.toString()}).apply(this, loaded).then(done);
|
||||
}.apply(this, args);`,
|
||||
[deps],
|
||||
function(result) {
|
||||
if(typeof(callback) === "function") {
|
||||
callback.call(this, result.value);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
};
|
||||
48
awx/ui/client/test/e2e/commands/login.js
Normal file
48
awx/ui/client/test/e2e/commands/login.js
Normal file
@ -0,0 +1,48 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { inherits } from 'util';
|
||||
|
||||
|
||||
const Login = function() {
|
||||
EventEmitter.call(this);
|
||||
}
|
||||
|
||||
inherits(Login, EventEmitter);
|
||||
|
||||
|
||||
Login.prototype.command = function(username, password) {
|
||||
|
||||
username = username || this.api.globals.awxUsername;
|
||||
password = password || this.api.globals.awxPassword;
|
||||
|
||||
const loginPage = this.api.page.login();
|
||||
|
||||
loginPage
|
||||
.navigate()
|
||||
.waitForElementVisible('@submit', this.api.globals.longWait)
|
||||
.waitForElementNotVisible('div.spinny')
|
||||
.setValue('@username', username)
|
||||
.setValue('@password', password)
|
||||
.click('@submit')
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
// tempoary hack while login issue is resolved
|
||||
this.api.elements('css selector', '.LoginModal-alert', result => {
|
||||
let alertVisible = false;
|
||||
result.value.map(i => i.ELEMENT).forEach(id => {
|
||||
this.api.elementIdDisplayed(id, ({ value }) => {
|
||||
if (!alertVisible && value) {
|
||||
alertVisible = true;
|
||||
loginPage.setValue('@username', username)
|
||||
loginPage.setValue('@password', password)
|
||||
loginPage.click('@submit')
|
||||
loginPage.waitForElementVisible('div.spinny')
|
||||
loginPage.waitForElementNotVisible('div.spinny');
|
||||
}
|
||||
})
|
||||
})
|
||||
this.emit('complete');
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = Login;
|
||||
20
awx/ui/client/test/e2e/commands/waitForAngular.js
Normal file
20
awx/ui/client/test/e2e/commands/waitForAngular.js
Normal file
@ -0,0 +1,20 @@
|
||||
exports.command = function(callback) {
|
||||
let self = this;
|
||||
this.timeoutsAsyncScript(this.globals.asyncHookTimeout, function() {
|
||||
this.executeAsync(function(done) {
|
||||
if(angular && angular.getTestability) {
|
||||
angular.getTestability(document.body).whenStable(done);
|
||||
}
|
||||
else {
|
||||
done();
|
||||
}
|
||||
},
|
||||
[],
|
||||
function(result) {
|
||||
if(typeof(callback) === "function") {
|
||||
callback.call(self, result);
|
||||
}
|
||||
});
|
||||
});
|
||||
return this;
|
||||
};
|
||||
42
awx/ui/client/test/e2e/nightwatch.conf.js
Normal file
42
awx/ui/client/test/e2e/nightwatch.conf.js
Normal file
@ -0,0 +1,42 @@
|
||||
import path from 'path';
|
||||
|
||||
import chromedriver from 'chromedriver';
|
||||
|
||||
import { test_workers } from './settings.js';
|
||||
|
||||
|
||||
const resolve = location => path.resolve(__dirname, location);
|
||||
|
||||
|
||||
module.exports = {
|
||||
src_folders: [resolve('tests')],
|
||||
output_folder: resolve('reports'),
|
||||
custom_commands_path: resolve('commands'),
|
||||
page_objects_path: resolve('objects'),
|
||||
globals_path: resolve('settings.js'),
|
||||
test_settings: {
|
||||
default: {
|
||||
test_workers,
|
||||
skip_testcases_on_fail: false,
|
||||
desiredCapabilities: {
|
||||
browserName: 'chrome'
|
||||
}
|
||||
},
|
||||
debug: {
|
||||
selenium_port: 9515,
|
||||
selenium_host: 'localhost',
|
||||
default_path_prefix: '',
|
||||
test_workers: { enabled: false },
|
||||
globals: {
|
||||
before(done) {
|
||||
chromedriver.start();
|
||||
done();
|
||||
},
|
||||
after(done) {
|
||||
chromedriver.stop();
|
||||
done();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
10
awx/ui/client/test/e2e/objects/activityStream.js
Normal file
10
awx/ui/client/test/e2e/objects/activityStream.js
Normal file
@ -0,0 +1,10 @@
|
||||
module.exports = {
|
||||
url() {
|
||||
return `${this.api.globals.awxURL}/#/activity_stream`
|
||||
},
|
||||
elements: {
|
||||
title: '.List-titleText',
|
||||
subtitle: '.List-titleLockup',
|
||||
category: '#stream-dropdown-nav'
|
||||
}
|
||||
};
|
||||
65
awx/ui/client/test/e2e/objects/credentialTypes.js
Normal file
65
awx/ui/client/test/e2e/objects/credentialTypes.js
Normal file
@ -0,0 +1,65 @@
|
||||
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 search from './sections/search.js';
|
||||
import pagination from './sections/pagination.js';
|
||||
|
||||
|
||||
const addEditPanel = {
|
||||
selector: 'div[ui-view="form"]',
|
||||
elements: {
|
||||
title: 'div[class="Form-title"]',
|
||||
},
|
||||
sections: {
|
||||
details: createFormSection({
|
||||
selector: '#credential_type_form',
|
||||
labels: {
|
||||
name: "Name",
|
||||
description: "Description",
|
||||
inputConfiguration: "Input Configuration",
|
||||
injectorConfiguration: "Injector Configuration"
|
||||
},
|
||||
strategy: 'legacy'
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const listPanel = {
|
||||
selector: 'div[ui-view="list"]',
|
||||
elements: {
|
||||
add: '.List-buttonSubmit',
|
||||
badge: 'div[class="List-titleBadge]',
|
||||
titleText: 'div[class="List-titleText"]',
|
||||
noitems: 'div[class="List-noItems"]'
|
||||
},
|
||||
sections: {
|
||||
search,
|
||||
pagination,
|
||||
table: createTableSection({
|
||||
elements: {
|
||||
name: 'td[class~="name-column"]',
|
||||
kind: 'td[class~="kind-column"]',
|
||||
},
|
||||
sections: {
|
||||
actions
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
url() {
|
||||
return `${this.api.globals.awxURL}/#/credential_types`
|
||||
},
|
||||
sections: {
|
||||
header,
|
||||
breadcrumb,
|
||||
add: addEditPanel,
|
||||
edit: addEditPanel,
|
||||
list: listPanel
|
||||
}
|
||||
};
|
||||
280
awx/ui/client/test/e2e/objects/credentials.js
Normal file
280
awx/ui/client/test/e2e/objects/credentials.js
Normal file
@ -0,0 +1,280 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
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 dynamicSection from './sections/dynamicSection.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 common = createFormSection({
|
||||
selector: 'form',
|
||||
labels: {
|
||||
name: "Name",
|
||||
description: "Description",
|
||||
organization: "Organization",
|
||||
type: "Credential type"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const machine = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
sshKeyData: "SSH Private Key",
|
||||
sshKeyUnlock: "Private Key Passphrase",
|
||||
becomeMethod: "Privilege Escalation Method",
|
||||
becomeUsername: "Privilege Escalation Username",
|
||||
becomePassword: "Privilege Escalation Password"
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const vault = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
vaultPassword: "Vault Password",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const scm = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
sshKeyData: "SCM Private Key",
|
||||
sshKeyUnlock: "Private Key Passphrase",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const aws = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
accessKey: "Access Key",
|
||||
secretKey: "Secret Key",
|
||||
securityToken: "STS Token",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const gce = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
email: "Service Account Email Address",
|
||||
project: "Project",
|
||||
sshKeyData: "RSA Private Key",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const vmware = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
host: "VCenter Host",
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const azureClassic = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
subscription: "Subscription ID",
|
||||
sshKeyData: "Management Certificate",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const azure = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
subscription: "Subscription ID",
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
client: "Client ID",
|
||||
secret: "Client Secret",
|
||||
tenant: "Tenant ID",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const openStack = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
username: "Username",
|
||||
password: "Password (API Key)",
|
||||
host: "Host (Authentication URL)",
|
||||
project: "Project (Tenant Name)",
|
||||
domain: "Domain Name",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const rackspace = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const cloudForms = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
host: "Cloudforms URL",
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const network = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
sshKeyData: "SSH Private Key",
|
||||
sshKeyUnlock: "Private Key Passphrase",
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
authorizePassword: "Authorize Password",
|
||||
}
|
||||
});
|
||||
|
||||
network.elements.authorize = {
|
||||
locateStrategy: 'xpath',
|
||||
selector: '//input[../p/text() = "Authorize"]'
|
||||
};
|
||||
|
||||
|
||||
const sat6 = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
host: "Satellite 6 URL",
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const insights = createFormSection({
|
||||
selector: '.at-InputGroup-inset',
|
||||
labels: {
|
||||
username: "Username",
|
||||
password: "Password",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const details = _.merge({}, common, {
|
||||
elements: {
|
||||
cancel: '.btn[type="cancel"]',
|
||||
save: '.btn[type="save"]',
|
||||
},
|
||||
sections: {
|
||||
aws,
|
||||
azure,
|
||||
azureClassic,
|
||||
cloudForms,
|
||||
dynamicSection,
|
||||
gce,
|
||||
insights,
|
||||
machine,
|
||||
network,
|
||||
rackspace,
|
||||
sat6,
|
||||
scm,
|
||||
openStack,
|
||||
vault,
|
||||
vmware
|
||||
},
|
||||
commands: [{
|
||||
custom({ name, inputs }) {
|
||||
let labels = {};
|
||||
inputs.fields.map(f => labels[f.id] = f.label);
|
||||
|
||||
let selector = '.at-InputGroup-inset';
|
||||
let generated = createFormSection({ selector, labels });
|
||||
|
||||
let params = _.merge({ name }, generated);
|
||||
return this.section.dynamicSection.create(params);
|
||||
},
|
||||
clear() {
|
||||
this.clearValue('@name');
|
||||
this.clearValue('@organization');
|
||||
this.clearValue('@description');
|
||||
this.clearValue('@type');
|
||||
this.waitForElementNotVisible('.at-InputGroup-inset');
|
||||
return this;
|
||||
},
|
||||
clearAndSelectType(type) {
|
||||
this.clear();
|
||||
this.setValue('@type', type);
|
||||
this.waitForElementVisible('.at-InputGroup-inset');
|
||||
return this;
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
|
||||
module.exports = {
|
||||
url() {
|
||||
return `${this.api.globals.awxURL}/#/credentials`
|
||||
},
|
||||
sections: {
|
||||
header,
|
||||
navigation,
|
||||
breadcrumb,
|
||||
lookupModal,
|
||||
add: {
|
||||
selector: 'div[ui-view="add"]',
|
||||
sections: {
|
||||
details
|
||||
},
|
||||
elements: {
|
||||
title: 'h3[class="at-Panel-headingTitle"] span'
|
||||
}
|
||||
},
|
||||
edit: {
|
||||
selector: 'div[ui-view="edit"]',
|
||||
sections: {
|
||||
details,
|
||||
permissions
|
||||
},
|
||||
elements: {
|
||||
title: 'h3[class="at-Panel-headingTitle"] span'
|
||||
}
|
||||
},
|
||||
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~="kind-column"]'
|
||||
},
|
||||
sections: {
|
||||
actions
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
11
awx/ui/client/test/e2e/objects/login.js
Normal file
11
awx/ui/client/test/e2e/objects/login.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
url() {
|
||||
return `${this.api.globals.awxURL}/#/login`
|
||||
},
|
||||
elements: {
|
||||
username: '#login-username',
|
||||
password: '#login-password',
|
||||
submit: '#login-button',
|
||||
logo: '#main_menu_logo'
|
||||
}
|
||||
};
|
||||
16
awx/ui/client/test/e2e/objects/sections/actions.js
Normal file
16
awx/ui/client/test/e2e/objects/sections/actions.js
Normal file
@ -0,0 +1,16 @@
|
||||
const actions = {
|
||||
selector: 'td[class="List-actionsContainer"]',
|
||||
elements: {
|
||||
launch: 'i[class="fa icon-launch"]',
|
||||
schedule: 'i[class="fa icon-schedule"]',
|
||||
copy: 'i[class="fa icon-copy"]',
|
||||
edit: 'i[class="fa icon-pencil"]',
|
||||
delete: 'i[class="fa icon-trash-o"]',
|
||||
view: 'i[class="fa fa-search-plus"]',
|
||||
sync: 'i[class="fa fa-cloud-download"]',
|
||||
test: 'i[class="fa fa-bell-o'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = actions;
|
||||
8
awx/ui/client/test/e2e/objects/sections/breadcrumb.js
Normal file
8
awx/ui/client/test/e2e/objects/sections/breadcrumb.js
Normal file
@ -0,0 +1,8 @@
|
||||
const breadcrumb = {
|
||||
selector: 'bread-crumb > div',
|
||||
elements: {
|
||||
activity: 'i[class$="icon-activity-stream"]'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = breadcrumb;
|
||||
103
awx/ui/client/test/e2e/objects/sections/createFormSection.js
Normal file
103
awx/ui/client/test/e2e/objects/sections/createFormSection.js
Normal file
@ -0,0 +1,103 @@
|
||||
import { merge } from 'lodash';
|
||||
|
||||
|
||||
const translated = "translate(text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')";
|
||||
const normalized = `normalize-space(${translated})`;
|
||||
|
||||
|
||||
const inputContainerElements = {
|
||||
lookup: 'button > i[class="fa fa-search"]',
|
||||
error: '.at-InputMessage--rejected',
|
||||
help: 'i[class$="fa-question-circle"]',
|
||||
hint: '.at-InputLabel-hint',
|
||||
label: 'label',
|
||||
popover: '.at-Popover-container',
|
||||
yaml: 'input[type="radio", value="yaml"]',
|
||||
json: 'input[type="radio", value="json"]',
|
||||
revert: 'a[class~="reset"]',
|
||||
down: 'span[class^="fa-angle-down"]',
|
||||
up: 'span[class^="fa-angle-up"]',
|
||||
prompt: {
|
||||
locateStrategy: 'xpath',
|
||||
selector: `.//p[${normalized}='prompt on launch']/preceding-sibling::input`
|
||||
},
|
||||
show: {
|
||||
locateStrategy: 'xpath',
|
||||
selector: `.//button[${normalized}='show']`
|
||||
},
|
||||
hide: {
|
||||
locateStrategy: 'xpath',
|
||||
selector: `.//button[${normalized}='hide']`
|
||||
},
|
||||
on: {
|
||||
locateStrategy: 'xpath',
|
||||
selector: `.//button[${normalized}='on']`
|
||||
},
|
||||
off: {
|
||||
locateStrategy: 'xpath',
|
||||
selector: `.//button[${normalized}='off']`
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const legacyContainerElements = merge({}, inputContainerElements, {
|
||||
prompt: {
|
||||
locateStrategy: 'xpath',
|
||||
selector: `.//label[${normalized}='prompt on launch']/input`
|
||||
},
|
||||
error: 'div[class~="error"]',
|
||||
popover: ':root div[id^="popover"]',
|
||||
});
|
||||
|
||||
|
||||
const generateInputSelectors = function(label, containerElements) {
|
||||
// descend until span with matching text attribute is encountered
|
||||
let span = `.//span[text()="${label}"]`;
|
||||
// recurse upward until div with form-group in class attribute is encountered
|
||||
let container = `${span}/ancestor::div[contains(@class, 'form-group')]`;
|
||||
// descend until element with form-control in class attribute is encountered
|
||||
let input = `${container}//*[contains(@class, 'form-control')]`;
|
||||
|
||||
let inputContainer = {
|
||||
locateStrategy: 'xpath',
|
||||
selector: container,
|
||||
elements: containerElements
|
||||
};
|
||||
|
||||
let inputElement = {
|
||||
locateStrategy: 'xpath',
|
||||
selector: input
|
||||
};
|
||||
|
||||
return { inputElement, inputContainer };
|
||||
};
|
||||
|
||||
|
||||
const generatorOptions = {
|
||||
default: inputContainerElements,
|
||||
legacy: legacyContainerElements
|
||||
};
|
||||
|
||||
|
||||
const createFormSection = function({ selector, labels, strategy }) {
|
||||
let options = generatorOptions[strategy || 'default'];
|
||||
|
||||
let formSection = {
|
||||
selector,
|
||||
sections: {},
|
||||
elements: {}
|
||||
};
|
||||
|
||||
for (let key in labels) {
|
||||
let label = labels[key];
|
||||
|
||||
let { inputElement, inputContainer } = generateInputSelectors(label, options);
|
||||
|
||||
formSection.elements[key] = inputElement;
|
||||
formSection.sections[key] = inputContainer;
|
||||
};
|
||||
|
||||
return formSection;
|
||||
};
|
||||
|
||||
module.exports = createFormSection;
|
||||
@ -0,0 +1,63 @@
|
||||
import dynamicSection from './dynamicSection.js';
|
||||
|
||||
|
||||
const header = {
|
||||
selector: 'thead',
|
||||
sections: {
|
||||
dynamicSection
|
||||
},
|
||||
commands: [{
|
||||
findColumnByText(text) {
|
||||
return this.section.dynamicSection.create({
|
||||
name: `column[${text}]`,
|
||||
locateStrategy: 'xpath',
|
||||
selector: `.//*[normalize-space(text())='${text}']/ancestor-or-self::th`,
|
||||
elements: {
|
||||
sortable: {
|
||||
locateStrategy: 'xpath',
|
||||
selector: './/*[contains(@class, "fa-sort")]'
|
||||
},
|
||||
sorted: {
|
||||
locateStrategy: 'xpath',
|
||||
selector: './/*[contains(@class, "fa-sort-")]'
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
|
||||
const createTableSection = function({ elements, sections, commands }) {
|
||||
return {
|
||||
selector: 'table',
|
||||
sections: {
|
||||
header,
|
||||
dynamicSection
|
||||
},
|
||||
commands: [{
|
||||
findRowByText(text) {
|
||||
return this.section.dynamicSection.create({
|
||||
elements,
|
||||
sections,
|
||||
commands,
|
||||
name: `row[${text}]`,
|
||||
locateStrategy: 'xpath',
|
||||
selector: `.//tbody/tr/td//*[normalize-space(text())='${text}']/ancestor::tr`
|
||||
});
|
||||
},
|
||||
waitForRowCount(count) {
|
||||
let countReached = `tbody tr:nth-of-type(${count})`;
|
||||
this.waitForElementPresent(countReached);
|
||||
|
||||
let countExceeded = `tbody tr:nth-of-type(${count + 1})`;
|
||||
this.waitForElementNotPresent(countExceeded);
|
||||
|
||||
return this;
|
||||
}
|
||||
}]
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
module.exports = createTableSection;
|
||||
26
awx/ui/client/test/e2e/objects/sections/dynamicSection.js
Normal file
26
awx/ui/client/test/e2e/objects/sections/dynamicSection.js
Normal file
@ -0,0 +1,26 @@
|
||||
const dynamicSection = {
|
||||
selector: '.',
|
||||
commands: [{
|
||||
create({ name, locateStrategy, selector, elements, sections, commands }) {
|
||||
let Section = this.constructor;
|
||||
|
||||
let options = Object.assign(Object.create(this), {
|
||||
name,
|
||||
locateStrategy,
|
||||
elements,
|
||||
selector,
|
||||
sections,
|
||||
commands
|
||||
});
|
||||
|
||||
options.elements.self = {
|
||||
locateStrategy: 'xpath',
|
||||
selector: '.'
|
||||
};
|
||||
|
||||
return new Section(options);
|
||||
}
|
||||
}]
|
||||
};
|
||||
|
||||
module.exports = dynamicSection;
|
||||
12
awx/ui/client/test/e2e/objects/sections/header.js
Normal file
12
awx/ui/client/test/e2e/objects/sections/header.js
Normal file
@ -0,0 +1,12 @@
|
||||
const header = {
|
||||
selector: 'div[class="at-Layout-topNav"]',
|
||||
elements: {
|
||||
logo: 'div[class$="logo"] img',
|
||||
user: 'i[class="fa fa-user"] + span',
|
||||
documentation: 'i[class="fa fa-book"]',
|
||||
logout: 'i[class="fa fa-power-off"]',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = header;
|
||||
27
awx/ui/client/test/e2e/objects/sections/lookupModal.js
Normal file
27
awx/ui/client/test/e2e/objects/sections/lookupModal.js
Normal file
@ -0,0 +1,27 @@
|
||||
import createTableSection from './createTableSection.js';
|
||||
import pagination from './pagination.js';
|
||||
import search from './search.js';
|
||||
|
||||
|
||||
const lookupModal = {
|
||||
selector: '#form-modal',
|
||||
elements: {
|
||||
close: 'i[class="fa fa-times-circle"]',
|
||||
title: 'div[class^="Form-title"]',
|
||||
select: 'button[class*="save"]',
|
||||
cancel: 'button[class*="cancel"]'
|
||||
},
|
||||
sections: {
|
||||
search,
|
||||
pagination,
|
||||
table: createTableSection({
|
||||
elements: {
|
||||
name: 'td[class~="name-column"]',
|
||||
selected: 'input[type="radio", value="1"]',
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = lookupModal;
|
||||
25
awx/ui/client/test/e2e/objects/sections/navigation.js
Normal file
25
awx/ui/client/test/e2e/objects/sections/navigation.js
Normal file
@ -0,0 +1,25 @@
|
||||
const navigation = {
|
||||
selector: 'div[class^="at-Layout-side"]',
|
||||
elements: {
|
||||
expand: 'i[class="fa fa-bars"]',
|
||||
dashboard: 'i[class="fa fa-tachometer"]',
|
||||
jobs: 'i[class="fa fa-spinner"]',
|
||||
schedules: 'i[class="fa fa-calendar"]',
|
||||
portal: 'i[class="fa fa-columns"]',
|
||||
projects: 'i[class="fa fa-folder-open"]',
|
||||
credentials: 'i[class="fa fa-key"]',
|
||||
credentialTypes: 'i[class="fa fa-list-alt"]',
|
||||
inventories: 'i[class="fa fa-sitemap"]',
|
||||
templates: 'i[class="fa fa-pencil-square-o"]',
|
||||
organizations: 'i[class="fa fa-building"]',
|
||||
users: 'i[class="fa fa-user"]',
|
||||
teams: 'i[class="fa fa-users"]',
|
||||
inventoryScripts: 'i[class="fa fa-code"]',
|
||||
notifications: 'i[class="fa fa-bell"]',
|
||||
managementJobs: 'i[class="fa fa-wrench"]',
|
||||
instanceGroups: 'i[class="fa fa-server"]',
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = navigation;
|
||||
13
awx/ui/client/test/e2e/objects/sections/pagination.js
Normal file
13
awx/ui/client/test/e2e/objects/sections/pagination.js
Normal file
@ -0,0 +1,13 @@
|
||||
const pagination = {
|
||||
selector: 'paginate div',
|
||||
elements: {
|
||||
first: 'i[class="fa fa-angle-double-left"]',
|
||||
previous: 'i[class="fa fa-angle-left"]',
|
||||
next: 'i[class="fa fa-angle-right"]',
|
||||
last: 'i[class="fa fa-angle-double-right"]',
|
||||
pageCount: 'span[class~="pageof"]',
|
||||
itemCount: 'span[class~="itemsOf"]',
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = pagination;
|
||||
30
awx/ui/client/test/e2e/objects/sections/permissions.js
Normal file
30
awx/ui/client/test/e2e/objects/sections/permissions.js
Normal file
@ -0,0 +1,30 @@
|
||||
import actions from './actions.js';
|
||||
import createTableSection from './createTableSection.js';
|
||||
import pagination from './pagination.js';
|
||||
import search from './search.js';
|
||||
|
||||
|
||||
const permissions = {
|
||||
selector: 'div[ui-view="related"]',
|
||||
elements: {
|
||||
add: 'button[class="btn List-buttonSubmit"]',
|
||||
badge: 'div[class="List-titleBadge]',
|
||||
titleText: 'div[class="List-titleText"]',
|
||||
noitems: 'div[class="List-noItems"]'
|
||||
},
|
||||
sections: {
|
||||
search,
|
||||
pagination,
|
||||
table: createTableSection({
|
||||
elements: {
|
||||
username: 'td[class~="username"]',
|
||||
roles: 'td role-list:nth-of-type(1)',
|
||||
teamRoles: 'td role-list:nth-of-type(2)'
|
||||
},
|
||||
sections: { actions }
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = permissions;
|
||||
12
awx/ui/client/test/e2e/objects/sections/search.js
Normal file
12
awx/ui/client/test/e2e/objects/sections/search.js
Normal file
@ -0,0 +1,12 @@
|
||||
const search = {
|
||||
selector: 'smart-search',
|
||||
elements: {
|
||||
clearAll: '.SmartSearch-clearAll',
|
||||
searchButton: '.SmartSearch-searchButton',
|
||||
input: '.SmartSearch-input',
|
||||
tags: '.SmartSearch-tagContainer'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = search;
|
||||
3
awx/ui/client/test/e2e/runner.js
Executable file
3
awx/ui/client/test/e2e/runner.js
Executable file
@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
require('babel-register');
|
||||
require('nightwatch/bin/runner.js');
|
||||
29
awx/ui/client/test/e2e/settings.js
Normal file
29
awx/ui/client/test/e2e/settings.js
Normal file
@ -0,0 +1,29 @@
|
||||
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_PASSWORD = process.env.AWX_E2E_PASSWORD || 'password';
|
||||
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_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_LONG = process.env.AWX_E2E_TIMEOUT_LONG || 10000;
|
||||
const AWX_E2E_TIMEOUT_ASYNC = process.env.AWX_E2E_TIMEOUT_ASYNC || 30000;
|
||||
const AWX_E2E_WORKERS = process.env.AWX_E2E_WORKERS || 0;
|
||||
|
||||
|
||||
module.exports = {
|
||||
awxURL: AWX_E2E_URL,
|
||||
awxUsername: AWX_E2E_USERNAME,
|
||||
awxPassword: AWX_E2E_PASSWORD,
|
||||
asyncHookTimeout: AWX_E2E_TIMEOUT_ASYNC,
|
||||
longTmeout: AWX_E2E_TIMEOUT_LONG,
|
||||
mediumTimeout: AWX_E2E_TIMEOUT_MEDIUM,
|
||||
retryAssertionTimeout: AWX_E2E_TIMEOUT_MEDIUM,
|
||||
selenium_host: AWX_E2E_SELENIUM_HOST,
|
||||
selenium_port: AWX_E2E_SELENIUM_PORT,
|
||||
shortTimeout: AWX_E2E_TIMEOUT_SHORT,
|
||||
waitForConditionTimeout: AWX_E2E_TIMEOUT_MEDIUM,
|
||||
test_workers: {
|
||||
enabled: (AWX_E2E_WORKERS > 0),
|
||||
workers: AWX_E2E_WORKERS
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentialTypes = client.page.credentialTypes();
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
credentialTypes
|
||||
.navigate(`${credentialTypes.url()}/add/`)
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentialTypes.section.add
|
||||
.waitForElementVisible('@title', done);
|
||||
},
|
||||
'expected fields are present and enabled': function(client) {
|
||||
const credentialTypes = client.page.credentialTypes();
|
||||
const details = credentialTypes.section.add.section.details;
|
||||
|
||||
details.expect.element('@name').visible;
|
||||
details.expect.element('@description').visible;
|
||||
details.section.inputConfiguration.expect.element('.CodeMirror').visible;
|
||||
details.section.injectorConfiguration.expect.element('.CodeMirror').visible;
|
||||
|
||||
details.expect.element('@name').enabled;
|
||||
details.expect.element('@description').enabled;
|
||||
details.expect.element('@inputConfiguration').enabled;
|
||||
details.expect.element('@injectorConfiguration').enabled;
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
135
awx/ui/client/test/e2e/tests/test-credentials-add-edit-aws.js
Normal file
135
awx/ui/client/test/e2e/tests/test-credentials-add-edit-aws.js
Normal file
@ -0,0 +1,135 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
|
||||
let testID = uuid().substr(0,8);
|
||||
|
||||
let store = {
|
||||
organization: {
|
||||
name: `org-${testID}`
|
||||
},
|
||||
credential: {
|
||||
name: `cred-${testID}`
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
client.inject([store, 'OrganizationModel'], (store, model) => {
|
||||
return new model().http.post(store.organization);
|
||||
},
|
||||
({ data }) => {
|
||||
store.organization = data;
|
||||
});
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
credentials.section.add.section.details
|
||||
.waitForElementVisible('@save')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name)
|
||||
.setValue('@type', 'Amazon Web Services', done);
|
||||
},
|
||||
'expected fields are visible and enabled': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@name').visible;
|
||||
details.expect.element('@description').visible;
|
||||
details.expect.element('@organization').visible;
|
||||
details.expect.element('@type').visible;
|
||||
details.section.aws.expect.element('@accessKey').visible;
|
||||
details.section.aws.expect.element('@secretKey').visible;
|
||||
details.section.aws.expect.element('@securityToken').visible;
|
||||
|
||||
details.expect.element('@name').enabled;
|
||||
details.expect.element('@description').enabled;
|
||||
details.expect.element('@organization').enabled;
|
||||
details.expect.element('@type').enabled;
|
||||
details.section.aws.expect.element('@accessKey').enabled;
|
||||
details.section.aws.expect.element('@secretKey').enabled;
|
||||
details.section.aws.expect.element('@securityToken').enabled;
|
||||
},
|
||||
'required fields display \'*\'': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const required = [
|
||||
details.section.name,
|
||||
details.section.type,
|
||||
details.section.aws.section.accessKey,
|
||||
details.section.aws.section.secretKey
|
||||
]
|
||||
required.map(s => s.expect.element('@label').text.to.contain('*'));
|
||||
},
|
||||
'save button becomes enabled after providing required fields': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Amazon Web Services')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.expect.element('@save').not.enabled;
|
||||
details.section.aws.setValue('@accessKey', 'AAAAAAAAAAAAA');
|
||||
details.section.aws.setValue('@secretKey', 'AAAAAAAAAAAAA');
|
||||
details.expect.element('@save').enabled;
|
||||
},
|
||||
'create aws credential': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const add = credentials.section.add;
|
||||
const edit = credentials.section.edit;
|
||||
|
||||
add.section.details
|
||||
.clearAndSelectType('Amazon Web Services')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name);
|
||||
|
||||
add.section.details.section.aws
|
||||
.setValue('@accessKey', 'ABCD123456789')
|
||||
.setValue('@secretKey', '987654321DCBA');
|
||||
|
||||
add.section.details.click('@save');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
edit.expect.element('@title').text.equal(store.credential.name);
|
||||
},
|
||||
'edit details panel remains open after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
credentials.section.edit.expect.section('@details').visible;
|
||||
},
|
||||
'credential is searchable after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
const search = credentials.section.list.section.search;
|
||||
const table = credentials.section.list.section.table;
|
||||
|
||||
search
|
||||
.waitForElementVisible('@input')
|
||||
.setValue('@input', `name:${store.credential.name}`)
|
||||
.click('@searchButton');
|
||||
|
||||
table.waitForRowCount(1);
|
||||
table.findRowByText(store.credential.name)
|
||||
.waitForElementVisible('@self');
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
176
awx/ui/client/test/e2e/tests/test-credentials-add-edit-custom.js
Normal file
176
awx/ui/client/test/e2e/tests/test-credentials-add-edit-custom.js
Normal file
@ -0,0 +1,176 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
|
||||
let store = {
|
||||
credentialType: {
|
||||
name: `credentialType-${uuid().substr(0,8)}`,
|
||||
description: "custom cloud credential",
|
||||
kind: "cloud",
|
||||
inputs: {
|
||||
fields: [
|
||||
{
|
||||
id: "project",
|
||||
label: "Project",
|
||||
type: "string",
|
||||
help_text: "Name of your project"
|
||||
},
|
||||
{
|
||||
id: "token",
|
||||
label: "Token",
|
||||
secret: true,
|
||||
type: "string",
|
||||
help_text: "help"
|
||||
},
|
||||
{
|
||||
id: "secret_key_data",
|
||||
label: "Secret Key",
|
||||
type: "string",
|
||||
secret: true,
|
||||
multiline: true,
|
||||
help_text: "help",
|
||||
},
|
||||
{
|
||||
id: "public_key_data",
|
||||
label: "Public Key",
|
||||
type: "string",
|
||||
secret: true,
|
||||
multiline: true,
|
||||
help_text: "help",
|
||||
},
|
||||
{
|
||||
id: "secret_key_unlock",
|
||||
label: "Private Key Passphrase",
|
||||
type: "string",
|
||||
secret: true,
|
||||
//help_text: "help"
|
||||
},
|
||||
{
|
||||
id: "color",
|
||||
label: "Favorite Color",
|
||||
choices: [
|
||||
"",
|
||||
"red",
|
||||
"orange",
|
||||
"yellow",
|
||||
"green",
|
||||
"blue",
|
||||
"indigo",
|
||||
"violet"
|
||||
],
|
||||
help_text: "help",
|
||||
},
|
||||
],
|
||||
required: ['project', 'token']
|
||||
},
|
||||
injectors: {
|
||||
env: {
|
||||
CUSTOM_CREDENTIAL_TOKEN: "{{ token }}"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const inputs = store.credentialType.inputs;
|
||||
const fields = store.credentialType.inputs.fields;
|
||||
const help = fields.filter(f => f.help_text);
|
||||
const required = fields.filter(f => inputs.required.indexOf(f.id) > -1);
|
||||
const strings = fields.filter(f => f.type === undefined || f.type === 'string');
|
||||
|
||||
|
||||
const getObjects = function(client) {
|
||||
let credentials = client.page.credentials();
|
||||
let details = credentials.section.add.section.details;
|
||||
let type = details.custom(store.credentialType);
|
||||
return { credentials, details, type };
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
client.inject([store.credentialType, 'CredentialTypeModel'], (data, model) => {
|
||||
return new model().http.post(data);
|
||||
},
|
||||
({ data }) => {
|
||||
store.credentialType.response = data;
|
||||
});
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
credentials.section.add.section.details
|
||||
.waitForElementVisible('@save')
|
||||
.setValue('@name', `cred-${uuid()}`)
|
||||
.setValue('@type', store.credentialType.name, done);
|
||||
},
|
||||
'all fields are visible': function(client) {
|
||||
let { type } = getObjects(client);
|
||||
fields.map(f => type.expect.element(`@${f.id}`).visible);
|
||||
},
|
||||
'helplinks open popovers showing expected content': function(client) {
|
||||
let { type } = getObjects(client);
|
||||
|
||||
help.map(f => {
|
||||
let group = type.section[f.id];
|
||||
group.expect.element('@popover').not.visible;
|
||||
group.click('@help');
|
||||
group.expect.element('@popover').visible;
|
||||
group.expect.element('@popover').text.to.contain(f.help_text);
|
||||
group.click('@help');
|
||||
});
|
||||
|
||||
help.map(f => {
|
||||
let group = type.section[f.id];
|
||||
group.expect.element('@popover').not.visible;
|
||||
});
|
||||
},
|
||||
'secret field buttons hide and unhide input': function(client) {
|
||||
let { type } = getObjects(client);
|
||||
let secrets = strings.filter(f => f.secret && !f.multiline);
|
||||
|
||||
secrets.map(f => {
|
||||
let group = type.section[f.id];
|
||||
let input = `@${f.id}`;
|
||||
|
||||
group.expect.element('@show').visible;
|
||||
group.expect.element('@hide').not.present;
|
||||
|
||||
type.setValue(input, 'SECRET');
|
||||
type.expect.element(input).text.equal('');
|
||||
|
||||
group.click('@show');
|
||||
group.expect.element('@show').not.present;
|
||||
group.expect.element('@hide').visible;
|
||||
type.expect.element(input).value.contain('SECRET');
|
||||
|
||||
group.click('@hide');
|
||||
group.expect.element('@show').visible;
|
||||
group.expect.element('@hide').not.present;
|
||||
type.expect.element(input).text.equal('');
|
||||
})
|
||||
},
|
||||
'required fields show * symbol': function(client) {
|
||||
let { type } = getObjects(client);
|
||||
|
||||
required.map(f => {
|
||||
let group = type.section[f.id];
|
||||
group.expect.element('@label').text.to.contain('*');
|
||||
});
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
172
awx/ui/client/test/e2e/tests/test-credentials-add-edit-gce.js
Normal file
172
awx/ui/client/test/e2e/tests/test-credentials-add-edit-gce.js
Normal file
@ -0,0 +1,172 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
|
||||
let testID = uuid().substr(0,8);
|
||||
|
||||
let store = {
|
||||
organization: {
|
||||
name: `org-${testID}`
|
||||
},
|
||||
credential: {
|
||||
name: `cred-${testID}`
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
client.inject([store, 'OrganizationModel'], (store, model) => {
|
||||
return new model().http.post(store.organization);
|
||||
},
|
||||
({ data }) => {
|
||||
store.organization = data;
|
||||
});
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
details
|
||||
.waitForElementVisible('@save')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name)
|
||||
.setValue('@type', 'Google Compute Engine', done);
|
||||
},
|
||||
'expected fields are visible and enabled': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@name').visible;
|
||||
details.expect.element('@description').visible;
|
||||
details.expect.element('@organization').visible;
|
||||
details.expect.element('@type').visible;
|
||||
details.section.gce.expect.element('@email').visible;
|
||||
details.section.gce.expect.element('@sshKeyData').visible;
|
||||
details.section.gce.expect.element('@project').visible;
|
||||
|
||||
details.expect.element('@name').enabled;
|
||||
details.expect.element('@description').enabled;
|
||||
details.expect.element('@organization').enabled;
|
||||
details.expect.element('@type').enabled;
|
||||
details.section.gce.expect.element('@email').enabled;
|
||||
details.section.gce.expect.element('@sshKeyData').enabled;
|
||||
details.section.gce.expect.element('@project').enabled;
|
||||
|
||||
details.section.organization.expect.element('@lookup').visible;
|
||||
},
|
||||
'required fields display \'*\'': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const required = [
|
||||
details.section.name,
|
||||
details.section.type,
|
||||
details.section.gce.section.email,
|
||||
details.section.gce.section.sshKeyData
|
||||
]
|
||||
required.map(s => s.expect.element('@label').text.to.contain('*'));
|
||||
},
|
||||
'save button becomes enabled after providing required fields': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Google Compute Engine')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.expect.element('@save').not.enabled;
|
||||
|
||||
details.section.gce
|
||||
.setValue('@email', 'abc@123.com')
|
||||
.sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', 'AAAA')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----');
|
||||
|
||||
details.expect.element('@save').enabled;
|
||||
},
|
||||
'error displayed for invalid ssh key data': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const sshKeyData = details.section.gce.section.sshKeyData;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Google Compute Engine')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.section.gce
|
||||
.setValue('@email', 'abc@123.com')
|
||||
.setValue('@sshKeyData', 'invalid');
|
||||
|
||||
details.click('@save');
|
||||
|
||||
sshKeyData.expect.element('@error').visible;
|
||||
sshKeyData.expect.element('@error').text.to.contain('Invalid certificate or key');
|
||||
|
||||
details.section.gce.clearValue('@sshKeyData');
|
||||
|
||||
sshKeyData.expect.element('@error').visible;
|
||||
sshKeyData.expect.element('@error').text.to.contain('Please enter a value');
|
||||
|
||||
details.section.gce.setValue('@sshKeyData', 'AAAA');
|
||||
sshKeyData.expect.element('@error').not.present;
|
||||
},
|
||||
'create gce credential': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const add = credentials.section.add;
|
||||
const edit = credentials.section.edit;
|
||||
|
||||
add.section.details
|
||||
.clearAndSelectType('Google Compute Engine')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name);
|
||||
|
||||
add.section.details.section.gce
|
||||
.setValue('@email', 'abc@123.com')
|
||||
.sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', 'AAAA')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----');
|
||||
|
||||
add.section.details.click('@save');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
edit.expect.element('@title').text.equal(store.credential.name);
|
||||
},
|
||||
'edit details panel remains open after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
credentials.section.edit.expect.section('@details').visible;
|
||||
},
|
||||
'credential is searchable after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const row = '#credentials_table tbody tr';
|
||||
|
||||
credentials.section.list.section.search
|
||||
.waitForElementVisible('@input', client.globals.longWait)
|
||||
.setValue('@input', `name:${store.credential.name}`)
|
||||
.click('@searchButton');
|
||||
|
||||
credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`);
|
||||
credentials.expect.element(row).text.contain(store.credential.name);
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,134 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
|
||||
let testID = uuid().substr(0,8);
|
||||
|
||||
let store = {
|
||||
organization: {
|
||||
name: `org-${testID}`
|
||||
},
|
||||
credential: {
|
||||
name: `cred-${testID}`
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
client.inject([store, 'OrganizationModel'], (store, model) => {
|
||||
return new model().http.post(store.organization);
|
||||
},
|
||||
({ data }) => {
|
||||
store.organization = data;
|
||||
});
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
details
|
||||
.waitForElementVisible('@save')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name)
|
||||
.setValue('@type', 'Insights', done);
|
||||
},
|
||||
'expected fields are visible and enabled': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@name').visible;
|
||||
details.expect.element('@description').visible;
|
||||
details.expect.element('@organization').visible;
|
||||
details.expect.element('@type').visible;
|
||||
details.section.insights.expect.element('@username').visible;
|
||||
details.section.insights.expect.element('@password').visible;
|
||||
|
||||
details.expect.element('@name').enabled;
|
||||
details.expect.element('@description').enabled;
|
||||
details.expect.element('@organization').enabled;
|
||||
details.expect.element('@type').enabled;
|
||||
details.section.insights.expect.element('@username').enabled;
|
||||
details.section.insights.expect.element('@password').enabled;
|
||||
},
|
||||
'required fields display \'*\'': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const required = [
|
||||
details.section.name,
|
||||
details.section.type,
|
||||
details.section.insights.section.username,
|
||||
details.section.insights.section.password
|
||||
]
|
||||
required.map(s => s.expect.element('@label').text.to.contain('*'));
|
||||
},
|
||||
'save button becomes enabled after providing required fields': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Insights')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.expect.element('@save').not.enabled;
|
||||
|
||||
details.section.insights
|
||||
.setValue('@username', 'wrosellini')
|
||||
.setValue('@password', 'quintus');
|
||||
|
||||
details.expect.element('@save').enabled;
|
||||
},
|
||||
'create insights credential': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const add = credentials.section.add;
|
||||
const edit = credentials.section.edit;
|
||||
|
||||
add.section.details
|
||||
.clearAndSelectType('Insights')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name);
|
||||
|
||||
add.section.details.section.insights
|
||||
.setValue('@username', 'wrosellini')
|
||||
.setValue('@password', 'quintus');
|
||||
|
||||
add.section.details.click('@save');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
edit.expect.element('@title').text.equal(store.credential.name);
|
||||
},
|
||||
'edit details panel remains open after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
credentials.section.edit.expect.section('@details').visible;
|
||||
},
|
||||
'credential is searchable after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const row = '#credentials_table tbody tr';
|
||||
|
||||
credentials.section.list.section.search
|
||||
.waitForElementVisible('@input', client.globals.longWait)
|
||||
.setValue('@input', `name:${store.credential.name}`)
|
||||
.click('@searchButton');
|
||||
|
||||
credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`);
|
||||
credentials.expect.element(row).text.contain(store.credential.name);
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,183 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
|
||||
let testID = uuid().substr(0,8);
|
||||
|
||||
let store = {
|
||||
organization: {
|
||||
name: `org-${testID}`
|
||||
},
|
||||
credential: {
|
||||
name: `cred-${testID}`
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
client.inject([store, 'OrganizationModel'], (store, model) => {
|
||||
return new model().http.post(store.organization);
|
||||
},
|
||||
({ data }) => {
|
||||
store.organization = data;
|
||||
});
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
details.waitForElementVisible('@save', done);
|
||||
},
|
||||
'common fields are visible and enabled': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@name').visible;
|
||||
details.expect.element('@description').visible;
|
||||
details.expect.element('@organization').visible;
|
||||
details.expect.element('@type').visible;
|
||||
|
||||
details.expect.element('@name').enabled;
|
||||
details.expect.element('@description').enabled;
|
||||
details.expect.element('@organization').enabled;
|
||||
details.expect.element('@type').enabled;
|
||||
},
|
||||
'required common fields display \'*\'': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.section.name.expect.element('@label').text.to.contain('*');
|
||||
details.section.type.expect.element('@label').text.to.contain('*');
|
||||
},
|
||||
'save button becomes enabled after providing required fields': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@save').not.enabled;
|
||||
|
||||
details
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name)
|
||||
.setValue('@type', 'Machine');
|
||||
|
||||
details.expect.element('@save').enabled;
|
||||
},
|
||||
'machine credential fields are visible after choosing type': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const machine = details.section.machine;
|
||||
|
||||
machine.expect.element('@username').visible;
|
||||
machine.expect.element('@password').visible;
|
||||
machine.expect.element('@becomeUsername').visible;
|
||||
machine.expect.element('@becomePassword').visible;
|
||||
machine.expect.element('@sshKeyData').visible;
|
||||
machine.expect.element('@sshKeyUnlock').visible;
|
||||
},
|
||||
'error displayed for invalid ssh key data': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const sshKeyData = details.section.machine.section.sshKeyData;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Machine')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.section.machine.setValue('@sshKeyData', 'invalid');
|
||||
|
||||
details.click('@save');
|
||||
|
||||
sshKeyData.expect.element('@error').visible;
|
||||
sshKeyData.expect.element('@error').text.to.contain('Invalid certificate or key');
|
||||
|
||||
details.section.machine.clearValue('@sshKeyData');
|
||||
sshKeyData.expect.element('@error').not.present;
|
||||
},
|
||||
'error displayed for unencrypted ssh key with passphrase': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const sshKeyUnlock = details.section.machine.section.sshKeyUnlock;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Machine')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.section.machine
|
||||
.setValue('@sshKeyUnlock', 'password')
|
||||
.sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', 'AAAA')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----');
|
||||
|
||||
details.click('@save');
|
||||
|
||||
sshKeyUnlock.expect.element('@error').visible;
|
||||
sshKeyUnlock.expect.element('@error').text.to.contain('not encrypted');
|
||||
|
||||
details.section.machine.clearValue('@sshKeyUnlock');
|
||||
sshKeyUnlock.expect.element('@error').not.present;
|
||||
},
|
||||
'create machine credential': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const add = credentials.section.add;
|
||||
const edit = credentials.section.edit;
|
||||
|
||||
add.section.details
|
||||
.clearAndSelectType('Machine')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name);
|
||||
|
||||
add.section.details.section.machine
|
||||
.setValue('@username', 'dsarif')
|
||||
.setValue('@password', 'freneticpny')
|
||||
.setValue('@becomeMethod', 'sudo')
|
||||
.setValue('@becomeUsername', 'dsarif')
|
||||
.setValue('@becomePassword', 'freneticpny')
|
||||
.sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', 'AAAA')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----');
|
||||
|
||||
add.section.details.click('@save');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
edit.expect.element('@title').text.equal(store.credential.name);
|
||||
},
|
||||
'edit details panel remains open after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
credentials.section.edit.expect.section('@details').visible;
|
||||
},
|
||||
'credential is searchable after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const row = '#credentials_table tbody tr';
|
||||
|
||||
credentials.section.list.section.search
|
||||
.waitForElementVisible('@input', client.globals.longWait)
|
||||
.setValue('@input', `name:${store.credential.name}`)
|
||||
.click('@searchButton');
|
||||
|
||||
credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`);
|
||||
credentials.expect.element(row).text.contain(store.credential.name);
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,208 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
|
||||
let testID = uuid().substr(0,8);
|
||||
|
||||
let store = {
|
||||
organization: {
|
||||
name: `org-${testID}`
|
||||
},
|
||||
credential: {
|
||||
name: `cred-${testID}`
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
client.inject([store, 'OrganizationModel'], (store, model) => {
|
||||
return new model().http.post(store.organization);
|
||||
},
|
||||
({ data }) => {
|
||||
store.organization = data;
|
||||
});
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
details.waitForElementVisible('@save', done);
|
||||
},
|
||||
'common fields are visible and enabled': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@name').visible;
|
||||
details.expect.element('@description').visible;
|
||||
details.expect.element('@organization').visible;
|
||||
details.expect.element('@type').visible;
|
||||
|
||||
details.expect.element('@name').enabled;
|
||||
details.expect.element('@description').enabled;
|
||||
details.expect.element('@organization').enabled;
|
||||
details.expect.element('@type').enabled;
|
||||
},
|
||||
'required common fields display \'*\'': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.section.name.expect.element('@label').text.to.contain('*');
|
||||
details.section.type.expect.element('@label').text.to.contain('*');
|
||||
},
|
||||
'save button becomes enabled after providing required fields': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@save').not.enabled;
|
||||
|
||||
details
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name)
|
||||
.setValue('@type', 'Network');
|
||||
|
||||
details.section.network
|
||||
.waitForElementVisible('@username')
|
||||
.setValue('@username', 'sgrimes');
|
||||
|
||||
details.expect.element('@save').enabled;
|
||||
},
|
||||
'network credential fields are visible after choosing type': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const network = details.section.network;
|
||||
|
||||
network.expect.element('@username').visible;
|
||||
network.expect.element('@password').visible;
|
||||
network.expect.element('@authorizePassword').visible;
|
||||
network.expect.element('@sshKeyData').visible;
|
||||
network.expect.element('@sshKeyUnlock').visible;
|
||||
},
|
||||
'error displayed for invalid ssh key data': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const sshKeyData = details.section.network.section.sshKeyData;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Network')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.section.network
|
||||
.setValue('@username', 'sgrimes')
|
||||
.setValue('@sshKeyData', 'invalid');
|
||||
|
||||
details.click('@save');
|
||||
|
||||
sshKeyData.expect.element('@error').visible;
|
||||
sshKeyData.expect.element('@error').text.to.contain('Invalid certificate or key');
|
||||
|
||||
details.section.network.clearValue('@sshKeyData');
|
||||
sshKeyData.expect.element('@error').not.present;
|
||||
},
|
||||
'error displayed for unencrypted ssh key with passphrase': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const sshKeyUnlock = details.section.network.section.sshKeyUnlock;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Network')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.section.network
|
||||
.setValue('@username', 'sgrimes')
|
||||
.setValue('@sshKeyUnlock', 'password')
|
||||
.sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', 'AAAA')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----');
|
||||
|
||||
details.click('@save');
|
||||
|
||||
sshKeyUnlock.expect.element('@error').visible;
|
||||
sshKeyUnlock.expect.element('@error').text.to.contain('not encrypted');
|
||||
|
||||
details.section.network.clearValue('@sshKeyUnlock');
|
||||
sshKeyUnlock.expect.element('@error').not.present;
|
||||
},
|
||||
'error displayed for authorize password without authorize enabled': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const authorizePassword = details.section.network.section.authorizePassword;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Network')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.section.network
|
||||
.setValue('@username', 'sgrimes')
|
||||
.setValue('@authorizePassword', 'ovid');
|
||||
|
||||
details.click('@save');
|
||||
|
||||
let expected = 'cannot be set unless "Authorize" is set';
|
||||
authorizePassword.expect.element('@error').visible;
|
||||
authorizePassword.expect.element('@error').text.to.equal(expected);
|
||||
|
||||
details.section.network.clearValue('@authorizePassword');
|
||||
authorizePassword.expect.element('@error').not.present;
|
||||
},
|
||||
'create network credential': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const add = credentials.section.add;
|
||||
const edit = credentials.section.edit;
|
||||
|
||||
add.section.details
|
||||
.clearAndSelectType('Network')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name);
|
||||
|
||||
add.section.details.section.network
|
||||
.setValue('@username', 'sgrimes')
|
||||
.setValue('@password', 'ovid')
|
||||
.sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', 'AAAA')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----');
|
||||
|
||||
add.section.details.click('@save');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
edit.expect.element('@title').text.equal(store.credential.name);
|
||||
},
|
||||
'edit details panel remains open after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
credentials.section.edit.expect.section('@details').visible;
|
||||
},
|
||||
'credential is searchable after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const row = '#credentials_table tbody tr';
|
||||
|
||||
credentials.section.list.section.search
|
||||
.waitForElementVisible('@input', client.globals.longWait)
|
||||
.setValue('@input', `name:${store.credential.name}`)
|
||||
.click('@searchButton');
|
||||
|
||||
credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`);
|
||||
credentials.expect.element(row).text.contain(store.credential.name);
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
177
awx/ui/client/test/e2e/tests/test-credentials-add-edit-scm.js
Normal file
177
awx/ui/client/test/e2e/tests/test-credentials-add-edit-scm.js
Normal file
@ -0,0 +1,177 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
|
||||
let testID = uuid().substr(0,8);
|
||||
|
||||
let store = {
|
||||
organization: {
|
||||
name: `org-${testID}`
|
||||
},
|
||||
credential: {
|
||||
name: `cred-${testID}`
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
client.inject([store, 'OrganizationModel'], (store, model) => {
|
||||
return new model().http.post(store.organization);
|
||||
},
|
||||
({ data }) => {
|
||||
store.organization = data;
|
||||
});
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
details.waitForElementVisible('@save', done);
|
||||
},
|
||||
'common fields are visible and enabled': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@name').visible;
|
||||
details.expect.element('@description').visible;
|
||||
details.expect.element('@organization').visible;
|
||||
details.expect.element('@type').visible;
|
||||
|
||||
details.expect.element('@name').enabled;
|
||||
details.expect.element('@description').enabled;
|
||||
details.expect.element('@organization').enabled;
|
||||
details.expect.element('@type').enabled;
|
||||
},
|
||||
'required common fields display \'*\'': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.section.name.expect.element('@label').text.to.contain('*');
|
||||
details.section.type.expect.element('@label').text.to.contain('*');
|
||||
},
|
||||
'save button becomes enabled after providing required fields': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@save').not.enabled;
|
||||
|
||||
details
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name)
|
||||
.setValue('@type', 'Source Control');
|
||||
|
||||
details.expect.element('@save').enabled;
|
||||
},
|
||||
'scm credential fields are visible after choosing type': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.section.scm.expect.element('@username').visible;
|
||||
details.section.scm.expect.element('@password').visible;
|
||||
details.section.scm.expect.element('@sshKeyData').visible;
|
||||
details.section.scm.expect.element('@sshKeyUnlock').visible;
|
||||
},
|
||||
'error displayed for invalid ssh key data': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const sshKeyData = details.section.scm.section.sshKeyData;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Source Control')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.section.scm.setValue('@sshKeyData', 'invalid');
|
||||
|
||||
details.click('@save');
|
||||
|
||||
sshKeyData.expect.element('@error').visible;
|
||||
sshKeyData.expect.element('@error').text.to.contain('Invalid certificate or key');
|
||||
|
||||
details.section.scm.clearValue('@sshKeyData');
|
||||
sshKeyData.expect.element('@error').not.present;
|
||||
},
|
||||
'error displayed for unencrypted ssh key with passphrase': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const sshKeyUnlock = details.section.scm.section.sshKeyUnlock;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Source Control')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.section.scm
|
||||
.setValue('@sshKeyUnlock', 'password')
|
||||
.sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', 'AAAA')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----');
|
||||
|
||||
details.click('@save');
|
||||
|
||||
sshKeyUnlock.expect.element('@error').visible;
|
||||
sshKeyUnlock.expect.element('@error').text.to.contain('not encrypted');
|
||||
|
||||
details.section.scm.clearValue('@sshKeyUnlock');
|
||||
sshKeyUnlock.expect.element('@error').not.present;
|
||||
},
|
||||
'create SCM credential': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const add = credentials.section.add;
|
||||
const edit = credentials.section.edit;
|
||||
|
||||
add.section.details
|
||||
.clearAndSelectType('Source Control')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name);
|
||||
|
||||
add.section.details.section.scm
|
||||
.setValue('@username', 'gthorpe')
|
||||
.setValue('@password', 'hydro')
|
||||
.sendKeys('@sshKeyData', '-----BEGIN RSA PRIVATE KEY-----')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', 'AAAA')
|
||||
.sendKeys('@sshKeyData', client.Keys.ENTER)
|
||||
.sendKeys('@sshKeyData', '-----END RSA PRIVATE KEY-----');
|
||||
|
||||
add.section.details.click('@save');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
edit.expect.element('@title').text.equal(store.credential.name);
|
||||
},
|
||||
'edit details panel remains open after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
credentials.section.edit.expect.section('@details').visible;
|
||||
},
|
||||
'credential is searchable after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const row = '#credentials_table tbody tr';
|
||||
|
||||
credentials.section.list.section.search
|
||||
.waitForElementVisible('@input', client.globals.longWait)
|
||||
.setValue('@input', `name:${store.credential.name}`)
|
||||
.click('@searchButton');
|
||||
|
||||
credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`);
|
||||
credentials.expect.element(row).text.to.contain(store.credential.name);
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
137
awx/ui/client/test/e2e/tests/test-credentials-add-edit-vault.js
Normal file
137
awx/ui/client/test/e2e/tests/test-credentials-add-edit-vault.js
Normal file
@ -0,0 +1,137 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
|
||||
let testID = uuid().substr(0,8);
|
||||
|
||||
let store = {
|
||||
organization: {
|
||||
name: `org-${testID}`
|
||||
},
|
||||
credential: {
|
||||
name: `cred-${testID}`
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
client.inject([store, 'OrganizationModel'], (store, model) => {
|
||||
return new model().http.post(store.organization);
|
||||
},
|
||||
({ data }) => {
|
||||
store.organization = data;
|
||||
});
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
details
|
||||
.waitForElementVisible('@save')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name)
|
||||
.setValue('@type', 'Vault', done);
|
||||
},
|
||||
'expected fields are visible and enabled': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details.expect.element('@name').visible;
|
||||
details.expect.element('@description').visible;
|
||||
details.expect.element('@organization').visible;
|
||||
details.expect.element('@type').visible;
|
||||
details.section.vault.expect.element('@vaultPassword').visible;
|
||||
|
||||
details.expect.element('@name').enabled;
|
||||
details.expect.element('@description').enabled;
|
||||
details.expect.element('@organization').enabled;
|
||||
details.expect.element('@type').enabled;
|
||||
details.section.vault.expect.element('@vaultPassword').enabled;
|
||||
},
|
||||
'required fields display \'*\'': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const required = [
|
||||
details.section.name,
|
||||
details.section.type,
|
||||
details.section.vault.section.vaultPassword,
|
||||
]
|
||||
required.map(s => s.expect.element('@label').text.to.contain('*'));
|
||||
},
|
||||
'save button becomes enabled after providing required fields': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Vault')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.expect.element('@save').not.enabled;
|
||||
details.section.vault.setValue('@vaultPassword', 'ch@ng3m3');
|
||||
details.expect.element('@save').enabled;
|
||||
},
|
||||
'vault password field is disabled when prompt on launch is selected': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
details
|
||||
.clearAndSelectType('Vault')
|
||||
.setValue('@name', store.credential.name);
|
||||
|
||||
details.section.vault.expect.element('@vaultPassword').enabled;
|
||||
details.section.vault.section.vaultPassword.click('@prompt');
|
||||
details.section.vault.expect.element('@vaultPassword').not.enabled;
|
||||
},
|
||||
'create vault credential': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const add = credentials.section.add;
|
||||
const edit = credentials.section.edit;
|
||||
|
||||
add.section.details
|
||||
.clearAndSelectType('Vault')
|
||||
.setValue('@name', store.credential.name)
|
||||
.setValue('@organization', store.organization.name);
|
||||
|
||||
add.section.details.section.vault.setValue('@vaultPassword', 'ch@ng3m3');
|
||||
|
||||
add.section.details.click('@save');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
edit.expect.element('@title').text.equal(store.credential.name);
|
||||
},
|
||||
'edit details panel remains open after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
credentials.section.edit.expect.section('@details').visible;
|
||||
},
|
||||
'credential is searchable after saving': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const row = '#credentials_table tbody tr';
|
||||
|
||||
credentials.section.list.section.search
|
||||
.waitForElementVisible('@input', client.globals.longWait)
|
||||
.setValue('@input', `name:${store.credential.name}`)
|
||||
.click('@searchButton');
|
||||
|
||||
credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`);
|
||||
credentials.expect.element(row).text.contain(store.credential.name);
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,47 @@
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
details
|
||||
.waitForElementVisible('@save', done)
|
||||
},
|
||||
'open the lookup modal': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
const modal = 'div[class="modal-body"]';
|
||||
const title = 'div[class^="Form-title"]';
|
||||
|
||||
details.expect.element('@type').visible;
|
||||
details.expect.element('@type').enabled;
|
||||
|
||||
details.section.type.expect.element('@lookup').visible;
|
||||
details.section.type.expect.element('@lookup').enabled;
|
||||
|
||||
details.section.type.click('@lookup');
|
||||
|
||||
client.expect.element(modal).present;
|
||||
|
||||
let expected = 'SELECT CREDENTIAL TYPE';
|
||||
client.expect.element(title).visible;
|
||||
client.expect.element(title).text.equal(expected);
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,155 @@
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
credentials.section.navigation
|
||||
.waitForElementVisible('@credentials')
|
||||
.click('@credentials');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.section.list
|
||||
.waitForElementVisible('@add')
|
||||
.click('@add');
|
||||
|
||||
details
|
||||
.waitForElementVisible('@save', done);
|
||||
|
||||
},
|
||||
'open the lookup modal': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const lookupModal = credentials.section.lookupModal;
|
||||
|
||||
details.expect.element('@organization').visible;
|
||||
details.expect.element('@organization').enabled;
|
||||
|
||||
details.section.organization.expect.element('@lookup').visible;
|
||||
details.section.organization.expect.element('@lookup').enabled;
|
||||
|
||||
details.section.organization.click('@lookup');
|
||||
|
||||
credentials.expect.section('@lookupModal').present;
|
||||
|
||||
let expected = 'SELECT ORGANIZATION';
|
||||
lookupModal.expect.element('@title').visible;
|
||||
lookupModal.expect.element('@title').text.equal(expected);
|
||||
},
|
||||
'select button is disabled until item is selected': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const details = credentials.section.add.section.details;
|
||||
const lookupModal = credentials.section.lookupModal;
|
||||
const table = lookupModal.section.table;
|
||||
|
||||
details.section.organization.expect.element('@lookup').visible;
|
||||
details.section.organization.expect.element('@lookup').enabled;
|
||||
|
||||
details.section.organization.click('@lookup');
|
||||
|
||||
credentials.expect.section('@lookupModal').present;
|
||||
|
||||
table.expect.element('tbody tr:nth-child(1) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(2) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(3) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(4) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(5) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(6) input[type="radio"]').not.present;
|
||||
|
||||
lookupModal.expect.element('@select').visible;
|
||||
lookupModal.expect.element('@select').not.enabled;
|
||||
|
||||
table.click('tbody tr:nth-child(2)');
|
||||
table.expect.element('tbody tr:nth-child(2) input[type="radio"]').selected;
|
||||
|
||||
lookupModal.expect.element('@select').visible;
|
||||
lookupModal.expect.element('@select').enabled;
|
||||
},
|
||||
'sort and unsort the table by name with an item selected': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const lookupModal = credentials.section.lookupModal;
|
||||
const table = lookupModal.section.table;
|
||||
|
||||
let column = table.section.header.findColumnByText('Name');
|
||||
|
||||
column.expect.element('@self').visible;
|
||||
column.expect.element('@sortable').visible;
|
||||
|
||||
column.click('@self');
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
table.expect.element('tbody tr:nth-child(1) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(2) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(3) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(4) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(5) input[type="radio"]').not.selected;
|
||||
|
||||
column.click('@self');
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
table.expect.element('tbody tr:nth-child(1) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(2) input[type="radio"]').selected;
|
||||
table.expect.element('tbody tr:nth-child(3) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(4) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(5) input[type="radio"]').not.selected;
|
||||
},
|
||||
'use the pagination controls with an item selected': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const lookupModal = credentials.section.lookupModal;
|
||||
const table = lookupModal.section.table;
|
||||
const pagination = lookupModal.section.pagination;
|
||||
|
||||
pagination.click('@next');
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
table.expect.element('tbody tr:nth-child(1) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(2) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(3) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(4) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(5) input[type="radio"]').not.selected;
|
||||
|
||||
pagination.click('@previous');
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
table.expect.element('tbody tr:nth-child(1) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(2) input[type="radio"]').selected;
|
||||
table.expect.element('tbody tr:nth-child(3) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(4) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(5) input[type="radio"]').not.selected;
|
||||
|
||||
pagination.click('@last');
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
pagination.click('@previous');
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
table.expect.element('tbody tr:nth-child(1) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(2) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(3) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(4) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(5) input[type="radio"]').not.selected;
|
||||
|
||||
pagination.click('@first');
|
||||
credentials.waitForElementVisible('div.spinny');
|
||||
credentials.waitForElementNotVisible('div.spinny');
|
||||
|
||||
table.expect.element('tbody tr:nth-child(1) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(2) input[type="radio"]').selected;
|
||||
table.expect.element('tbody tr:nth-child(3) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(4) input[type="radio"]').not.selected;
|
||||
table.expect.element('tbody tr:nth-child(5) input[type="radio"]').not.selected;
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
beforeEach: function(client, done) {
|
||||
const credentials = client.useCss().page.credentials();
|
||||
|
||||
client.login();
|
||||
client.waitForAngular();
|
||||
|
||||
credentials
|
||||
.navigate()
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny', done);
|
||||
},
|
||||
'activity link is visible and takes user to activity stream': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const activityStream = client.page.activityStream();
|
||||
|
||||
credentials.expect.section('@breadcrumb').visible;
|
||||
credentials.section.breadcrumb.expect.element('@activity').visible;
|
||||
credentials.section.breadcrumb.click('@activity');
|
||||
|
||||
activityStream
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny')
|
||||
.waitForElementVisible('@title')
|
||||
.waitForElementVisible('@category');
|
||||
|
||||
activityStream.expect.element('@title').text.contain('CREDENTIALS');
|
||||
activityStream.expect.element('@category').value.contain('credential');
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
97
awx/ui/client/test/e2e/tests/test-credentials-read-only.js
Normal file
97
awx/ui/client/test/e2e/tests/test-credentials-read-only.js
Normal file
@ -0,0 +1,97 @@
|
||||
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();
|
||||
}
|
||||
};
|
||||
57
awx/ui/client/test/e2e/tests/test-credentials-search-sort.js
Normal file
57
awx/ui/client/test/e2e/tests/test-credentials-search-sort.js
Normal file
@ -0,0 +1,57 @@
|
||||
const columns = ['Name', 'Kind', 'Owners', 'Actions'];
|
||||
const sortable = ['Name'];
|
||||
const defaultSorted = ['Name'];
|
||||
|
||||
|
||||
module.exports = {
|
||||
before: function(client, done) {
|
||||
const credentials = client.page.credentials();
|
||||
|
||||
client.login();
|
||||
client.resizeWindow(1200, 800);
|
||||
client.waitForAngular();
|
||||
|
||||
credentials
|
||||
.navigate()
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
credentials.waitForElementVisible('#credentials_table', done);
|
||||
},
|
||||
'expected table columns are visible': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const table = credentials.section.list.section.table;
|
||||
|
||||
columns.map(label => {
|
||||
table.section.header.findColumnByText(label)
|
||||
.expect.element('@self').visible;
|
||||
});
|
||||
},
|
||||
'only fields expected to be sortable show sort icon': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const table = credentials.section.list.section.table;
|
||||
|
||||
sortable.map(label => {
|
||||
table.section.header.findColumnByText(label)
|
||||
.expect.element('@sortable').visible;
|
||||
});
|
||||
},
|
||||
'sort all columns expected to be sortable': function(client) {
|
||||
const credentials = client.page.credentials();
|
||||
const table = credentials.section.list.section.table;
|
||||
|
||||
sortable.map(label => {
|
||||
let column = table.section.header.findColumnByText(label);
|
||||
|
||||
column.click('@self');
|
||||
|
||||
credentials
|
||||
.waitForElementVisible('div.spinny')
|
||||
.waitForElementNotVisible('div.spinny');
|
||||
|
||||
column.expect.element('@sorted').visible;
|
||||
});
|
||||
|
||||
client.end();
|
||||
}
|
||||
};
|
||||
@ -29,7 +29,8 @@
|
||||
"lint-dev": "./node_modules/.bin/nodemon --exec \"./node_modules/.bin/eslint -c .eslintrc.js .\" --watch \"client/components/**/*.js\"",
|
||||
"dev": "./node_modules/.bin/webpack --config build/webpack.development.js --progress",
|
||||
"watch": "./node_modules/.bin/webpack-dev-server --config build/webpack.watch.js --progress",
|
||||
"production": "./node_modules/.bin/webpack --config build/webpack.production.js"
|
||||
"production": "./node_modules/.bin/webpack --config build/webpack.production.js",
|
||||
"e2e": "./client/test/e2e/runner.js --config ./client/test/e2e/nightwatch.conf.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "~1.4.14",
|
||||
@ -38,6 +39,7 @@
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-plugin-istanbul": "^2.0.0",
|
||||
"babel-preset-es2015": "^6.24.1",
|
||||
"chromedriver": "^2.31.0",
|
||||
"clean-webpack-plugin": "^0.1.16",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"css-loader": "^0.28.5",
|
||||
@ -79,6 +81,7 @@
|
||||
"load-grunt-configs": "^1.0.0",
|
||||
"load-grunt-tasks": "^3.5.0",
|
||||
"minimist": "^1.2.0",
|
||||
"nightwatch": "^0.9.16",
|
||||
"ngtemplate-loader": "^2.0.1",
|
||||
"phantomjs-prebuilt": "^2.1.12",
|
||||
"script-loader": "^0.7.0",
|
||||
@ -123,4 +126,4 @@
|
||||
"select2": "^4.0.2",
|
||||
"sprintf-js": "^1.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user