Merge pull request #3455 from ansible/websockets-again

e2e fixtures and WS

Reviewed-by: https://github.com/softwarefactory-project-zuul[bot]
This commit is contained in:
softwarefactory-project-zuul[bot]
2019-03-21 12:51:26 +00:00
committed by GitHub
4 changed files with 129 additions and 44 deletions

View File

@@ -1,14 +1,18 @@
const spinny = "//*[contains(@class, 'spinny')]";
/* Utility function for clicking elements; attempts to scroll to /* Utility function for clicking elements; attempts to scroll to
* the element if necessary, and waits for the page to finish loading. * the element if necessary, and waits for the page to finish loading.
* *
* @param selector - xpath of the element to click. */ * @param selector - xpath or css selector of the element to click.
exports.command = function findThenClick (selector) { * @param [locatoryStrategy='xpath'] - locator strategy used. */
exports.command = function findThenClick (selector, locatorStrategy = 'xpath') {
this.waitForElementPresent(selector, () => { this.waitForElementPresent(selector, () => {
this.moveToElement(selector, 0, 0, () => { this.moveToElement(selector, 0, 0, () => {
this.click(selector, () => { this.click(selector, () => {
this.waitForElementNotVisible(spinny); if (locatorStrategy === 'css') {
this.waitForElementNotVisible('.spinny');
} else {
this.waitForElementNotVisible('//*[contains(@class, "spinny")]');
}
}); });
}); });
}); });

View File

@@ -5,8 +5,9 @@ exports.command = function navigateTo (url, expectSpinny = true) {
this.url(url); this.url(url);
if (expectSpinny) { if (expectSpinny) {
this.waitForElementVisible(spinny); this.waitForElementVisible(spinny, () => {
this.waitForElementNotVisible(spinny); this.waitForElementNotVisible(spinny);
});
} }
return this; return this;

View File

@@ -288,16 +288,18 @@ const waitForJob = endpoint => {
}); });
}; };
const getUpdatedProject = (namespace = session) => getProject(namespace) const getUpdatedProject = (namespace = session) => {
.then(project => { const promises = [
const updateURL = project.related.current_update; getProject(namespace),
];
if (updateURL) { return Promise.all(promises)
return waitForJob(updateURL).then(() => project); .then(([project]) => {
} post(`/api/v2/projects/${project.id}/update/`, {})
.then(update => waitForJob(update.data.url))
return project; .then(() => { project = getProject(namespace); });
}); return project;
});
};
/* Retrieves a job template, and creates it if it does not exist. /* Retrieves a job template, and creates it if it does not exist.
* name prefix. This function also runs getOrCreate for an inventory, * name prefix. This function also runs getOrCreate for an inventory,

View File

@@ -1,24 +1,29 @@
/* Websocket tests. These tests verify that the sparkline (colored box rows which /* Websocket tests. These tests verify that items like the sparkline (colored box rows which
* display job status) update correctly as the jobs progress. * display job status) and other status icons update correctly as the jobs progress.
*/ */
import { import {
getInventorySource, getInventorySource,
getOrganization,
getProject, getProject,
getJob getJob,
getUpdatedProject
} from '../fixtures'; } from '../fixtures';
let data; import {
const spinny = '//*[contains(@class, "spinny")]'; AWX_E2E_URL,
const dashboard = '//at-side-nav-item[contains(@name, "DASHBOARD")]'; AWX_E2E_TIMEOUT_LONG,
AWX_E2E_TIMEOUT_MEDIUM,
} from '../settings';
// UI elements for recently run job templates on the dashboard. let data;
// Xpath selectors for recently run job templates on the dashboard.
const successfulJt = '//a[contains(text(), "test-websockets-successful")]/../..'; const successfulJt = '//a[contains(text(), "test-websockets-successful")]/../..';
const failedJt = '//a[contains(text(), "test-websockets-failed")]/../..'; const failedJt = '//a[contains(text(), "test-websockets-failed")]/../..';
const sparklineIcon = '//div[contains(@class, "SmartStatus-iconContainer")]'; const sparklineIcon = '//div[contains(@class, "SmartStatus-iconContainer")]';
// Sparkline icon statuses. // Xpath selectors for sparkline icon statuses.
// Running is blinking green, successful is green, fail/error/cancellation is red.
const running = '//div[@ng-show="job.status === \'running\'"]'; const running = '//div[@ng-show="job.status === \'running\'"]';
const success = '//div[contains(@class, "SmartStatus-iconIndicator--success")]'; const success = '//div[contains(@class, "SmartStatus-iconIndicator--success")]';
const fail = '//div[contains(@class, "SmartStatus-iconIndicator--failed")]'; const fail = '//div[contains(@class, "SmartStatus-iconIndicator--failed")]';
@@ -26,18 +31,18 @@ const fail = '//div[contains(@class, "SmartStatus-iconIndicator--failed")]';
module.exports = { module.exports = {
before: (client, done) => { before: (client, done) => {
// Jobs only display on the dashboard if they have been run at least once.
const resources = [ const resources = [
getInventorySource('test-websockets'), getInventorySource('test-websockets'),
getProject('test-websockets', 'https://github.com/ansible/test-playbooks'), getProject('test-websockets', 'https://github.com/ansible/test-playbooks'),
// launch job templates once before running tests. getOrganization('test-websockets'),
// launch job templates once before running tests so that they appear on the dashboard.
getJob('test-websockets', 'debug.yml', 'test-websockets-successful', done), getJob('test-websockets', 'debug.yml', 'test-websockets-successful', done),
getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed', done) getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed', done)
]; ];
Promise.all(resources) Promise.all(resources)
.then(([inventory, project, jt1, jt2]) => { .then(([inventory, project, org, jt1, jt2]) => {
data = { inventory, project, jt1, jt2 }; data = { inventory, project, org, jt1, jt2 };
done(); done();
}); });
@@ -48,28 +53,101 @@ module.exports = {
}, },
'Test job template status updates for a successful job on dashboard': client => { 'Test job template status updates for a successful job on dashboard': client => {
client.useXpath().findThenClick(dashboard); client
.useCss()
.navigateTo(`${AWX_E2E_URL}/#/home`);
getJob('test-websockets', 'debug.yml', 'test-websockets-successful'); getJob('test-websockets', 'debug.yml', 'test-websockets-successful');
client.expect.element(spinny).to.not.be.visible.before(5000);
client.expect.element(`${sparklineIcon}[1]${running}`)
.to.be.visible.before(5000);
// Allow a maximum amount of 30 seconds for the job to complete. client.expect.element('.spinny')
client.expect.element(`${successfulJt}${sparklineIcon}[1]${success}`) .to.not.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
.to.be.present.after(30000); client.useXpath().expect.element(`${sparklineIcon}[1]${running}`)
.to.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
client.useXpath().expect.element(`${successfulJt}${sparklineIcon}[1]${success}`)
.to.be.present.before(AWX_E2E_TIMEOUT_LONG);
}, },
'Test job template status updates for a failed job on dashboard': client => { 'Test job template status updates for a failed job on dashboard': client => {
client.useXpath().findThenClick(dashboard); client
.useCss()
.navigateTo(`${AWX_E2E_URL}/#/home`);
getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed'); getJob('test-websockets', 'fail_unless.yml', 'test-websockets-failed');
client.expect.element(spinny).to.not.be.visible.before(5000); client.expect.element('.spinny')
client.expect.element(`${sparklineIcon}[1]${running}`) .to.not.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
.to.be.visible.before(5000); client.useXpath().expect.element(`${sparklineIcon}[1]${running}`)
.to.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
client.useXpath().expect.element(`${failedJt}${sparklineIcon}[1]${fail}`)
.to.be.present.before(AWX_E2E_TIMEOUT_LONG);
},
// Allow a maximum amount of 30 seconds for the job to complete. 'Test projects list blinking icon': client => {
client.expect.element(`${failedJt}${sparklineIcon}[1]${fail}`) client
.to.be.present.after(30000); .useCss()
.findThenClick('[ui-sref=projects]', 'css')
.clearValue('.SmartSearch-input')
.setValue(
'.SmartSearch-input',
['name.iexact:"test-websockets-project"', client.Keys.ENTER]
);
getUpdatedProject('test-websockets');
client.expect.element('i.icon-job-running')
.to.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
client.expect.element('i.icon-job-success')
.to.be.visible.before(AWX_E2E_TIMEOUT_LONG);
},
'Test successful job within an organization view': client => {
client
.useCss()
.navigateTo(`${AWX_E2E_URL}/#/organizations/${data.org.id}/job_templates`)
.clearValue('[ui-view=templatesList] .SmartSearch-input')
.setValue(
'[ui-view=templatesList] .SmartSearch-input',
['test-websockets-successful', client.Keys.ENTER]
);
getJob('test-websockets', 'debug.yml', 'test-websockets-successful');
client.expect.element('.spinny')
.to.not.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
client.useXpath().expect.element(`${sparklineIcon}[1]${running}`)
.to.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
client.useXpath().expect.element(`${sparklineIcon}[1]${success}`)
.to.be.present.before(AWX_E2E_TIMEOUT_LONG);
},
'Test failed job within an organization view': client => {
client
.useCss()
.navigateTo(`${AWX_E2E_URL}/#/organizations/${data.org.id}/job_templates`)
.clearValue('[ui-view=templatesList] .SmartSearch-input')
.setValue(
'[ui-view=templatesList] .SmartSearch-input',
['test-websockets-failed', client.Keys.ENTER]
);
getJob('test-websockets', 'debug.yml', 'test-websockets-failed');
client.expect.element('.spinny')
.to.not.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
client.useXpath().expect.element(`${sparklineIcon}[1]${running}`)
.to.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
client.useXpath().expect.element(`${sparklineIcon}[1]${fail}`)
.to.be.present.before(AWX_E2E_TIMEOUT_LONG);
},
'Test project blinking icon within an organization view': client => {
client
.useCss()
.navigateTo(`${AWX_E2E_URL}/#/organizations/${data.org.id}/projects`)
.clearValue('.projectsList .SmartSearch-input')
.setValue(
'.projectsList .SmartSearch-input',
['test-websockets-project', client.Keys.ENTER]
);
getUpdatedProject('test-websockets');
client.expect.element('i.icon-job-running')
.to.be.visible.before(AWX_E2E_TIMEOUT_MEDIUM);
client.expect.element('i.icon-job-success')
.to.be.visible.before(AWX_E2E_TIMEOUT_LONG);
}, },
after: client => { after: client => {