diff --git a/awx/ui/.eslintignore b/awx/ui/.eslintignore
index f290f33893..f1daf7c9ab 100644
--- a/awx/ui/.eslintignore
+++ b/awx/ui/.eslintignore
@@ -1,6 +1,7 @@
-webpack.*.js
Gruntfile.js
karma.*.js
+webpack.*.js
+nightwatch.*.js
etc
coverage
@@ -9,12 +10,10 @@ node_modules
po
static
templates
-tests
-client/**/*.js
-test
-!client/lib/components/**/*.js
-!client/lib/models/**/*.js
-!client/lib/services/**/*.js
-!client/features/**/*.js
+client/src/**/*.js
+client/assets/**/*.js
+test/spec/**/*.js
+
!client/src/app.start.js
+!client/src/vendor.js
diff --git a/awx/ui/.eslintrc.js b/awx/ui/.eslintrc.js
index c624bf962a..c8b8c73bba 100644
--- a/awx/ui/.eslintrc.js
+++ b/awx/ui/.eslintrc.js
@@ -6,13 +6,19 @@ module.exports = {
'airbnb-base'
],
plugins: [
- 'import'
+ 'import',
+ 'disable'
],
settings: {
'import/resolver': {
webpack: {
config: path.join(__dirname, 'build/webpack.development.js')
}
+ },
+ 'eslint-plugin-disable': {
+ paths: {
+ import: ['**/build/*.js']
+ }
}
},
env: {
@@ -44,7 +50,9 @@ module.exports = {
'no-plusplus': 'off',
'no-underscore-dangle': 'off',
'no-use-before-define': 'off',
+ 'no-multiple-empty-lines': ['error', { max: 1 }],
'object-curly-newline': 'off',
- 'space-before-function-paren': ['error', 'always']
+ 'space-before-function-paren': ['error', 'always'],
+ 'no-trailing-spaces': ['error']
}
};
diff --git a/awx/ui/build/webpack.base.js b/awx/ui/build/webpack.base.js
index ae4c3a856d..371a6934aa 100644
--- a/awx/ui/build/webpack.base.js
+++ b/awx/ui/build/webpack.base.js
@@ -18,12 +18,13 @@ const LANGUAGES_PATH = path.join(CLIENT_PATH, 'languages');
const MODELS_PATH = path.join(LIB_PATH, 'models');
const NODE_MODULES_PATH = path.join(UI_PATH, 'node_modules');
const SERVICES_PATH = path.join(LIB_PATH, 'services');
-const SOURCE_PATH = path.join(CLIENT_PATH, 'src');
+const SRC_PATH = path.join(CLIENT_PATH, 'src');
const STATIC_PATH = path.join(UI_PATH, 'static');
+const TEST_PATH = path.join(UI_PATH, 'test');
const THEME_PATH = path.join(LIB_PATH, 'theme');
-const APP_ENTRY = path.join(SOURCE_PATH, 'app.js');
-const VENDOR_ENTRY = path.join(SOURCE_PATH, 'vendor.js');
+const APP_ENTRY = path.join(SRC_PATH, 'app.js');
+const VENDOR_ENTRY = path.join(SRC_PATH, 'vendor.js');
const INDEX_ENTRY = path.join(CLIENT_PATH, 'index.template.ejs');
const INDEX_OUTPUT = path.join(UI_PATH, 'templates/ui/index.html');
const THEME_ENTRY = path.join(LIB_PATH, 'theme', 'index.less');
@@ -49,7 +50,7 @@ const base = {
chunks: false,
excludeAssets: name => {
const chunkNames = `(${CHUNKS.join('|')})`;
- const outputPattern = new RegExp(`${chunkNames}\.[a-f0-9]+\.(js|css)(|\.map)$`, 'i');
+ const outputPattern = new RegExp(`${chunkNames}.[a-f0-9]+.(js|css)(|.map)$`, 'i');
return !outputPattern.test(name);
}
@@ -82,7 +83,7 @@ const base = {
})
},
{
- test: /\lib\/theme\/index.less$/,
+ test: /lib\/theme\/index.less$/,
use: ExtractTextPlugin.extract({
use: ['css-loader', 'less-loader']
})
@@ -92,7 +93,8 @@ const base = {
use: ['ngtemplate-loader', 'html-loader'],
include: [
/lib\/components\//,
- /features\//
+ /features\//,
+ /src\//
]
},
{
@@ -149,17 +151,17 @@ const base = {
context: NODE_MODULES_PATH
},
{
- from: path.join(SOURCE_PATH, '**/*.partial.html'),
+ from: path.join(SRC_PATH, '**/*.partial.html'),
to: path.join(STATIC_PATH, 'partials/'),
- context: SOURCE_PATH
+ context: SRC_PATH
},
{
- from: path.join(SOURCE_PATH, 'partials', '*.html'),
+ from: path.join(SRC_PATH, 'partials', '*.html'),
to: STATIC_PATH,
- context: SOURCE_PATH
+ context: SRC_PATH
},
{
- from: path.join(SOURCE_PATH, '*config.js'),
+ from: path.join(SRC_PATH, '*config.js'),
to: STATIC_PATH,
flatten: true
}
@@ -170,31 +172,34 @@ const base = {
filename: INDEX_OUTPUT,
inject: false,
chunks: CHUNKS,
- chunksSortMode: chunk => chunk.names[0] === 'vendor' ? -1 : 1
+ chunksSortMode: chunk => (chunk.names[0] === 'vendor' ? -1 : 1)
})
],
resolve: {
alias: {
+ '~assets': ASSETS_PATH,
+ '~components': COMPONENTS_PATH,
'~features': FEATURES_PATH,
'~models': MODELS_PATH,
+ '~node_modules': NODE_MODULES_PATH,
'~services': SERVICES_PATH,
- '~components': COMPONENTS_PATH,
+ '~src': SRC_PATH,
+ '~test': TEST_PATH,
'~theme': THEME_PATH,
- '~modules': NODE_MODULES_PATH,
- '~assets': ASSETS_PATH,
- 'd3$': '~modules/d3/d3.min.js',
- 'codemirror.jsonlint$': '~modules/codemirror/addon/lint/json-lint.js',
- 'jquery': '~modules/jquery/dist/jquery.js',
- 'jquery-resize$': '~modules/javascript-detect-element-resize/jquery.resize.js',
- 'select2$': '~modules/select2/dist/js/select2.full.min.js',
- 'js-yaml$': '~modules/js-yaml/dist/js-yaml.min.js',
- 'lr-infinite-scroll$': '~modules/lr-infinite-scroll/lrInfiniteScroll.js',
- 'angular-tz-extensions$': '~modules/angular-tz-extensions/lib/angular-tz-extensions.js',
- 'angular-ui-router$': '~modules/angular-ui-router/release/angular-ui-router.js',
- 'angular-ui-router-state-events$': '~modules/angular-ui-router/release/stateEvents.js',
- 'ng-toast-provider$': '~modules/ng-toast/src/scripts/provider.js',
- 'ng-toast-directives$': '~modules/ng-toast/src/scripts/directives.js',
- 'ng-toast$': '~modules/ng-toast/src/scripts/module.js'
+ '~ui': UI_PATH,
+ d3$: '~node_modules/d3/d3.min.js',
+ 'codemirror.jsonlint$': '~node_modules/codemirror/addon/lint/json-lint.js',
+ jquery: '~node_modules/jquery/dist/jquery.js',
+ 'jquery-resize$': '~node_modules/javascript-detect-element-resize/jquery.resize.js',
+ select2$: '~node_modules/select2/dist/js/select2.full.min.js',
+ 'js-yaml$': '~node_modules/js-yaml/dist/js-yaml.min.js',
+ 'lr-infinite-scroll$': '~node_modules/lr-infinite-scroll/lrInfiniteScroll.js',
+ 'angular-tz-extensions$': '~node_modules/angular-tz-extensions/lib/angular-tz-extensions.js',
+ 'angular-ui-router$': '~node_modules/angular-ui-router/release/angular-ui-router.js',
+ 'angular-ui-router-state-events$': '~node_modules/angular-ui-router/release/stateEvents.js',
+ 'ng-toast-provider$': '~node_modules/ng-toast/src/scripts/provider.js',
+ 'ng-toast-directives$': '~node_modules/ng-toast/src/scripts/directives.js',
+ 'ng-toast$': '~node_modules/ng-toast/src/scripts/module.js'
}
}
};
diff --git a/awx/ui/build/webpack.development.js b/awx/ui/build/webpack.development.js
index a9b2db20d8..56e2d90b51 100644
--- a/awx/ui/build/webpack.development.js
+++ b/awx/ui/build/webpack.development.js
@@ -1,4 +1,4 @@
-const _ = require('lodash');
+const merge = require('webpack-merge');
const base = require('./webpack.base');
@@ -6,4 +6,4 @@ const development = {
devtool: 'source-map'
};
-module.exports = _.merge(base, development);
+module.exports = merge(base, development);
diff --git a/awx/ui/build/webpack.production.js b/awx/ui/build/webpack.production.js
index 4c61eef4a4..04ff1cd29b 100644
--- a/awx/ui/build/webpack.production.js
+++ b/awx/ui/build/webpack.production.js
@@ -1,6 +1,6 @@
const path = require('path');
-const _ = require('lodash');
+const merge = require('webpack-merge');
const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
@@ -25,16 +25,14 @@ const production = {
filename: INSTALL_RUNNING_OUTPUT,
inject: false,
chunks: CHUNKS,
- chunksSortMode: chunk => chunk.names[0] === 'vendor' ? -1 : 1
+ chunksSortMode: chunk => (chunk.names[0] === 'vendor' ? -1 : 1)
}),
new webpack.DefinePlugin({
- 'process.env': {
- 'NODE_ENV': JSON.stringify('production')
+ 'process.env': {
+ NODE_ENV: JSON.stringify('production')
}
})
]
};
-production.plugins = base.plugins.concat(production.plugins);
-
-module.exports = _.merge(base, production);
+module.exports = merge(base, production);
diff --git a/awx/ui/build/webpack.watch.js b/awx/ui/build/webpack.watch.js
index 5030bdf4d4..bdda9363de 100644
--- a/awx/ui/build/webpack.watch.js
+++ b/awx/ui/build/webpack.watch.js
@@ -2,6 +2,8 @@ const path = require('path');
const _ = require('lodash');
const webpack = require('webpack');
+const merge = require('webpack-merge');
+const nodeObjectHash = require('node-object-hash');
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
@@ -33,9 +35,7 @@ const watch = {
new HardSourceWebpackPlugin({
cacheDirectory: 'node_modules/.cache/hard-source/[confighash]',
recordsPath: 'node_modules/.cache/hard-source/[confighash]/records.json',
- configHash: config => {
- return require('node-object-hash')({ sort: false }).hash(config);
- },
+ configHash: config => nodeObjectHash({ sort: false }).hash(config),
environmentHash: {
root: process.cwd(),
directories: ['node_modules'],
@@ -68,7 +68,4 @@ const watch = {
}
};
-watch.module.rules = development.module.rules.concat(watch.module.rules);
-watch.plugins = development.plugins.concat(watch.plugins);
-
-module.exports = _.merge(development, watch);
+module.exports = merge(development, watch);
diff --git a/awx/ui/client/src/vendor.js b/awx/ui/client/src/vendor.js
index 11c361d314..89e148e7e2 100644
--- a/awx/ui/client/src/vendor.js
+++ b/awx/ui/client/src/vendor.js
@@ -2,18 +2,20 @@
require('~assets/custom-theme/jquery-ui-1.10.3.custom.min.css');
require('~assets/ansible-bootstrap.min.css');
require('~assets/fontcustom/fontcustom.css');
-require('~modules/components-font-awesome/css/font-awesome.min.css');
-require('~modules/select2/dist/css/select2.css');
-require('~modules/codemirror/lib/codemirror.css');
-require('~modules/codemirror/theme/elegant.css');
-require('~modules/codemirror/addon/lint/lint.css');
-require('~modules/nvd3/build/nv.d3.css');
-require('~modules/ng-toast/dist/ngToast.min.css');
+require('~node_modules/components-font-awesome/css/font-awesome.min.css');
+require('~node_modules/select2/dist/css/select2.css');
+require('~node_modules/codemirror/lib/codemirror.css');
+require('~node_modules/codemirror/theme/elegant.css');
+require('~node_modules/codemirror/addon/lint/lint.css');
+require('~node_modules/nvd3/build/nv.d3.css');
+require('~node_modules/ng-toast/dist/ngToast.min.css');
// jQuery + extensions
global.jQuery = require('jquery');
+
global.jquery = global.jQuery;
global.$ = global.jQuery;
+
require('jquery-resize');
require('jquery-ui');
require('bootstrap');
diff --git a/awx/ui/client/test/e2e/api.js b/awx/ui/client/test/e2e/api.js
deleted file mode 100644
index 464401cb24..0000000000
--- a/awx/ui/client/test/e2e/api.js
+++ /dev/null
@@ -1,104 +0,0 @@
-import https from 'https';
-
-import axios from 'axios';
-
-import {
- awxURL,
- awxUsername,
- awxPassword
-} from './settings.js';
-
-
-let authenticated;
-
-const session = axios.create({
- baseURL: awxURL,
- xsrfHeaderName: 'X-CSRFToken',
- xsrfCookieName: 'csrftoken',
- httpsAgent: new https.Agent({
- rejectUnauthorized: false
- })
-});
-
-
-const endpoint = function(location) {
-
- if (location.indexOf('/api/v') === 0) {
- return location;
- }
-
- if (location.indexOf('://') > 0) {
- return location;
- }
-
- return `${awxURL}/api/v2${location}`;
-};
-
-
-const authenticate = function() {
- if (authenticated) {
- return Promise.resolve();
- }
-
- let uri = endpoint('/authtoken/');
-
- let credentials = {
- username: awxUsername,
- password: awxPassword
- };
-
- return session.post(uri, credentials).then(res => {
- session.defaults.headers.Authorization = `Token ${res.data.token}`;
- authenticated = true;
- return res
- });
-};
-
-
-const request = function(method, location, data) {
- let uri = endpoint(location);
- let action = session[method.toLowerCase()];
-
- return authenticate().then(() => action(uri, data)).then(res => {
- console.log([
- res.config.method.toUpperCase(),
- uri,
- res.status,
- res.statusText
- ].join(' '));
-
- return res;
- });
-};
-
-
-const get = function(endpoint, data) {
- return request('GET', endpoint, data);
-};
-
-const options = function(endpoint) {
- return request('OPTIONS', endpoint);
-};
-
-const post = function(endpoint, data) {
- return request('POST', endpoint, data);
-};
-
-const patch = function(endpoint, data) {
- return request('PATCH', endpoint, data)
-};
-
-const put = function(endpoint, data) {
- return request('PUT', endpoint, data);
-};
-
-
-module.exports = {
- get,
- options,
- post,
- patch,
- put,
- all: axios.all,
- spread: axios.spread
-};
diff --git a/awx/ui/client/test/e2e/commands/waitForAngular.js b/awx/ui/client/test/e2e/commands/waitForAngular.js
deleted file mode 100644
index ee0e337641..0000000000
--- a/awx/ui/client/test/e2e/commands/waitForAngular.js
+++ /dev/null
@@ -1,20 +0,0 @@
-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;
-};
diff --git a/awx/ui/client/test/e2e/fixtures.js b/awx/ui/client/test/e2e/fixtures.js
deleted file mode 100644
index 0ef3a080f6..0000000000
--- a/awx/ui/client/test/e2e/fixtures.js
+++ /dev/null
@@ -1,263 +0,0 @@
-import uuid from 'uuid';
-
-import {
- all,
- get,
- post,
- spread
-} from './api.js';
-
-
-const sid = uuid().substr(0,8);
-
-let store = {};
-
-
-const getOrCreate = function(endpoint, data) {
- let identifier = Object.keys(data).find(key => ['name', 'username'].includes(key));
-
- if (identifier === undefined) {
- throw new Error('A unique key value must be provided.');
- }
-
- let identity = data[identifier];
-
- if (store[endpoint] && store[endpoint][identity]) {
- return store[endpoint][identity].then(created => created.data);
- }
-
- if (!store[endpoint]) {
- store[endpoint] = {};
- }
-
- let query = { params: { [identifier]: identity } };
-
- store[endpoint][identity] = get(endpoint, query).then(res => {
-
- if (res.data.results.length > 1) {
- return Promise.reject(new Error('More than one matching result.'));
- }
-
- if (res.data.results.length === 1) {
- return get(res.data.results[0].url);
- }
-
- if (res.data.results.length === 0) {
- return post(endpoint, data);
- }
-
- return Promise.reject(new Error(`unexpected response: ${res}`));
- });
-
- return store[endpoint][identity].then(created => created.data);
-};
-
-
-const getOrganization = function() {
- return getOrCreate('/organizations/', {
- name: `e2e-organization-${sid}`
- });
-};
-
-
-const getInventory = function() {
- return getOrganization().then(organization => {
- return getOrCreate('/inventories/', {
- name: `e2e-inventory-${sid}`,
- organization: organization.id
- });
- });
-};
-
-
-const getInventoryScript = function() {
- return getOrganization().then(organization => {
- return getOrCreate('/inventory_scripts/', {
- name: `e2e-inventory-script-${sid}`,
- organization: organization.id,
- script: '#!/usr/bin/env python'
- });
- });
-};
-
-
-const getAdminAWSCredential = function() {
- return all([
- get('/me/'),
- getOrCreate('/credential_types/', {
- name: "Amazon Web Services"
- })
- ])
- .then(spread((me, credentialType) => {
- let admin = me.data.results[0];
- return getOrCreate('/credentials/', {
- name: `e2e-aws-credential-${sid}`,
- credential_type: credentialType.id,
- user: admin.id,
- inputs: {
- username: 'admin',
- password: 'password',
- security_token: 'AAAAAAAAAAAAAAAA'
- }
- });
- }));
-};
-
-
-const getAdminMachineCredential = function() {
- return all([
- get('/me/'),
- getOrCreate('/credential_types/', { name: "Machine" })
- ])
- .then(spread((me, credentialType) => {
- let admin = me.data.results[0];
- return getOrCreate('/credentials/', {
- name: `e2e-machine-credential-${sid}`,
- credential_type: credentialType.id,
- user: admin.id
- });
- }));
-};
-
-
-const getTeam = function() {
- return getOrganization().then(organization => {
- return getOrCreate('/teams/', {
- name: `e2e-team-${sid}`,
- organization: organization.id,
- });
- });
-};
-
-
-const getSmartInventory = function() {
- return getOrganization().then(organization => {
- return getOrCreate('/inventories/', {
- name: `e2e-smart-inventory-${sid}`,
- organization: organization.id,
- host_filter: 'search=localhost',
- kind: 'smart'
- });
- });
-};
-
-
-const getNotificationTemplate = function() {
- return getOrganization().then(organization => {
- return getOrCreate('/notification_templates/', {
- name: `e2e-notification-template-${sid}`,
- organization: organization.id,
- notification_type: 'slack',
- notification_configuration: {
- token: '54321GFEDCBAABCDEFG12345',
- channels: ['awx-e2e']
- }
- });
- });
-};
-
-
-const getProject = function() {
- return getOrganization().then(organization => {
- return getOrCreate('/projects/', {
- name: `e2e-project-${sid}`,
- organization: organization.id,
- scm_url: 'https://github.com/ansible/ansible-tower-samples',
- scm_type: 'git'
- });
- });
-};
-
-
-const waitForJob = function(endpoint) {
- const interval = 2000;
- const statuses = ['successful', 'failed', 'error', 'canceled'];
-
- let attempts = 20;
-
- return new Promise((resolve, reject) => {
- (function pollStatus() {
- get(endpoint).then(update => {
- let completed = statuses.indexOf(update.data.status) > -1;
- if (completed) return resolve();
- if (--attempts <= 0) return reject('Retry limit exceeded.');
- setTimeout(pollStatus, interval);
- });
- })();
- });
-};
-
-
-const getUpdatedProject = function() {
- return getProject().then(project => {
- let updateURL = project.related.current_update;
- if (updateURL) {
- return waitForJob(updateURL).then(() => project);
- }
- return project;
- });
-};
-
-
-const getJobTemplate = function() {
- return all([
- getInventory(),
- getAdminMachineCredential(),
- getUpdatedProject()
- ])
- .then(spread((inventory, credential, project) => {
- return getOrCreate('/job_templates', {
- name: `e2e-job-template-${sid}`,
- inventory: inventory.id,
- credential: credential.id,
- project: project.id,
- playbook: 'hello_world.yml'
- });
- }));
-};
-
-
-const getAuditor = function() {
- return getOrganization().then(organization => {
- return getOrCreate('/users/', {
- organization: organization.id,
- username: `e2e-auditor-${sid}`,
- first_name: 'auditor',
- last_name: 'last',
- email: 'null@ansible.com',
- is_superuser: false,
- is_system_auditor: true,
- password: 'password'
- })
- });
-};
-
-
-const getUser = function() {
- return getOrCreate('/users/', {
- username: `e2e-user-${sid}`,
- first_name: `user-${sid}-first`,
- last_name: `user-${sid}-last`,
- email: `null-${sid}@ansible.com`,
- is_superuser: false,
- is_system_auditor: false,
- password: 'password'
- });
-};
-
-
-module.exports = {
- getAdminAWSCredential,
- getAdminMachineCredential,
- getAuditor,
- getInventory,
- getInventoryScript,
- getJobTemplate,
- getNotificationTemplate,
- getOrCreate,
- getOrganization,
- getSmartInventory,
- getTeam,
- getUpdatedProject,
- getUser
-};
diff --git a/awx/ui/client/test/e2e/objects/sections/createTableSection.js b/awx/ui/client/test/e2e/objects/sections/createTableSection.js
deleted file mode 100644
index 58f134c053..0000000000
--- a/awx/ui/client/test/e2e/objects/sections/createTableSection.js
+++ /dev/null
@@ -1,63 +0,0 @@
-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;
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-custom.js b/awx/ui/client/test/e2e/tests/test-credentials-add-edit-custom.js
deleted file mode 100644
index 0606cc7d39..0000000000
--- a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-custom.js
+++ /dev/null
@@ -1,176 +0,0 @@
-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();
- }
-};
diff --git a/awx/ui/package.json b/awx/ui/package.json
index e6893ae15c..2f81d84a18 100644
--- a/awx/ui/package.json
+++ b/awx/ui/package.json
@@ -21,12 +21,12 @@
"languages": "grunt nggettext_compile",
"build-release": "npm run production",
"pretest": "",
- "test": "karma start karma.conf.js",
+ "test": "karma start test/spec/karma.spec.js",
"jshint": "grunt jshint:source --no-color",
"test:ci": "npm run test -- --single-run --reporter junit,dots --browsers=PhantomJS",
- "e2e": "./client/test/e2e/runner.js --config ./client/test/e2e/nightwatch.conf.js",
- "component-test": "karma start client/test/unit/karma.conf.js",
- "lint": "eslint -c .eslintrc.js .",
+ "e2e": "./test/e2e/runner.js --config ./test/e2e/nightwatch.conf.js",
+ "unit": "karma start test/unit/karma.unit.js",
+ "lint": "eslint .",
"dev": "webpack --config build/webpack.development.js --progress",
"watch": "webpack-dev-server --config build/webpack.watch.js --progress",
"production": "webpack --config build/webpack.production.js"
@@ -47,6 +47,7 @@
"eslint-config-airbnb-base": "^12.0.0",
"eslint-import-resolver-webpack": "^0.8.3",
"eslint-loader": "^1.9.0",
+ "eslint-plugin-disable": "^0.3.0",
"eslint-plugin-import": "^2.7.0",
"extract-text-webpack-plugin": "^3.0.0",
"grunt": "^1.0.1",
@@ -84,8 +85,10 @@
"phantomjs-prebuilt": "^2.1.12",
"time-grunt": "^1.4.0",
"uglifyjs-webpack-plugin": "^0.4.6",
+ "uuid": "^3.1.0",
"webpack": "^3.0.0",
- "webpack-dev-server": "^2.7.1"
+ "webpack-dev-server": "^2.7.1",
+ "webpack-merge": "^4.1.0"
},
"dependencies": {
"angular": "~1.4.14",
diff --git a/awx/ui/client/test/e2e/.babelrc b/awx/ui/test/e2e/.babelrc
similarity index 100%
rename from awx/ui/client/test/e2e/.babelrc
rename to awx/ui/test/e2e/.babelrc
diff --git a/awx/ui/test/e2e/.eslintrc.js b/awx/ui/test/e2e/.eslintrc.js
new file mode 100644
index 0000000000..7ce4f5483c
--- /dev/null
+++ b/awx/ui/test/e2e/.eslintrc.js
@@ -0,0 +1,5 @@
+module.exports = {
+ rules: {
+ 'no-unused-expressions': 'off'
+ }
+};
diff --git a/awx/ui/client/test/e2e/README.md b/awx/ui/test/e2e/README.md
similarity index 100%
rename from awx/ui/client/test/e2e/README.md
rename to awx/ui/test/e2e/README.md
diff --git a/awx/ui/test/e2e/api.js b/awx/ui/test/e2e/api.js
new file mode 100644
index 0000000000..90b29c4355
--- /dev/null
+++ b/awx/ui/test/e2e/api.js
@@ -0,0 +1,82 @@
+import https from 'https';
+
+import axios from 'axios';
+
+import {
+ awxURL,
+ awxUsername,
+ awxPassword
+} from './settings';
+
+let authenticated;
+
+const session = axios.create({
+ baseURL: awxURL,
+ xsrfHeaderName: 'X-CSRFToken',
+ xsrfCookieName: 'csrftoken',
+ httpsAgent: new https.Agent({
+ rejectUnauthorized: false
+ })
+});
+
+const getEndpoint = location => {
+ if (location.indexOf('/api/v') === 0 || location.indexOf('://') > 0) {
+ return location;
+ }
+
+ return `${awxURL}/api/v2${location}`;
+};
+
+const authenticate = () => {
+ if (authenticated) {
+ return Promise.resolve();
+ }
+
+ const uri = getEndpoint('/authtoken/');
+
+ const credentials = {
+ username: awxUsername,
+ password: awxPassword
+ };
+
+ return session.post(uri, credentials).then(res => {
+ session.defaults.headers.Authorization = `Token ${res.data.token}`;
+ authenticated = true;
+
+ return res;
+ });
+};
+
+const request = (method, location, data) => {
+ const uri = getEndpoint(location);
+ const action = session[method.toLowerCase()];
+
+ return authenticate()
+ .then(() => action(uri, data))
+ .then(res => {
+ console.log([ // eslint-disable-line no-console
+ res.config.method.toUpperCase(),
+ uri,
+ res.status,
+ res.statusText
+ ].join(' '));
+
+ return res;
+ });
+};
+
+const get = (endpoint, data) => request('GET', endpoint, data);
+const options = endpoint => request('OPTIONS', endpoint);
+const post = (endpoint, data) => request('POST', endpoint, data);
+const patch = (endpoint, data) => request('PATCH', endpoint, data);
+const put = (endpoint, data) => request('PUT', endpoint, data);
+
+module.exports = {
+ get,
+ options,
+ post,
+ patch,
+ put,
+ all: axios.all,
+ spread: axios.spread
+};
diff --git a/awx/ui/client/test/e2e/cluster/devel-override.yml b/awx/ui/test/e2e/cluster/devel-override.yml
similarity index 100%
rename from awx/ui/client/test/e2e/cluster/devel-override.yml
rename to awx/ui/test/e2e/cluster/devel-override.yml
diff --git a/awx/ui/client/test/e2e/cluster/docker-compose.yml b/awx/ui/test/e2e/cluster/docker-compose.yml
similarity index 100%
rename from awx/ui/client/test/e2e/cluster/docker-compose.yml
rename to awx/ui/test/e2e/cluster/docker-compose.yml
diff --git a/awx/ui/client/test/e2e/commands/inject.js b/awx/ui/test/e2e/commands/inject.js
similarity index 57%
rename from awx/ui/client/test/e2e/commands/inject.js
rename to awx/ui/test/e2e/commands/inject.js
index 50006e57b8..3ecbbcd61c 100644
--- a/awx/ui/client/test/e2e/commands/inject.js
+++ b/awx/ui/test/e2e/commands/inject.js
@@ -1,5 +1,7 @@
-exports.command = function(deps, script, callback) {
- this.executeAsync(`let args = Array.prototype.slice.call(arguments,0);
+exports.command = function inject (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 => {
@@ -11,11 +13,13 @@ exports.command = function(deps, script, callback) {
});
(${script.toString()}).apply(this, loaded).then(done);
}.apply(this, args);`,
- [deps],
- function(result) {
- if(typeof(callback) === "function") {
- callback.call(this, result.value);
+ [deps],
+ function handleResult (result) {
+ if (typeof callback === 'function') {
+ callback.call(this, result.value);
+ }
}
- });
+ );
+
return this;
};
diff --git a/awx/ui/client/test/e2e/commands/login.js b/awx/ui/test/e2e/commands/login.js
similarity index 80%
rename from awx/ui/client/test/e2e/commands/login.js
rename to awx/ui/test/e2e/commands/login.js
index ddf753c496..49acc34553 100644
--- a/awx/ui/client/test/e2e/commands/login.js
+++ b/awx/ui/test/e2e/commands/login.js
@@ -1,16 +1,13 @@
import { EventEmitter } from 'events';
import { inherits } from 'util';
-
-const Login = function() {
+function Login () {
EventEmitter.call(this);
}
inherits(Login, EventEmitter);
-
-Login.prototype.command = function(username, password) {
-
+Login.prototype.command = function command (username, password) {
username = username || this.api.globals.awxUsername;
password = password || this.api.globals.awxPassword;
@@ -33,16 +30,17 @@ Login.prototype.command = function(username, password) {
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.setValue('@username', username);
+ loginPage.setValue('@password', password);
+ loginPage.click('@submit');
+ loginPage.waitForElementVisible('div.spinny');
loginPage.waitForElementNotVisible('div.spinny');
}
- })
- })
+ });
+ });
+
this.emit('complete');
- })
+ });
};
module.exports = Login;
diff --git a/awx/ui/test/e2e/commands/waitForAngular.js b/awx/ui/test/e2e/commands/waitForAngular.js
new file mode 100644
index 0000000000..3f1acf5cd6
--- /dev/null
+++ b/awx/ui/test/e2e/commands/waitForAngular.js
@@ -0,0 +1,17 @@
+exports.command = function waitForAngular (callback) {
+ this.timeoutsAsyncScript(this.globals.asyncHookTimeout, () => {
+ this.executeAsync(done => {
+ if (angular && angular.getTestability) {
+ angular.getTestability(document.body).whenStable(done);
+ } else {
+ done();
+ }
+ }, [], result => {
+ if (typeof callback === 'function') {
+ callback.call(this, result);
+ }
+ });
+ });
+
+ return this;
+};
diff --git a/awx/ui/test/e2e/fixtures.js b/awx/ui/test/e2e/fixtures.js
new file mode 100644
index 0000000000..9f81fd2083
--- /dev/null
+++ b/awx/ui/test/e2e/fixtures.js
@@ -0,0 +1,235 @@
+import uuid from 'uuid';
+
+import {
+ all,
+ get,
+ post,
+ spread
+} from './api';
+
+const sid = uuid().substr(0, 8);
+
+const store = {};
+
+const getOrCreate = (endpoint, data) => {
+ const identifier = Object.keys(data).find(key => ['name', 'username'].includes(key));
+
+ if (identifier === undefined) {
+ throw new Error('A unique key value must be provided.');
+ }
+
+ const identity = data[identifier];
+
+ if (store[endpoint] && store[endpoint][identity]) {
+ return store[endpoint][identity].then(created => created.data);
+ }
+
+ if (!store[endpoint]) {
+ store[endpoint] = {};
+ }
+
+ const query = { params: { [identifier]: identity } };
+
+ store[endpoint][identity] = get(endpoint, query)
+ .then(res => {
+ if (res.data.results.length > 1) {
+ return Promise.reject(new Error('More than one matching result.'));
+ }
+
+ if (res.data.results.length === 1) {
+ return get(res.data.results[0].url);
+ }
+
+ if (res.data.results.length === 0) {
+ return post(endpoint, data);
+ }
+
+ return Promise.reject(new Error(`unexpected response: ${res}`));
+ });
+
+ return store[endpoint][identity].then(created => created.data);
+};
+
+const getOrganization = () => getOrCreate('/organizations/', {
+ name: `e2e-organization-${sid}`
+});
+
+const getInventory = () => getOrganization()
+ .then(organization => getOrCreate('/inventories/', {
+ name: `e2e-inventory-${sid}`,
+ organization: organization.id
+ }));
+
+const getInventoryScript = () => getOrganization()
+ .then(organization => getOrCreate('/inventory_scripts/', {
+ name: `e2e-inventory-script-${sid}`,
+ organization: organization.id,
+ script: '#!/usr/bin/env python'
+ }));
+
+const getAdminAWSCredential = () => {
+ const promises = [
+ get('/me/'),
+ getOrCreate('/credential_types/', {
+ name: 'Amazon Web Services'
+ })
+ ];
+
+ return all(promises)
+ .then(spread((me, credentialType) => {
+ const [admin] = me.data.results;
+
+ return getOrCreate('/credentials/', {
+ name: `e2e-aws-credential-${sid}`,
+ credential_type: credentialType.id,
+ user: admin.id,
+ inputs: {
+ username: 'admin',
+ password: 'password',
+ security_token: 'AAAAAAAAAAAAAAAA'
+ }
+ });
+ }));
+};
+
+const getAdminMachineCredential = () => {
+ const promises = [
+ get('/me/'),
+ getOrCreate('/credential_types/', { name: 'Machine' })
+ ];
+
+ return all(promises)
+ .then(spread((me, credentialType) => {
+ const [admin] = me.data.results;
+
+ return getOrCreate('/credentials/', {
+ name: `e2e-machine-credential-${sid}`,
+ credential_type: credentialType.id,
+ user: admin.id
+ });
+ }));
+};
+
+const getTeam = () => getOrganization()
+ .then(organization => getOrCreate('/teams/', {
+ name: `e2e-team-${sid}`,
+ organization: organization.id,
+ }));
+
+const getSmartInventory = () => getOrganization()
+ .then(organization => getOrCreate('/inventories/', {
+ name: `e2e-smart-inventory-${sid}`,
+ organization: organization.id,
+ host_filter: 'search=localhost',
+ kind: 'smart'
+ }));
+
+const getNotificationTemplate = () => getOrganization()
+ .then(organization => getOrCreate('/notification_templates/', {
+ name: `e2e-notification-template-${sid}`,
+ organization: organization.id,
+ notification_type: 'slack',
+ notification_configuration: {
+ token: '54321GFEDCBAABCDEFG12345',
+ channels: ['awx-e2e']
+ }
+ }));
+
+const getProject = () => getOrganization()
+ .then(organization => getOrCreate('/projects/', {
+ name: `e2e-project-${sid}`,
+ organization: organization.id,
+ scm_url: 'https://github.com/ansible/ansible-tower-samples',
+ scm_type: 'git'
+ }));
+
+const waitForJob = endpoint => {
+ const interval = 2000;
+ const statuses = ['successful', 'failed', 'error', 'canceled'];
+
+ let attempts = 20;
+
+ return new Promise((resolve, reject) => {
+ (function pollStatus () {
+ get(endpoint).then(update => {
+ const completed = statuses.indexOf(update.data.status) > -1;
+
+ if (completed) {
+ return resolve();
+ }
+
+ if (--attempts <= 0) {
+ return reject(new Error('Retry limit exceeded.'));
+ }
+
+ return setTimeout(pollStatus, interval);
+ });
+ }());
+ });
+};
+
+const getUpdatedProject = () => getProject()
+ .then(project => {
+ const updateURL = project.related.current_update;
+
+ if (updateURL) {
+ return waitForJob(updateURL).then(() => project);
+ }
+
+ return project;
+ });
+
+const getJobTemplate = () => {
+ const promises = [
+ getInventory(),
+ getAdminMachineCredential(),
+ getUpdatedProject()
+ ];
+
+ return all(promises)
+ .then(spread((inventory, credential, project) => getOrCreate('/job_templates', {
+ name: `e2e-job-template-${sid}`,
+ inventory: inventory.id,
+ credential: credential.id,
+ project: project.id,
+ playbook: 'hello_world.yml'
+ })));
+};
+
+const getAuditor = () => getOrganization()
+ .then(organization => getOrCreate('/users/', {
+ organization: organization.id,
+ username: `e2e-auditor-${sid}`,
+ first_name: 'auditor',
+ last_name: 'last',
+ email: 'null@ansible.com',
+ is_superuser: false,
+ is_system_auditor: true,
+ password: 'password'
+ }));
+
+const getUser = () => getOrCreate('/users/', {
+ username: `e2e-user-${sid}`,
+ first_name: `user-${sid}-first`,
+ last_name: `user-${sid}-last`,
+ email: `null-${sid}@ansible.com`,
+ is_superuser: false,
+ is_system_auditor: false,
+ password: 'password'
+});
+
+module.exports = {
+ getAdminAWSCredential,
+ getAdminMachineCredential,
+ getAuditor,
+ getInventory,
+ getInventoryScript,
+ getJobTemplate,
+ getNotificationTemplate,
+ getOrCreate,
+ getOrganization,
+ getSmartInventory,
+ getTeam,
+ getUpdatedProject,
+ getUser
+};
diff --git a/awx/ui/client/test/e2e/nightwatch.conf.js b/awx/ui/test/e2e/nightwatch.conf.js
similarity index 100%
rename from awx/ui/client/test/e2e/nightwatch.conf.js
rename to awx/ui/test/e2e/nightwatch.conf.js
diff --git a/awx/ui/client/test/e2e/objects/activityStream.js b/awx/ui/test/e2e/objects/activityStream.js
similarity index 93%
rename from awx/ui/client/test/e2e/objects/activityStream.js
rename to awx/ui/test/e2e/objects/activityStream.js
index b122c02c68..896fa32886 100644
--- a/awx/ui/client/test/e2e/objects/activityStream.js
+++ b/awx/ui/test/e2e/objects/activityStream.js
@@ -1,6 +1,6 @@
module.exports = {
- url() {
- return `${this.api.globals.launch_url}/#/activity_stream`
+ url () {
+ return `${this.api.globals.launch_url}/#/activity_stream`;
},
elements: {
title: '.List-titleText',
diff --git a/awx/ui/client/test/e2e/objects/credentialTypes.js b/awx/ui/test/e2e/objects/credentialTypes.js
similarity index 65%
rename from awx/ui/client/test/e2e/objects/credentialTypes.js
rename to awx/ui/test/e2e/objects/credentialTypes.js
index 4584309543..6c98d1e631 100644
--- a/awx/ui/client/test/e2e/objects/credentialTypes.js
+++ b/awx/ui/test/e2e/objects/credentialTypes.js
@@ -1,11 +1,10 @@
-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';
-
+import actions from './sections/actions';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import createTableSection from './sections/createTableSection';
+import header from './sections/header';
+import search from './sections/search';
+import pagination from './sections/pagination';
const addEditPanel = {
selector: 'div[ui-view="form"]',
@@ -16,17 +15,16 @@ const addEditPanel = {
details: createFormSection({
selector: '#credential_type_form',
labels: {
- name: "Name",
- description: "Description",
- inputConfiguration: "Input Configuration",
- injectorConfiguration: "Injector Configuration"
+ name: 'Name',
+ description: 'Description',
+ inputConfiguration: 'Input Configuration',
+ injectorConfiguration: 'Injector Configuration'
},
strategy: 'legacy'
})
}
};
-
const listPanel = {
selector: 'div[ui-view="list"]',
elements: {
@@ -50,10 +48,9 @@ const listPanel = {
}
};
-
module.exports = {
- url() {
- return `${this.api.globals.launch_url}/#/credential_types`
+ url () {
+ return `${this.api.globals.launch_url}/#/credential_types`;
},
sections: {
header,
diff --git a/awx/ui/client/test/e2e/objects/credentials.js b/awx/ui/test/e2e/objects/credentials.js
similarity index 56%
rename from awx/ui/client/test/e2e/objects/credentials.js
rename to awx/ui/test/e2e/objects/credentials.js
index 7cb0a6de78..23fd718c44 100644
--- a/awx/ui/client/test/e2e/objects/credentials.js
+++ b/awx/ui/test/e2e/objects/credentials.js
@@ -1,153 +1,140 @@
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';
-
+import actions from './sections/actions';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import createTableSection from './sections/createTableSection';
+import dynamicSection from './sections/dynamicSection';
+import header from './sections/header';
+import lookupModal from './sections/lookupModal';
+import navigation from './sections/navigation';
+import pagination from './sections/pagination';
+import permissions from './sections/permissions';
+import search from './sections/search';
const common = createFormSection({
selector: 'form',
labels: {
- name: "Name",
- description: "Description",
- organization: "Organization",
- type: "Credential type"
+ 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"
+ 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",
+ vaultPassword: 'Vault Password',
}
});
-
const scm = createFormSection({
selector: '.at-InputGroup-inset',
labels: {
- username: "Username",
- password: "Password",
- sshKeyData: "SCM Private Key",
- sshKeyUnlock: "Private Key Passphrase",
+ 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",
+ 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",
+ 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",
+ host: 'VCenter Host',
+ username: 'Username',
+ password: 'Password',
}
});
-
const azureClassic = createFormSection({
selector: '.at-InputGroup-inset',
labels: {
- subscription: "Subscription ID",
- sshKeyData: "Management Certificate",
+ 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",
+ 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",
+ 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",
+ username: 'Username',
+ password: 'Password',
}
});
-
const cloudForms = createFormSection({
selector: '.at-InputGroup-inset',
labels: {
- host: "Cloudforms URL",
- username: "Username",
- password: "Password",
+ 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",
+ sshKeyData: 'SSH Private Key',
+ sshKeyUnlock: 'Private Key Passphrase',
+ username: 'Username',
+ password: 'Password',
+ authorizePassword: 'Authorize Password',
}
});
@@ -156,26 +143,23 @@ network.elements.authorize = {
selector: '//input[../p/text() = "Authorize"]'
};
-
const sat6 = createFormSection({
selector: '.at-InputGroup-inset',
labels: {
- host: "Satellite 6 URL",
- username: "Username",
- password: "Password",
+ host: 'Satellite 6 URL',
+ username: 'Username',
+ password: 'Password',
}
});
-
const insights = createFormSection({
selector: '.at-InputGroup-inset',
labels: {
- username: "Username",
- password: "Password",
+ username: 'Username',
+ password: 'Password',
},
});
-
const details = _.merge({}, common, {
elements: {
cancel: '.btn[type="cancel"]',
@@ -199,17 +183,17 @@ const details = _.merge({}, common, {
vmware
},
commands: [{
- custom({ name, inputs }) {
- let labels = {};
- inputs.fields.map(f => labels[f.id] = f.label);
+ custom ({ name, inputs }) {
+ const labels = {};
+ inputs.fields.forEach(f => { labels[f.id] = f.label; });
- let selector = '.at-InputGroup-inset';
- let generated = createFormSection({ selector, labels });
+ const selector = '.at-InputGroup-inset';
+ const generated = createFormSection({ selector, labels });
- let params = _.merge({ name }, generated);
+ const params = _.merge({ name }, generated);
return this.section.dynamicSection.create(params);
},
- clear() {
+ clear () {
this.clearValue('@name');
this.clearValue('@organization');
this.clearValue('@description');
@@ -217,7 +201,7 @@ const details = _.merge({}, common, {
this.waitForElementNotVisible('.at-InputGroup-inset');
return this;
},
- clearAndSelectType(type) {
+ clearAndSelectType (type) {
this.clear();
this.setValue('@type', type);
this.waitForElementVisible('.at-InputGroup-inset');
@@ -226,10 +210,9 @@ const details = _.merge({}, common, {
}]
});
-
module.exports = {
- url() {
- return `${this.api.globals.launch_url}/#/credentials`
+ url () {
+ return `${this.api.globals.launch_url}/#/credentials`;
},
sections: {
header,
diff --git a/awx/ui/client/test/e2e/objects/inventories.js b/awx/ui/test/e2e/objects/inventories.js
similarity index 84%
rename from awx/ui/client/test/e2e/objects/inventories.js
rename to awx/ui/test/e2e/objects/inventories.js
index bbef08ff2e..5a87c039f3 100644
--- a/awx/ui/client/test/e2e/objects/inventories.js
+++ b/awx/ui/test/e2e/objects/inventories.js
@@ -1,13 +1,13 @@
-import actions from './sections/actions.js';
-import breadcrumb from './sections/breadcrumb.js';
-import createFormSection from './sections/createFormSection.js';
-import createTableSection from './sections/createTableSection.js';
-import header from './sections/header.js';
-import lookupModal from './sections/lookupModal.js';
-import navigation from './sections/navigation.js';
-import pagination from './sections/pagination.js';
-import permissions from './sections/permissions.js';
-import search from './sections/search.js';
+import actions from './sections/actions';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import createTableSection from './sections/createTableSection';
+import header from './sections/header';
+import lookupModal from './sections/lookupModal';
+import navigation from './sections/navigation';
+import pagination from './sections/pagination';
+import permissions from './sections/permissions';
+import search from './sections/search';
const standardInvDetails = createFormSection({
selector: 'form',
@@ -36,7 +36,7 @@ const smartInvDetails = createFormSection({
});
module.exports = {
- url() {
+ url () {
return `${this.api.globals.launch_url}/#/inventories`;
},
sections: {
diff --git a/awx/ui/client/test/e2e/objects/inventoryScripts.js b/awx/ui/test/e2e/objects/inventoryScripts.js
similarity index 76%
rename from awx/ui/client/test/e2e/objects/inventoryScripts.js
rename to awx/ui/test/e2e/objects/inventoryScripts.js
index 3a9da46f00..4bea0c55e9 100644
--- a/awx/ui/client/test/e2e/objects/inventoryScripts.js
+++ b/awx/ui/test/e2e/objects/inventoryScripts.js
@@ -1,13 +1,13 @@
-import actions from './sections/actions.js';
-import breadcrumb from './sections/breadcrumb.js';
-import createFormSection from './sections/createFormSection.js';
-import createTableSection from './sections/createTableSection.js';
-import header from './sections/header.js';
-import lookupModal from './sections/lookupModal.js';
-import navigation from './sections/navigation.js';
-import pagination from './sections/pagination.js';
-import permissions from './sections/permissions.js';
-import search from './sections/search.js';
+import actions from './sections/actions';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import createTableSection from './sections/createTableSection';
+import header from './sections/header';
+import lookupModal from './sections/lookupModal';
+import navigation from './sections/navigation';
+import pagination from './sections/pagination';
+import permissions from './sections/permissions';
+import search from './sections/search';
const details = createFormSection({
selector: 'form',
@@ -21,7 +21,7 @@ const details = createFormSection({
});
module.exports = {
- url() {
+ url () {
return `${this.api.globals.launch_url}/#/inventory_scripts`;
},
sections: {
diff --git a/awx/ui/client/test/e2e/objects/login.js b/awx/ui/test/e2e/objects/login.js
similarity index 73%
rename from awx/ui/client/test/e2e/objects/login.js
rename to awx/ui/test/e2e/objects/login.js
index 7613ee855b..7fa8e5d8d4 100644
--- a/awx/ui/client/test/e2e/objects/login.js
+++ b/awx/ui/test/e2e/objects/login.js
@@ -1,6 +1,6 @@
module.exports = {
- url() {
- return `${this.api.globals.launch_url}/#/login`
+ url () {
+ return `${this.api.globals.launch_url}/#/login`;
},
elements: {
username: '#login-username',
diff --git a/awx/ui/client/test/e2e/objects/notificationTemplates.js b/awx/ui/test/e2e/objects/notificationTemplates.js
similarity index 79%
rename from awx/ui/client/test/e2e/objects/notificationTemplates.js
rename to awx/ui/test/e2e/objects/notificationTemplates.js
index 72ebd53045..f98fb4bd2e 100644
--- a/awx/ui/client/test/e2e/objects/notificationTemplates.js
+++ b/awx/ui/test/e2e/objects/notificationTemplates.js
@@ -1,13 +1,13 @@
-import actions from './sections/actions.js';
-import breadcrumb from './sections/breadcrumb.js';
-import createFormSection from './sections/createFormSection.js';
-import createTableSection from './sections/createTableSection.js';
-import header from './sections/header.js';
-import lookupModal from './sections/lookupModal.js';
-import navigation from './sections/navigation.js';
-import pagination from './sections/pagination.js';
-import permissions from './sections/permissions.js';
-import search from './sections/search.js';
+import actions from './sections/actions';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import createTableSection from './sections/createTableSection';
+import header from './sections/header';
+import lookupModal from './sections/lookupModal';
+import navigation from './sections/navigation';
+import pagination from './sections/pagination';
+import permissions from './sections/permissions';
+import search from './sections/search';
const details = createFormSection({
selector: 'form',
@@ -26,7 +26,7 @@ const details = createFormSection({
});
module.exports = {
- url() {
+ url () {
return `${this.api.globals.launch_url}/#/notification_templates`;
},
sections: {
diff --git a/awx/ui/client/test/e2e/objects/organizations.js b/awx/ui/test/e2e/objects/organizations.js
similarity index 77%
rename from awx/ui/client/test/e2e/objects/organizations.js
rename to awx/ui/test/e2e/objects/organizations.js
index b17c1d55f1..d1927affbd 100644
--- a/awx/ui/client/test/e2e/objects/organizations.js
+++ b/awx/ui/test/e2e/objects/organizations.js
@@ -1,11 +1,11 @@
-import breadcrumb from './sections/breadcrumb.js';
-import createFormSection from './sections/createFormSection.js';
-import header from './sections/header.js';
-import lookupModal from './sections/lookupModal.js';
-import navigation from './sections/navigation.js';
-import pagination from './sections/pagination.js';
-import permissions from './sections/permissions.js';
-import search from './sections/search.js';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import header from './sections/header';
+import lookupModal from './sections/lookupModal';
+import navigation from './sections/navigation';
+import pagination from './sections/pagination';
+import permissions from './sections/permissions';
+import search from './sections/search';
const details = createFormSection({
selector: 'form',
@@ -19,7 +19,7 @@ const details = createFormSection({
});
module.exports = {
- url() {
+ url () {
return `${this.api.globals.launch_url}/#/organizations`;
},
sections: {
diff --git a/awx/ui/client/test/e2e/objects/projects.js b/awx/ui/test/e2e/objects/projects.js
similarity index 78%
rename from awx/ui/client/test/e2e/objects/projects.js
rename to awx/ui/test/e2e/objects/projects.js
index e1956a8e83..75194e727b 100644
--- a/awx/ui/client/test/e2e/objects/projects.js
+++ b/awx/ui/test/e2e/objects/projects.js
@@ -1,13 +1,13 @@
-import actions from './sections/actions.js';
-import breadcrumb from './sections/breadcrumb.js';
-import createFormSection from './sections/createFormSection.js';
-import createTableSection from './sections/createTableSection.js';
-import header from './sections/header.js';
-import lookupModal from './sections/lookupModal.js';
-import navigation from './sections/navigation.js';
-import pagination from './sections/pagination.js';
-import permissions from './sections/permissions.js';
-import search from './sections/search.js';
+import actions from './sections/actions';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import createTableSection from './sections/createTableSection';
+import header from './sections/header';
+import lookupModal from './sections/lookupModal';
+import navigation from './sections/navigation';
+import pagination from './sections/pagination';
+import permissions from './sections/permissions';
+import search from './sections/search';
const details = createFormSection({
selector: 'form',
@@ -22,7 +22,7 @@ const details = createFormSection({
});
module.exports = {
- url() {
+ url () {
return `${this.api.globals.launch_url}/#/projects`;
},
sections: {
diff --git a/awx/ui/client/test/e2e/objects/sections/actions.js b/awx/ui/test/e2e/objects/sections/actions.js
similarity index 99%
rename from awx/ui/client/test/e2e/objects/sections/actions.js
rename to awx/ui/test/e2e/objects/sections/actions.js
index d99b4521c9..afa182e9b0 100644
--- a/awx/ui/client/test/e2e/objects/sections/actions.js
+++ b/awx/ui/test/e2e/objects/sections/actions.js
@@ -12,5 +12,4 @@ const actions = {
}
};
-
module.exports = actions;
diff --git a/awx/ui/client/test/e2e/objects/sections/breadcrumb.js b/awx/ui/test/e2e/objects/sections/breadcrumb.js
similarity index 100%
rename from awx/ui/client/test/e2e/objects/sections/breadcrumb.js
rename to awx/ui/test/e2e/objects/sections/breadcrumb.js
diff --git a/awx/ui/client/test/e2e/objects/sections/createFormSection.js b/awx/ui/test/e2e/objects/sections/createFormSection.js
similarity index 69%
rename from awx/ui/client/test/e2e/objects/sections/createFormSection.js
rename to awx/ui/test/e2e/objects/sections/createFormSection.js
index 1bdeeb0c54..3a8ebb810b 100644
--- a/awx/ui/client/test/e2e/objects/sections/createFormSection.js
+++ b/awx/ui/test/e2e/objects/sections/createFormSection.js
@@ -1,10 +1,8 @@
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',
@@ -39,7 +37,6 @@ const inputContainerElements = {
}
};
-
const legacyContainerElements = merge({}, inputContainerElements, {
prompt: {
locateStrategy: 'xpath',
@@ -49,22 +46,21 @@ const legacyContainerElements = merge({}, inputContainerElements, {
popover: ':root div[id^="popover"]',
});
-
-const generateInputSelectors = function(label, containerElements) {
+const generateInputSelectors = (label, containerElements) => {
// descend until span with matching text attribute is encountered
- let span = `.//span[text()="${label}"]`;
+ const 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')]`;
+ const 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')]`;
+ const input = `${container}//*[contains(@class, 'form-control')]`;
- let inputContainer = {
+ const inputContainer = {
locateStrategy: 'xpath',
selector: container,
elements: containerElements
};
- let inputElement = {
+ const inputElement = {
locateStrategy: 'xpath',
selector: input
};
@@ -72,15 +68,14 @@ const generateInputSelectors = function(label, containerElements) {
return { inputElement, inputContainer };
};
+function checkAllFieldsDisabled () {
+ const client = this.client.api;
-const checkAllFieldsDisabled = function() {
- let client = this.client.api;
-
- let selectors = this.props.formElementSelectors ? this.props.formElementSelectors : [
+ const selectors = this.props.formElementSelectors ? this.props.formElementSelectors : [
'.at-Input'
];
- selectors.forEach(function(selector) {
+ selectors.forEach(selector => {
client.elements('css selector', selector, inputs => {
inputs.value.map(o => o.ELEMENT).forEach(id => {
client.elementIdAttribute(id, 'disabled', ({ value }) => {
@@ -89,37 +84,37 @@ const checkAllFieldsDisabled = function() {
});
});
});
-};
-
+}
const generatorOptions = {
default: inputContainerElements,
legacy: legacyContainerElements
};
+const createFormSection = ({ selector, labels, strategy, props }) => {
+ const options = generatorOptions[strategy || 'default'];
-const createFormSection = function({ selector, labels, strategy, props }) {
- let options = generatorOptions[strategy || 'default'];
-
- let formSection = {
+ const formSection = {
+ props,
selector,
sections: {},
elements: {},
- commands: [{
- checkAllFieldsDisabled: checkAllFieldsDisabled
- }],
- props: props
+ commands: [{ checkAllFieldsDisabled }]
};
- for (let key in labels) {
- let label = labels[key];
-
- let { inputElement, inputContainer } = generateInputSelectors(label, options);
-
- formSection.elements[key] = inputElement;
- formSection.sections[key] = inputContainer;
+ if (!labels) {
+ return formSection;
}
+ Object.keys(labels)
+ .forEach(key => {
+ const label = labels[key];
+ const { inputElement, inputContainer } = generateInputSelectors(label, options);
+
+ formSection.elements[key] = inputElement;
+ formSection.sections[key] = inputContainer;
+ });
+
return formSection;
};
diff --git a/awx/ui/test/e2e/objects/sections/createTableSection.js b/awx/ui/test/e2e/objects/sections/createTableSection.js
new file mode 100644
index 0000000000..54dfd67eba
--- /dev/null
+++ b/awx/ui/test/e2e/objects/sections/createTableSection.js
@@ -0,0 +1,57 @@
+import dynamicSection from './dynamicSection';
+
+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-")]'
+ },
+ }
+ });
+ }
+ }]
+};
+
+module.exports = ({ elements, sections, commands }) => ({
+ 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) {
+ const countReached = `tbody tr:nth-of-type(${count})`;
+ this.waitForElementPresent(countReached);
+
+ const countExceeded = `tbody tr:nth-of-type(${count + 1})`;
+ this.waitForElementNotPresent(countExceeded);
+
+ return this;
+ }
+ }]
+});
+
diff --git a/awx/ui/client/test/e2e/objects/sections/dynamicSection.js b/awx/ui/test/e2e/objects/sections/dynamicSection.js
similarity index 70%
rename from awx/ui/client/test/e2e/objects/sections/dynamicSection.js
rename to awx/ui/test/e2e/objects/sections/dynamicSection.js
index d2cc472f2a..3478066318 100644
--- a/awx/ui/client/test/e2e/objects/sections/dynamicSection.js
+++ b/awx/ui/test/e2e/objects/sections/dynamicSection.js
@@ -1,10 +1,10 @@
const dynamicSection = {
selector: '.',
commands: [{
- create({ name, locateStrategy, selector, elements, sections, commands }) {
- let Section = this.constructor;
+ create ({ name, locateStrategy, selector, elements, sections, commands }) {
+ const Section = this.constructor;
- let options = Object.assign(Object.create(this), {
+ const options = Object.assign(Object.create(this), {
name,
locateStrategy,
elements,
diff --git a/awx/ui/client/test/e2e/objects/sections/header.js b/awx/ui/test/e2e/objects/sections/header.js
similarity index 99%
rename from awx/ui/client/test/e2e/objects/sections/header.js
rename to awx/ui/test/e2e/objects/sections/header.js
index f0ecb73e2a..ceea0f004b 100644
--- a/awx/ui/client/test/e2e/objects/sections/header.js
+++ b/awx/ui/test/e2e/objects/sections/header.js
@@ -8,5 +8,4 @@ const header = {
}
};
-
module.exports = header;
diff --git a/awx/ui/client/test/e2e/objects/sections/lookupModal.js b/awx/ui/test/e2e/objects/sections/lookupModal.js
similarity index 79%
rename from awx/ui/client/test/e2e/objects/sections/lookupModal.js
rename to awx/ui/test/e2e/objects/sections/lookupModal.js
index e6ac0f9a64..15c0b3bee8 100644
--- a/awx/ui/client/test/e2e/objects/sections/lookupModal.js
+++ b/awx/ui/test/e2e/objects/sections/lookupModal.js
@@ -1,7 +1,6 @@
-import createTableSection from './createTableSection.js';
-import pagination from './pagination.js';
-import search from './search.js';
-
+import createTableSection from './createTableSection';
+import pagination from './pagination';
+import search from './search';
const lookupModal = {
selector: '#form-modal',
diff --git a/awx/ui/client/test/e2e/objects/sections/navigation.js b/awx/ui/test/e2e/objects/sections/navigation.js
similarity index 99%
rename from awx/ui/client/test/e2e/objects/sections/navigation.js
rename to awx/ui/test/e2e/objects/sections/navigation.js
index fd978a0f94..62f3359202 100644
--- a/awx/ui/client/test/e2e/objects/sections/navigation.js
+++ b/awx/ui/test/e2e/objects/sections/navigation.js
@@ -21,5 +21,4 @@ const navigation = {
}
};
-
module.exports = navigation;
diff --git a/awx/ui/client/test/e2e/objects/sections/pagination.js b/awx/ui/test/e2e/objects/sections/pagination.js
similarity index 100%
rename from awx/ui/client/test/e2e/objects/sections/pagination.js
rename to awx/ui/test/e2e/objects/sections/pagination.js
diff --git a/awx/ui/client/test/e2e/objects/sections/permissions.js b/awx/ui/test/e2e/objects/sections/permissions.js
similarity index 79%
rename from awx/ui/client/test/e2e/objects/sections/permissions.js
rename to awx/ui/test/e2e/objects/sections/permissions.js
index 359206c0d9..66a9d467fe 100644
--- a/awx/ui/client/test/e2e/objects/sections/permissions.js
+++ b/awx/ui/test/e2e/objects/sections/permissions.js
@@ -1,8 +1,7 @@
-import actions from './actions.js';
-import createTableSection from './createTableSection.js';
-import pagination from './pagination.js';
-import search from './search.js';
-
+import actions from './actions';
+import createTableSection from './createTableSection';
+import pagination from './pagination';
+import search from './search';
const permissions = {
selector: 'div[ui-view="related"]',
@@ -26,5 +25,4 @@ const permissions = {
}
};
-
module.exports = permissions;
diff --git a/awx/ui/client/test/e2e/objects/sections/search.js b/awx/ui/test/e2e/objects/sections/search.js
similarity index 99%
rename from awx/ui/client/test/e2e/objects/sections/search.js
rename to awx/ui/test/e2e/objects/sections/search.js
index f88d1f282b..2fedb8fa37 100644
--- a/awx/ui/client/test/e2e/objects/sections/search.js
+++ b/awx/ui/test/e2e/objects/sections/search.js
@@ -8,5 +8,4 @@ const search = {
}
};
-
module.exports = search;
diff --git a/awx/ui/client/test/e2e/objects/teams.js b/awx/ui/test/e2e/objects/teams.js
similarity index 76%
rename from awx/ui/client/test/e2e/objects/teams.js
rename to awx/ui/test/e2e/objects/teams.js
index 245ee2eba2..bc1a4bd940 100644
--- a/awx/ui/client/test/e2e/objects/teams.js
+++ b/awx/ui/test/e2e/objects/teams.js
@@ -1,13 +1,13 @@
-import actions from './sections/actions.js';
-import breadcrumb from './sections/breadcrumb.js';
-import createFormSection from './sections/createFormSection.js';
-import createTableSection from './sections/createTableSection.js';
-import header from './sections/header.js';
-import lookupModal from './sections/lookupModal.js';
-import navigation from './sections/navigation.js';
-import pagination from './sections/pagination.js';
-import permissions from './sections/permissions.js';
-import search from './sections/search.js';
+import actions from './sections/actions';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import createTableSection from './sections/createTableSection';
+import header from './sections/header';
+import lookupModal from './sections/lookupModal';
+import navigation from './sections/navigation';
+import pagination from './sections/pagination';
+import permissions from './sections/permissions';
+import search from './sections/search';
const details = createFormSection({
selector: 'form',
@@ -20,7 +20,7 @@ const details = createFormSection({
});
module.exports = {
- url() {
+ url () {
return `${this.api.globals.launch_url}/#/teams`;
},
sections: {
diff --git a/awx/ui/client/test/e2e/objects/templates.js b/awx/ui/test/e2e/objects/templates.js
similarity index 82%
rename from awx/ui/client/test/e2e/objects/templates.js
rename to awx/ui/test/e2e/objects/templates.js
index 4e3b69cc4f..b200315427 100644
--- a/awx/ui/client/test/e2e/objects/templates.js
+++ b/awx/ui/test/e2e/objects/templates.js
@@ -1,13 +1,13 @@
-import actions from './sections/actions.js';
-import breadcrumb from './sections/breadcrumb.js';
-import createFormSection from './sections/createFormSection.js';
-import createTableSection from './sections/createTableSection.js';
-import header from './sections/header.js';
-import lookupModal from './sections/lookupModal.js';
-import navigation from './sections/navigation.js';
-import pagination from './sections/pagination.js';
-import permissions from './sections/permissions.js';
-import search from './sections/search.js';
+import actions from './sections/actions';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import createTableSection from './sections/createTableSection';
+import header from './sections/header';
+import lookupModal from './sections/lookupModal';
+import navigation from './sections/navigation';
+import pagination from './sections/pagination';
+import permissions from './sections/permissions';
+import search from './sections/search';
const details = createFormSection({
selector: 'form',
@@ -24,7 +24,7 @@ const details = createFormSection({
});
module.exports = {
- url() {
+ url () {
return `${this.api.globals.launch_url}/#/templates`;
},
sections: {
diff --git a/awx/ui/client/test/e2e/objects/users.js b/awx/ui/test/e2e/objects/users.js
similarity index 76%
rename from awx/ui/client/test/e2e/objects/users.js
rename to awx/ui/test/e2e/objects/users.js
index 9d8cdc0eeb..784d7f57fd 100644
--- a/awx/ui/client/test/e2e/objects/users.js
+++ b/awx/ui/test/e2e/objects/users.js
@@ -1,13 +1,13 @@
-import actions from './sections/actions.js';
-import breadcrumb from './sections/breadcrumb.js';
-import createFormSection from './sections/createFormSection.js';
-import createTableSection from './sections/createTableSection.js';
-import header from './sections/header.js';
-import lookupModal from './sections/lookupModal.js';
-import navigation from './sections/navigation.js';
-import pagination from './sections/pagination.js';
-import permissions from './sections/permissions.js';
-import search from './sections/search.js';
+import actions from './sections/actions';
+import breadcrumb from './sections/breadcrumb';
+import createFormSection from './sections/createFormSection';
+import createTableSection from './sections/createTableSection';
+import header from './sections/header';
+import lookupModal from './sections/lookupModal';
+import navigation from './sections/navigation';
+import pagination from './sections/pagination';
+import permissions from './sections/permissions';
+import search from './sections/search';
const details = createFormSection({
selector: 'form',
@@ -20,7 +20,7 @@ const details = createFormSection({
});
module.exports = {
- url() {
+ url () {
return `${this.api.globals.launch_url}/#/users`;
},
sections: {
diff --git a/awx/ui/client/test/e2e/runner.js b/awx/ui/test/e2e/runner.js
similarity index 100%
rename from awx/ui/client/test/e2e/runner.js
rename to awx/ui/test/e2e/runner.js
diff --git a/awx/ui/client/test/e2e/settings.js b/awx/ui/test/e2e/settings.js
similarity index 99%
rename from awx/ui/client/test/e2e/settings.js
rename to awx/ui/test/e2e/settings.js
index 0a5c892461..f3bbf1f4b2 100644
--- a/awx/ui/client/test/e2e/settings.js
+++ b/awx/ui/test/e2e/settings.js
@@ -10,7 +10,6 @@ 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,
diff --git a/awx/ui/client/test/e2e/tests/test-auditor-read-only-forms.js b/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js
similarity index 61%
rename from awx/ui/client/test/e2e/tests/test-auditor-read-only-forms.js
rename to awx/ui/test/e2e/tests/test-auditor-read-only-forms.js
index e0646fe718..f31d33e173 100644
--- a/awx/ui/client/test/e2e/tests/test-auditor-read-only-forms.js
+++ b/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js
@@ -1,4 +1,4 @@
-import { all } from '../api.js';
+import { all } from '../api';
import {
getAdminAWSCredential,
@@ -7,80 +7,77 @@ import {
getInventory,
getInventoryScript,
getNotificationTemplate,
- getOrCreate,
getOrganization,
getSmartInventory,
getTeam,
getUpdatedProject,
getUser
-} from '../fixtures.js';
+} from '../fixtures';
+const data = {};
+let credentials;
+let inventoryScripts;
+let notificationTemplates;
+let organizations;
+let projects;
+// let templates;
+let users;
+let inventories;
+let teams;
-let data = {};
-
-let credentials,
- inventoryScripts,
- templates,
- notificationTemplates,
- organizations,
- projects,
- users,
- inventories,
- teams;
-
-
-function navigateAndWaitForSpinner(client, url) {
+function navigateAndWaitForSpinner (client, url) {
client
.url(url)
.waitForElementVisible('div.spinny')
.waitForElementNotVisible('div.spinny');
}
-
module.exports = {
- before: function (client, done) {
- all([
- getAuditor().then(obj => data.auditor = obj),
- getOrganization().then(obj => data.organization = obj),
- getInventory().then(obj => data.inventory = obj),
- getInventoryScript().then(obj => data.inventoryScript = obj),
- getAdminAWSCredential().then(obj => data.adminAWSCredential = obj),
- getAdminMachineCredential().then(obj => data.adminMachineCredential = obj),
- getSmartInventory().then(obj => data.smartInventory = obj),
- getTeam().then(obj => data.team = obj),
- getUser().then(obj => data.user = obj),
- getNotificationTemplate().then(obj => data.notificationTemplate = obj),
- getUpdatedProject().then(obj => data.project = obj)
- ])
- .then(() => {
- client.useCss();
+ before: (client, done) => {
+ const promises = [
+ getAuditor().then(obj => { data.auditor = obj; }),
+ getOrganization().then(obj => { data.organization = obj; }),
+ getInventory().then(obj => { data.inventory = obj; }),
+ getInventoryScript().then(obj => { data.inventoryScript = obj; }),
+ getAdminAWSCredential().then(obj => { data.adminAWSCredential = obj; }),
+ getAdminMachineCredential().then(obj => { data.adminMachineCredential = obj; }),
+ getSmartInventory().then(obj => { data.smartInventory = obj; }),
+ getTeam().then(obj => { data.team = obj; }),
+ getUser().then(obj => { data.user = obj; }),
+ getNotificationTemplate().then(obj => { data.notificationTemplate = obj; }),
+ getUpdatedProject().then(obj => { data.project = obj; })
+ ];
- credentials = client.page.credentials();
- inventoryScripts = client.page.inventoryScripts();
- templates = client.page.templates();
- notificationTemplates = client.page.notificationTemplates();
- organizations = client.page.organizations();
- projects = client.page.projects();
- users = client.page.users();
- inventories = client.page.inventories();
- teams = client.page.teams();
+ all(promises)
+ .then(() => {
+ client.useCss();
- client.login(data.auditor.username, data.auditor.password);
- client.waitForAngular();
+ credentials = client.page.credentials();
+ inventoryScripts = client.page.inventoryScripts();
+ // templates = client.page.templates();
+ notificationTemplates = client.page.notificationTemplates();
+ organizations = client.page.organizations();
+ projects = client.page.projects();
+ users = client.page.users();
+ inventories = client.page.inventories();
+ teams = client.page.teams();
- done();
- });
+ client.login(data.auditor.username, data.auditor.password);
+ client.waitForAngular();
+
+ done();
+ });
},
- 'verify an auditor\'s credentials inputs are read-only': function (client) {
+ 'verify an auditor\'s credentials inputs are read-only': client => {
navigateAndWaitForSpinner(client, `${credentials.url()}/${data.adminAWSCredential.id}/`);
credentials.section.edit
.expect.element('@title').text.contain(data.adminAWSCredential.name);
-
+
credentials.section.edit.section.details.checkAllFieldsDisabled();
},
- 'verify an auditor\'s inventory scripts inputs are read-only': function (client) {
+ 'verify an auditor\'s inventory scripts inputs are read-only': client => {
navigateAndWaitForSpinner(client, `${inventoryScripts.url()}/${data.inventoryScript.id}/`);
inventoryScripts.section.edit
@@ -88,13 +85,16 @@ module.exports = {
inventoryScripts.section.edit.section.details.checkAllFieldsDisabled();
},
- 'verify save button hidden from auditor on inventory scripts form': function () {
+ 'verify save button hidden from auditor on inventory scripts form': () => {
inventoryScripts.expect.element('@save').to.not.be.visible;
},
- // TODO: re-enable these tests when JT edit has been re-factored to reliably show/remove the loading spinner
- // only one time. Without this, we can't tell when all the requisite data is available.
+ // TODO: re-enable these tests when JT edit has been re-factored to reliably show/remove the
+ // loading spinner only one time. Without this, we can't tell when all the requisite data is
+ // available.
+ //
// 'verify an auditor\'s job template inputs are read-only': function (client) {
- // navigateAndWaitForSpinner(client, `${templates.url()}/job_template/${data.jobTemplate.id}/`);
+ // const url = `${templates.url()}/job_template/${data.jobTemplate.id}/`;
+ // navigateAndWaitForSpinner(client, url);
//
// templates.section.editJobTemplate
// .expect.element('@title').text.contain(data.jobTemplate.name);
@@ -104,7 +104,7 @@ module.exports = {
// 'verify save button hidden from auditor on job templates form': function () {
// templates.expect.element('@save').to.not.be.visible;
// },
- 'verify an auditor\'s notification templates inputs are read-only': function (client) {
+ 'verify an auditor\'s notification templates inputs are read-only': client => {
navigateAndWaitForSpinner(client, `${notificationTemplates.url()}/${data.notificationTemplate.id}/`);
notificationTemplates.section.edit
@@ -112,10 +112,10 @@ module.exports = {
notificationTemplates.section.edit.section.details.checkAllFieldsDisabled();
},
- 'verify save button hidden from auditor on notification templates page': function () {
+ 'verify save button hidden from auditor on notification templates page': () => {
notificationTemplates.expect.element('@save').to.not.be.visible;
},
- 'verify an auditor\'s organizations inputs are read-only': function (client) {
+ 'verify an auditor\'s organizations inputs are read-only': client => {
navigateAndWaitForSpinner(client, `${organizations.url()}/${data.organization.id}/`);
organizations.section.edit
@@ -123,10 +123,10 @@ module.exports = {
organizations.section.edit.section.details.checkAllFieldsDisabled();
},
- 'verify save button hidden from auditor on organizations form': function () {
+ 'verify save button hidden from auditor on organizations form': () => {
organizations.expect.element('@save').to.not.be.visible;
},
- 'verify an auditor\'s smart inventory inputs are read-only': function (client) {
+ 'verify an auditor\'s smart inventory inputs are read-only': client => {
navigateAndWaitForSpinner(client, `${inventories.url()}/smart/${data.smartInventory.id}/`);
inventories.section.editSmartInventory
@@ -134,10 +134,10 @@ module.exports = {
inventories.section.editSmartInventory.section.smartInvDetails.checkAllFieldsDisabled();
},
- 'verify save button hidden from auditor on smart inventories form': function () {
+ 'verify save button hidden from auditor on smart inventories form': () => {
inventories.expect.element('@save').to.not.be.visible;
},
- 'verify an auditor\'s project inputs are read-only': function (client) {
+ 'verify an auditor\'s project inputs are read-only': client => {
navigateAndWaitForSpinner(client, `${projects.url()}/${data.project.id}/`);
projects.section.edit
@@ -145,21 +145,22 @@ module.exports = {
projects.section.edit.section.details.checkAllFieldsDisabled();
},
- 'verify save button hidden from auditor on projects form': function () {
+ 'verify save button hidden from auditor on projects form': () => {
projects.expect.element('@save').to.not.be.visible;
},
- 'verify an auditor\'s standard inventory inputs are read-only': function (client) {
+ 'verify an auditor\'s standard inventory inputs are read-only': client => {
navigateAndWaitForSpinner(client, `${inventories.url()}/inventory/${data.inventory.id}/`);
inventories.section.editStandardInventory
.expect.element('@title').text.contain(data.inventory.name);
- inventories.section.editStandardInventory.section.standardInvDetails.checkAllFieldsDisabled();
+ inventories.section.editStandardInventory.section.standardInvDetails
+ .checkAllFieldsDisabled();
},
- 'verify save button hidden from auditor on standard inventory form': function () {
+ 'verify save button hidden from auditor on standard inventory form': () => {
inventories.expect.element('@save').to.not.be.visible;
},
- 'verify an auditor\'s teams inputs are read-only': function (client) {
+ 'verify an auditor\'s teams inputs are read-only': client => {
navigateAndWaitForSpinner(client, `${teams.url()}/${data.team.id}/`);
teams.section.edit
@@ -167,10 +168,10 @@ module.exports = {
teams.section.edit.section.details.checkAllFieldsDisabled();
},
- 'verify save button hidden from auditor on teams form': function () {
+ 'verify save button hidden from auditor on teams form': () => {
teams.expect.element('@save').to.not.be.visible;
},
- 'verify an auditor\'s user inputs are read-only': function (client) {
+ 'verify an auditor\'s user inputs are read-only': client => {
navigateAndWaitForSpinner(client, `${users.url()}/${data.user.id}/`);
users.section.edit
@@ -178,7 +179,7 @@ module.exports = {
users.section.edit.section.details.checkAllFieldsDisabled();
},
- 'verify save button hidden from auditor on users form': function (client) {
+ 'verify save button hidden from auditor on users form': client => {
users.expect.element('@save').to.not.be.visible;
client.end();
diff --git a/awx/ui/client/test/e2e/tests/test-credential-types-add-edit.js b/awx/ui/test/e2e/tests/test-credential-types-add-edit.js
similarity index 85%
rename from awx/ui/client/test/e2e/tests/test-credential-types-add-edit.js
rename to awx/ui/test/e2e/tests/test-credential-types-add-edit.js
index 120d50eb45..5b343ad0c5 100644
--- a/awx/ui/client/test/e2e/tests/test-credential-types-add-edit.js
+++ b/awx/ui/test/e2e/tests/test-credential-types-add-edit.js
@@ -1,5 +1,5 @@
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentialTypes = client.page.credentialTypes();
client.login();
@@ -13,9 +13,9 @@ module.exports = {
credentialTypes.section.add
.waitForElementVisible('@title', done);
},
- 'expected fields are present and enabled': function(client) {
+ 'expected fields are present and enabled': client => {
const credentialTypes = client.page.credentialTypes();
- const details = credentialTypes.section.add.section.details;
+ const { details } = credentialTypes.section.add.section;
details.expect.element('@name').visible;
details.expect.element('@description').visible;
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-aws.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-aws.js
similarity index 76%
rename from awx/ui/client/test/e2e/tests/test-credentials-add-edit-aws.js
rename to awx/ui/test/e2e/tests/test-credentials-add-edit-aws.js
index 6d807cdbc1..d7a3bec3f2 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-aws.js
+++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-aws.js
@@ -1,9 +1,8 @@
import uuid from 'uuid';
+const testID = uuid().substr(0, 8);
-let testID = uuid().substr(0,8);
-
-let store = {
+const store = {
organization: {
name: `org-${testID}`
},
@@ -13,18 +12,17 @@ let store = {
};
module.exports = {
- before: function(client, done) {
+ before: (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;
- });
+ client.inject(
+ [store, 'OrganizationModel'],
+ (_store_, Model) => new Model().http.post(_store_.organization),
+ ({ data }) => { store.organization = data; }
+ );
credentials.section.navigation
.waitForElementVisible('@credentials')
@@ -44,9 +42,9 @@ module.exports = {
.setValue('@organization', store.organization.name)
.setValue('@type', 'Amazon Web Services', done);
},
- 'expected fields are visible and enabled': function(client) {
+ 'expected fields are visible and enabled': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@name').visible;
details.expect.element('@description').visible;
@@ -64,20 +62,23 @@ module.exports = {
details.section.aws.expect.element('@secretKey').enabled;
details.section.aws.expect.element('@securityToken').enabled;
},
- 'required fields display \'*\'': function(client) {
+ 'required fields display \'*\'': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
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('*'));
+ ];
+
+ required.forEach(s => {
+ s.expect.element('@label').text.to.contain('*');
+ });
},
- 'save button becomes enabled after providing required fields': function(client) {
+ 'save button becomes enabled after providing required fields': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details
.clearAndSelectType('Amazon Web Services')
@@ -88,10 +89,10 @@ module.exports = {
details.section.aws.setValue('@secretKey', 'AAAAAAAAAAAAA');
details.expect.element('@save').enabled;
},
- 'create aws credential': function(client) {
+ 'create aws credential': client => {
const credentials = client.page.credentials();
- const add = credentials.section.add;
- const edit = credentials.section.edit;
+ const { add } = credentials.section;
+ const { edit } = credentials.section;
add.section.details
.clearAndSelectType('Amazon Web Services')
@@ -110,16 +111,16 @@ module.exports = {
edit.expect.element('@title').text.equal(store.credential.name);
},
- 'edit details panel remains open after saving': function(client) {
+ 'edit details panel remains open after saving': client => {
const credentials = client.page.credentials();
credentials.section.edit.expect.section('@details').visible;
},
- 'credential is searchable after saving': function(client) {
+ 'credential is searchable after saving': client => {
const credentials = client.page.credentials();
- const search = credentials.section.list.section.search;
- const table = credentials.section.list.section.table;
+ const { search } = credentials.section.list.section;
+ const { table } = credentials.section.list.section;
search
.waitForElementVisible('@input')
diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-custom.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-custom.js
new file mode 100644
index 0000000000..7f3caee6a4
--- /dev/null
+++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-custom.js
@@ -0,0 +1,175 @@
+import uuid from 'uuid';
+
+const 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;
+const { fields } = inputs;
+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 = client => {
+ const credentials = client.page.credentials();
+ const { details } = credentials.section.add.section;
+ const type = details.custom(store.credentialType);
+
+ return { credentials, details, type };
+};
+
+module.exports = {
+ before: (client, done) => {
+ const credentials = client.page.credentials();
+
+ client.login();
+ client.waitForAngular();
+
+ client.inject(
+ [store.credentialType, 'CredentialTypeModel'],
+ (data, Model) => 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': client => {
+ const { type } = getObjects(client);
+ fields.forEach(f => {
+ type.expect.element(`@${f.id}`).visible;
+ });
+ },
+ 'helplinks open popovers showing expected content': client => {
+ const { type } = getObjects(client);
+
+ help.forEach(f => {
+ const 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.forEach(f => {
+ const group = type.section[f.id];
+ group.expect.element('@popover').not.visible;
+ });
+ },
+ 'secret field buttons hide and unhide input': client => {
+ const { type } = getObjects(client);
+ const secrets = strings.filter(f => f.secret && !f.multiline);
+
+ secrets.forEach(f => {
+ const group = type.section[f.id];
+ const 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': client => {
+ const { type } = getObjects(client);
+
+ required.forEach(f => {
+ const group = type.section[f.id];
+ group.expect.element('@label').text.to.contain('*');
+ });
+
+ client.end();
+ }
+};
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-gce.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-gce.js
similarity index 80%
rename from awx/ui/client/test/e2e/tests/test-credentials-add-edit-gce.js
rename to awx/ui/test/e2e/tests/test-credentials-add-edit-gce.js
index 92e39fea0f..f0f59f5ba9 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-gce.js
+++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-gce.js
@@ -1,9 +1,8 @@
import uuid from 'uuid';
+const testID = uuid().substr(0, 8);
-let testID = uuid().substr(0,8);
-
-let store = {
+const store = {
organization: {
name: `org-${testID}`
},
@@ -13,19 +12,18 @@ let store = {
};
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
client.login();
client.waitForAngular();
- client.inject([store, 'OrganizationModel'], (store, model) => {
- return new model().http.post(store.organization);
- },
- ({ data }) => {
- store.organization = data;
- });
+ client.inject(
+ [store, 'OrganizationModel'],
+ (_store_, Model) => new Model().http.post(_store_.organization),
+ ({ data }) => { store.organization = data; }
+ );
credentials.section.navigation
.waitForElementVisible('@credentials')
@@ -45,9 +43,9 @@ module.exports = {
.setValue('@organization', store.organization.name)
.setValue('@type', 'Google Compute Engine', done);
},
- 'expected fields are visible and enabled': function(client) {
+ 'expected fields are visible and enabled': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@name').visible;
details.expect.element('@description').visible;
@@ -67,20 +65,23 @@ module.exports = {
details.section.organization.expect.element('@lookup').visible;
},
- 'required fields display \'*\'': function(client) {
+ 'required fields display \'*\'': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
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('*'));
+ ];
+
+ required.forEach(s => {
+ s.expect.element('@label').text.to.contain('*');
+ });
},
- 'save button becomes enabled after providing required fields': function(client) {
+ 'save button becomes enabled after providing required fields': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details
.clearAndSelectType('Google Compute Engine')
@@ -98,10 +99,10 @@ module.exports = {
details.expect.element('@save').enabled;
},
- 'error displayed for invalid ssh key data': function(client) {
+ 'error displayed for invalid ssh key data': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const sshKeyData = details.section.gce.section.sshKeyData;
+ const { details } = credentials.section.add.section;
+ const { sshKeyData } = details.section.gce.section;
details
.clearAndSelectType('Google Compute Engine')
@@ -124,10 +125,10 @@ module.exports = {
details.section.gce.setValue('@sshKeyData', 'AAAA');
sshKeyData.expect.element('@error').not.present;
},
- 'create gce credential': function(client) {
+ 'create gce credential': client => {
const credentials = client.page.credentials();
- const add = credentials.section.add;
- const edit = credentials.section.edit;
+ const { add } = credentials.section;
+ const { edit } = credentials.section;
add.section.details
.clearAndSelectType('Google Compute Engine')
@@ -150,12 +151,12 @@ module.exports = {
edit.expect.element('@title').text.equal(store.credential.name);
},
- 'edit details panel remains open after saving': function(client) {
+ 'edit details panel remains open after saving': client => {
const credentials = client.page.credentials();
credentials.section.edit.expect.section('@details').visible;
},
- 'credential is searchable after saving': function(client) {
+ 'credential is searchable after saving': client => {
const credentials = client.page.credentials();
const row = '#credentials_table tbody tr';
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-insights.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-insights.js
similarity index 77%
rename from awx/ui/client/test/e2e/tests/test-credentials-add-edit-insights.js
rename to awx/ui/test/e2e/tests/test-credentials-add-edit-insights.js
index 97a3eaed10..a58457b1d7 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-insights.js
+++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-insights.js
@@ -1,9 +1,8 @@
import uuid from 'uuid';
+const testID = uuid().substr(0, 8);
-let testID = uuid().substr(0,8);
-
-let store = {
+const store = {
organization: {
name: `org-${testID}`
},
@@ -13,19 +12,18 @@ let store = {
};
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
client.login();
client.waitForAngular();
- client.inject([store, 'OrganizationModel'], (store, model) => {
- return new model().http.post(store.organization);
- },
- ({ data }) => {
- store.organization = data;
- });
+ client.inject(
+ [store, 'OrganizationModel'],
+ (_store_, Model) => new Model().http.post(_store_.organization),
+ ({ data }) => { store.organization = data; }
+ );
credentials.section.navigation
.waitForElementVisible('@credentials')
@@ -45,9 +43,9 @@ module.exports = {
.setValue('@organization', store.organization.name)
.setValue('@type', 'Insights', done);
},
- 'expected fields are visible and enabled': function(client) {
+ 'expected fields are visible and enabled': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@name').visible;
details.expect.element('@description').visible;
@@ -63,20 +61,23 @@ module.exports = {
details.section.insights.expect.element('@username').enabled;
details.section.insights.expect.element('@password').enabled;
},
- 'required fields display \'*\'': function(client) {
+ 'required fields display \'*\'': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
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('*'));
+ ];
+
+ required.forEach(s => {
+ s.expect.element('@label').text.to.contain('*');
+ });
},
- 'save button becomes enabled after providing required fields': function(client) {
+ 'save button becomes enabled after providing required fields': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details
.clearAndSelectType('Insights')
@@ -90,10 +91,10 @@ module.exports = {
details.expect.element('@save').enabled;
},
- 'create insights credential': function(client) {
+ 'create insights credential': client => {
const credentials = client.page.credentials();
- const add = credentials.section.add;
- const edit = credentials.section.edit;
+ const { add } = credentials.section;
+ const { edit } = credentials.section;
add.section.details
.clearAndSelectType('Insights')
@@ -112,12 +113,12 @@ module.exports = {
edit.expect.element('@title').text.equal(store.credential.name);
},
- 'edit details panel remains open after saving': function(client) {
+ 'edit details panel remains open after saving': client => {
const credentials = client.page.credentials();
credentials.section.edit.expect.section('@details').visible;
},
- 'credential is searchable after saving': function(client) {
+ 'credential is searchable after saving': client => {
const credentials = client.page.credentials();
const row = '#credentials_table tbody tr';
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-machine.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js
similarity index 77%
rename from awx/ui/client/test/e2e/tests/test-credentials-add-edit-machine.js
rename to awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js
index 22afd7e4cf..2c1ed44f6d 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-machine.js
+++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js
@@ -1,9 +1,8 @@
import uuid from 'uuid';
+const testID = uuid().substr(0, 8);
-let testID = uuid().substr(0,8);
-
-let store = {
+const store = {
organization: {
name: `org-${testID}`
},
@@ -13,19 +12,18 @@ let store = {
};
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
client.login();
client.waitForAngular();
- client.inject([store, 'OrganizationModel'], (store, model) => {
- return new model().http.post(store.organization);
- },
- ({ data }) => {
- store.organization = data;
- });
+ client.inject(
+ [store, 'OrganizationModel'],
+ (_store_, Model) => new Model().http.post(_store_.organization),
+ ({ data }) => { store.organization = data; }
+ );
credentials.section.navigation
.waitForElementVisible('@credentials')
@@ -41,9 +39,9 @@ module.exports = {
details.waitForElementVisible('@save', done);
},
- 'common fields are visible and enabled': function(client) {
+ 'common fields are visible and enabled': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@name').visible;
details.expect.element('@description').visible;
@@ -55,16 +53,16 @@ module.exports = {
details.expect.element('@organization').enabled;
details.expect.element('@type').enabled;
},
- 'required common fields display \'*\'': function(client) {
+ 'required common fields display \'*\'': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
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) {
+ 'save button becomes enabled after providing required fields': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@save').not.enabled;
@@ -75,10 +73,10 @@ module.exports = {
details.expect.element('@save').enabled;
},
- 'machine credential fields are visible after choosing type': function(client) {
+ 'machine credential fields are visible after choosing type': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const machine = details.section.machine;
+ const { details } = credentials.section.add.section;
+ const { machine } = details.section;
machine.expect.element('@username').visible;
machine.expect.element('@password').visible;
@@ -87,10 +85,10 @@ module.exports = {
machine.expect.element('@sshKeyData').visible;
machine.expect.element('@sshKeyUnlock').visible;
},
- 'error displayed for invalid ssh key data': function(client) {
+ 'error displayed for invalid ssh key data': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const sshKeyData = details.section.machine.section.sshKeyData;
+ const { details } = credentials.section.add.section;
+ const { sshKeyData } = details.section.machine.section;
details
.clearAndSelectType('Machine')
@@ -106,10 +104,10 @@ module.exports = {
details.section.machine.clearValue('@sshKeyData');
sshKeyData.expect.element('@error').not.present;
},
- 'error displayed for unencrypted ssh key with passphrase': function(client) {
+ 'error displayed for unencrypted ssh key with passphrase': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const sshKeyUnlock = details.section.machine.section.sshKeyUnlock;
+ const { details } = credentials.section.add.section;
+ const { sshKeyUnlock } = details.section.machine.section;
details
.clearAndSelectType('Machine')
@@ -130,11 +128,11 @@ module.exports = {
details.section.machine.clearValue('@sshKeyUnlock');
sshKeyUnlock.expect.element('@error').not.present;
- },
- 'create machine credential': function(client) {
+ },
+ 'create machine credential': client => {
const credentials = client.page.credentials();
- const add = credentials.section.add;
- const edit = credentials.section.edit;
+ const { add } = credentials.section;
+ const { edit } = credentials.section;
add.section.details
.clearAndSelectType('Machine')
@@ -161,12 +159,12 @@ module.exports = {
edit.expect.element('@title').text.equal(store.credential.name);
},
- 'edit details panel remains open after saving': function(client) {
+ 'edit details panel remains open after saving': client => {
const credentials = client.page.credentials();
credentials.section.edit.expect.section('@details').visible;
},
- 'credential is searchable after saving': function(client) {
+ 'credential is searchable after saving': client => {
const credentials = client.page.credentials();
const row = '#credentials_table tbody tr';
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-network.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-network.js
similarity index 76%
rename from awx/ui/client/test/e2e/tests/test-credentials-add-edit-network.js
rename to awx/ui/test/e2e/tests/test-credentials-add-edit-network.js
index 7f71ff8dec..e2cde4e2ce 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-network.js
+++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-network.js
@@ -1,9 +1,8 @@
import uuid from 'uuid';
+const testID = uuid().substr(0, 8);
-let testID = uuid().substr(0,8);
-
-let store = {
+const store = {
organization: {
name: `org-${testID}`
},
@@ -13,19 +12,18 @@ let store = {
};
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
client.login();
client.waitForAngular();
- client.inject([store, 'OrganizationModel'], (store, model) => {
- return new model().http.post(store.organization);
- },
- ({ data }) => {
- store.organization = data;
- });
+ client.inject(
+ [store, 'OrganizationModel'],
+ (_store_, Model) => new Model().http.post(_store_.organization),
+ ({ data }) => { store.organization = data; }
+ );
credentials.section.navigation
.waitForElementVisible('@credentials')
@@ -41,9 +39,9 @@ module.exports = {
details.waitForElementVisible('@save', done);
},
- 'common fields are visible and enabled': function(client) {
+ 'common fields are visible and enabled': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@name').visible;
details.expect.element('@description').visible;
@@ -55,16 +53,16 @@ module.exports = {
details.expect.element('@organization').enabled;
details.expect.element('@type').enabled;
},
- 'required common fields display \'*\'': function(client) {
+ 'required common fields display \'*\'': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
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) {
+ 'save button becomes enabled after providing required fields': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@save').not.enabled;
@@ -79,10 +77,10 @@ module.exports = {
details.expect.element('@save').enabled;
},
- 'network credential fields are visible after choosing type': function(client) {
+ 'network credential fields are visible after choosing type': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const network = details.section.network;
+ const { details } = credentials.section.add.section;
+ const { network } = details.section;
network.expect.element('@username').visible;
network.expect.element('@password').visible;
@@ -90,10 +88,10 @@ module.exports = {
network.expect.element('@sshKeyData').visible;
network.expect.element('@sshKeyUnlock').visible;
},
- 'error displayed for invalid ssh key data': function(client) {
+ 'error displayed for invalid ssh key data': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const sshKeyData = details.section.network.section.sshKeyData;
+ const { details } = credentials.section.add.section;
+ const { sshKeyData } = details.section.network.section;
details
.clearAndSelectType('Network')
@@ -111,10 +109,10 @@ module.exports = {
details.section.network.clearValue('@sshKeyData');
sshKeyData.expect.element('@error').not.present;
},
- 'error displayed for unencrypted ssh key with passphrase': function(client) {
+ 'error displayed for unencrypted ssh key with passphrase': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const sshKeyUnlock = details.section.network.section.sshKeyUnlock;
+ const { details } = credentials.section.add.section;
+ const { sshKeyUnlock } = details.section.network.section;
details
.clearAndSelectType('Network')
@@ -136,11 +134,11 @@ module.exports = {
details.section.network.clearValue('@sshKeyUnlock');
sshKeyUnlock.expect.element('@error').not.present;
- },
- 'error displayed for authorize password without authorize enabled': function(client) {
+ },
+ 'error displayed for authorize password without authorize enabled': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const authorizePassword = details.section.network.section.authorizePassword;
+ const { details } = credentials.section.add.section;
+ const { authorizePassword } = details.section.network.section;
details
.clearAndSelectType('Network')
@@ -152,17 +150,17 @@ module.exports = {
details.click('@save');
- let expected = 'cannot be set unless "Authorize" is set';
+ const 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) {
+ },
+ 'create network credential': client => {
const credentials = client.page.credentials();
- const add = credentials.section.add;
- const edit = credentials.section.edit;
+ const { add } = credentials.section;
+ const { edit } = credentials.section;
add.section.details
.clearAndSelectType('Network')
@@ -186,12 +184,12 @@ module.exports = {
edit.expect.element('@title').text.equal(store.credential.name);
},
- 'edit details panel remains open after saving': function(client) {
+ 'edit details panel remains open after saving': client => {
const credentials = client.page.credentials();
credentials.section.edit.expect.section('@details').visible;
},
- 'credential is searchable after saving': function(client) {
+ 'credential is searchable after saving': client => {
const credentials = client.page.credentials();
const row = '#credentials_table tbody tr';
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-scm.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-scm.js
similarity index 76%
rename from awx/ui/client/test/e2e/tests/test-credentials-add-edit-scm.js
rename to awx/ui/test/e2e/tests/test-credentials-add-edit-scm.js
index cdcc438f46..9231907afc 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-scm.js
+++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-scm.js
@@ -1,9 +1,8 @@
import uuid from 'uuid';
+const testID = uuid().substr(0, 8);
-let testID = uuid().substr(0,8);
-
-let store = {
+const store = {
organization: {
name: `org-${testID}`
},
@@ -13,19 +12,18 @@ let store = {
};
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
client.login();
client.waitForAngular();
- client.inject([store, 'OrganizationModel'], (store, model) => {
- return new model().http.post(store.organization);
- },
- ({ data }) => {
- store.organization = data;
- });
+ client.inject(
+ [store, 'OrganizationModel'],
+ (_store_, Model) => new Model().http.post(_store_.organization),
+ ({ data }) => { store.organization = data; }
+ );
credentials.section.navigation
.waitForElementVisible('@credentials')
@@ -41,9 +39,9 @@ module.exports = {
details.waitForElementVisible('@save', done);
},
- 'common fields are visible and enabled': function(client) {
+ 'common fields are visible and enabled': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@name').visible;
details.expect.element('@description').visible;
@@ -55,16 +53,16 @@ module.exports = {
details.expect.element('@organization').enabled;
details.expect.element('@type').enabled;
},
- 'required common fields display \'*\'': function(client) {
+ 'required common fields display \'*\'': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
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) {
+ 'save button becomes enabled after providing required fields': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@save').not.enabled;
@@ -75,19 +73,19 @@ module.exports = {
details.expect.element('@save').enabled;
},
- 'scm credential fields are visible after choosing type': function(client) {
+ 'scm credential fields are visible after choosing type': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
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) {
+ 'error displayed for invalid ssh key data': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const sshKeyData = details.section.scm.section.sshKeyData;
+ const { details } = credentials.section.add.section;
+ const { sshKeyData } = details.section.scm.section;
details
.clearAndSelectType('Source Control')
@@ -103,10 +101,10 @@ module.exports = {
details.section.scm.clearValue('@sshKeyData');
sshKeyData.expect.element('@error').not.present;
},
- 'error displayed for unencrypted ssh key with passphrase': function(client) {
+ 'error displayed for unencrypted ssh key with passphrase': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const sshKeyUnlock = details.section.scm.section.sshKeyUnlock;
+ const { details } = credentials.section.add.section;
+ const { sshKeyUnlock } = details.section.scm.section;
details
.clearAndSelectType('Source Control')
@@ -127,11 +125,11 @@ module.exports = {
details.section.scm.clearValue('@sshKeyUnlock');
sshKeyUnlock.expect.element('@error').not.present;
- },
- 'create SCM credential': function(client) {
+ },
+ 'create SCM credential': client => {
const credentials = client.page.credentials();
- const add = credentials.section.add;
- const edit = credentials.section.edit;
+ const { add } = credentials.section;
+ const { edit } = credentials.section;
add.section.details
.clearAndSelectType('Source Control')
@@ -155,12 +153,12 @@ module.exports = {
edit.expect.element('@title').text.equal(store.credential.name);
},
- 'edit details panel remains open after saving': function(client) {
+ 'edit details panel remains open after saving': client => {
const credentials = client.page.credentials();
credentials.section.edit.expect.section('@details').visible;
},
- 'credential is searchable after saving': function(client) {
+ 'credential is searchable after saving': client => {
const credentials = client.page.credentials();
const row = '#credentials_table tbody tr';
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-vault.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-vault.js
similarity index 77%
rename from awx/ui/client/test/e2e/tests/test-credentials-add-edit-vault.js
rename to awx/ui/test/e2e/tests/test-credentials-add-edit-vault.js
index 153f4984ce..9987666e0f 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-add-edit-vault.js
+++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-vault.js
@@ -1,31 +1,28 @@
import uuid from 'uuid';
-
-let testID = uuid().substr(0,8);
-
-let store = {
+const testID = uuid().substr(0, 8);
+const store = {
organization: {
name: `org-${testID}`
},
credential: {
name: `cred-${testID}`
- },
+ }
};
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
client.login();
client.waitForAngular();
- client.inject([store, 'OrganizationModel'], (store, model) => {
- return new model().http.post(store.organization);
- },
- ({ data }) => {
- store.organization = data;
- });
+ client.inject(
+ [store, 'OrganizationModel'],
+ (_store_, Model) => new Model().http.post(_store_.organization),
+ ({ data }) => { store.organization = data; }
+ );
credentials.section.navigation
.waitForElementVisible('@credentials')
@@ -45,9 +42,9 @@ module.exports = {
.setValue('@organization', store.organization.name)
.setValue('@type', 'Vault', done);
},
- 'expected fields are visible and enabled': function(client) {
+ 'expected fields are visible and enabled': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details.expect.element('@name').visible;
details.expect.element('@description').visible;
@@ -61,19 +58,20 @@ module.exports = {
details.expect.element('@type').enabled;
details.section.vault.expect.element('@vaultPassword').enabled;
},
- 'required fields display \'*\'': function(client) {
+ 'required fields display \'*\'': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
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) {
+ 'save button becomes enabled after providing required fields': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details
.clearAndSelectType('Vault')
@@ -83,9 +81,9 @@ module.exports = {
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) {
+ 'vault password field is disabled when prompt on launch is selected': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
details
.clearAndSelectType('Vault')
@@ -95,10 +93,10 @@ module.exports = {
details.section.vault.section.vaultPassword.click('@prompt');
details.section.vault.expect.element('@vaultPassword').not.enabled;
},
- 'create vault credential': function(client) {
+ 'create vault credential': client => {
const credentials = client.page.credentials();
- const add = credentials.section.add;
- const edit = credentials.section.edit;
+ const { add } = credentials.section;
+ const { edit } = credentials.section;
add.section.details
.clearAndSelectType('Vault')
@@ -115,12 +113,12 @@ module.exports = {
edit.expect.element('@title').text.equal(store.credential.name);
},
- 'edit details panel remains open after saving': function(client) {
+ 'edit details panel remains open after saving': client => {
const credentials = client.page.credentials();
credentials.section.edit.expect.section('@details').visible;
},
- 'credential is searchable after saving': function(client) {
+ 'credential is searchable after saving': client => {
const credentials = client.page.credentials();
const row = '#credentials_table tbody tr';
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-lookup-credential-type.js b/awx/ui/test/e2e/tests/test-credentials-lookup-credential-type.js
similarity index 78%
rename from awx/ui/client/test/e2e/tests/test-credentials-lookup-credential-type.js
rename to awx/ui/test/e2e/tests/test-credentials-lookup-credential-type.js
index 7bed8eed7b..72b1c891a1 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-lookup-credential-type.js
+++ b/awx/ui/test/e2e/tests/test-credentials-lookup-credential-type.js
@@ -1,7 +1,7 @@
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
client.login();
client.waitForAngular();
@@ -19,11 +19,11 @@ module.exports = {
.click('@add');
details
- .waitForElementVisible('@save', done)
+ .waitForElementVisible('@save', done);
},
- 'open the lookup modal': function(client) {
+ 'open the lookup modal': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
const modal = 'div[class="modal-body"]';
const title = 'div[class^="Form-title"]';
@@ -38,7 +38,7 @@ module.exports = {
client.expect.element(modal).present;
- let expected = 'SELECT CREDENTIAL TYPE';
+ const expected = 'SELECT CREDENTIAL TYPE';
client.expect.element(title).visible;
client.expect.element(title).text.equal(expected);
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-lookup-organization.js b/awx/ui/test/e2e/tests/test-credentials-lookup-organization.js
similarity index 85%
rename from awx/ui/client/test/e2e/tests/test-credentials-lookup-organization.js
rename to awx/ui/test/e2e/tests/test-credentials-lookup-organization.js
index 9b8f2ecc6e..5b62fac9ac 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-lookup-organization.js
+++ b/awx/ui/test/e2e/tests/test-credentials-lookup-organization.js
@@ -1,7 +1,7 @@
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
+ const { details } = credentials.section.add.section;
client.login();
client.waitForAngular();
@@ -20,12 +20,11 @@ module.exports = {
details
.waitForElementVisible('@save', done);
-
},
- 'open the lookup modal': function(client) {
+ 'open the lookup modal': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const lookupModal = credentials.section.lookupModal;
+ const { details } = credentials.section.add.section;
+ const { lookupModal } = credentials.section;
details.expect.element('@organization').visible;
details.expect.element('@organization').enabled;
@@ -37,15 +36,15 @@ module.exports = {
credentials.expect.section('@lookupModal').present;
- let expected = 'SELECT ORGANIZATION';
+ const 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) {
+ 'select button is disabled until item is selected': client => {
const credentials = client.page.credentials();
- const details = credentials.section.add.section.details;
- const lookupModal = credentials.section.lookupModal;
- const table = lookupModal.section.table;
+ const { details } = credentials.section.add.section;
+ const { lookupModal } = credentials.section;
+ const { table } = lookupModal.section;
details.section.organization.expect.element('@lookup').visible;
details.section.organization.expect.element('@lookup').enabled;
@@ -70,12 +69,12 @@ module.exports = {
lookupModal.expect.element('@select').visible;
lookupModal.expect.element('@select').enabled;
},
- 'sort and unsort the table by name with an item selected': function(client) {
+ 'sort and unsort the table by name with an item selected': client => {
const credentials = client.page.credentials();
- const lookupModal = credentials.section.lookupModal;
- const table = lookupModal.section.table;
+ const { lookupModal } = credentials.section;
+ const { table } = lookupModal.section;
- let column = table.section.header.findColumnByText('Name');
+ const column = table.section.header.findColumnByText('Name');
column.expect.element('@self').visible;
column.expect.element('@sortable').visible;
@@ -100,11 +99,11 @@ module.exports = {
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) {
+ 'use the pagination controls with an item selected': client => {
const credentials = client.page.credentials();
- const lookupModal = credentials.section.lookupModal;
- const table = lookupModal.section.table;
- const pagination = lookupModal.section.pagination;
+ const { lookupModal } = credentials.section;
+ const { table } = lookupModal.section;
+ const { pagination } = lookupModal.section;
pagination.click('@next');
credentials.waitForElementVisible('div.spinny');
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-navigation-click-through.js b/awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js
similarity index 94%
rename from awx/ui/client/test/e2e/tests/test-credentials-navigation-click-through.js
rename to awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js
index f9f618756f..19eae2dd9d 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-navigation-click-through.js
+++ b/awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js
@@ -1,5 +1,5 @@
module.exports = {
- beforeEach: function(client, done) {
+ beforeEach: (client, done) => {
const credentials = client.useCss().page.credentials();
client.login();
@@ -10,7 +10,7 @@ module.exports = {
.waitForElementVisible('div.spinny')
.waitForElementNotVisible('div.spinny', done);
},
- 'activity link is visible and takes user to activity stream': function(client) {
+ 'activity link is visible and takes user to activity stream': client => {
const credentials = client.page.credentials();
const activityStream = client.page.activityStream();
diff --git a/awx/ui/client/test/e2e/tests/test-credentials-search-sort.js b/awx/ui/test/e2e/tests/test-credentials-search-sort.js
similarity index 65%
rename from awx/ui/client/test/e2e/tests/test-credentials-search-sort.js
rename to awx/ui/test/e2e/tests/test-credentials-search-sort.js
index 12a8d79ef4..525901b4ae 100644
--- a/awx/ui/client/test/e2e/tests/test-credentials-search-sort.js
+++ b/awx/ui/test/e2e/tests/test-credentials-search-sort.js
@@ -1,10 +1,8 @@
const columns = ['Name', 'Kind', 'Owners', 'Actions'];
const sortable = ['Name'];
-const defaultSorted = ['Name'];
-
module.exports = {
- before: function(client, done) {
+ before: (client, done) => {
const credentials = client.page.credentials();
client.login();
@@ -18,30 +16,30 @@ module.exports = {
credentials.waitForElementVisible('#credentials_table', done);
},
- 'expected table columns are visible': function(client) {
+ 'expected table columns are visible': client => {
const credentials = client.page.credentials();
- const table = credentials.section.list.section.table;
+ const { table } = credentials.section.list.section;
- columns.map(label => {
+ columns.forEach(label => {
table.section.header.findColumnByText(label)
.expect.element('@self').visible;
});
},
- 'only fields expected to be sortable show sort icon': function(client) {
+ 'only fields expected to be sortable show sort icon': client => {
const credentials = client.page.credentials();
- const table = credentials.section.list.section.table;
+ const { table } = credentials.section.list.section;
- sortable.map(label => {
+ sortable.forEach(label => {
table.section.header.findColumnByText(label)
.expect.element('@sortable').visible;
});
},
- 'sort all columns expected to be sortable': function(client) {
+ 'sort all columns expected to be sortable': client => {
const credentials = client.page.credentials();
- const table = credentials.section.list.section.table;
+ const { table } = credentials.section.list.section;
- sortable.map(label => {
- let column = table.section.header.findColumnByText(label);
+ sortable.forEach(label => {
+ const column = table.section.header.findColumnByText(label);
column.click('@self');
diff --git a/awx/ui/tests/spec/column-sort/column-sort.directive-test.js b/awx/ui/test/spec/column-sort/column-sort.directive-test.js
similarity index 98%
rename from awx/ui/tests/spec/column-sort/column-sort.directive-test.js
rename to awx/ui/test/spec/column-sort/column-sort.directive-test.js
index eff9ddb378..64b0eb4971 100644
--- a/awx/ui/tests/spec/column-sort/column-sort.directive-test.js
+++ b/awx/ui/test/spec/column-sort/column-sort.directive-test.js
@@ -1,6 +1,6 @@
'use strict';
-describe('Directive: column-sort', () =>{
+xdescribe('Directive: column-sort', () =>{
let $scope, template, $compile, QuerySet, GetBasePath;
diff --git a/awx/ui/tests/spec/features/features.directive-test.js b/awx/ui/test/spec/features/features.directive-test.js
similarity index 100%
rename from awx/ui/tests/spec/features/features.directive-test.js
rename to awx/ui/test/spec/features/features.directive-test.js
diff --git a/awx/ui/tests/spec/inventories/insights/data/high.insights-data.js b/awx/ui/test/spec/inventories/insights/data/high.insights-data.js
similarity index 100%
rename from awx/ui/tests/spec/inventories/insights/data/high.insights-data.js
rename to awx/ui/test/spec/inventories/insights/data/high.insights-data.js
diff --git a/awx/ui/tests/spec/inventories/insights/data/insights-data.js b/awx/ui/test/spec/inventories/insights/data/insights-data.js
similarity index 100%
rename from awx/ui/tests/spec/inventories/insights/data/insights-data.js
rename to awx/ui/test/spec/inventories/insights/data/insights-data.js
diff --git a/awx/ui/tests/spec/inventories/insights/data/low.insights-data.js b/awx/ui/test/spec/inventories/insights/data/low.insights-data.js
similarity index 100%
rename from awx/ui/tests/spec/inventories/insights/data/low.insights-data.js
rename to awx/ui/test/spec/inventories/insights/data/low.insights-data.js
diff --git a/awx/ui/tests/spec/inventories/insights/data/medium.insights-data.js b/awx/ui/test/spec/inventories/insights/data/medium.insights-data.js
similarity index 100%
rename from awx/ui/tests/spec/inventories/insights/data/medium.insights-data.js
rename to awx/ui/test/spec/inventories/insights/data/medium.insights-data.js
diff --git a/awx/ui/tests/spec/inventories/insights/data/not_solvable.insights-data.js b/awx/ui/test/spec/inventories/insights/data/not_solvable.insights-data.js
similarity index 100%
rename from awx/ui/tests/spec/inventories/insights/data/not_solvable.insights-data.js
rename to awx/ui/test/spec/inventories/insights/data/not_solvable.insights-data.js
diff --git a/awx/ui/tests/spec/inventories/insights/data/solvable.insights-data.js b/awx/ui/test/spec/inventories/insights/data/solvable.insights-data.js
similarity index 100%
rename from awx/ui/tests/spec/inventories/insights/data/solvable.insights-data.js
rename to awx/ui/test/spec/inventories/insights/data/solvable.insights-data.js
diff --git a/awx/ui/tests/spec/inventories/insights/insights.service-test.js b/awx/ui/test/spec/inventories/insights/insights.service-test.js
similarity index 100%
rename from awx/ui/tests/spec/inventories/insights/insights.service-test.js
rename to awx/ui/test/spec/inventories/insights/insights.service-test.js
diff --git a/awx/ui/tests/spec/inventories/manage/inventory-manage.service-test.js b/awx/ui/test/spec/inventories/manage/inventory-manage.service-test.js
similarity index 100%
rename from awx/ui/tests/spec/inventories/manage/inventory-manage.service-test.js
rename to awx/ui/test/spec/inventories/manage/inventory-manage.service-test.js
diff --git a/awx/ui/tests/spec/job-results/job-results.controller-test.js b/awx/ui/test/spec/job-results/job-results.controller-test.js
similarity index 100%
rename from awx/ui/tests/spec/job-results/job-results.controller-test.js
rename to awx/ui/test/spec/job-results/job-results.controller-test.js
diff --git a/awx/ui/tests/spec/job-results/job-results.service-test.js b/awx/ui/test/spec/job-results/job-results.service-test.js
similarity index 100%
rename from awx/ui/tests/spec/job-results/job-results.service-test.js
rename to awx/ui/test/spec/job-results/job-results.service-test.js
diff --git a/awx/ui/tests/spec/job-results/parse-stdout.service-test.js b/awx/ui/test/spec/job-results/parse-stdout.service-test.js
similarity index 100%
rename from awx/ui/tests/spec/job-results/parse-stdout.service-test.js
rename to awx/ui/test/spec/job-results/parse-stdout.service-test.js
diff --git a/awx/ui/test/spec/karma.spec.js b/awx/ui/test/spec/karma.spec.js
new file mode 100644
index 0000000000..4d8bfc8101
--- /dev/null
+++ b/awx/ui/test/spec/karma.spec.js
@@ -0,0 +1,45 @@
+const path = require('path');
+
+const SRC_PATH = path.resolve(__dirname, '../../client/src');
+const NODE_MODULES = path.resolve(__dirname, '../../node_modules');
+
+const webpackConfig = require('./webpack.spec');
+
+module.exports = function(config) {
+ config.set({
+ autoWatch: true,
+ colors: true,
+ browsers: ['Chrome', 'Firefox'],
+ coverageReporter: {
+ reporters: [
+ { type: 'html', subdir: 'html' },
+ ]
+ },
+ frameworks: [
+ 'jasmine',
+ ],
+ reporters: ['progress', 'coverage', 'junit'],
+ files:[
+ path.join(SRC_PATH, '**/*.html'),
+ path.join(SRC_PATH, 'vendor.js'),
+ path.join(NODE_MODULES, 'angular-mocks/angular-mocks.js'),
+ path.join(SRC_PATH, 'app.js'),
+ '**/*-test.js',
+ ],
+ preprocessors: {
+ [path.join(SRC_PATH, '**/*.html')]: 'html2js',
+ [path.join(SRC_PATH, 'vendor.js')]: 'webpack',
+ [path.join(SRC_PATH, 'app.js')]: 'webpack',
+ '**/*-test.js': 'webpack'
+ },
+ webpack: webpackConfig,
+ webpackMiddleware: {
+ noInfo: true
+ },
+ junitReporter: {
+ outputDir: 'coverage',
+ outputFile: 'ui-unit-test-results.xml',
+ useBrowserName: false
+ }
+ });
+};
diff --git a/awx/ui/tests/spec/license/license.controller-test.js b/awx/ui/test/spec/license/license.controller-test.js
similarity index 100%
rename from awx/ui/tests/spec/license/license.controller-test.js
rename to awx/ui/test/spec/license/license.controller-test.js
diff --git a/awx/ui/tests/spec/lookup/lookup-modal.directive-test.js b/awx/ui/test/spec/lookup/lookup-modal.directive-test.js
similarity index 100%
rename from awx/ui/tests/spec/lookup/lookup-modal.directive-test.js
rename to awx/ui/test/spec/lookup/lookup-modal.directive-test.js
diff --git a/awx/ui/tests/spec/multi-credential/multi-credential.service-test.js b/awx/ui/test/spec/multi-credential/multi-credential.service-test.js
similarity index 100%
rename from awx/ui/tests/spec/multi-credential/multi-credential.service-test.js
rename to awx/ui/test/spec/multi-credential/multi-credential.service-test.js
diff --git a/awx/ui/tests/spec/paginate/paginate.directive-test.js b/awx/ui/test/spec/paginate/paginate.directive-test.js
similarity index 100%
rename from awx/ui/tests/spec/paginate/paginate.directive-test.js
rename to awx/ui/test/spec/paginate/paginate.directive-test.js
diff --git a/awx/ui/tests/spec/shared/filters/append.filter-test.js b/awx/ui/test/spec/shared/filters/append.filter-test.js
similarity index 100%
rename from awx/ui/tests/spec/shared/filters/append.filter-test.js
rename to awx/ui/test/spec/shared/filters/append.filter-test.js
diff --git a/awx/ui/tests/spec/shared/filters/capitalize.filter-test.js b/awx/ui/test/spec/shared/filters/capitalize.filter-test.js
similarity index 100%
rename from awx/ui/tests/spec/shared/filters/capitalize.filter-test.js
rename to awx/ui/test/spec/shared/filters/capitalize.filter-test.js
diff --git a/awx/ui/tests/spec/shared/filters/format-epoch.filter-test.js b/awx/ui/test/spec/shared/filters/format-epoch.filter-test.js
similarity index 100%
rename from awx/ui/tests/spec/shared/filters/format-epoch.filter-test.js
rename to awx/ui/test/spec/shared/filters/format-epoch.filter-test.js
diff --git a/awx/ui/tests/spec/shared/filters/is-empty.filter-test.js b/awx/ui/test/spec/shared/filters/is-empty.filter-test.js
similarity index 100%
rename from awx/ui/tests/spec/shared/filters/is-empty.filter-test.js
rename to awx/ui/test/spec/shared/filters/is-empty.filter-test.js
diff --git a/awx/ui/tests/spec/shared/filters/long-date.filter-test.js b/awx/ui/test/spec/shared/filters/long-date.filter-test.js
similarity index 100%
rename from awx/ui/tests/spec/shared/filters/long-date.filter-test.js
rename to awx/ui/test/spec/shared/filters/long-date.filter-test.js
diff --git a/awx/ui/tests/spec/shared/filters/prepend.filter-test.js b/awx/ui/test/spec/shared/filters/prepend.filter-test.js
similarity index 100%
rename from awx/ui/tests/spec/shared/filters/prepend.filter-test.js
rename to awx/ui/test/spec/shared/filters/prepend.filter-test.js
diff --git a/awx/ui/tests/spec/shared/filters/xss-sanitizer.filter-test.js b/awx/ui/test/spec/shared/filters/xss-sanitizer.filter-test.js
similarity index 100%
rename from awx/ui/tests/spec/shared/filters/xss-sanitizer.filter-test.js
rename to awx/ui/test/spec/shared/filters/xss-sanitizer.filter-test.js
diff --git a/awx/ui/tests/spec/smart-search/queryset.service-test.js b/awx/ui/test/spec/smart-search/queryset.service-test.js
similarity index 100%
rename from awx/ui/tests/spec/smart-search/queryset.service-test.js
rename to awx/ui/test/spec/smart-search/queryset.service-test.js
diff --git a/awx/ui/tests/spec/smart-search/smart-search.directive-test.js b/awx/ui/test/spec/smart-search/smart-search.directive-test.js
similarity index 100%
rename from awx/ui/tests/spec/smart-search/smart-search.directive-test.js
rename to awx/ui/test/spec/smart-search/smart-search.directive-test.js
diff --git a/awx/ui/tests/spec/smart-search/smart-search.service-test.js b/awx/ui/test/spec/smart-search/smart-search.service-test.js
similarity index 100%
rename from awx/ui/tests/spec/smart-search/smart-search.service-test.js
rename to awx/ui/test/spec/smart-search/smart-search.service-test.js
diff --git a/awx/ui/tests/spec/socket/socket.service-test.js b/awx/ui/test/spec/socket/socket.service-test.js
similarity index 100%
rename from awx/ui/tests/spec/socket/socket.service-test.js
rename to awx/ui/test/spec/socket/socket.service-test.js
diff --git a/awx/ui/tests/spec/templates/templates-list.controller-test.js b/awx/ui/test/spec/templates/templates-list.controller-test.js
similarity index 100%
rename from awx/ui/tests/spec/templates/templates-list.controller-test.js
rename to awx/ui/test/spec/templates/templates-list.controller-test.js
diff --git a/awx/ui/test/spec/webpack.spec.js b/awx/ui/test/spec/webpack.spec.js
new file mode 100644
index 0000000000..6ed0f049ad
--- /dev/null
+++ b/awx/ui/test/spec/webpack.spec.js
@@ -0,0 +1,18 @@
+const path = require('path');
+
+const webpack = require('webpack');
+const merge = require('webpack-merge');
+const base = require(path.resolve(__dirname, '../..', 'build/webpack.base'));
+
+const STATIC_URL = '/static/';
+
+const test = {
+ devtool: 'inline-source-map',
+ plugins: [
+ new webpack.DefinePlugin({
+ $basePath: STATIC_URL
+ })
+ ]
+};
+
+module.exports = merge(base, test);
diff --git a/awx/ui/tests/spec/workflow--results/data/workflow_job.js b/awx/ui/test/spec/workflow--results/data/workflow_job.js
similarity index 100%
rename from awx/ui/tests/spec/workflow--results/data/workflow_job.js
rename to awx/ui/test/spec/workflow--results/data/workflow_job.js
diff --git a/awx/ui/tests/spec/workflow--results/data/workflow_job_options.js b/awx/ui/test/spec/workflow--results/data/workflow_job_options.js
similarity index 100%
rename from awx/ui/tests/spec/workflow--results/data/workflow_job_options.js
rename to awx/ui/test/spec/workflow--results/data/workflow_job_options.js
diff --git a/awx/ui/tests/spec/workflow--results/workflow-results.controller-test.js b/awx/ui/test/spec/workflow--results/workflow-results.controller-test.js
similarity index 100%
rename from awx/ui/tests/spec/workflow--results/workflow-results.controller-test.js
rename to awx/ui/test/spec/workflow--results/workflow-results.controller-test.js
diff --git a/awx/ui/tests/spec/workflow--results/workflow-results.service-test.js b/awx/ui/test/spec/workflow--results/workflow-results.service-test.js
similarity index 100%
rename from awx/ui/tests/spec/workflow--results/workflow-results.service-test.js
rename to awx/ui/test/spec/workflow--results/workflow-results.service-test.js
diff --git a/awx/ui/tests/spec/workflows/workflow-add.controller-test.js b/awx/ui/test/spec/workflows/workflow-add.controller-test.js
similarity index 100%
rename from awx/ui/tests/spec/workflows/workflow-add.controller-test.js
rename to awx/ui/test/spec/workflows/workflow-add.controller-test.js
diff --git a/awx/ui/tests/spec/workflows/workflow-maker.controller-test.js b/awx/ui/test/spec/workflows/workflow-maker.controller-test.js
similarity index 100%
rename from awx/ui/tests/spec/workflows/workflow-maker.controller-test.js
rename to awx/ui/test/spec/workflows/workflow-maker.controller-test.js
diff --git a/awx/ui/test/unit/.eslintrc.js b/awx/ui/test/unit/.eslintrc.js
new file mode 100644
index 0000000000..c8aebfd813
--- /dev/null
+++ b/awx/ui/test/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+module.exports = {
+ env: {
+ jasmine: true
+ }
+};
+
diff --git a/awx/ui/client/test/unit/index.js b/awx/ui/test/unit/components/index.js
similarity index 54%
rename from awx/ui/client/test/unit/index.js
rename to awx/ui/test/unit/components/index.js
index d26d4c6870..398445bc6f 100644
--- a/awx/ui/client/test/unit/index.js
+++ b/awx/ui/test/unit/components/index.js
@@ -2,6 +2,7 @@
import 'angular-mocks';
// Import tests
-import './layout.spec';
-import './side-nav.spec';
-import './side-nav-item.spec';
\ No newline at end of file
+import './layout.unit';
+import './side-nav.unit';
+import './side-nav-item.unit';
+
diff --git a/awx/ui/test/unit/components/layout.unit.js b/awx/ui/test/unit/components/layout.unit.js
new file mode 100644
index 0000000000..9e586d5a40
--- /dev/null
+++ b/awx/ui/test/unit/components/layout.unit.js
@@ -0,0 +1,160 @@
+describe('Components | Layout', () => {
+ let $compile;
+ let $rootScope;
+ let element;
+ let scope;
+
+ beforeEach(() => {
+ angular.mock.module('gettext');
+ angular.mock.module('I18N');
+ angular.mock.module('ui.router');
+ angular.mock.module('at.lib.services');
+ angular.mock.module('at.lib.components');
+ });
+
+ beforeEach(angular.mock.inject((_$compile_, _$rootScope_) => {
+ $compile = _$compile_;
+ $rootScope = _$rootScope_;
+ scope = $rootScope.$new();
+
+ element = angular.element('');
+ element = $compile(element)(scope);
+ scope.$digest();
+ }));
+
+ describe('AtLayoutController', () => {
+ let controller;
+
+ beforeEach(() => {
+ controller = element.controller('atLayout');
+ });
+
+ it('$scope.$on($stateChangeSuccess) should assign toState name to currentState', () => {
+ const next = { name: 'dashboard' };
+ $rootScope.$broadcast('$stateChangeSuccess', next);
+ expect(controller.currentState).toBe('dashboard');
+ });
+
+ describe('$root.current_user watcher should assign value to ', () => {
+ beforeEach(() => {
+ const val = {
+ username: 'admin',
+ id: 1
+ };
+ $rootScope.current_user = val;
+ scope.$digest();
+ });
+
+ it('isLoggedIn', () => {
+ expect(controller.isLoggedIn).toBe('admin');
+
+ $rootScope.current_user = { id: 1 };
+ scope.$digest();
+ expect(controller.isLoggedIn).not.toBeDefined();
+ });
+
+ it('isSuperUser', () => {
+ $rootScope.current_user = 'one';
+ $rootScope.user_is_superuser = true;
+ $rootScope.user_is_system_auditor = false;
+ scope.$digest();
+ expect(controller.isSuperUser).toBe(true);
+
+ $rootScope.current_user = 'two';
+ $rootScope.user_is_superuser = false;
+ $rootScope.user_is_system_auditor = true;
+ scope.$digest();
+ expect(controller.isSuperUser).toBe(true);
+
+ $rootScope.current_user = 'three';
+ $rootScope.user_is_superuser = true;
+ $rootScope.user_is_system_auditor = true;
+ scope.$digest();
+ expect(controller.isSuperUser).toBe(true);
+
+ $rootScope.current_user = 'four';
+ $rootScope.user_is_superuser = false;
+ $rootScope.user_is_system_auditor = false;
+ scope.$digest();
+ expect(controller.isSuperUser).toBe(false);
+ });
+
+ it('currentUsername', () => {
+ expect(controller.currentUsername).toBeTruthy();
+ expect(controller.currentUsername).toBe('admin');
+ });
+
+ it('currentUserId', () => {
+ expect(controller.currentUserId).toBeTruthy();
+ expect(controller.currentUserId).toBe(1);
+ });
+ });
+
+ describe('$root.socketStatus watcher should assign newStatus to', () => {
+ const statuses = ['connecting', 'error', 'ok'];
+
+ it('socketState', () => {
+ _.forEach(statuses, (status) => {
+ $rootScope.socketStatus = status;
+ scope.$digest();
+ expect(controller.socketState).toBeTruthy();
+ expect(controller.socketState).toBe(status);
+ });
+ });
+
+ it('socketIconClass', () => {
+ _.forEach(statuses, (status) => {
+ $rootScope.socketStatus = status;
+ scope.$digest();
+ expect(controller.socketIconClass).toBe(`icon-socket-${status}`);
+ });
+ });
+ });
+
+ describe('$root.licenseMissing watcher should assign true or false to', () => {
+ it('licenseIsMissing', () => {
+ $rootScope.licenseMissing = true;
+ scope.$digest();
+ expect(controller.licenseIsMissing).toBe(true);
+
+ $rootScope.licenseMissing = false;
+ scope.$digest();
+ expect(controller.licenseIsMissing).toBe(false);
+ });
+ });
+
+ describe('getString()', () => {
+ it('calls ComponentsStrings get() method', angular.mock.inject((_ComponentsStrings_) => {
+ spyOn(_ComponentsStrings_, 'get');
+ controller.getString('VIEW_DOCS');
+ expect(_ComponentsStrings_.get).toHaveBeenCalled();
+ }));
+
+ it('ComponentsStrings get() method should throw an error if string is not a property name of the layout class', () => {
+ expect(controller.getString.bind(null, 'SUBMISSION_ERROR_TITLE')).toThrow();
+ });
+
+ it('should return layout string', () => {
+ const layoutStrings = {
+ CURRENT_USER_LABEL: 'Logged in as',
+ VIEW_DOCS: 'View Documentation',
+ LOGOUT: 'Logout',
+ };
+
+ _.forEach(layoutStrings, (value, key) => {
+ expect(controller.getString(key)).toBe(value);
+ });
+ });
+
+ it('should return default string', () => {
+ const defaultStrings = {
+ BRAND_NAME: 'AWX'
+ };
+
+ _.forEach(defaultStrings, (value, key) => {
+ expect(controller.getString(key)).toBe(value);
+ });
+ });
+ });
+ });
+});
diff --git a/awx/ui/test/unit/components/side-nav-item.unit.js b/awx/ui/test/unit/components/side-nav-item.unit.js
new file mode 100644
index 0000000000..ac5631404d
--- /dev/null
+++ b/awx/ui/test/unit/components/side-nav-item.unit.js
@@ -0,0 +1,60 @@
+describe('Components | Side Nav Item', () => {
+ let $compile;
+ let $rootScope;
+ let element;
+ let scope;
+
+ beforeEach(() => {
+ angular.mock.module('gettext');
+ angular.mock.module('I18N');
+ angular.mock.module('ui.router');
+ angular.mock.module('at.lib.services');
+ angular.mock.module('at.lib.components');
+ });
+
+ beforeEach(angular.mock.inject((_$compile_, _$rootScope_) => {
+ $compile = _$compile_;
+ $rootScope = _$rootScope_;
+ scope = $rootScope.$new();
+
+ element = angular.element('');
+ element = $compile(element)(scope);
+ scope.name = 'dashboard';
+ scope.$digest();
+ }));
+
+ describe('Side Nav Item Controller', () => {
+ let SideNavItem;
+ let SideNavItemCtrl;
+
+ beforeEach(() => {
+ SideNavItem = angular.element(element[0].querySelector('at-side-nav-item'));
+ SideNavItemCtrl = SideNavItem.controller('atSideNavItem');
+ });
+
+ it('layoutVm.currentState watcher should assign isRoute', () => {
+ let current = { name: 'dashboard' };
+ $rootScope.$broadcast('$stateChangeSuccess', current);
+ scope.$digest();
+ expect(SideNavItemCtrl.isRoute).toBe(true);
+
+ current = { name: 'inventories' };
+ $rootScope.$broadcast('$stateChangeSuccess', current);
+ scope.$digest();
+ expect(SideNavItemCtrl.isRoute).toBe(false);
+ });
+
+ it('go() should call $state.go()', angular.mock.inject((_$state_) => {
+ spyOn(_$state_, 'go');
+ SideNavItemCtrl.go();
+ expect(_$state_.go).toHaveBeenCalled();
+ expect(_$state_.go).toHaveBeenCalledWith('dashboard', jasmine.any(Object), jasmine.any(Object));
+ }));
+
+ it('should load name, icon, and route from scope', () => {
+ expect(SideNavItem.isolateScope().name).toBeDefined();
+ expect(SideNavItem.isolateScope().iconClass).toBeDefined();
+ expect(SideNavItem.isolateScope().route).toBeDefined();
+ });
+ });
+});
diff --git a/awx/ui/test/unit/components/side-nav.unit.js b/awx/ui/test/unit/components/side-nav.unit.js
new file mode 100644
index 0000000000..03d0130a96
--- /dev/null
+++ b/awx/ui/test/unit/components/side-nav.unit.js
@@ -0,0 +1,78 @@
+describe('Components | Side Nav', () => {
+ let $compile;
+ let $rootScope;
+ let element;
+ let scope;
+ const windowMock = {
+ innerWidth: 500
+ };
+
+ beforeEach(() => {
+ angular.mock.module('gettext');
+ angular.mock.module('I18N');
+ angular.mock.module('ui.router');
+ angular.mock.module('at.lib.services');
+ angular.mock.module('at.lib.components', ($provide) => {
+ $provide.value('$window', windowMock);
+ });
+ });
+
+ beforeEach(angular.mock.inject((_$compile_, _$rootScope_) => {
+ $compile = _$compile_;
+ $rootScope = _$rootScope_;
+ scope = $rootScope.$new();
+
+ element = angular.element('');
+ element = $compile(element)(scope);
+ scope.$digest();
+ }));
+
+ describe('Side Nav Controller', () => {
+ let sideNav;
+ let sideNavCtrl;
+
+ beforeEach(() => {
+ sideNav = angular.element(element[0].querySelector('.at-Layout-side'));
+ sideNavCtrl = sideNav.controller('atSideNav');
+ });
+
+ it('isExpanded defaults to false', () => {
+ expect(sideNavCtrl.isExpanded).toBe(false);
+ });
+
+ it('toggleExpansion()', () => {
+ expect(sideNavCtrl.isExpanded).toBe(false);
+
+ sideNavCtrl.toggleExpansion();
+ expect(sideNavCtrl.isExpanded).toBe(true);
+
+ sideNavCtrl.toggleExpansion();
+ expect(sideNavCtrl.isExpanded).toBe(false);
+
+ sideNavCtrl.toggleExpansion();
+ expect(sideNavCtrl.isExpanded).toBe(true);
+
+ sideNavCtrl.toggleExpansion();
+ expect(sideNavCtrl.isExpanded).toBe(false);
+ });
+
+ it('isExpanded should be false after state change event', () => {
+ sideNavCtrl.isExpanded = true;
+
+ const current = {
+ name: 'dashboard'
+ };
+ $rootScope.$broadcast('$stateChangeSuccess', current);
+ scope.$digest();
+ expect(sideNavCtrl.isExpanded).toBe(false);
+ });
+
+ it('clickOutsideSideNav watcher should assign isExpanded to false', () => {
+ sideNavCtrl.isExpanded = true;
+
+ $rootScope.$broadcast('clickOutsideSideNav');
+ scope.$digest();
+ expect(sideNavCtrl.isExpanded).toBe(false);
+ });
+ });
+});
diff --git a/awx/ui/test/unit/index.js b/awx/ui/test/unit/index.js
new file mode 100644
index 0000000000..6fd7b1b02e
--- /dev/null
+++ b/awx/ui/test/unit/index.js
@@ -0,0 +1,2 @@
+import './components';
+
diff --git a/awx/ui/client/test/unit/karma.conf.js b/awx/ui/test/unit/karma.conf.js
similarity index 100%
rename from awx/ui/client/test/unit/karma.conf.js
rename to awx/ui/test/unit/karma.conf.js
diff --git a/awx/ui/test/unit/karma.unit.js b/awx/ui/test/unit/karma.unit.js
new file mode 100644
index 0000000000..24a63c0cf9
--- /dev/null
+++ b/awx/ui/test/unit/karma.unit.js
@@ -0,0 +1,39 @@
+const path = require('path');
+
+const SRC_PATH = path.resolve(__dirname, '../../client/src');
+
+const webpackConfig = require('./webpack.unit');
+
+module.exports = config => {
+ config.set({
+ basePath: '',
+ singleRun: true,
+ autoWatch: false,
+ colors: true,
+ frameworks: ['jasmine'],
+ browsers: ['PhantomJS'],
+ reporters: ['progress'],
+ files: [
+ path.join(SRC_PATH, 'vendor.js'),
+ path.join(SRC_PATH, 'app.js'),
+ path.join(SRC_PATH, '**/*.html'),
+ 'index.js'
+ ],
+ plugins: [
+ 'karma-webpack',
+ 'karma-jasmine',
+ 'karma-phantomjs-launcher',
+ 'karma-html2js-preprocessor'
+ ],
+ preprocessors: {
+ [path.join(SRC_PATH, 'vendor.js')]: 'webpack',
+ [path.join(SRC_PATH, 'app.js')]: 'webpack',
+ [path.join(SRC_PATH, '**/*.html')]: 'html2js',
+ 'index.js': 'webpack'
+ },
+ webpack: webpackConfig,
+ webpackMiddleware: {
+ noInfo: 'errors-only'
+ }
+ });
+};
diff --git a/awx/ui/client/test/unit/layout.spec.js b/awx/ui/test/unit/layout.spec.js
similarity index 100%
rename from awx/ui/client/test/unit/layout.spec.js
rename to awx/ui/test/unit/layout.spec.js
diff --git a/awx/ui/client/test/unit/side-nav-item.spec.js b/awx/ui/test/unit/side-nav-item.spec.js
similarity index 100%
rename from awx/ui/client/test/unit/side-nav-item.spec.js
rename to awx/ui/test/unit/side-nav-item.spec.js
diff --git a/awx/ui/client/test/unit/side-nav.spec.js b/awx/ui/test/unit/side-nav.spec.js
similarity index 100%
rename from awx/ui/client/test/unit/side-nav.spec.js
rename to awx/ui/test/unit/side-nav.spec.js
diff --git a/awx/ui/test/unit/webpack.unit.js b/awx/ui/test/unit/webpack.unit.js
new file mode 100644
index 0000000000..7ec1a9a723
--- /dev/null
+++ b/awx/ui/test/unit/webpack.unit.js
@@ -0,0 +1,16 @@
+const webpack = require('webpack');
+const merge = require('webpack-merge');
+const base = require('../../build/webpack.base');
+
+const STATIC_URL = '/static/';
+
+const test = {
+ devtool: 'cheap-source-map',
+ plugins: [
+ new webpack.DefinePlugin({
+ $basePath: STATIC_URL
+ })
+ ]
+};
+
+module.exports = merge(base, test);