mirror of
https://github.com/ansible/awx.git
synced 2026-02-28 08:18:43 -03:30
5
Makefile
5
Makefile
@@ -77,7 +77,7 @@ I18N_FLAG_FILE = .i18n_built
|
|||||||
receiver test test_unit test_ansible test_coverage coverage_html \
|
receiver test test_unit test_ansible test_coverage coverage_html \
|
||||||
dev_build release_build release_clean sdist \
|
dev_build release_build release_clean sdist \
|
||||||
ui-docker-machine ui-docker ui-release ui-devel \
|
ui-docker-machine ui-docker ui-release ui-devel \
|
||||||
ui-test ui-deps ui-test-ci ui-test-saucelabs VERSION
|
ui-test ui-deps ui-test-ci VERSION
|
||||||
|
|
||||||
# remove ui build artifacts
|
# remove ui build artifacts
|
||||||
clean-ui:
|
clean-ui:
|
||||||
@@ -502,9 +502,6 @@ testjs_ci:
|
|||||||
jshint: $(UI_DEPS_FLAG_FILE)
|
jshint: $(UI_DEPS_FLAG_FILE)
|
||||||
$(NPM_BIN) run --prefix awx/ui jshint
|
$(NPM_BIN) run --prefix awx/ui jshint
|
||||||
|
|
||||||
ui-test-saucelabs: $(UI_DEPS_FLAG_FILE)
|
|
||||||
$(NPM_BIN) --prefix awx/ui run test:saucelabs
|
|
||||||
|
|
||||||
# END UI TASKS
|
# END UI TASKS
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
|
|
||||||
|
|||||||
23
awx/ui/client/test/e2e/README.md
Normal file
23
awx/ui/client/test/e2e/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
## 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
|
||||||
|
make --directory=/awx_devel DATA_GEN_PRESET=e2e bulk_data
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -3,9 +3,9 @@ import 'angular';
|
|||||||
import 'angular-mocks';
|
import 'angular-mocks';
|
||||||
|
|
||||||
// Import custom Angular module dependencies
|
// Import custom Angular module dependencies
|
||||||
import '../src/i18n';
|
import '../../src/i18n';
|
||||||
import '../lib/services';
|
import '../../lib/services';
|
||||||
import '../lib/components';
|
import '../../lib/components';
|
||||||
|
|
||||||
// Import tests
|
// Import tests
|
||||||
import './panel-body.spec';
|
import './panel-body.spec';
|
||||||
@@ -11,7 +11,7 @@ module.exports = config => {
|
|||||||
reporters: ['progress'],
|
reporters: ['progress'],
|
||||||
files: [
|
files: [
|
||||||
'./index.js',
|
'./index.js',
|
||||||
'../lib/components/**/*.html'
|
'../../lib/components/**/*.html'
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
'karma-webpack',
|
'karma-webpack',
|
||||||
@@ -20,13 +20,13 @@ module.exports = config => {
|
|||||||
'karma-ng-html2js-preprocessor'
|
'karma-ng-html2js-preprocessor'
|
||||||
],
|
],
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
'../lib/components/**/*.html': 'ng-html2js',
|
'../../lib/components/**/*.html': 'ng-html2js',
|
||||||
'./index.js': 'webpack'
|
'./index.js': 'webpack'
|
||||||
},
|
},
|
||||||
ngHtml2JsPreprocessor: {
|
ngHtml2JsPreprocessor: {
|
||||||
moduleName: 'at.test.templates',
|
moduleName: 'at.test.templates',
|
||||||
cacheIdFromPath: function (filepath) {
|
cacheIdFromPath: function (filepath) {
|
||||||
filepath = filepath.replace(path.join(__dirname, '../lib'), '');
|
filepath = filepath.replace(path.join(__dirname, '../../lib'), '');
|
||||||
return '/static/partials' + filepath;
|
return '/static/partials' + filepath;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
var path = require('path'),
|
|
||||||
webpack = require('webpack');
|
|
||||||
|
|
||||||
var sauceLaunchers = {
|
|
||||||
sl_chrome: {
|
|
||||||
base: 'SauceLabs',
|
|
||||||
browserName: 'chrome',
|
|
||||||
platform: 'Windows 7',
|
|
||||||
version: '35'
|
|
||||||
},
|
|
||||||
sl_firefox: {
|
|
||||||
base: 'SauceLabs',
|
|
||||||
browserName: 'firefox',
|
|
||||||
version: '30'
|
|
||||||
},
|
|
||||||
sl_ios_safari: {
|
|
||||||
base: 'SauceLabs',
|
|
||||||
browserName: 'iphone',
|
|
||||||
platform: 'OS X 10.9',
|
|
||||||
version: '7.1'
|
|
||||||
},
|
|
||||||
sl_ie_11: {
|
|
||||||
base: 'SauceLabs',
|
|
||||||
browserName: 'internet explorer',
|
|
||||||
platform: 'Windows 8.1',
|
|
||||||
version: '11'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = function(config) {
|
|
||||||
config.set({
|
|
||||||
singleRun: true,
|
|
||||||
colors: true,
|
|
||||||
logLevel: config.LOG_INFO,
|
|
||||||
customLaunchers: sauceLaunchers,
|
|
||||||
browsers: Object.keys(sauceLaunchers),
|
|
||||||
coverageReporter: {
|
|
||||||
reporters: [
|
|
||||||
{ type: 'html', subdir: 'html' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
frameworks: [
|
|
||||||
'jasmine',
|
|
||||||
],
|
|
||||||
reporters: ['dots', 'saucelabs'],
|
|
||||||
files: [
|
|
||||||
'./client/src/app.js',
|
|
||||||
'./node_modules/angular-mocks/angular-mocks.js',
|
|
||||||
{ pattern: './tests/protractor/**/*-test.js' },
|
|
||||||
],
|
|
||||||
preprocessors: {
|
|
||||||
'./client/src/app.js': ['webpack', 'sourcemap'],
|
|
||||||
'./tests/protractor/**/*-test.js': ['webpack', 'sourcemap'],
|
|
||||||
},
|
|
||||||
webpack: {
|
|
||||||
plugins: [
|
|
||||||
// Django-provided definitions
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
$basePath: '/static/'
|
|
||||||
}),
|
|
||||||
// vendor shims:
|
|
||||||
// [{expected_local_var : dependency}, ...]
|
|
||||||
new webpack.ProvidePlugin({
|
|
||||||
$: 'jquery',
|
|
||||||
jQuery: 'jquery',
|
|
||||||
'window.jQuery': 'jquery',
|
|
||||||
_: 'lodash',
|
|
||||||
'CodeMirror': 'codemirror',
|
|
||||||
'$.fn.datepicker': 'bootstrap-datepicker'
|
|
||||||
})
|
|
||||||
],
|
|
||||||
module: {
|
|
||||||
loaders: [{
|
|
||||||
test: /\.angular.js$/,
|
|
||||||
loader: 'expose?angular'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
include: [path.resolve() + '/tests/'],
|
|
||||||
exclude: '/(node_modules)/',
|
|
||||||
query: {
|
|
||||||
presets: ['es2015']
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
test: /\.js$/,
|
|
||||||
loader: 'babel-loader',
|
|
||||||
include: [path.resolve() + '/client/src/'],
|
|
||||||
exclude: '/(node_modules)/',
|
|
||||||
query: {
|
|
||||||
presets: ['es2015'],
|
|
||||||
plugins: ['istanbul']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
root: [],
|
|
||||||
modulesDirectory: ['node_modules'],
|
|
||||||
alias: {
|
|
||||||
'jquery.resize': path.resolve() + '/node_modules/javascript-detect-element-resize/jquery.resize.js',
|
|
||||||
'select2': path.resolve() + '/node_modules/select2/dist/js/select2.full.js'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
devtool: 'inline-source-map',
|
|
||||||
debug: true,
|
|
||||||
cache: true
|
|
||||||
},
|
|
||||||
webpackMiddleware: {
|
|
||||||
stats: {
|
|
||||||
colors: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
junitReporter: {
|
|
||||||
outputFile: 'coverage/test-results.xml'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@@ -25,11 +25,12 @@
|
|||||||
"jshint": "grunt jshint:source --no-color",
|
"jshint": "grunt jshint:source --no-color",
|
||||||
"test:ci": "npm run test -- --single-run --reporter junit,dots --browsers=PhantomJS",
|
"test:ci": "npm run test -- --single-run --reporter junit,dots --browsers=PhantomJS",
|
||||||
"lint": "./node_modules/.bin/eslint -c .eslintrc.js .",
|
"lint": "./node_modules/.bin/eslint -c .eslintrc.js .",
|
||||||
"component-test": "./node_modules/.bin/karma start client/test/karma.conf.js",
|
"component-test": "./node_modules/.bin/karma start client/test/unit/karma.conf.js",
|
||||||
"lint-dev": "./node_modules/.bin/nodemon --exec \"./node_modules/.bin/eslint -c .eslintrc.js .\" --watch \"client/components/**/*.js\"",
|
"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",
|
"dev": "./node_modules/.bin/webpack --config build/webpack.development.js --progress",
|
||||||
"watch": "./node_modules/.bin/webpack-dev-server --config build/webpack.watch.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": {
|
"devDependencies": {
|
||||||
"angular-mocks": "~1.4.14",
|
"angular-mocks": "~1.4.14",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"babel-loader": "^7.1.2",
|
"babel-loader": "^7.1.2",
|
||||||
"babel-plugin-istanbul": "^2.0.0",
|
"babel-plugin-istanbul": "^2.0.0",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
"babel-preset-es2015": "^6.24.1",
|
||||||
|
"chromedriver": "^2.31.0",
|
||||||
"clean-webpack-plugin": "^0.1.16",
|
"clean-webpack-plugin": "^0.1.16",
|
||||||
"copy-webpack-plugin": "^4.0.1",
|
"copy-webpack-plugin": "^4.0.1",
|
||||||
"css-loader": "^0.28.5",
|
"css-loader": "^0.28.5",
|
||||||
@@ -70,7 +72,6 @@
|
|||||||
"karma-junit-reporter": "^1.2.0",
|
"karma-junit-reporter": "^1.2.0",
|
||||||
"karma-ng-html2js-preprocessor": "^1.0.0",
|
"karma-ng-html2js-preprocessor": "^1.0.0",
|
||||||
"karma-phantomjs-launcher": "^1.0.2",
|
"karma-phantomjs-launcher": "^1.0.2",
|
||||||
"karma-sauce-launcher": "^1.0.0",
|
|
||||||
"karma-sourcemap-loader": "^0.3.7",
|
"karma-sourcemap-loader": "^0.3.7",
|
||||||
"karma-webpack": "^1.8.0",
|
"karma-webpack": "^1.8.0",
|
||||||
"less": "^2.7.2",
|
"less": "^2.7.2",
|
||||||
@@ -79,6 +80,7 @@
|
|||||||
"load-grunt-configs": "^1.0.0",
|
"load-grunt-configs": "^1.0.0",
|
||||||
"load-grunt-tasks": "^3.5.0",
|
"load-grunt-tasks": "^3.5.0",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.0",
|
||||||
|
"nightwatch": "^0.9.16",
|
||||||
"ngtemplate-loader": "^2.0.1",
|
"ngtemplate-loader": "^2.0.1",
|
||||||
"phantomjs-prebuilt": "^2.1.12",
|
"phantomjs-prebuilt": "^2.1.12",
|
||||||
"script-loader": "^0.7.0",
|
"script-loader": "^0.7.0",
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
resource small medium Jan2017 jobs1k jobs10k jobs50k jobs100k jobs200k
|
resource small medium Jan2017 jobs1k jobs10k jobs50k jobs100k jobs200k e2e
|
||||||
organizations 50 500 1 1 1 1 1 1
|
organizations 50 500 1 1 1 1 1 1 110
|
||||||
users 500 5000 3 3 3 3 3 3
|
users 500 5000 3 3 3 3 3 3 110
|
||||||
teams 200 500 2 2 2 2 2 2
|
teams 200 500 2 2 2 2 2 2 100
|
||||||
projects 150 1000 30 30 30 30 30 30
|
projects 150 1000 30 30 30 30 30 30 110
|
||||||
job_templates 300 2000 127 127 127 127 127 127
|
job_templates 300 2000 127 127 127 127 127 127 110
|
||||||
credentials 150 2000 50 50 50 50 50 50
|
credentials 150 2000 50 50 50 50 50 50 110
|
||||||
inventories 150 2000 6 6 6 6 6 6
|
inventories 150 2000 6 6 6 6 6 6 110
|
||||||
inventory_groups 700 500 15 15 15 15 15 15
|
inventory_groups 700 500 15 15 15 15 15 15 110
|
||||||
inventory_hosts 1100 2500 15 15 15 15 15 15
|
inventory_hosts 1100 2500 15 15 15 15 15 15 110
|
||||||
wfjts 50 100 0 0 0 0 0 0
|
wfjts 50 100 0 0 0 0 0 0 0
|
||||||
nodes 1000 1000 0 0 0 0 0 0
|
nodes 1000 1000 0 0 0 0 0 0 0
|
||||||
labels 1000 1000 0 0 0 0 0 0
|
labels 1000 1000 0 0 0 0 0 0 0
|
||||||
jobs 2000 5000 157208 1000 10000 50000 100000 200000
|
jobs 2000 5000 157208 1000 10000 50000 100000 200000 1000
|
||||||
job_events 40000 100000 3370942 20000 200000 1000000 2000000 4000000
|
job_events 40000 100000 3370942 20000 200000 1000000 2000000 4000000 20000
|
||||||
|
@@ -107,6 +107,9 @@ if options['preset']:
|
|||||||
|
|
||||||
options.update({cols[0]: cols[col + 1] for cols in split_lines})
|
options.update({cols[0]: cols[col + 1] for cols in split_lines})
|
||||||
|
|
||||||
|
if not options['prefix']:
|
||||||
|
options['prefix'] = options['preset']
|
||||||
|
|
||||||
|
|
||||||
n_organizations = int(options['organizations'])
|
n_organizations = int(options['organizations'])
|
||||||
n_users = int(options['users'])
|
n_users = int(options['users'])
|
||||||
|
|||||||
Reference in New Issue
Block a user