diff --git a/CHANGELOG.md b/CHANGELOG.md index 38c99bf..19237ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 3.0.0 + +* Don't save image when option "light" is enabled +* Improved comparing diffs + +* major: Do not support node lower than 4.0 + ## 2.0.0 * major: Add supporting of imagePath instead of image field in fail data diff --git a/lib/errors/base-error.js b/lib/errors/base-error.js index 8de40a6..2fa4383 100644 --- a/lib/errors/base-error.js +++ b/lib/errors/base-error.js @@ -1,32 +1,32 @@ 'use strict'; -var _ = require('lodash'), - URI = require('urijs'); +const _ = require('lodash'); +const URI = require('urijs'); -function BaseError(data, config) { - var browserConfig = config.forBrowser(data.browserId); - this._browserCapabilities = browserConfig.desiredCapabilities; - this._quota = new URI(browserConfig.gridUrl).username(); - this._timestamp = new Date(); +module.exports = class BaseError { + constructor(data, config) { + const browserConfig = config.forBrowser(data.browserId); + this._browserCapabilities = browserConfig.desiredCapabilities; + this._quota = new URI(browserConfig.gridUrl).username(); + this._timestamp = new Date(); - this.message = data.message || 'Image diff found'; // diff Error don't have "message" key; - this.browserId = data.browserId; - this.sessionId = data.sessionId; - this.name = _.compact([ - data.suite.fullName, - data.state && data.state.name, - data.browserId - ]).join('.'); -} + this.message = data.message || 'Image diff found'; // diff Error don't have "message" key; + this.browserId = data.browserId; + this.sessionId = data.sessionId; + this.name = _.compact([ + data.suite.fullName, + data.state && data.state.name, + data.browserId + ]).join('.'); + } -BaseError.prototype.getData = function() { - return { + getData() { + return { timestamp: this._timestamp, message: this.message, sessionId: this.sessionId, browserCapabilities: this._browserCapabilities, seleniumQuota: this._quota }; + } }; - -module.exports = BaseError; diff --git a/lib/errors/error-factory.js b/lib/errors/error-factory.js index cdcbb5b..6431098 100644 --- a/lib/errors/error-factory.js +++ b/lib/errors/error-factory.js @@ -1,30 +1,10 @@ 'use strict'; -var q = require('q'), - tempFS = require('../temp-fs'), +const BaseError = require('./base-error'); +const ImageError = require('./image-error'); - BaseError = require('./base-error'), - ImageError = require('./image-error'), - imageProcessor = require('../image-processor'); - -exports.buildError = function (data, geminiConfig, options) { - if (!data.imagePath && !data.saveDiffTo) { - return q(new BaseError(data, geminiConfig)); - } - - var imagePath = data.imagePath || tempFS.resolveImagePath(), - saveImg = data.imagePath ? q : data.saveDiffTo.bind(data); - - return saveImg(imagePath) - .then(function() { - if (!options.light) { - return imageProcessor.pngToBase64(imagePath); - } - }) - .then(function(base64) { - return new ImageError(data, geminiConfig, { - path: imagePath, - base64: base64 - }); - }); +exports.buildError = (data, config, options) => { + return (data.imagePath || data.saveDiffTo) + ? new ImageError(data, config, options) + : new BaseError(data, config); }; diff --git a/lib/errors/image-error.js b/lib/errors/image-error.js index c275f21..ee02a3d 100644 --- a/lib/errors/image-error.js +++ b/lib/errors/image-error.js @@ -1,23 +1,39 @@ 'use strict'; -var _ = require('lodash'), - inherit = require('inherit'), - - BaseError = require('./base-error'); - -var ImageError = inherit(BaseError, { - __constructor: function(data, config, img) { - this.__base(data, config); - this.imagePath = img.path; - this.base64 = img.base64; - this.isDiff = data.equal !== undefined; - }, - - getData: function() { - return _.defaults(this.__base(), { - base64: this.base64 - }); +const _ = require('lodash'); +const q = require('q'); +const fs = require('q-io/fs'); + +const tempFS = require('../temp-fs'); +const BaseError = require('./base-error'); +const imageProcessor = require('../image-processor'); + +module.exports = class ImageError extends BaseError { + constructor(data, config, options) { + super(data, config); + + this.imagePath = data.imagePath || tempFS.resolveImagePath(); + this.saveImg = data.imagePath ? q : data.saveDiffTo.bind(data); + this.light = options.light; + this.isDiff = !_.isUndefined(data.equal); } -}); -module.exports = ImageError; + getData() { + const baseErrData = super.getData(); + + return this.light ? baseErrData : this._addBase64(baseErrData); + } + + save() { + return this.saveImg(this.imagePath) + .catch((error) => console.error(`Error occurred while saving image: ${error.stack || error}`)); + } + + _addBase64(baseErrData) { + return fs.exists(this.imagePath) + .then((isImgExists) => isImgExists || this.save()) + .then(() => imageProcessor.pngToBase64(this.imagePath)) + .then((base64) => _.extend(baseErrData, {base64})) + .catch((error) => console.error(error.stack || error)); + } +}; diff --git a/lib/fail-collector.js b/lib/fail-collector.js index ae56e5c..de7da10 100644 --- a/lib/fail-collector.js +++ b/lib/fail-collector.js @@ -1,83 +1,83 @@ 'use strict'; -var _ = require('lodash'), - inherit = require('inherit'), - fs = require('q-io/fs'), - q = require('q'), +const _ = require('lodash'); +const fs = require('q-io/fs'); +const q = require('q'); - errorFactory = require('./errors/error-factory'), - imageProcessor = require('./image-processor'); +const errorFactory = require('./errors/error-factory'); +const imageProcessor = require('./image-processor'); -module.exports = inherit({ - __constructor: function (config, opts) { +module.exports = class FailCollector { + constructor(config, opts) { this._config = config; this._options = opts || {}; this.fails = []; - }, + } - addFail: function (fail) { - this.fails.push(errorFactory.buildError(fail, this._config, this._options)); - }, + addFail(fail) { + this.fails.push(q.resolve(errorFactory.buildError(fail, this._config, this._options))); + } - collect: function() { + collect() { return q.all(this.fails) - .then(function(fails) { - return _.groupBy(fails, 'name'); - }) - .then(this._filterDiffs.bind(this)) - .then(this._getFailsData.bind(this)) - .then(this._saveToFile.bind(this)) - .catch(function (error) { - console.error('Some error while collecting fails: ', error.stack); - }); - }, + .then((fails) => _.groupBy(fails, 'name')) + .then((fails) => this._filterDiffs(fails)) + .then((fails) => this._getFailsData(fails)) + .then((fails) => this._saveToFile(fails)) + .catch((error) => console.error(`Error occurred while collecting fails: ${error.stack}`)); + } - _filterDiffs: function(fails) { + _filterDiffs(fails) { return this._getRealFailedTests(fails) - .then(function(realFails) { - return _.omit(fails, realFails); - }); - }, + .then((realFails) => _.omit(fails, realFails)); + } - _getRealFailedTests: function(errors) { + _getRealFailedTests(errors) { return _(errors) - .map(function(fails) { + .map((fails) => { return fails.length === this._maxRuns(fails[0].browserId) && _.every(fails, {isDiff: true}) && getRealFailedTestName_(fails); - }.bind(this)) + }) .thru(q.all).value() .then(_.compact); function getRealFailedTestName_(fails) { return _(fails) + // remove the comparison of the first diff with yourself + .slice(1) .map(compareWith_(fails[0])) .thru(q.all).value() - .then(function(compares) { - return _.every(compares) && fails[0].name; - }); + .then((compares) => _.every(compares) && fails[0].name); } function compareWith_(reference) { - return function(fail) { - return imageProcessor.compare(reference.imagePath, fail.imagePath); - }; + const promise = reference.save(); + + return (fail) => promise + .then(fail.save()) + .then(() => imageProcessor.compare(reference.imagePath, fail.imagePath)); } - }, + } - _maxRuns: function(browserId) { + _maxRuns(browserId) { return this._config.forBrowser(browserId).retry + 1; - }, + } - _getFailsData: function (fails) { - return _.mapValues(fails, function (failList) { - return failList.map(function (fail) { - return fail.getData(); - }); - }); - }, + _getFailsData(fails) { + const keys = _.keys(fails); + + return _(fails) + .map((failList) => _(failList) + .map((fail) => fail.getData()) + .thru(q.all) + .value()) + .thru(q.all) + .value() + .then((res) => _.zipObject(keys, res)); + } - _saveToFile: function (errors) { + _saveToFile(errors) { return fs.write('faildump.json', JSON.stringify(errors)); } -}); +}; diff --git a/lib/plugin.js b/lib/plugin.js index 8d33dde..99ec5f0 100644 --- a/lib/plugin.js +++ b/lib/plugin.js @@ -1,20 +1,15 @@ 'use strict'; -var FailCollector = require('./fail-collector'); +const FailCollector = require('./fail-collector'); -module.exports = function (gemini, opts) { +module.exports = (gemini, opts) => { + const fails = new FailCollector(gemini.config, opts); - gemini.on('startRunner', function(runner) { - var fails = new FailCollector(gemini.config, opts); + gemini.on(gemini.events.ERROR, (data) => fails.addFail(data)); - runner.on('err', fails.addFail.bind(fails)); + gemini.on(gemini.events.RETRY, (data) => fails.addFail(data)); - runner.on('retry', fails.addFail.bind(fails)); + gemini.on(gemini.events.TEST_RESULT, (data) => data.equal || fails.addFail(data)); - runner.on('endTest', function (data) { - data.equal || fails.addFail(data); - }); - - runner.on('end', fails.collect.bind(fails)); - }); + gemini.on(gemini.events.END_RUNNER, () => fails.collect()); }; diff --git a/package.json b/package.json index 2e9a072..5ee8cb5 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,8 @@ "unit-test": "istanbul test _mocha -- --recursive test/lib" }, "dependencies": { - "inherit": "~2.2.1", "lodash": "^3.10.1", - "looks-same": "2.2.2", + "looks-same": "^3.2.0", "q": "^1.1.2", "q-io": "^1.13.2", "temp": "^0.8.3", diff --git a/test/lib/errors/base-error.js b/test/lib/errors/base-error.js index 6b2e2dc..78b1991 100644 --- a/test/lib/errors/base-error.js +++ b/test/lib/errors/base-error.js @@ -1,46 +1,48 @@ 'use strict'; -var BaseError = require('../../../lib/errors/base-error'), - utils = require('../utils'), - mkErrorStub = utils.mkErrorStub, - mkConfigStub = utils.mkConfigStub; - -describe('errors/base-error', function() { - it('should generate "name" property for error data', function() { - var failedTestError = mkErrorStub({ - suite: { - fullName: 'suite-fullname' - }, - state: { - name: 'state-name' - }, - browserId: 'browserId' - }), - config = mkConfigStub(); - - var failedTest = new BaseError(failedTestError, config); +const BaseError = require('../../../lib/errors/base-error'); +const utils = require('../utils'); +const mkErrorStub = utils.mkErrorStub; +const mkConfigStub = utils.mkConfigStub; + +describe('errors/base-error', () => { + it('should generate "name" property for error data', () => { + const failedTestError = mkErrorStub({ + suite: { + fullName: 'suite-fullname' + }, + state: { + name: 'state-name' + }, + browserId: 'browserId' + }); + const config = mkConfigStub(); + + const failedTest = new BaseError(failedTestError, config); assert.equal(failedTest.name, 'suite-fullname.state-name.browserId'); }); - it('should get quota from gridUrl param', function() { - var failedTestError = mkErrorStub(), - config = mkConfigStub({gridUrl: 'http://quotaName:quotaPass@grid.url'}), - failedTest = new BaseError(failedTestError, config); + it('should get quota from gridUrl param', () => { + const failedTestError = mkErrorStub(); + const config = mkConfigStub({gridUrl: 'http://quotaName:quotaPass@grid.url'}); + const failedTest = new BaseError(failedTestError, config); - var errorData = failedTest.getData(); + const errorData = failedTest.getData(); assert.equal(errorData.seleniumQuota, 'quotaName'); }); - describe('getData()', function(){ - ['timestamp', 'message', 'sessionId', 'browserCapabilities', 'seleniumQuota'].forEach(function(key) { - it('should return object with ' + key + ' value', function() { - var failedTestError = mkErrorStub(), - config = mkConfigStub(), - failedTest = new BaseError(failedTestError, config); + describe('getData', () => { + [ + 'timestamp', 'message', 'sessionId', 'browserCapabilities', 'seleniumQuota' + ].forEach((key) => { + it(`should return object with ${key} value`, () => { + const failedTestError = mkErrorStub(); + const config = mkConfigStub(); + const failedTest = new BaseError(failedTestError, config); - var errorData = failedTest.getData(); + const errorData = failedTest.getData(); assert.property(errorData, key); }); diff --git a/test/lib/errors/error-factory.js b/test/lib/errors/error-factory.js index 41f13d4..eaf4f3f 100644 --- a/test/lib/errors/error-factory.js +++ b/test/lib/errors/error-factory.js @@ -1,115 +1,64 @@ 'use strict'; -var q = require('q'), - tempFS = require('../../../lib/temp-fs'), - errorFactory = require('../../../lib/errors/error-factory'), - ImageError = require('../../../lib/errors/image-error'), - BaseError = require('../../../lib/errors/base-error'), - imageProcessor = require('../../../lib/image-processor'), - - utils = require('../utils'), - mkErrorStub = utils.mkErrorStub, - mkStateErrorStub = utils.mkStateErrorStub, - mkDiffErrorStub = utils.mkDiffErrorStub; - -describe('error factory', function() { - var config; - - beforeEach(function() { - config = utils.mkConfigStub(); - }); +const errorFactory = require('../../../lib/errors/error-factory'); +const ImageError = require('../../../lib/errors/image-error'); +const BaseError = require('../../../lib/errors/base-error'); - it('should return instance of BaseError by default', function() { - var failedTestError = mkErrorStub(); +const utils = require('../utils'); +const mkErrorStub = utils.mkErrorStub; +const mkStateErrorStub = utils.mkStateErrorStub; +const mkDiffErrorStub = utils.mkDiffErrorStub; - return errorFactory.buildError(failedTestError, config) - .then(function(errorData) { - assert.instanceOf(errorData, BaseError); - }); +describe('error factory', () => { + let config; + + beforeEach(() => { + config = utils.mkConfigStub(); }); - describe('ImageError', function() { - var sandbox = sinon.sandbox.create(); + it('should return instance of BaseError by default', () => { + const failedTestError = mkErrorStub(); - beforeEach(function() { - sandbox.stub(imageProcessor, 'pngToBase64').returns(q()); - }); + const failedTest = errorFactory.buildError(failedTestError, config); - afterEach(function() { - sandbox.restore(); - }); + return assert.instanceOf(failedTest, BaseError); + }); - it('should create ImageError instance for state errors', function() { - var failedTestError = mkStateErrorStub(); + describe('ImageError', () => { + const sandbox = sinon.sandbox.create(); - return errorFactory.buildError(failedTestError, config, {}) - .then(function(errorData) { - assert.instanceOf(errorData, ImageError); - }); - }); + afterEach(() => sandbox.restore()); - it('should not create ImageError instance if it is impossible to save image', function() { - var failedTestError = mkErrorStub({name: 'StateError'}); // without image + it('should create ImageError instance for state errors', () => { + const failedTestError = mkStateErrorStub(); - return errorFactory.buildError(failedTestError, config, {}) - .then(function(errorData) { - assert.notInstanceOf(errorData, ImageError); - }); - }); - - it('should create ImageError instance for diff errors', function() { - var failedTestError = mkDiffErrorStub(); + const failedTest = errorFactory.buildError(failedTestError, config, {}); - return errorFactory.buildError(failedTestError, config, {}) - .then(function(errorData) { - assert.instanceOf(errorData, ImageError); - }); + return assert.instanceOf(failedTest, ImageError); }); - it('should save screenshot of the test failed with diff error type', function () { - var failedTestError = mkDiffErrorStub(); - - return errorFactory.buildError(failedTestError, config, {}) - .then(function(errorData) { - assert.calledOnce(failedTestError.saveDiffTo); - }); - }); + it('should not create ImageError instance if it is impossible to save image', () => { + const failedTestError = mkErrorStub({name: 'StateError'}); // without image - it('should convert image to base64 by default', function() { - var failedTestError = mkDiffErrorStub(); + const failedTest = errorFactory.buildError(failedTestError, config, {}); - return errorFactory.buildError(failedTestError, config, {}) - .then(function(errorData) { - assert.calledOnce(imageProcessor.pngToBase64); - }); + return assert.notInstanceOf(failedTest, ImageError); }); - it('should not convert image to base64 if "light" option exist', function() { - var failedTestError = mkDiffErrorStub(); + it('should create ImageError instance for diff errors', () => { + const failedTestError = mkDiffErrorStub(); - return errorFactory.buildError(failedTestError, config, {light: true}) - .then(function(errorData) { - assert.notCalled(imageProcessor.pngToBase64); - }); - }); - - it('should use imagePath for converting failed image to base64', function () { - var failedTestError = mkStateErrorStub(); + const failedTest = errorFactory.buildError(failedTestError, config, {}); - return errorFactory.buildError(failedTestError, config, {}) - .then(function(errorData) { - assert.calledWith(imageProcessor.pngToBase64, failedTestError.imagePath); - }); + return assert.instanceOf(failedTest, ImageError); }); - it('should generate temporary path for image', function() { - sandbox.stub(tempFS, 'resolveImagePath').returns('tempPath'); - var failedTestError = mkDiffErrorStub(); + it('should pass "light" option to ImageError instance', () => { + const failedTestError = mkDiffErrorStub(); + + const failedTest = errorFactory.buildError(failedTestError, config, {light: true}); - return errorFactory.buildError(failedTestError, config, {}) - .then(function(errorData) { - assert.equal(errorData.imagePath, 'tempPath'); - }); + return assert.equal(failedTest.light, true); }); }); }); diff --git a/test/lib/errors/image-error.js b/test/lib/errors/image-error.js index eec21dd..46255dc 100644 --- a/test/lib/errors/image-error.js +++ b/test/lib/errors/image-error.js @@ -1,43 +1,104 @@ 'use strict'; -var q = require('q'), - temp = require('temp'), - ImageError = require('../../../lib/errors/image-error'), - BaseError = require('../../../lib/errors/base-error'), - utils = require('../utils'), - mkErrorStub = require('../utils').mkErrorStub; +const _ = require('lodash'); +const q = require('q'); -describe('errors/image-error', function() { - var config; +const ImageError = require('../../../lib/errors/image-error'); +const BaseError = require('../../../lib/errors/base-error'); +const imageProcessor = require('../../../lib/image-processor'); +const tempFS = require('../../../lib/temp-fs'); - beforeEach(function() { +const utils = require('../utils'); +const mkStateErrorStub = utils.mkStateErrorStub; +const mkDiffErrorStub = utils.mkDiffErrorStub; + +describe('errors/image-error', () => { + const sandbox = sinon.sandbox.create(); + let config; + + beforeEach(() => { config = utils.mkConfigStub(); + sandbox.stub(imageProcessor, 'pngToBase64').returns(q()); }); - it('should be instance of BaseError', function() { - var failedTestError = mkErrorStub(); + afterEach(() => sandbox.restore()); + + it('should be instance of BaseError', () => { + const failedTestError = mkStateErrorStub(); - var failedTest = new ImageError(failedTestError, config, {}); + const failedTest = new ImageError(failedTestError, config, {}); assert.instanceOf(failedTest, BaseError); }); - it('should mark test error as Diff type if it has an "equal" key', function() { - var failedTestError = mkErrorStub({equal: false}); + it('should mark test error as Diff type if it has an "equal" key', () => { + const failedTestError = mkDiffErrorStub({equal: false}); - var failedTest = new ImageError(failedTestError, config, {}); + const failedTest = new ImageError(failedTestError, config, {}); assert.ok(failedTest.isDiff); }); - describe('getData()', function() { - it('should return extended data with "base64" key', function() { - var failedTestError = mkErrorStub(); + it('should create ImageError instance with saveImg method', () => { + const failedTestError = mkDiffErrorStub(); + + const failedTest = new ImageError(failedTestError, config, {}); + + return failedTest.saveImg() + .then(() => assert.calledOnce(failedTestError.saveDiffTo)); + }); + + it('should generate temporary image path for ImageError instance', () => { + sandbox.stub(tempFS, 'resolveImagePath').returns('tempPath'); + const failedTestError = mkDiffErrorStub(); + + const failedTest = new ImageError(failedTestError, config, {}); + + return assert.equal(failedTest.imagePath, 'tempPath'); + }); + + describe('getData', () => { + const mkImgErrorData_ = (opts) => { + opts = _.defaults(opts || {}, { + data: mkDiffErrorStub(), + config, + light: false + }); - var failedTest = new ImageError(failedTestError, config, {base64: 'base64-value'}), - errorData = failedTest.getData(); + return q.resolve(new ImageError(opts.data, opts.config, { + path: opts.data.path, + saveImg: opts.saveImg, + light: opts.light + }).getData()); + }; + + it('should return extended data with "base64" key', () => { + imageProcessor.pngToBase64.returns(q.resolve('base64-value')); + + return mkImgErrorData_({light: false}) + .then((error) => assert.equal(error.base64, 'base64-value')); + }); + + it('should convert image to base64 by default', () => { + return mkImgErrorData_() + .then(() => assert.calledOnce(imageProcessor.pngToBase64)); + }); + + it('should not convert image to base64 if "light" option exists', () => { + return mkImgErrorData_({light: true}) + .then(() => assert.notCalled(imageProcessor.pngToBase64)); + }); + + it('should call parent method "getData"', () => { + sandbox.stub(BaseError.prototype, 'getData'); + + return mkImgErrorData_() + .then(() => assert.calledOnce(BaseError.prototype.getData)); + }); - assert.equal(errorData.base64, 'base64-value'); + it('should use imagePath for converting failed image to base64', () => { + return mkImgErrorData_({data: mkStateErrorStub({imagePath: '/path/test'})}) + .then(() => assert.calledWith(imageProcessor.pngToBase64, '/path/test')); }); }); }); diff --git a/test/lib/fail-collector.js b/test/lib/fail-collector.js index e1859fc..438beb2 100644 --- a/test/lib/fail-collector.js +++ b/test/lib/fail-collector.js @@ -1,156 +1,207 @@ 'use strict'; -var q = require('q'), - fs = require('q-io/fs'), +const q = require('q'); +const fs = require('q-io/fs'); - FailCollector = require('../../lib/fail-collector'), - BaseError = require('../../lib/errors/base-error'), - ImageError = require('../../lib/errors/image-error'), - imageProcessor = require('../../lib/image-processor'), +const FailCollector = require('../../lib/fail-collector'); +const BaseError = require('../../lib/errors/base-error'); +const ImageError = require('../../lib/errors/image-error'); +const imageProcessor = require('../../lib/image-processor'); +const tempFS = require('../../lib/temp-fs'); - utils = require('./utils'), - mkErrorStub = utils.mkErrorStub, - mkDiffErrorStub = utils.mkDiffErrorStub, - mkStateErrorStub = utils.mkStateErrorStub, - mkConfigStub = utils.mkConfigStub; +const utils = require('./utils'); +const mkErrorStub = utils.mkErrorStub; +const mkDiffErrorStub = utils.mkDiffErrorStub; +const mkStateErrorStub = utils.mkStateErrorStub; +const mkConfigStub = utils.mkConfigStub; -describe('fail-collector', function() { - var sandbox = sinon.sandbox.create(); +describe('fail-collector', () => { + const sandbox = sinon.sandbox.create(); - beforeEach(function() { + beforeEach(() => { sandbox.stub(imageProcessor); imageProcessor.pngToBase64.returns(q()); sandbox.stub(fs); }); - afterEach(function() { - sandbox.restore(); - }); + afterEach(() => sandbox.restore()); - function getFinalErrorData_() { - return JSON.parse(fs.write.lastCall.args[1]); - } + const getFinalErrorData_ = () => JSON.parse(fs.write.lastCall.args[1]); + + it('should sort errors by failed test name', () => { + const config = mkConfigStub(); + const failCollector = new FailCollector(config); + + const errorData = mkErrorStub({ + suite: {fullName: 'suite-fullname'}, + state: {name: 'state-name'}, + browserId: 'browserId' + }); + const anotherErrorData = mkErrorStub({ + suite: {fullName: 'another-suite'}, + state: {name: 'another-state'}, + browserId: 'another-browser' + }); - it('should sort errors by failed test name', function(){ - var config = mkConfigStub(), - failCollector = new FailCollector(config), - errorData = mkErrorStub({ - suite: {fullName: 'suite-fullname'}, - state: {name: 'state-name'}, - browserId: 'browserId' - }), - anotherErrorData = mkErrorStub({ - suite: {fullName: 'another-suite'}, - state: {name: 'another-state'}, - browserId: 'another-browser' - }); failCollector.addFail(errorData); failCollector.addFail(anotherErrorData); return failCollector.collect() - .then(function() { - var resultData = getFinalErrorData_(); + .then(() => { + const resultData = getFinalErrorData_(); assert.property(resultData, 'suite-fullname.state-name.browserId'); assert.property(resultData, 'another-suite.another-state.another-browser'); }); }); - describe('data filtering', function() { - var config, failCollector; + describe('data filtering', () => { + let config; + let failCollector; - beforeEach(function() { + beforeEach(() => { config = mkConfigStub({retry: 1}); failCollector = new FailCollector(config); + fs.exists.returns(q.resolve(true)); }); - it('should include failed test to report if test was passed after retry', function() { - var errorData = mkDiffErrorStub(); + it('should save screenshots for failed tests', () => { + const errorData = mkDiffErrorStub(); + failCollector.addFail(errorData); + failCollector.addFail(errorData); + + sandbox.stub(ImageError.prototype, 'save').returns(q.resolve()); return failCollector.collect() - .then(function() { - var resultData = getFinalErrorData_(); - assert.property(resultData, 'suite-fullname.state-name.browserId'); - }); + .then(() => assert.calledTwice(ImageError.prototype.save)); }); - it('should include failed test to report if images are not the same', function() { - var errorData = mkDiffErrorStub(); + it('should compare failed tests screenshots', () => { + const errorData = mkDiffErrorStub(); + sandbox.stub(tempFS, 'resolveImagePath') + .onFirstCall().returns('/reference/path') + .onSecondCall().returns('/temp/path'); + failCollector.addFail(errorData); failCollector.addFail(errorData); - imageProcessor.compare.returns(q(false)); return failCollector.collect() - .then(function() { - var resultData = getFinalErrorData_(); + .then(() => assert.calledWith(imageProcessor.compare, '/reference/path', '/temp/path')); + }); + + it('should include failed test to report if test was passed after retry', () => { + const errorData = mkDiffErrorStub({ + suite: {fullName: 'suite-fullname'}, + state: {name: 'state-name'}, + browserId: 'browserId' + }); + failCollector.addFail(errorData); + + return failCollector.collect() + .then(() => { + const resultData = getFinalErrorData_(); assert.property(resultData, 'suite-fullname.state-name.browserId'); }); }); - it('should include failed test to report if error types are different', function() { - var diffErrorData = mkDiffErrorStub(), - stateErrorData = mkStateErrorStub(); + it('should include failed test to report if images are not the same', () => { + const errorData = mkDiffErrorStub({ + suite: {fullName: 'suite-fullname'}, + state: {name: 'state-name'}, + browserId: 'browserId' + }); + imageProcessor.compare.returns(q(false)); + + failCollector.addFail(errorData); + failCollector.addFail(errorData); + + return failCollector.collect() + .then(() => assert.property(getFinalErrorData_(), 'suite-fullname.state-name.browserId')); + }); + + it('should include failed test to report if error types are different', () => { + const diffErrorData = mkDiffErrorStub({ + suite: {fullName: 'suite-fullname'}, + state: {name: 'state-name'}, + browserId: 'browserId' + }); + const stateErrorData = mkStateErrorStub({ + suite: {fullName: 'suite-fullname'}, + state: {name: 'state-name'}, + browserId: 'browserId' + }); + failCollector.addFail(diffErrorData); failCollector.addFail(stateErrorData); return failCollector.collect() - .then(function() { - var resultData = getFinalErrorData_(); + .then(() => { + const resultData = getFinalErrorData_(); assert.property(resultData, 'suite-fullname.state-name.browserId'); }); }); - it('should not include to report tests that was failed all the time with the same diff error', function() { - var errorData = mkDiffErrorStub(); + it('should not include to report tests that was failed all the time with the same diff error', () => { + const errorData = mkDiffErrorStub({ + suite: {fullName: 'suite-fullname'}, + state: {name: 'state-name'}, + browserId: 'browserId' + }); + imageProcessor.compare.returns(q(true)); + failCollector.addFail(errorData); failCollector.addFail(errorData); - imageProcessor.compare.returns(q(true)); return failCollector.collect() - .then(function() { - var resultData = getFinalErrorData_(); + .then(() => { + const resultData = getFinalErrorData_(); assert.notProperty(resultData, 'suite-fullname.state-name.browserId'); }); }); }); - it('should add to report formatted data', function(){ - var config = mkConfigStub(), - failCollector = new FailCollector(config), - errorData = mkErrorStub({ - suite: {fullName: 'suite-fullname'}, - state: {name: 'state-name'}, - browserId: 'browserId', - equal: false, - saveDiffTo: sinon.stub().returns(q()) - }); - sandbox.stub(ImageError.prototype, 'getData').returns({some: 'value'}); + it('should add to report formatted data', () => { + const config = mkConfigStub({retry: 1}); + const failCollector = new FailCollector(config); + const errorData = mkErrorStub({ + suite: {fullName: 'suite-fullname'}, + state: {name: 'state-name'}, + browserId: 'browserId', + equal: false, + saveDiffTo: sinon.stub().returns(q()) + }); + + imageProcessor.compare.returns(q(false)); + sandbox.stub(ImageError.prototype, 'getData') + .onFirstCall().returns({some: 'value1'}) + .onSecondCall().returns({another: 'value2'}); failCollector.addFail(errorData); + failCollector.addFail(errorData); return failCollector.collect() - .then(function() { - var resultData = getFinalErrorData_(); + .then(() => { + const resultData = getFinalErrorData_(); assert.deepEqual(resultData, { - 'suite-fullname.state-name.browserId': [{ some: 'value' }] + 'suite-fullname.state-name.browserId': [{some: 'value1'}, {another: 'value2'}] }); }); }); - it('should write formatted error data to the "faildump.json"', function() { - var config = mkConfigStub(), - failCollector = new FailCollector(config), - errorData = mkErrorStub({ - suite: {fullName: 'suite-fullname'}, - state: {name: 'state-name'}, - browserId: 'browserId' - }); + it('should write formatted error data to the "faildump.json"', () => { + const config = mkConfigStub(); + const failCollector = new FailCollector(config); + const errorData = mkErrorStub({ + suite: {fullName: 'suite-fullname'}, + state: {name: 'state-name'}, + browserId: 'browserId' + }); sandbox.stub(BaseError.prototype, 'getData').returns({some: 'value'}); failCollector.addFail(errorData); return failCollector.collect() - .then(function() { + .then(() => { assert.calledWith(fs.write, 'faildump.json', '{"suite-fullname.state-name.browserId":[{"some":"value"}]}' diff --git a/test/lib/plugin.js b/test/lib/plugin.js index 169af3b..243a35f 100644 --- a/test/lib/plugin.js +++ b/test/lib/plugin.js @@ -1,47 +1,57 @@ 'use strict'; -var EventEmitter = require('events').EventEmitter, - FailCollector = require('../../lib/fail-collector'), - plugin = require('../../lib/plugin'); - -describe('plugin', function(){ - var sandbox = sinon.sandbox.create(); - - beforeEach(function() { - sandbox.stub(FailCollector.prototype); - +const _ = require('lodash'); +const EventEmitter = require('events').EventEmitter; +const FailCollector = require('../../lib/fail-collector'); +const plugin = require('../../lib/plugin'); + +describe('plugin', () => { + const sandbox = sinon.sandbox.create(); + const geminiEvents = { + END_RUNNER: 'endRunner', + RETRY: 'retry', + ERROR: 'err', + TEST_RESULT: 'testResult' + }; + + beforeEach(() => { this.gemini = new EventEmitter(); this.runner = new EventEmitter(); - plugin(this.gemini); - this.gemini.emit('startRunner', this.runner); - }); + sandbox.stub(FailCollector.prototype, 'addFail'); + sandbox.stub(FailCollector.prototype, 'collect'); - afterEach(function() { - sandbox.restore(); + plugin(_.extend(this.gemini, {events: geminiEvents})); }); - it('should add failed test to storage on "err" event', function() { - this.runner.emit('err'); + afterEach(() => sandbox.restore()); + + it('should add failed test to storage on "ERROR" event', () => { + this.gemini.emit(geminiEvents.ERROR); + assert.called(FailCollector.prototype.addFail); }); - it('should add failed test to storage on "retry" event', function() { - this.runner.emit('retry'); + it('should add failed test to storage on "RETRY" event', () => { + this.gemini.emit(geminiEvents.RETRY); + assert.called(FailCollector.prototype.addFail); }); - it('should add failed test to storage on "endTest" event if some diff found', function() { - this.runner.emit('endTest', {equal: false}); + it('should add failed test to storage on "TEST_RESULT" event if some diff found', () => { + this.gemini.emit(geminiEvents.TEST_RESULT, {equal: false}); + assert.called(FailCollector.prototype.addFail); }); - it('should not add failed test to storage on "endTest" event if no diff found', function() { - this.runner.emit('endTest', {equal: true}); + it('should not add failed test to storage on "TEST_RESULT" event if no diff found', () => { + this.gemini.emit(geminiEvents.TEST_RESULT, {equal: true}); + assert.notCalled(FailCollector.prototype.addFail); }); - it('should collect all fails on "end" event', function() { - this.runner.emit('end'); + it('should collect all fails on "END_RUNNER" event', () => { + this.gemini.emit(geminiEvents.END_RUNNER); + assert.called(FailCollector.prototype.collect); }); }); diff --git a/test/lib/utils.js b/test/lib/utils.js index b233f97..dae1888 100644 --- a/test/lib/utils.js +++ b/test/lib/utils.js @@ -1,25 +1,26 @@ 'use strict'; -var _ = require('lodash'), - q = require('q'); +const q = require('q'); +const _ = require('lodash'); -var errorDefaults = { - suite: {fullName: 'suite-fullname'}, - state: {name: 'state-name'}, - browserId: 'browserId' - }; +const errorDefaults = { + suite: {fullName: 'suite-fullname'}, + state: {name: 'state-name'}, + browserId: 'browserId' +}; module.exports = { - mkErrorStub: function(opts) { + mkErrorStub: (opts) => { opts = opts || {}; + return _.defaults(opts, errorDefaults); }, - mkConfigStub: function(opts) { + mkConfigStub: (opts) => { opts = opts || {}; - var defaults = { + const defaults = { getBrowserIds: sinon.stub().returns([opts.browserId || 'default-browser']), - forBrowser: function(id) { + forBrowser: () => { return { id: opts.browserId || 'default-browser', retry: opts.retry || 0, @@ -27,19 +28,23 @@ module.exports = { desiredCapabilities: {} }; } - } + }; return _.defaults(opts, defaults); }, - mkStateErrorStub: function() { - return _.defaults({ + mkStateErrorStub: (opts) => { + opts = opts || {}; + + return _.defaults(opts, { name: 'StateError', imagePath: '/some/path/to/image' }, errorDefaults); }, - mkDiffErrorStub: function() { - return _.defaults({ + mkDiffErrorStub: (opts) => { + opts = opts || {}; + + return _.defaults(opts, { equal: false, saveDiffTo: sinon.stub().returns(q()) }, errorDefaults);