Merge pull request #26 from jakemcdermott/test-e2e

add e2e tests
This commit is contained in:
Jake McDermott
2017-09-15 17:35:30 -04:00
committed by GitHub
48 changed files with 2696 additions and 147 deletions

View File

@@ -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
# -------------------------------------- # --------------------------------------

View 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`.

View 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'

View 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

View 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;
};

View 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;

View 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;
};

View 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();
}
}
}
}
};

View 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'
}
};

View 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
}
};

View 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
}
})
}
},
}
};

View 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'
}
};

View 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;

View File

@@ -0,0 +1,8 @@
const breadcrumb = {
selector: 'bread-crumb > div',
elements: {
activity: 'i[class$="icon-activity-stream"]'
}
};
module.exports = breadcrumb;

View 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;

View File

@@ -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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -0,0 +1,3 @@
#!/usr/bin/env node
require('babel-register');
require('nightwatch/bin/runner.js');

View 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
}
};

View File

@@ -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();
}
};

View 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();
}
};

View 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();
}
};

View 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();
}
};

View File

@@ -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();
}
};

View File

@@ -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();
}
};

View File

@@ -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();
}
};

View 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();
}
};

View 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();
}
};

View File

@@ -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();
}
};

View File

@@ -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();
}
};

View File

@@ -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();
}
};

View 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();
}
};

View 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();
}
};

View File

@@ -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';

View File

@@ -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;
} }
}, },

View File

@@ -17,7 +17,7 @@ describe('Components | panel/body', () => {
it('Should produce at-Panel-body HTML content', () => { it('Should produce at-Panel-body HTML content', () => {
let element = $compile('<at-panel-body>yo</at-panel-body>')($rootScope); let element = $compile('<at-panel-body>yo</at-panel-body>')($rootScope);
$rootScope.$digest(); $rootScope.$digest();
expect(element.hasClass('at-Panel-body')).toBe(true); expect(element.hasClass('at-Panel-body')).toBe(true);
expect(element.html()).toContain('yo'); expect(element.html()).toContain('yo');
}); });

View File

@@ -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'
}
});
};

View File

@@ -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",

View File

@@ -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
1 resource small medium Jan2017 jobs1k jobs10k jobs50k jobs100k jobs200k e2e
2 organizations 50 500 1 1 1 1 1 1 110
3 users 500 5000 3 3 3 3 3 3 110
4 teams 200 500 2 2 2 2 2 2 100
5 projects 150 1000 30 30 30 30 30 30 110
6 job_templates 300 2000 127 127 127 127 127 127 110
7 credentials 150 2000 50 50 50 50 50 50 110
8 inventories 150 2000 6 6 6 6 6 6 110
9 inventory_groups 700 500 15 15 15 15 15 15 110
10 inventory_hosts 1100 2500 15 15 15 15 15 15 110
11 wfjts 50 100 0 0 0 0 0 0 0
12 nodes 1000 1000 0 0 0 0 0 0 0
13 labels 1000 1000 0 0 0 0 0 0 0
14 jobs 2000 5000 157208 1000 10000 50000 100000 200000 1000
15 job_events 40000 100000 3370942 20000 200000 1000000 2000000 4000000 20000

View File

@@ -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'])