From 6e3f74a847ef5984bdd94adacbd364439b5cdd77 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Wed, 11 Nov 2015 17:02:27 -0700 Subject: [PATCH] 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 --- gulp/tasks/dev-resources.js | 10 +- gulp/tasks/e2e.js | 16 ++ gulp/tasks/protractor.js | 4 + package.json | 7 +- test/e2e/example_spec.js | 21 -- test/e2e/mocks/config.js | 11 + test/e2e/mocks/home_project.js | 401 +++++++++++++++++++++++++++++ test/e2e/mocks/project_taskflow.js | 34 +++ test/e2e/routes_spec.js | 44 ++++ test/protractor.conf.js | 21 +- 10 files changed, 533 insertions(+), 36 deletions(-) create mode 100644 gulp/tasks/e2e.js delete mode 100644 test/e2e/example_spec.js create mode 100644 test/e2e/mocks/config.js create mode 100644 test/e2e/mocks/home_project.js create mode 100644 test/e2e/mocks/project_taskflow.js diff --git a/gulp/tasks/dev-resources.js b/gulp/tasks/dev-resources.js index 291d47a1..08b309f2 100644 --- a/gulp/tasks/dev-resources.js +++ b/gulp/tasks/dev-resources.js @@ -7,11 +7,9 @@ var browserSync = require('browser-sync'); gulp.task('dev-resources', function() { - if (!global.isProd) { - return gulp.src(config.devResources.src) - .pipe(changed(config.devResources.dest)) - .pipe(gulp.dest(config.devResources.dest)) - .pipe(browserSync.reload({ stream: true, once: true })); - } + return gulp.src(config.devResources.src) + .pipe(changed(config.devResources.dest)) + .pipe(gulp.dest(config.devResources.dest)) + .pipe(browserSync.reload({ stream: true, once: true })); }); diff --git a/gulp/tasks/e2e.js b/gulp/tasks/e2e.js new file mode 100644 index 00000000..09fdd2a4 --- /dev/null +++ b/gulp/tasks/e2e.js @@ -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); + +}); diff --git a/gulp/tasks/protractor.js b/gulp/tasks/protractor.js index 1d7d95c8..0a3a6e63 100644 --- a/gulp/tasks/protractor.js +++ b/gulp/tasks/protractor.js @@ -18,6 +18,10 @@ gulp.task('protractor', ['webdriver-update', 'webdriver', 'server'], function() .on('error', function(err) { // Make sure failed tests cause gulp to exit non-zero throw err; + }) + .on('end', function() { + // server task will wait for user to quit, so force it to end here + process.exit(); }); }); diff --git a/package.json b/package.json index 440b8a0c..448d4b17 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ "phantomjs": "1.9.17", "pretty-hrtime": "^1.0.0", "protractor": "^2.2.0", + "protractor-http-mock": "^0.1.18", "run-sequence": "^1.1.2", "tiny-lr": "^0.1.6", "uglifyify": "^3.0.1", @@ -70,9 +71,9 @@ }, "scripts": { "pretest": "npm install", - "test": "gulp unit", - "preprotractor": "npm run update-webdriver", - "protractor": "npm run protractor test/protractor.conf.js", + "unit": "gulp unit", + "protractor": "gulp e2e", + "test": "gulp unit && gulp e2e", "lint": "eslint ./" }, "dependencies": {}, diff --git a/test/e2e/example_spec.js b/test/e2e/example_spec.js deleted file mode 100644 index 09fec70d..00000000 --- a/test/e2e/example_spec.js +++ /dev/null @@ -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'); - }); - -}); diff --git a/test/e2e/mocks/config.js b/test/e2e/mocks/config.js new file mode 100644 index 00000000..4de66449 --- /dev/null +++ b/test/e2e/mocks/config.js @@ -0,0 +1,11 @@ +module.exports = { + request: { + method: 'GET', + path: 'config.json' + }, + response: { + data: { + "apiRoot": "http://localhost:5000" + } + } +}; diff --git a/test/e2e/mocks/home_project.js b/test/e2e/mocks/home_project.js new file mode 100644 index 00000000..e1f12ea9 --- /dev/null +++ b/test/e2e/mocks/home_project.js @@ -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 + } + ] + } + } + } + } +}; diff --git a/test/e2e/mocks/project_taskflow.js b/test/e2e/mocks/project_taskflow.js new file mode 100644 index 00000000..766931ef --- /dev/null +++ b/test/e2e/mocks/project_taskflow.js @@ -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 + } + ] + } + ] + } + } +}; diff --git a/test/e2e/routes_spec.js b/test/e2e/routes_spec.js index f2a73dff..396f3fa6 100644 --- a/test/e2e/routes_spec.js +++ b/test/e2e/routes_spec.js @@ -2,11 +2,55 @@ 'use strict'; +var mock = require('protractor-http-mock'); + describe('E2E: Routes', function() { it('should have a working home route', function() { + mock(['config', 'home_project']); + browser.get('#/'); + + // route should be defined (will redirect to / if not) 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(); }); }); diff --git a/test/protractor.conf.js b/test/protractor.conf.js index abda3b4a..7b04fff9 100644 --- a/test/protractor.conf.js +++ b/test/protractor.conf.js @@ -1,5 +1,7 @@ 'use strict'; +var phantomjs = require('phantomjs'); + var gulpConfig = require('../gulp/config'); exports.config = { @@ -8,12 +10,9 @@ exports.config = { baseUrl: 'http://localhost:' + gulpConfig.serverPort + '/', - directConnect: true, - capabilities: { - browserName: 'chrome', - version: '', - platform: 'ANY' + browserName: 'phantomjs', + 'phantomjs.binary.path': phantomjs.path }, framework: 'jasmine', @@ -27,6 +26,16 @@ exports.config = { specs: [ 'e2e/**/*.js' - ] + ], + + mocks: { + dir: 'e2e/mocks' + }, + + onPrepare: function() { + require('protractor-http-mock').config = { + rootDirectory: __dirname + }; + } };