Enable Protractor end-to-end testing.
This enables end-to-end testing with Protractor, along with some basic route and page load testing for the home and project pages. PhantomJS is used to execute tests, and API requests are mocked using `protractor-http-mock` (see `test/e2e/mocks/`). A new gulp `e2e` task is also added to prepare and execute the tests, along with a shortcut, `npm run protractor`. The default `npm run test` is also amended to include E2E tests, with unit tests moved to the new shortcut `npm run unit`. Change-Id: Idb2eef2d851035c715e23553db56fc80deeab8e7
This commit is contained in:
parent
a90ac92bed
commit
6e3f74a847
|
@ -7,11 +7,9 @@ var browserSync = require('browser-sync');
|
||||||
|
|
||||||
gulp.task('dev-resources', function() {
|
gulp.task('dev-resources', function() {
|
||||||
|
|
||||||
if (!global.isProd) {
|
return gulp.src(config.devResources.src)
|
||||||
return gulp.src(config.devResources.src)
|
.pipe(changed(config.devResources.dest))
|
||||||
.pipe(changed(config.devResources.dest))
|
.pipe(gulp.dest(config.devResources.dest))
|
||||||
.pipe(gulp.dest(config.devResources.dest))
|
.pipe(browserSync.reload({ stream: true, once: true }));
|
||||||
.pipe(browserSync.reload({ stream: true, once: true }));
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var gulp = require('gulp');
|
||||||
|
var runSequence = require('run-sequence');
|
||||||
|
|
||||||
|
gulp.task('e2e', function(callback) {
|
||||||
|
|
||||||
|
callback = callback || function() {};
|
||||||
|
|
||||||
|
runSequence(
|
||||||
|
'prod',
|
||||||
|
'dev-resources',
|
||||||
|
'protractor',
|
||||||
|
callback);
|
||||||
|
|
||||||
|
});
|
|
@ -18,6 +18,10 @@ gulp.task('protractor', ['webdriver-update', 'webdriver', 'server'], function()
|
||||||
.on('error', function(err) {
|
.on('error', function(err) {
|
||||||
// Make sure failed tests cause gulp to exit non-zero
|
// Make sure failed tests cause gulp to exit non-zero
|
||||||
throw err;
|
throw err;
|
||||||
|
})
|
||||||
|
.on('end', function() {
|
||||||
|
// server task will wait for user to quit, so force it to end here
|
||||||
|
process.exit();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -60,6 +60,7 @@
|
||||||
"phantomjs": "1.9.17",
|
"phantomjs": "1.9.17",
|
||||||
"pretty-hrtime": "^1.0.0",
|
"pretty-hrtime": "^1.0.0",
|
||||||
"protractor": "^2.2.0",
|
"protractor": "^2.2.0",
|
||||||
|
"protractor-http-mock": "^0.1.18",
|
||||||
"run-sequence": "^1.1.2",
|
"run-sequence": "^1.1.2",
|
||||||
"tiny-lr": "^0.1.6",
|
"tiny-lr": "^0.1.6",
|
||||||
"uglifyify": "^3.0.1",
|
"uglifyify": "^3.0.1",
|
||||||
|
@ -70,9 +71,9 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"pretest": "npm install",
|
"pretest": "npm install",
|
||||||
"test": "gulp unit",
|
"unit": "gulp unit",
|
||||||
"preprotractor": "npm run update-webdriver",
|
"protractor": "gulp e2e",
|
||||||
"protractor": "npm run protractor test/protractor.conf.js",
|
"test": "gulp unit && gulp e2e",
|
||||||
"lint": "eslint ./"
|
"lint": "eslint ./"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {},
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
/*global browser, by */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
describe('E2E: Example', function() {
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
browser.get('/');
|
|
||||||
browser.waitForAngular();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should route correctly', function() {
|
|
||||||
expect(browser.getLocationAbsUrl()).toMatch('/');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show the number defined in the controller', function() {
|
|
||||||
var element = browser.findElement(by.css('.number-example'));
|
|
||||||
expect(element.getText()).toEqual('1234');
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = {
|
||||||
|
request: {
|
||||||
|
method: 'GET',
|
||||||
|
path: 'config.json'
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
data: {
|
||||||
|
"apiRoot": "http://localhost:5000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,401 @@
|
||||||
|
module.exports = {
|
||||||
|
request: {
|
||||||
|
method: 'JSONP',
|
||||||
|
path: '/runs/group_by/project'
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
data: {
|
||||||
|
"runs": {
|
||||||
|
"2015-10-20T20:00:00": {
|
||||||
|
"openstack/keystone": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/requirements": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"2015-10-20T21:00:00": {
|
||||||
|
"openstack/ceilometer": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 55,
|
||||||
|
"skip": 38
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1164,
|
||||||
|
"skip": 116
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1164,
|
||||||
|
"skip": 116
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1164,
|
||||||
|
"skip": 116
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1444,
|
||||||
|
"skip": 82
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 55,
|
||||||
|
"skip": 38
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1164,
|
||||||
|
"skip": 116
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/keystone": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 3,
|
||||||
|
"skip": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 3,
|
||||||
|
"skip": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/neutron": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 3,
|
||||||
|
"skip": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 3,
|
||||||
|
"skip": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 108,
|
||||||
|
"skip": 23
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 112,
|
||||||
|
"skip": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 112,
|
||||||
|
"skip": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 108,
|
||||||
|
"skip": 23
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/python-novaclient": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/requirements": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 3,
|
||||||
|
"skip": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/taskflow": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 1,
|
||||||
|
"pass": 1433,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"2015-10-20T22:00:00": {
|
||||||
|
"openstack-infra/devstack-gate": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 3,
|
||||||
|
"skip": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/ceilometer": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1444,
|
||||||
|
"skip": 82
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/diskimage-builder": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 71,
|
||||||
|
"skip": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/keystone": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/neutron": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 3,
|
||||||
|
"skip": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/requirements": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/taskflow": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/trove": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 6,
|
||||||
|
"skip": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"2015-10-20T23:00:00": {
|
||||||
|
"openstack-dev/pbr": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 0,
|
||||||
|
"skip": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack-infra/devstack-gate": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 3,
|
||||||
|
"skip": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 58,
|
||||||
|
"skip": 34
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/keystone": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 3,
|
||||||
|
"skip": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1154,
|
||||||
|
"skip": 120
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/neutron": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 112,
|
||||||
|
"skip": 18
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 112,
|
||||||
|
"skip": 18
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/python-neutronclient": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/python-novaclient": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 1434,
|
||||||
|
"skip": 86
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"openstack/trove": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"pass": 6,
|
||||||
|
"skip": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
module.exports = {
|
||||||
|
request: {
|
||||||
|
method: 'JSONP',
|
||||||
|
path: '/projects/openstack/taskflow/runs'
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
data: {
|
||||||
|
"timedelta": [
|
||||||
|
{
|
||||||
|
"datetime": "2015-10-23T20:00:00",
|
||||||
|
"job_data": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"job_name": "gate-tempest-dsvm-neutron-src-taskflow",
|
||||||
|
"mean_run_time": 4859.3,
|
||||||
|
"pass": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"datetime": "2015-11-10T23:00:00",
|
||||||
|
"job_data": [
|
||||||
|
{
|
||||||
|
"fail": 0,
|
||||||
|
"job_name": "gate-tempest-dsvm-neutron-src-taskflow",
|
||||||
|
"mean_run_time": 6231.47,
|
||||||
|
"pass": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -2,11 +2,55 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var mock = require('protractor-http-mock');
|
||||||
|
|
||||||
describe('E2E: Routes', function() {
|
describe('E2E: Routes', function() {
|
||||||
|
|
||||||
it('should have a working home route', function() {
|
it('should have a working home route', function() {
|
||||||
|
mock(['config', 'home_project']);
|
||||||
|
|
||||||
browser.get('#/');
|
browser.get('#/');
|
||||||
|
|
||||||
|
// route should be defined (will redirect to / if not)
|
||||||
expect(browser.getLocationAbsUrl()).toMatch('/');
|
expect(browser.getLocationAbsUrl()).toMatch('/');
|
||||||
|
|
||||||
|
// data should actually be requested (no request if error)
|
||||||
|
expect(mock.requestsMade()).toContain(jasmine.objectContaining({
|
||||||
|
url: 'http://localhost:5000/runs/group_by/project',
|
||||||
|
method: 'JSONP'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// should have a link to the next page
|
||||||
|
var selector = 'a[href="#/project/openstack%252Ftaskflow"]';
|
||||||
|
expect(element(by.css(selector)).isPresent()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a working project route', function() {
|
||||||
|
mock(['config', 'project_taskflow']);
|
||||||
|
|
||||||
|
browser.get('#/project/openstack%252Ftaskflow');
|
||||||
|
|
||||||
|
// route should be defined (will redirect to / if not)
|
||||||
|
browser.getLocationAbsUrl().then(function(url) {
|
||||||
|
// note: phantomjs converts the octal escape to '/' for getLocationAbsUrl
|
||||||
|
// for browsers that don't do this (chrome, firefox, etc), escape it
|
||||||
|
// manually to make sure the expectation works correctly
|
||||||
|
expect(url.replace('%252F', '/')).toMatch('/project/openstack/taskflow');
|
||||||
|
});
|
||||||
|
|
||||||
|
// data should actually be requested (no request if error)
|
||||||
|
expect(mock.requestsMade()).toContain(jasmine.objectContaining({
|
||||||
|
url: 'http://localhost:5000/projects/openstack/taskflow/runs',
|
||||||
|
method: 'JSONP'
|
||||||
|
}));
|
||||||
|
|
||||||
|
// should have a link to the next page
|
||||||
|
var selector = 'a[href="#/job/gate-tempest-dsvm-neutron-src-taskflow"]';
|
||||||
|
expect(element(by.css(selector)).isPresent()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
mock.teardown();
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
var phantomjs = require('phantomjs');
|
||||||
|
|
||||||
var gulpConfig = require('../gulp/config');
|
var gulpConfig = require('../gulp/config');
|
||||||
|
|
||||||
exports.config = {
|
exports.config = {
|
||||||
|
@ -8,12 +10,9 @@ exports.config = {
|
||||||
|
|
||||||
baseUrl: 'http://localhost:' + gulpConfig.serverPort + '/',
|
baseUrl: 'http://localhost:' + gulpConfig.serverPort + '/',
|
||||||
|
|
||||||
directConnect: true,
|
|
||||||
|
|
||||||
capabilities: {
|
capabilities: {
|
||||||
browserName: 'chrome',
|
browserName: 'phantomjs',
|
||||||
version: '',
|
'phantomjs.binary.path': phantomjs.path
|
||||||
platform: 'ANY'
|
|
||||||
},
|
},
|
||||||
|
|
||||||
framework: 'jasmine',
|
framework: 'jasmine',
|
||||||
|
@ -27,6 +26,16 @@ exports.config = {
|
||||||
|
|
||||||
specs: [
|
specs: [
|
||||||
'e2e/**/*.js'
|
'e2e/**/*.js'
|
||||||
]
|
],
|
||||||
|
|
||||||
|
mocks: {
|
||||||
|
dir: 'e2e/mocks'
|
||||||
|
},
|
||||||
|
|
||||||
|
onPrepare: function() {
|
||||||
|
require('protractor-http-mock').config = {
|
||||||
|
rootDirectory: __dirname
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue