From 59e278a648065fd4cf12ff3e86da1b87870b8cb7 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Sat, 24 Feb 2018 20:08:51 -0500 Subject: [PATCH 1/7] ensure correct url is built for inventory hosts page --- awx/ui/test/e2e/fixtures.js | 42 +++++++++++++++---------------- awx/ui/test/e2e/tests/test-xss.js | 12 +++++++-- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/awx/ui/test/e2e/fixtures.js b/awx/ui/test/e2e/fixtures.js index ecb45db9dc..ddc705cdf7 100644 --- a/awx/ui/test/e2e/fixtures.js +++ b/awx/ui/test/e2e/fixtures.js @@ -10,24 +10,17 @@ import { const session = `e2e-${uuid().substr(0, 8)}`; const store = {}; -const getOrCreate = (endpoint, data, unique = ['name', 'username', 'id']) => { - const identifier = Object.keys(data).find(key => unique.includes(key)); +const getOrCreate = (endpoint, data, unique = ['name']) => { + const identifiers = Object.keys(data).filter(key => unique.indexOf(key) > -1); - if (identifier === undefined) { + if (identifiers.length < 1) { throw new Error('A unique key value must be provided.'); } - const identity = data[identifier]; + const lookup = `${endpoint}/${identifiers.map(key => data[key]).join('-')}`; + const params = Object.assign(...identifiers.map(key => ({ [key]: data[key] }))); - store[endpoint] = store[endpoint] || {}; - - if (store[endpoint][identity]) { - return store[endpoint][identity].then(created => created.data); - } - - const query = { params: { [identifier]: identity } }; - - store[endpoint][identity] = get(endpoint, query) + store[lookup] = store[lookup] || get(endpoint, { params }) .then(res => { if (res.data.results.length > 1) { return Promise.reject(new Error('More than one matching result.')); @@ -44,7 +37,7 @@ const getOrCreate = (endpoint, data, unique = ['name', 'username', 'id']) => { return Promise.reject(new Error(`unexpected response: ${res}`)); }); - return store[endpoint][identity].then(created => created.data); + return store[lookup].then(created => created.data); }; const getOrganization = (namespace = session) => getOrCreate('/organizations/', { @@ -57,7 +50,12 @@ const getInventory = (namespace = session) => getOrganization(namespace) name: `${namespace}-inventory`, description: namespace, organization: organization.id - })); + }).then(inventory => getOrCreate('/hosts/', { + name: `${namespace}-host`, + description: namespace, + inventory: inventory.id, + variables: JSON.stringify({ ansible_connection: 'local' }), + }, ['name', 'inventory']).then(() => inventory))); const getHost = (namespace = session) => getInventory(namespace) .then(inventory => getOrCreate('/hosts/', { @@ -65,7 +63,7 @@ const getHost = (namespace = session) => getInventory(namespace) description: namespace, inventory: inventory.id, variables: JSON.stringify({ ansible_connection: 'local' }), - })); + }, ['name', 'inventory'])); const getInventoryScript = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/inventory_scripts/', { @@ -265,8 +263,8 @@ const getWorkflowTemplate = (namespace = session) => { ]; const createSuccessNodes = ([projectNode, jobNode, sourceNode]) => Promise.all([ - getOrCreate(projectNode.related.success_nodes, { id: jobNode.id }), - getOrCreate(jobNode.related.success_nodes, { id: sourceNode.id }), + getOrCreate(projectNode.related.success_nodes, { id: jobNode.id }, ['id']), + getOrCreate(jobNode.related.success_nodes, { id: sourceNode.id }, ['id']), ]); return Promise.all(nodes) @@ -287,7 +285,7 @@ const getAuditor = (namespace = session) => getOrganization(namespace) is_superuser: false, is_system_auditor: true, password: AWX_E2E_PASSWORD - })); + }, ['username'])); const getUser = (namespace = session) => getOrganization(namespace) .then(organization => getOrCreate('/users/', { @@ -299,7 +297,7 @@ const getUser = (namespace = session) => getOrganization(namespace) is_superuser: false, is_system_auditor: false, password: AWX_E2E_PASSWORD - })); + }, ['username'])); const getJobTemplateAdmin = (namespace = session) => { const rolePromise = getJobTemplate(namespace) @@ -315,7 +313,7 @@ const getJobTemplateAdmin = (namespace = session) => { is_superuser: false, is_system_auditor: false, password: AWX_E2E_PASSWORD - })); + }, ['username'])); const assignRolePromise = Promise.all([userPromise, rolePromise]) .then(([user, role]) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id })); @@ -338,7 +336,7 @@ const getProjectAdmin = (namespace = session) => { is_superuser: false, is_system_auditor: false, password: AWX_E2E_PASSWORD - })); + }, ['username'])); const assignRolePromise = Promise.all([userPromise, rolePromise]) .then(([user, role]) => post(`/api/v2/roles/${role.id}/users/`, { id: user.id })); diff --git a/awx/ui/test/e2e/tests/test-xss.js b/awx/ui/test/e2e/tests/test-xss.js index 5d15bdfc99..b8ac4dba2f 100644 --- a/awx/ui/test/e2e/tests/test-xss.js +++ b/awx/ui/test/e2e/tests/test-xss.js @@ -40,7 +40,7 @@ module.exports = { getTeam(namespace).then(obj => { data.team = obj; }), getProjectAdmin(namespace).then(obj => { data.user = obj; }), getNotificationTemplate(namespace).then(obj => { data.notification = obj; }), - getJob(namespaceShort).then(obj => { data.job = obj; }), + getJob(namespace).then(obj => { data.job = obj; }), ]; Promise.all(resources) @@ -58,7 +58,6 @@ module.exports = { urls.organization = `${pages.organizations.url()}/${data.organization.id}`; urls.inventory = `${pages.inventories.url()}/inventory/${data.inventory.id}`; - urls.inventoryHosts = `${urls.inventory}/hosts`; urls.inventoryScript = `${pages.inventoryScripts.url()}/${data.inventoryScript.id}`; urls.inventorySource = `${urls.inventory}/inventory_sources/edit/${data.inventorySource.id}`; urls.sourceSchedule = `${urls.inventorySource}/schedules/${data.sourceSchedule.id}`; @@ -72,6 +71,7 @@ module.exports = { urls.notification = `${pages.notificationTemplates.url()}/${data.notification.id}`; urls.jobs = `${pages.jobs.url()}`; urls.jobsSchedules = `${pages.jobs.url()}/schedules`; + urls.inventoryHosts = `${pages.inventories.url()}/inventory/${data.host.summary_fields.inventory.id}/hosts`; client.useCss(); client.login(); @@ -691,6 +691,14 @@ module.exports = { const popOver = `${itemRow} td[class*="active_failures-"] div[class*="popover"]`; client.navigateTo(urls.inventoryHosts); + client.expect.element('div[class^="Panel"] smart-search').visible; + client.expect.element('div[class^="Panel"] smart-search input').enabled; + + client.sendKeys('div[class^="Panel"] smart-search input', `id:>${data.host.id - 1} id:<${data.host.id + 1}`); + client.sendKeys('div[class^="Panel"] smart-search input', client.Keys.ENTER); + + client.expect.element('div.spinny').visible; + client.expect.element('div.spinny').not.visible; client.click(itemName); client.expect.element(popOver).present; From 0e7db2a8164887e80194c6f3cfdeb094dc3bc42b Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Sat, 24 Feb 2018 20:18:21 -0500 Subject: [PATCH 2/7] do searchability check last This fixes a small race condition that sometimes occurs when running locally by ensuring that the delayed paged scrolling that happens from using search doesn't put the password reset button out of view when the test runner is trying to find and click it. --- .../test-credentials-add-edit-machine.js | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js index e21e8c02f5..68507171b0 100644 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js +++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-machine.js @@ -166,18 +166,6 @@ module.exports = { credentials.section.edit.expect.section('@details').visible; }, - 'credential is searchable after saving': client => { - const credentials = client.page.credentials(); - const row = '#credentials_table tbody tr'; - - credentials.section.list.section.search - .waitForElementVisible('@input', AWX_E2E_TIMEOUT_LONG) - .setValue('@input', `name:${store.credential.name}`) - .click('@searchButton'); - - credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`); - credentials.expect.element(row).text.contain(store.credential.name); - }, 'change the password after saving': client => { const credentials = client.page.credentials(); const { edit } = credentials.section; @@ -190,6 +178,7 @@ module.exports = { machine.expect.element('@password').not.enabled; machine.section.password.click('@replace'); + machine.section.password.expect.element('@replace').not.present; machine.section.password.expect.element('@revert').visible; @@ -201,7 +190,19 @@ module.exports = { credentials .waitForElementVisible('div.spinny') .waitForElementNotVisible('div.spinny'); + }, + 'credential is searchable after saving': client => { + const credentials = client.page.credentials(); + const row = '#credentials_table tbody tr'; + + credentials.section.list.section.search + .waitForElementVisible('@input', AWX_E2E_TIMEOUT_LONG) + .setValue('@input', `name:${store.credential.name}`) + .click('@searchButton'); + + credentials.waitForElementNotPresent(`${row}:nth-of-type(2)`); + credentials.expect.element(row).text.contain(store.credential.name); client.end(); - } + }, }; From 8bf31600b05a398deb34fea6a2d11734d43034f4 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Sat, 24 Feb 2018 20:22:07 -0500 Subject: [PATCH 3/7] stabilize local test runs --- awx/ui/test/e2e/settings.js | 2 +- awx/ui/test/e2e/tests/smoke.js | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/awx/ui/test/e2e/settings.js b/awx/ui/test/e2e/settings.js index a68f9fcc22..acee4dca4d 100644 --- a/awx/ui/test/e2e/settings.js +++ b/awx/ui/test/e2e/settings.js @@ -4,7 +4,7 @@ const AWX_E2E_CLUSTER_WORKERS = process.env.AWX_E2E_CLUSTER_WORKERS || 0; const AWX_E2E_PASSWORD = process.env.AWX_E2E_PASSWORD || 'password'; const AWX_E2E_URL = process.env.AWX_E2E_URL || 'https://localhost:8043'; const AWX_E2E_USERNAME = process.env.AWX_E2E_USERNAME || 'awx-e2e'; -const AWX_E2E_TIMEOUT_ASYNC = process.env.AWX_E2E_TIMEOUT_ASYNC || 30000; +const AWX_E2E_TIMEOUT_ASYNC = process.env.AWX_E2E_TIMEOUT_ASYNC || 90000; const AWX_E2E_TIMEOUT_LONG = process.env.AWX_E2E_TIMEOUT_LONG || 10000; const AWX_E2E_TIMEOUT_MEDIUM = process.env.AWX_E2E_TIMEOUT_MEDIUM || 5000; const AWX_E2E_TIMEOUT_SHORT = process.env.AWX_E2E_TIMEOUT_SHORT || 1000; diff --git a/awx/ui/test/e2e/tests/smoke.js b/awx/ui/test/e2e/tests/smoke.js index 8d27ec4f39..5ef4a4ebfd 100644 --- a/awx/ui/test/e2e/tests/smoke.js +++ b/awx/ui/test/e2e/tests/smoke.js @@ -58,7 +58,8 @@ module.exports = { projects.section.list.waitForElementVisible('@add'); projects.section.list.expect.element('@add').enabled; - projects.section.list.click('@add'); + + client.pause(1000); projects.section.list.click('@add'); projects.waitForElementVisible('label[for="name"] + div input'); projects.waitForElementVisible('label[for="organization"] + div input'); @@ -269,6 +270,7 @@ module.exports = { templates.waitForElementVisible('smart-search input'); templates.expect.element('smart-search input').enabled; + client.pause(1000).waitForElementNotVisible('div.spinny'); templates.sendKeys('smart-search input', `${TEMPLATE_NAME}${client.Keys.ENTER}`); templates.waitForElementVisible('div.spinny'); templates.waitForElementNotVisible('div.spinny'); From 24fd4a360e99d83cde76b0a23c8faa38e4f9237b Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Sat, 24 Feb 2018 21:28:49 -0500 Subject: [PATCH 4/7] use updated project when checking copy --- awx/ui/test/e2e/tests/test-projects-list-actions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awx/ui/test/e2e/tests/test-projects-list-actions.js b/awx/ui/test/e2e/tests/test-projects-list-actions.js index e8bd6c0bc3..9524254189 100644 --- a/awx/ui/test/e2e/tests/test-projects-list-actions.js +++ b/awx/ui/test/e2e/tests/test-projects-list-actions.js @@ -1,10 +1,10 @@ -import { getProject } from '../fixtures'; +import { getUpdatedProject } from '../fixtures'; const data = {}; module.exports = { before: (client, done) => { - getProject('test-actions') + getUpdatedProject('test-actions') .then(obj => { data.project = obj; }) .then(done); }, From a23e4732b65d2aee5012a0babe10b6c0c9821c82 Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Sat, 24 Feb 2018 21:30:42 -0500 Subject: [PATCH 5/7] bump nightwatch and chromedriver versions --- awx/ui/package.json | 4 +- awx/ui/test/e2e/cluster/docker-compose.yml | 8 ++-- awx/ui/test/e2e/commands/navigateTo.js | 11 +++-- awx/ui/test/e2e/objects/activityStream.js | 6 +++ awx/ui/test/e2e/objects/credentialTypes.js | 6 +++ awx/ui/test/e2e/objects/credentials.js | 6 +++ awx/ui/test/e2e/objects/inventories.js | 4 ++ awx/ui/test/e2e/objects/inventoryScripts.js | 6 +++ awx/ui/test/e2e/objects/jobs.js | 7 ++- awx/ui/test/e2e/objects/login.js | 6 +++ .../test/e2e/objects/notificationTemplates.js | 6 +++ awx/ui/test/e2e/objects/organizations.js | 6 +++ awx/ui/test/e2e/objects/projects.js | 6 +++ awx/ui/test/e2e/objects/teams.js | 6 +++ awx/ui/test/e2e/objects/templates.js | 4 ++ awx/ui/test/e2e/objects/users.js | 6 +++ .../e2e/tests/test-auditor-read-only-forms.js | 45 ++++++++++++------- .../tests/test-credential-types-add-edit.js | 5 +-- .../tests/test-credentials-list-actions.js | 2 +- ...st-credentials-navigation-click-through.js | 2 +- .../e2e/tests/test-credentials-search-sort.js | 2 +- .../tests/test-inventories-list-actions.js | 2 +- .../test-inventory-scripts-list-actions.js | 2 +- .../tests/test-notifications-list-actions.js | 2 +- .../e2e/tests/test-projects-list-actions.js | 2 +- .../test-templates-copy-delete-warnings.js | 6 +-- .../e2e/tests/test-templates-list-actions.js | 4 +- awx/ui/test/e2e/tests/test-xss.js | 4 +- 28 files changed, 131 insertions(+), 45 deletions(-) diff --git a/awx/ui/package.json b/awx/ui/package.json index c152d7be52..97031fbffd 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -39,7 +39,7 @@ "babel-loader": "^7.1.2", "babel-plugin-istanbul": "^4.1.5", "babel-preset-env": "^1.6.0", - "chromedriver": "^2.31.0", + "chromedriver": "^2.35.0", "clean-webpack-plugin": "^0.1.16", "copy-webpack-plugin": "^4.0.1", "css-loader": "^0.28.5", @@ -80,7 +80,7 @@ "load-grunt-configs": "^1.0.0", "load-grunt-tasks": "^3.5.0", "ngtemplate-loader": "^2.0.1", - "nightwatch": "^0.9.16", + "nightwatch": "^0.9.19", "node-object-hash": "^1.3.0", "phantomjs-prebuilt": "^2.1.12", "time-grunt": "^1.4.0", diff --git a/awx/ui/test/e2e/cluster/docker-compose.yml b/awx/ui/test/e2e/cluster/docker-compose.yml index 583090f555..de4a7f920b 100644 --- a/awx/ui/test/e2e/cluster/docker-compose.yml +++ b/awx/ui/test/e2e/cluster/docker-compose.yml @@ -2,13 +2,13 @@ version: '2' services: hub: - image: selenium/hub:3.8.1-erbium + image: selenium/hub ports: - 4444:4444 chrome: - image: selenium/node-chrome:3.8.1-erbium + image: selenium/node-chrome # uncomment the two lines below to make tests watchable at vnc://localhost:secret@localhost:5900 - # image: selenium/node-chrome-debug:3.8.1-erbium + # image: selenium/node-chrome-debug # ports: ['5900:5900'] links: - hub @@ -18,7 +18,7 @@ services: HUB_PORT_4444_TCP_ADDR: hub HUB_PORT_4444_TCP_PORT: 4444 firefox: - image: selenium/node-firefox:3.8.1-erbium + image: selenium/node-firefox links: - hub environment: diff --git a/awx/ui/test/e2e/commands/navigateTo.js b/awx/ui/test/e2e/commands/navigateTo.js index 4b8bd3048a..4c4509cf61 100644 --- a/awx/ui/test/e2e/commands/navigateTo.js +++ b/awx/ui/test/e2e/commands/navigateTo.js @@ -1,8 +1,13 @@ -exports.command = function navigateTo (url) { +const spinny = 'div.spinny'; + +exports.command = function navigateTo (url, expectSpinny = true) { + this.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 this.url(url); - this.waitForElementVisible('div.spinny'); - this.waitForElementNotVisible('div.spinny'); + if (expectSpinny) { + this.waitForElementVisible(spinny); + this.waitForElementNotVisible(spinny); + } return this; }; diff --git a/awx/ui/test/e2e/objects/activityStream.js b/awx/ui/test/e2e/objects/activityStream.js index 896fa32886..961ef6056e 100644 --- a/awx/ui/test/e2e/objects/activityStream.js +++ b/awx/ui/test/e2e/objects/activityStream.js @@ -2,6 +2,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/activity_stream`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], elements: { title: '.List-titleText', subtitle: '.List-titleLockup', diff --git a/awx/ui/test/e2e/objects/credentialTypes.js b/awx/ui/test/e2e/objects/credentialTypes.js index 6c98d1e631..302bbd61ae 100644 --- a/awx/ui/test/e2e/objects/credentialTypes.js +++ b/awx/ui/test/e2e/objects/credentialTypes.js @@ -52,6 +52,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/credential_types`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], sections: { header, breadcrumb, diff --git a/awx/ui/test/e2e/objects/credentials.js b/awx/ui/test/e2e/objects/credentials.js index d271eb953a..a4225f2873 100644 --- a/awx/ui/test/e2e/objects/credentials.js +++ b/awx/ui/test/e2e/objects/credentials.js @@ -216,6 +216,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/credentials`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], sections: { header, navigation, diff --git a/awx/ui/test/e2e/objects/inventories.js b/awx/ui/test/e2e/objects/inventories.js index 9fe5761ef5..794a72f760 100644 --- a/awx/ui/test/e2e/objects/inventories.js +++ b/awx/ui/test/e2e/objects/inventories.js @@ -116,6 +116,10 @@ module.exports = { save: 'button[class*="Form-saveButton"]' }, commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, selectAdd (name) { this.api.waitForElementVisible('button span[class="List-dropdownCarat"]'); this.expect.element('button span[class="List-dropdownCarat"]').enabled; diff --git a/awx/ui/test/e2e/objects/inventoryScripts.js b/awx/ui/test/e2e/objects/inventoryScripts.js index 4bea0c55e9..4555ac11ff 100644 --- a/awx/ui/test/e2e/objects/inventoryScripts.js +++ b/awx/ui/test/e2e/objects/inventoryScripts.js @@ -24,6 +24,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/inventory_scripts`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], sections: { header, navigation, diff --git a/awx/ui/test/e2e/objects/jobs.js b/awx/ui/test/e2e/objects/jobs.js index 93c43caccd..8e1b3fe82e 100644 --- a/awx/ui/test/e2e/objects/jobs.js +++ b/awx/ui/test/e2e/objects/jobs.js @@ -6,5 +6,10 @@ module.exports = { }, sections: {}, // TODO: Fill this out elements: {}, // TODO: Fill this out - commands: [], // TODO: Fill this out as needed + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], }; diff --git a/awx/ui/test/e2e/objects/login.js b/awx/ui/test/e2e/objects/login.js index 7fa8e5d8d4..75623f1300 100644 --- a/awx/ui/test/e2e/objects/login.js +++ b/awx/ui/test/e2e/objects/login.js @@ -2,6 +2,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/login`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], elements: { username: '#login-username', password: '#login-password', diff --git a/awx/ui/test/e2e/objects/notificationTemplates.js b/awx/ui/test/e2e/objects/notificationTemplates.js index f98fb4bd2e..dabe450cbb 100644 --- a/awx/ui/test/e2e/objects/notificationTemplates.js +++ b/awx/ui/test/e2e/objects/notificationTemplates.js @@ -29,6 +29,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/notification_templates`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], sections: { header, navigation, diff --git a/awx/ui/test/e2e/objects/organizations.js b/awx/ui/test/e2e/objects/organizations.js index e6f8b36398..f5eda6580b 100644 --- a/awx/ui/test/e2e/objects/organizations.js +++ b/awx/ui/test/e2e/objects/organizations.js @@ -26,6 +26,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/organizations`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], sections: { header, navigation, diff --git a/awx/ui/test/e2e/objects/projects.js b/awx/ui/test/e2e/objects/projects.js index fcdfeb35bb..d40bcffb65 100644 --- a/awx/ui/test/e2e/objects/projects.js +++ b/awx/ui/test/e2e/objects/projects.js @@ -25,6 +25,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/projects`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], sections: { header, navigation, diff --git a/awx/ui/test/e2e/objects/teams.js b/awx/ui/test/e2e/objects/teams.js index bc1a4bd940..64d33b2b1a 100644 --- a/awx/ui/test/e2e/objects/teams.js +++ b/awx/ui/test/e2e/objects/teams.js @@ -23,6 +23,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/teams`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], sections: { header, navigation, diff --git a/awx/ui/test/e2e/objects/templates.js b/awx/ui/test/e2e/objects/templates.js index 86a5478d3e..cb59cdaaf2 100644 --- a/awx/ui/test/e2e/objects/templates.js +++ b/awx/ui/test/e2e/objects/templates.js @@ -116,6 +116,10 @@ module.exports = { save: 'button[class*="Form-saveButton"]' }, commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, clickWhenEnabled (selector) { this.api.waitForElementVisible(selector); this.expect.element(selector).enabled; diff --git a/awx/ui/test/e2e/objects/users.js b/awx/ui/test/e2e/objects/users.js index 784d7f57fd..d808dd5103 100644 --- a/awx/ui/test/e2e/objects/users.js +++ b/awx/ui/test/e2e/objects/users.js @@ -23,6 +23,12 @@ module.exports = { url () { return `${this.api.globals.launch_url}/#/users`; }, + commands: [{ + load () { + this.api.url('data:,'); // https://github.com/nightwatchjs/nightwatch/issues/1724 + return this.navigate(); + }, + }], sections: { header, navigation, diff --git a/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js b/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js index 9a9fc07225..ede45a1a8e 100644 --- a/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js +++ b/awx/ui/test/e2e/tests/test-auditor-read-only-forms.js @@ -24,13 +24,6 @@ let users; let inventories; let teams; -function navigateAndWaitForSpinner (client, url) { - client - .url(url) - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); -} - module.exports = { before: (client, done) => { const promises = [ @@ -68,7 +61,9 @@ module.exports = { }); }, 'verify an auditor\'s credentials inputs are read-only': client => { - navigateAndWaitForSpinner(client, `${credentials.url()}/${data.adminAWSCredential.id}/`); + const url = `${credentials.url()}/${data.adminAWSCredential.id}/`; + + client.navigateTo(url); credentials.section.edit .expect.element('@title').text.contain(data.adminAWSCredential.name); @@ -76,7 +71,9 @@ module.exports = { credentials.section.edit.section.details.checkAllFieldsDisabled(); }, 'verify an auditor\'s inventory scripts inputs are read-only': client => { - navigateAndWaitForSpinner(client, `${inventoryScripts.url()}/${data.inventoryScript.id}/`); + const url = `${inventoryScripts.url()}/${data.inventoryScript.id}/`; + + client.navigateTo(url); inventoryScripts.section.edit .expect.element('@title').text.contain(data.inventoryScript.name); @@ -92,7 +89,7 @@ module.exports = { // // 'verify an auditor\'s job template inputs are read-only': function (client) { // const url = `${templates.url()}/job_template/${data.jobTemplate.id}/`; - // navigateAndWaitForSpinner(client, url); + // client.navigateTo(url)'' // // templates.section.editJobTemplate // .expect.element('@title').text.contain(data.jobTemplate.name); @@ -103,7 +100,9 @@ module.exports = { // templates.expect.element('@save').to.not.be.visible; // }, 'verify an auditor\'s notification templates inputs are read-only': client => { - navigateAndWaitForSpinner(client, `${notificationTemplates.url()}/${data.notificationTemplate.id}/`); + const url = `${notificationTemplates.url()}/${data.notificationTemplate.id}/`; + + client.navigateTo(url); notificationTemplates.section.edit .expect.element('@title').text.contain(data.notificationTemplate.name); @@ -114,7 +113,9 @@ module.exports = { notificationTemplates.expect.element('@save').to.not.be.visible; }, 'verify an auditor\'s organizations inputs are read-only': client => { - navigateAndWaitForSpinner(client, `${organizations.url()}/${data.organization.id}/`); + const url = `${organizations.url()}/${data.organization.id}/`; + + client.navigateTo(url); organizations.section.edit .expect.element('@title').text.contain(data.organization.name); @@ -125,7 +126,9 @@ module.exports = { organizations.expect.element('@save').to.not.be.visible; }, 'verify an auditor\'s smart inventory inputs are read-only': client => { - navigateAndWaitForSpinner(client, `${inventories.url()}/smart/${data.smartInventory.id}/`); + const url = `${inventories.url()}/smart/${data.smartInventory.id}/`; + + client.navigateTo(url); inventories.section.editSmartInventory .expect.element('@title').text.contain(data.smartInventory.name); @@ -136,7 +139,9 @@ module.exports = { inventories.expect.element('@save').to.not.be.visible; }, 'verify an auditor\'s project inputs are read-only': client => { - navigateAndWaitForSpinner(client, `${projects.url()}/${data.project.id}/`); + const url = `${projects.url()}/${data.project.id}/`; + + client.navigateTo(url); projects.section.edit .expect.element('@title').text.contain(data.project.name); @@ -147,7 +152,9 @@ module.exports = { projects.expect.element('@save').to.not.be.visible; }, 'verify an auditor\'s standard inventory inputs are read-only': client => { - navigateAndWaitForSpinner(client, `${inventories.url()}/inventory/${data.inventory.id}/`); + const url = `${inventories.url()}/inventory/${data.inventory.id}/`; + + client.navigateTo(url); inventories.section.editStandardInventory .expect.element('@title').text.contain(data.inventory.name); @@ -159,7 +166,9 @@ module.exports = { inventories.expect.element('@save').to.not.be.visible; }, 'verify an auditor\'s teams inputs are read-only': client => { - navigateAndWaitForSpinner(client, `${teams.url()}/${data.team.id}/`); + const url = `${teams.url()}/${data.team.id}/`; + + client.navigateTo(url); teams.section.edit .expect.element('@title').text.contain(data.team.name); @@ -170,7 +179,9 @@ module.exports = { teams.expect.element('@save').to.not.be.visible; }, 'verify an auditor\'s user inputs are read-only': client => { - navigateAndWaitForSpinner(client, `${users.url()}/${data.user.id}/`); + const url = `${users.url()}/${data.user.id}/`; + + client.navigateTo(url); users.section.edit .expect.element('@title').text.contain(data.user.username); diff --git a/awx/ui/test/e2e/tests/test-credential-types-add-edit.js b/awx/ui/test/e2e/tests/test-credential-types-add-edit.js index 5b343ad0c5..6108db871c 100644 --- a/awx/ui/test/e2e/tests/test-credential-types-add-edit.js +++ b/awx/ui/test/e2e/tests/test-credential-types-add-edit.js @@ -5,10 +5,7 @@ module.exports = { client.login(); client.waitForAngular(); - credentialTypes - .navigate(`${credentialTypes.url()}/add/`) - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); + credentialTypes.navigateTo(`${credentialTypes.url()}/add/`); credentialTypes.section.add .waitForElementVisible('@title', done); diff --git a/awx/ui/test/e2e/tests/test-credentials-list-actions.js b/awx/ui/test/e2e/tests/test-credentials-list-actions.js index 11f075dc79..dd5c274496 100644 --- a/awx/ui/test/e2e/tests/test-credentials-list-actions.js +++ b/awx/ui/test/e2e/tests/test-credentials-list-actions.js @@ -16,7 +16,7 @@ module.exports = { client.login(); client.waitForAngular(); - credentials.navigate(); + credentials.load(); credentials.waitForElementVisible('div.spinny'); credentials.waitForElementNotVisible('div.spinny'); diff --git a/awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js b/awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js index 19eae2dd9d..9fe4a7c79b 100644 --- a/awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js +++ b/awx/ui/test/e2e/tests/test-credentials-navigation-click-through.js @@ -6,7 +6,7 @@ module.exports = { client.waitForAngular(); credentials - .navigate() + .load() .waitForElementVisible('div.spinny') .waitForElementNotVisible('div.spinny', done); }, diff --git a/awx/ui/test/e2e/tests/test-credentials-search-sort.js b/awx/ui/test/e2e/tests/test-credentials-search-sort.js index 525901b4ae..cd46775aae 100644 --- a/awx/ui/test/e2e/tests/test-credentials-search-sort.js +++ b/awx/ui/test/e2e/tests/test-credentials-search-sort.js @@ -10,7 +10,7 @@ module.exports = { client.waitForAngular(); credentials - .navigate() + .load() .waitForElementVisible('div.spinny') .waitForElementNotVisible('div.spinny'); diff --git a/awx/ui/test/e2e/tests/test-inventories-list-actions.js b/awx/ui/test/e2e/tests/test-inventories-list-actions.js index 280608b197..e705daeacf 100644 --- a/awx/ui/test/e2e/tests/test-inventories-list-actions.js +++ b/awx/ui/test/e2e/tests/test-inventories-list-actions.js @@ -16,7 +16,7 @@ module.exports = { client.login(); client.waitForAngular(); - inventories.navigate(); + inventories.load(); inventories.waitForElementVisible('div.spinny'); inventories.waitForElementNotVisible('div.spinny'); diff --git a/awx/ui/test/e2e/tests/test-inventory-scripts-list-actions.js b/awx/ui/test/e2e/tests/test-inventory-scripts-list-actions.js index 51c52798ea..b75bb73516 100644 --- a/awx/ui/test/e2e/tests/test-inventory-scripts-list-actions.js +++ b/awx/ui/test/e2e/tests/test-inventory-scripts-list-actions.js @@ -16,7 +16,7 @@ module.exports = { client.login(); client.waitForAngular(); - inventoryScripts.navigate(); + inventoryScripts.load(); inventoryScripts.waitForElementVisible('div.spinny'); inventoryScripts.waitForElementNotVisible('div.spinny'); diff --git a/awx/ui/test/e2e/tests/test-notifications-list-actions.js b/awx/ui/test/e2e/tests/test-notifications-list-actions.js index fc32d1553b..8e126e075d 100644 --- a/awx/ui/test/e2e/tests/test-notifications-list-actions.js +++ b/awx/ui/test/e2e/tests/test-notifications-list-actions.js @@ -16,7 +16,7 @@ module.exports = { client.login(); client.waitForAngular(); - notifications.navigate(); + notifications.load(); notifications.waitForElementVisible('div.spinny'); notifications.waitForElementNotVisible('div.spinny'); diff --git a/awx/ui/test/e2e/tests/test-projects-list-actions.js b/awx/ui/test/e2e/tests/test-projects-list-actions.js index 9524254189..17881ed906 100644 --- a/awx/ui/test/e2e/tests/test-projects-list-actions.js +++ b/awx/ui/test/e2e/tests/test-projects-list-actions.js @@ -16,7 +16,7 @@ module.exports = { client.login(); client.waitForAngular(); - projects.navigate(); + projects.load(); projects.waitForElementVisible('div.spinny'); projects.waitForElementNotVisible('div.spinny'); diff --git a/awx/ui/test/e2e/tests/test-templates-copy-delete-warnings.js b/awx/ui/test/e2e/tests/test-templates-copy-delete-warnings.js index 4feb1eb346..b6544ff2da 100644 --- a/awx/ui/test/e2e/tests/test-templates-copy-delete-warnings.js +++ b/awx/ui/test/e2e/tests/test-templates-copy-delete-warnings.js @@ -65,7 +65,7 @@ module.exports = { client.login(); client.waitForAngular(); - templates.navigate(); + templates.load(); templates.waitForElementVisible('div.spinny'); templates.waitForElementNotVisible('div.spinny'); @@ -120,7 +120,7 @@ module.exports = { client.login(); client.waitForAngular(); - templates.navigate(); + templates.load(); templates.waitForElementVisible('div.spinny'); templates.waitForElementNotVisible('div.spinny'); @@ -170,7 +170,7 @@ module.exports = { client.login(data.user.username); client.waitForAngular(); - templates.navigate(); + templates.load(); templates.waitForElementVisible('div.spinny'); templates.waitForElementNotVisible('div.spinny'); diff --git a/awx/ui/test/e2e/tests/test-templates-list-actions.js b/awx/ui/test/e2e/tests/test-templates-list-actions.js index 000338126e..ae551e7768 100644 --- a/awx/ui/test/e2e/tests/test-templates-list-actions.js +++ b/awx/ui/test/e2e/tests/test-templates-list-actions.js @@ -30,7 +30,7 @@ module.exports = { client.login(); client.waitForAngular(); - templates.navigate(); + templates.load(); templates.waitForElementVisible('div.spinny'); templates.waitForElementNotVisible('div.spinny'); @@ -70,7 +70,7 @@ module.exports = { client.login(); client.waitForAngular(); - templates.navigate(); + templates.load(); templates.waitForElementVisible('div.spinny'); templates.waitForElementNotVisible('div.spinny'); diff --git a/awx/ui/test/e2e/tests/test-xss.js b/awx/ui/test/e2e/tests/test-xss.js index b8ac4dba2f..6383926709 100644 --- a/awx/ui/test/e2e/tests/test-xss.js +++ b/awx/ui/test/e2e/tests/test-xss.js @@ -85,7 +85,7 @@ module.exports = { const multiCredentialOpen = 'multi-credential button i[class*="search"]'; const multiCredentialExit = 'multi-credential-modal button[class*="exit"]'; - client.url(urls.jobTemplate); + client.navigateTo(urls.jobTemplate, false); client.expect.element('#job_template_form').visible; client.expect.element('#xss').not.present; @@ -371,7 +371,7 @@ module.exports = { client.expect.element('#prompt-header').not.visible; }, 'check smart inventory form for unsanitized content': client => { - client.url(urls.smartInventory); + client.navigateTo(urls.smartInventory, false); client.expect.element('#smartinventory_form').visible; From 01a8b2771afb3b0413581d4e446365b3de05417e Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Sun, 25 Feb 2018 00:09:17 -0500 Subject: [PATCH 6/7] add worker file push command --- awx/ui/package.json | 1 + awx/ui/test/e2e/commands/pushFileToWorker.js | 57 +++++++++++++++++++ .../test-credentials-add-edit-gce-file.js | 20 +++++-- 3 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 awx/ui/test/e2e/commands/pushFileToWorker.js diff --git a/awx/ui/package.json b/awx/ui/package.json index 97031fbffd..335d033aa5 100644 --- a/awx/ui/package.json +++ b/awx/ui/package.json @@ -33,6 +33,7 @@ }, "devDependencies": { "angular-mocks": "~1.6.6", + "archiver": "^2.1.1", "axios": "^0.16.2", "babel-core": "^6.26.0", "babel-istanbul": "^0.12.2", diff --git a/awx/ui/test/e2e/commands/pushFileToWorker.js b/awx/ui/test/e2e/commands/pushFileToWorker.js new file mode 100644 index 0000000000..191164f778 --- /dev/null +++ b/awx/ui/test/e2e/commands/pushFileToWorker.js @@ -0,0 +1,57 @@ +import { basename } from 'path'; +import { EventEmitter } from 'events'; +import { inherits } from 'util'; + +import archiver from 'archiver'; + +function pushFileToWorker (localFilePath, callback) { + const name = basename(localFilePath); + + const push = handler => { + const archive = archiver('zip'); + + const buffers = []; + + archive + .on('data', data => buffers.push(data)) + .on('error', err => { throw err; }) + .on('finish', () => { + const file = Buffer.concat(buffers).toString('base64'); + + this.api.session(session => { + const params = { + path: `/session/${session.sessionId}/file`, + method: 'POST', + data: { file }, + }; + + this.client.runProtocolAction(params, handler).send(); + }); + }); + + archive.file(localFilePath, { name }); + archive.finalize(); + }; + + push(({ status, value }) => { + if (status !== 0) { + throw new Error(value.message); + } + + if (typeof callback === 'function') { + callback.call(this, value); + } + + this.emit('complete'); + }); + + return this; +} + +function PushFileToWorker () { EventEmitter.call(this); } + +inherits(PushFileToWorker, EventEmitter); + +PushFileToWorker.prototype.command = pushFileToWorker; + +module.exports = PushFileToWorker; diff --git a/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js b/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js index 458b735630..cdf871c36a 100644 --- a/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js +++ b/awx/ui/test/e2e/tests/test-credentials-add-edit-gce-file.js @@ -64,7 +64,9 @@ module.exports = { const { details } = credentials.section.add.section; const { gce } = details.section; - gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE); + client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE, file => { + gce.section.serviceAccountFile.setValue('form input[type="file"]', file); + }); gce.expect.element('@email').not.enabled; gce.expect.element('@sshKeyData').not.enabled; @@ -100,7 +102,9 @@ module.exports = { const { details } = credentials.section.add.section; const { gce } = details.section; - gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE_MISSING); + client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE_MISSING, file => { + gce.section.serviceAccountFile.setValue('form input[type="file"]', file); + }); gce.expect.element('@email').not.enabled; gce.expect.element('@sshKeyData').not.enabled; @@ -142,7 +146,9 @@ module.exports = { const { details } = credentials.section.add.section; const { gce } = details.section; - gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE_INVALID); + client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE_INVALID, file => { + gce.section.serviceAccountFile.setValue('form input[type="file"]', file); + }); gce.expect.element('@email').not.enabled; gce.expect.element('@sshKeyData').not.enabled; @@ -184,7 +190,9 @@ module.exports = { const add = credentials.section.add.section.details; const edit = credentials.section.edit.section.details; - add.section.gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE); + client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE, file => { + add.section.gce.section.serviceAccountFile.setValue('form input[type="file"]', file); + }); add.section.gce.expect.element('@email').not.enabled; add.section.gce.expect.element('@sshKeyData').not.enabled; @@ -224,7 +232,9 @@ module.exports = { gce.section.project.expect.element('@error').not.present; gce.section.serviceAccountFile.expect.element('@error').not.present; - gce.section.serviceAccountFile.setValue('form input[type="file"]', GCE_SERVICE_ACCOUNT_FILE_ALT); + client.pushFileToWorker(GCE_SERVICE_ACCOUNT_FILE_ALT, file => { + gce.section.serviceAccountFile.setValue('form input[type="file"]', file); + }); gce.expect.element('@serviceAccountFile').enabled; From c786736688983a76b6c51ccfb36b0e3d19937d4e Mon Sep 17 00:00:00 2001 From: Jake McDermott Date: Sun, 25 Feb 2018 15:12:56 -0500 Subject: [PATCH 7/7] add setup step for org lookup check --- awx/ui/test/e2e/README.md | 1 - .../test-credentials-lookup-organization.js | 37 +++++++++++-------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/awx/ui/test/e2e/README.md b/awx/ui/test/e2e/README.md index 86ff61f36b..3f31335edb 100644 --- a/awx/ui/test/e2e/README.md +++ b/awx/ui/test/e2e/README.md @@ -4,7 +4,6 @@ docker exec -i tools_awx_1 sh <<-EOSH awx-manage createsuperuser --noinput --username=awx-e2e --email=null@ansible.com awx-manage update_password --username=awx-e2e --password=password - make --directory=/awx_devel DATA_GEN_PRESET=e2e bulk_data EOSH # run all of the tests with a live browser diff --git a/awx/ui/test/e2e/tests/test-credentials-lookup-organization.js b/awx/ui/test/e2e/tests/test-credentials-lookup-organization.js index 5b62fac9ac..948c6693de 100644 --- a/awx/ui/test/e2e/tests/test-credentials-lookup-organization.js +++ b/awx/ui/test/e2e/tests/test-credentials-lookup-organization.js @@ -1,25 +1,32 @@ +import { range } from 'lodash'; + +import { getOrganization } from '../fixtures'; + module.exports = { before: (client, done) => { - const credentials = client.page.credentials(); - const { details } = credentials.section.add.section; + const resources = range(100).map(n => getOrganization(`test-lookup-${n}`)); - client.login(); - client.waitForAngular(); + Promise.all(resources) + .then(() => { + const credentials = client.page.credentials(); + const { details } = credentials.section.add.section; - credentials.section.navigation - .waitForElementVisible('@credentials') - .click('@credentials'); + client.login(); + client.waitForAngular(); - credentials - .waitForElementVisible('div.spinny') - .waitForElementNotVisible('div.spinny'); + credentials.section.navigation.waitForElementVisible('@credentials'); + credentials.section.navigation.click('@credentials'); - credentials.section.list - .waitForElementVisible('@add') - .click('@add'); + credentials.waitForElementVisible('div.spinny'); + credentials.waitForElementNotVisible('div.spinny'); - details - .waitForElementVisible('@save', done); + credentials.section.list.waitForElementVisible('@add'); + credentials.section.list.click('@add'); + + details.waitForElementVisible('@save'); + + done(); + }); }, 'open the lookup modal': client => { const credentials = client.page.credentials();