Внедрение e2e- и unit-тестов в сборку AngularJS-приложения: Protractor, Karma и gulp

Допустим, у нас уже есть сборка проекта на gulp.
И нужно добавить в процедуру сборки релиза e2e-тесты на protractor.

Добавляем в зависимости protractor и gulp-protractor.

gulp/package.json

{
  "name": "",
  "version": "0.0.1",
  "dependencies": {
    "gulp": "=3.6.2",
    "gulp-util": "=2.2.14",
    "gulp-uglify": "=0.3.0",
    "gulp-minify-html": "=0.1.3",
    "gulp-browserify": "=0.5.0",
    "gulp-concat": "=2.2.0",
    "gulp-imagemin": "=0.5.1",
    "gulp-clean": "=0.3.0",
    "gulp-livereload": "=1.5.0",
    "gulp-rename": "=1.2.0",
    "gulp-connect": "~2.0.6",
    "connect-route": "~0.1.4",
    "tiny-lr": "=0.0.7",
    "connect-livereload": "=0.4.0",
    "run-sequence": "=0.3.6",
    "gulp-csso": "=0.2.9",
    "gulp-rev": "=0.4.0",
    "gulp-replace": "=0.3.0",
    "gulp-sass": "~0.7.1",
    "gulp-protractor": "0.0.11",
    "protractor": "~1.3.1",
    "phantomjs": "~1.9.10",
    "gulp-karma": "0.0.4",
    "karma": "^0.12.24",
    "karma-chrome-launcher": "^0.1.5",
    "karma-firefox-launcher": "^0.1.3",
    "karma-jasmine": "^0.1.5",
    "karma-junit-reporter": "^0.2.2",
    "karma-phantomjs-launcher": "~0.1.4"
  },
  "engines": {
    "node": "=0.10.25"
  },
  "description": "",
  "main": "index.js",
  "authors": [
    "bullgare "
  ],
  "private": true,
  "license": "proprietary"
}

gulp/gulpfile.js

require('events').EventEmitter.prototype._maxListeners = 30;

var fs = require('fs'),
	_path = require('path'),
	gulp = require('gulp'),
	...
	connect = require('gulp-connect'), // Webserver
	connectRoute = require("connect-route"),
	protractor = require("gulp-protractor").protractor,
	webdriver_update = require("gulp-protractor").webdriver_update,
	karma = require('gulp-karma');

...

gulp.task('http-server-dev', function () {
	connect.server({
		root: pathBuildDev,
		port: 9000,
		middleware: function (connect, opt) {
			return [
					connectRoute(connectMockRoutes)
			];
		}
	});

	console.log('Dev Server listening on http://localhost:9000');
});

gulp.task('http-server-stop', function () {
	connect.serverClose();
	console.log('Server is shutting down');
});

...

// Downloads the selenium webdriver
gulp.task('webdriver-update', webdriver_update);
// Setting up the test task
gulp.task('protractor'/*, ['webdriver-update']*/, function(callback) {
	// TODO path into variables
	gulp.src([pathTestsE2e + "tests/*.js"])
		.pipe(protractor({
			configFile: pathTestsE2e + "protractor_config.js",
			debug: true,
			args: [
				//'--capabilities.browserName', 'firefox'
				//'--capabilities.browserName', 'chrome'
				'--capabilities.browserName', 'phantomjs',
				'--capabilities.phantomjs.binary.path', './node_modules/.bin/phantomjs'
			]
		}))
		.on('error', function (e) {
			throw e
		});
});
gulp.task('e2e', ['webdriver-update', 'protractor'], function(callback) {callback();});

gulp.task('unit', function () {
	return gulp.src([pathBuildProd + 'js/libs.js', pathApp + 'libs/angular-mocks/angular-mocks.js', pathBuildProd + 'js/app.js', pathTestsUnit + 'tests/**/*.js'])
		.pipe(karma({
			configFile: pathTestsUnit + 'karma_config.js',
			action: 'run',
			//browsers: ['Firefox']
			browsers: ['Chrome']
		} ) )
		.on('error', function (err) {
			// Make sure failed tests cause gulp to exit non-zero
			throw err;
		});
});

...

gulp.task('build-and-test', function (callback) {
	runSequence(
		'build',
		'http-server-prod',
		'unit',
		'e2e',
		'http-server-stop', callback);
});

<путь к e2e-тестам>protractor_config.js

// example from https://raw.github.com/angular/protractor/master/example/conf.js
exports.config = {
	// The address of a running selenium server.
	seleniumServerJar: '../../scripts/gulp/node_modules/protractor/selenium/selenium-server-standalone-2.43.1.jar', // Make use you check the version in the folder
	//seleniumAddress: 'http://localhost:4444/wd/hub',
	// Capabilities to be passed to the webdriver instance.
	capabilities: {
		// this could be overwritten by --capabilities.browserName=firefox
		'browserName': 'chrome'
	},

	// Options to be passed to Jasmine-node.
	jasmineNodeOpts: {
		showColors: true,
		showTiming: true,
		defaultTimeoutInterval: 30000
	}
};

<путь к e2e-тестам>tests/spec.js

describe('my tests', function ()
{
	it('should search for address', function ()
	{
		browser.get('/');

		element(by.model('address.dirty')).sendKeys('тверская 8');
		element(by.css('.search-submit-icon')).click();

		var firstResult = element.all(by.repeater('item in addressItems | first_item'));
		expect(firstResult.count()).toEqual(1);

		expect(firstResult.get(0).getText()).toContain('улица Тверская, 8');
		expect(firstResult.get(0).getText()).toContain('Россия, Москва');

		var otherResults = element.all(by.repeater('item in addressItems | except_first_item'));
		expect(otherResults.count()).toBeGreaterThan(-1);
	});

	it('should build routes', function ()
	{
		browser.get('/#?type=routes');

		element(by.model('from.title')).sendKeys('москва тверская 8');
		element(by.model('from.title')).sendKeys(protractor.Key.ENTER)/*.perform()*/;

		element(by.model('to.title')).sendKeys('москва пушкинская 10');
		element(by.model('to.title')).sendKeys(protractor.Key.ENTER)/*.perform()*/;


		var routes = element.all(by.repeater('route in routes'));
		expect(routes.count()).toBeGreaterThan(0);

		routes.get(0).click();


		var instructions = element.all(by.repeater('item in route.instructions'));
		expect(instructions.count()).toBeGreaterThan(5);
	});
});

<путь к unit-тестам>karma_config.js

module.exports = function(config) {
	config.set({
		frameworks: ['jasmine'],

		reporters: ['progress', 'junit'],

		junitReporter: {
			outputFile: 'test-results.xml'
		},

		port: 9876,

		colors: true,

		logLevel: config.LOG_INFO,

		autoWatch: false,

		captureTimeout: 20000,

		singleRun: true,
		reportSlowerThan: 500,

		plugins: [
			'karma-jasmine',
			'karma-chrome-launcher',
			'karma-firefox-launcher',
			'karma-phantomjs-launcher',
			'karma-junit-reporter'
		]
	});
};

<путь к unit-тестам>tests/spec.js

'use strict';

/* jasmine specs for filters */

describe('Filters:', function() {

	beforeEach(angular.mock.module('SmMapApp'));

	describe('lat:', function() {
		var lat;

		beforeEach(inject(function ($filter) {
			lat = $filter('lat');
		}));

		it('should exist', inject(function() {
			expect(lat).not.toEqual(null);
		}));

		it('should work properly', inject(function() {
			expect(lat(undefined)).toEqual(undefined);
			expect(lat('')).toEqual('');
			expect(lat({})).toEqual('');
			expect(lat({position: 1})).toEqual('');
			expect(lat({position: {lat: 1}})).toEqual(1);
			expect(lat({location: 1})).toEqual('');
			expect(lat({location: {lat: 1}})).toEqual(1);
		}));
	});

	describe('lng:', function() {
		var lng;

		beforeEach(inject(function ($filter) {
			lng = $filter('lng');
		}));

		it('should exist', inject(function() {
			expect(lng).not.toEqual(null);
		}));

		it('should work properly', inject(function() {
			expect(lng(undefined)).toEqual(undefined);
			expect(lng('')).toEqual('');
			expect(lng({})).toEqual('');
			expect(lng({position: 1})).toEqual('');
			expect(lng({position: {lng: 1}})).toEqual(1);
			expect(lng({position: {lon: 1}})).toEqual(1);
			expect(lng({location: 1})).toEqual('');
			expect(lng({location: {lng: 1}})).toEqual(1);
			expect(lng({location: {lon: 1}})).toEqual(1);
		}));
	});
});

После чего в консоли

cd ./gulp
npm install
gulp build-and-test

Это запустит webdriver-update, который скажет, куда поставил selenium, например:

...
selenium-server-standalone-2.43.1.jar downloaded to /home/user/projects/maps_ui/scripts/gulp/node_modules/protractor/selenium/selenium-server-standalone-2.43.1.jar

Есть большая вероятность, что путь не совпадёт с указанным в конфиге <путь к тестам>protractor_config.js, и нужно будет поправить параметр seleniumServerJar.
После этого снова запускаем в консоли

gulp build-and-test

и видим, как у нас автоматически стартует хром и проходят тесты.

Полезные ссылки:
http://angular.github.io/protractor/
https://github.com/mllrsohn/gulp-protractor
http://stackoverflow.com/questions/23135649/how-can-i-use-command-line-arguments-in-angularjs-protractor
http://jasmine.github.io/2.0/introduction.html
https://github.com/jtomaszewski/ionic-cordova-gulp-seed/blob/master/gulpfile.coffee
http://karma-runner.github.io/0.8/intro/configuration.html

LEAVE A COMMENT