From 4148b4096f42604902dea4e0beafd3f0319c6afc Mon Sep 17 00:00:00 2001 From: Marcin Date: Thu, 9 Nov 2017 19:13:00 +0100 Subject: [PATCH] Fixed performance issue and stability; Added test sorting for sharded runs; --- CHANGES.md | 4 + index.js | 277 +++++++++++++++++++++++++++++---------------------- lib/app.js | 16 ++- lib/util.js | 12 ++- package.json | 2 +- 5 files changed, 191 insertions(+), 120 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 233e530..bffda87 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changelog +## Version 0.5.2 +* Fixed performance issue and stability. PR by yjaaidi +* Added test sorting for sharded runs + ## Version 0.5.1 * Updated CSS link * Added CSS file to the repo for later use diff --git a/index.js b/index.js index 1baa3c2..f7f55ff 100644 --- a/index.js +++ b/index.js @@ -179,128 +179,171 @@ function ScreenshotReporter(options) { } } +class Jasmine2Reporter { + + constructor({screenshotReporter}) { + + /* `_asyncFlow` is a promise. + * It is a "flow" that we create in `specDone`. + * `suiteDone`, `suiteStarted` and `specStarted` will then add their steps to the flow and the `beforeEach` + * function will wait for the flow to finish before running the next spec. */ + this._asyncFlow = null; + + this._screenshotReporter = screenshotReporter; + this._suiteNames = []; + + } + + jasmineStarted() { + + /* Register `beforeEach` that will wait for all tasks in flow to be finished. */ + beforeEach(() => this._beforeEach()); + + } + + suiteStarted(result) { + this._addTaskToFlow(async () => this._suiteNames.push(result.description)); + } + + suiteDone(result) { + this._addTaskToFlow(async () => this._suiteNames.pop()); + } + + specStarted(result) { + this._addTaskToFlow(async () => result.started = nowString()); + } + + specDone(result) { + this._addTaskToFlow(async () => this._asyncSpecDone(result)); + } + + _addTaskToFlow(callback) { + + /* Create. */ + if (this._asyncFlow == null) { + this._asyncFlow = callback(); + } + /* Chain. */ + else { + this._asyncFlow = this._asyncFlow.then(callback); + } + + } + + /* @hack: `beforeEach` waits for `specDone` task to finish before running the next spec.*/ + async _beforeEach() { + await this._asyncFlow; + this._asyncFlow = null; + } + + async _asyncSpecDone(result) { + + result.stopped = nowString(); + + await this._gatherBrowserLogs(result); + await this._takeScreenShotAndAddMetaData(result); + + } + + async _gatherBrowserLogs(result) { + + if (!this._screenshotReporter.gatherBrowserLogs) { + return; + } + + const capabilities = await browser.getCapabilities(); + const browserName = capabilities.get('browserName'); + + /* Skip incompatible browsers. */ + if (browserName == null || !browserName.toLowerCase().match(/chrome/)) { + return; + } + + result.browserLogs = await browser.manage().logs().get('browser'); + + } + + async _takeScreenShotAndAddMetaData(result) { + + const capabilities = await browser.getCapabilities(); + const suite = this._buildSuite(); + + var descriptions = util.gatherDescriptions( + suite, + [result.description] + ), + + baseName = this._screenshotReporter.pathBuilder( + null, + descriptions, + result, + capabilities + ), + + metaData = this._screenshotReporter.jasmine2MetaDataBuilder( + null, + descriptions, + result, + capabilities + ), + + screenShotFileName = path.basename(baseName + '.png'), + screenShotFilePath = path.join(path.dirname(baseName + '.png'), this._screenshotReporter.screenshotsSubfolder), + + metaFile = baseName + '.json', + screenShotPath = path.join(this._screenshotReporter.baseDirectory, screenShotFilePath, screenShotFileName), + metaDataPath = path.join(this._screenshotReporter.baseDirectory, metaFile), + jsonPartsPath = path.join(this._screenshotReporter.baseDirectory, path.dirname(metaFile), this._screenshotReporter.jsonsSubfolder, path.basename(metaFile)), + + // pathBuilder can return a subfoldered path too. So extract the + // directory path without the baseName + directory = path.dirname(screenShotPath), + jsonsDirectory = path.dirname(jsonPartsPath); + + metaData.browserLogs = []; + + if (!(this._screenshotReporter.takeScreenShotsOnlyForFailedSpecs && result.status === 'passed')) { + metaData.screenShotFile = path.join(this._screenshotReporter.screenshotsSubfolder, screenShotFileName); + } + + if (result.browserLogs) { metaData.browserLogs = result.browserLogs }; + metaData.duration = new Date(result.stopped) - new Date(result.started); + + if ((result.status != 'pending' && result.status != 'disabled') && !(this._screenshotReporter.takeScreenShotsOnlyForFailedSpecs && result.status === 'passed')) { + const png = await browser.takeScreenshot(); + util.storeScreenShot(png, screenShotPath); + } + + util.storeMetaData(metaData, jsonPartsPath, descriptions); + util.addMetaData(metaData, metaDataPath, this._screenshotReporter.finalOptions); + + } + + // Enabling backwards-compat. Construct Jasmine v1 style spec.suite. + _buildSuite() { + + const buildSuite = (suiteNames, i) => { + if(i<0) {return null;} + return { + description: suiteNames[i], + parentSuite: buildSuite(suiteNames, i-1) + }; + }; + + return buildSuite(this._suiteNames, this._suiteNames.length); + + } + +} + /** * Returns a reporter that complies with the new Jasmine 2.x custom_reporter.js spec: * http://jasmine.github.io/2.1/custom_reporter.html */ ScreenshotReporter.prototype.getJasmine2Reporter = function() { - var self = this; - - return { - suiteNames: [], - suiteStarted: function(result){ - this.suiteNames.push(result.description); - }, - specStarted: function (result) { - result.started = nowString(); - - afterEach(function() { - if (self.gatherBrowserLogs) { - browser.driver.getCapabilities().then(function(caps){ - browserName = caps.get('browserName'); - if (browserName && browserName.toLowerCase().indexOf('chrome') > -1) { - browser.manage().logs().get('browser').then(function (browserLogs) { - result.browserLogs = []; - result.browserLogs = browserLogs; - }) - } - }); - } - }) - }, - suiteDone: function(){ - this.suiteNames.pop(); - }, - - specDone: function(result) { - self2 = this; - return new Promise( function (resolve, reject) { - result.stopped = nowString(); - - // Enabling backwards-compat. Construct Jasmine v1 style spec.suite. - function buildSuite(suiteNames, i) { - if(i<0) {return null;} - return { - description: suiteNames[i], - parentSuite: buildSuite(suiteNames, i-1) - }; - } - - var suite = buildSuite(self2.suiteNames, self2.suiteNames.length-1); - - browser.getCapabilities().then(function (capabilities) { - var descriptions = util.gatherDescriptions( - suite, - [result.description] - ), - - baseName = self.pathBuilder( - null, - descriptions, - result, - capabilities - ), - - metaData = self.jasmine2MetaDataBuilder( - null, - descriptions, - result, - capabilities - ), - - screenShotFileName = path.basename(baseName + '.png'), - screenShotFilePath = path.join(path.dirname(baseName + '.png'), self.screenshotsSubfolder), - - metaFile = baseName + '.json', - screenShotPath = path.join(self.baseDirectory, screenShotFilePath, screenShotFileName), - metaDataPath = path.join(self.baseDirectory, metaFile), - jsonPartsPath = path.join(self.baseDirectory, path.dirname(metaFile), self.jsonsSubfolder, path.basename(metaFile)), - - // pathBuilder can return a subfoldered path too. So extract the - // directory path without the baseName - directory = path.dirname(screenShotPath), - jsonsDirectory = path.dirname(jsonPartsPath); - - metaData.browserLogs = []; - - if (!(self.takeScreenShotsOnlyForFailedSpecs && result.status === 'passed')) { - metaData.screenShotFile = path.join(self.screenshotsSubfolder, screenShotFileName); - } - - if (result.browserLogs) { metaData.browserLogs = result.browserLogs }; - metaData.duration = new Date(result.stopped) - new Date(result.started); - - if ((result.status != 'pending' && result.status != 'disabled') && !(self.takeScreenShotsOnlyForFailedSpecs && result.status === 'passed')) { - browser.takeScreenshot().then(function (png) { - util.storeScreenShot(png, screenShotPath); - util.storeMetaData(metaData, jsonPartsPath, descriptions); - util.addMetaData(metaData, metaDataPath, self.finalOptions); - resolve(); - }).catch(function(e) { - console.warn('Could not store Meta Data or add Meta Data to combined.js and generate report'); - console.warn(e); - reject(); - }); - } else { - util.storeMetaData(metaData, jsonPartsPath, descriptions); - util.addMetaData(metaData, metaDataPath, self.finalOptions); - resolve(); - } - - }).catch(function(e) { - console.warn('Something went wrong:'); - console.warn(e); - reject(); - }); - - } - ).catch(function(err) { - console.warn(err); - return Promise.reject(err) - }) - } - }; + return new Jasmine2Reporter({screenshotReporter: this}); + }; @@ -376,4 +419,4 @@ function nowString() { return (new Date()).toISOString(); } -module.exports = ScreenshotReporter; +module.exports = ScreenshotReporter; \ No newline at end of file diff --git a/lib/app.js b/lib/app.js index 74431a7..eb39e3a 100644 --- a/lib/app.js +++ b/lib/app.js @@ -129,7 +129,21 @@ app.controller('ScreenshotReportController', function ($scope) { }; - this.results =''; + var results =''; + + this.init = function () { + this.results = results.sort(function(a, b) { + if (a.cachedName == null) { a.cachedName = a.description.split('|').reverse().join() }; + if (b.cachedName == null) { b.cachedName = b.description.split('|').reverse().join() }; + + var first = a.cachedName; + var second = b.cachedName; + + return first.localeCompare(second) + }); + }; + + this.init(); }); app.filter('bySearchSettings', function () { diff --git a/lib/util.js b/lib/util.js index f616b80..6c2c618 100644 --- a/lib/util.js +++ b/lib/util.js @@ -103,7 +103,7 @@ function addMetaData(test, baseName, options){ */ function storeMetaData(metaData, file, descriptions) { try { - metaData.description = descriptions.join('|'); + metaData.description = cleanArray(descriptions).join('|'); fse.outputJsonSync(file, metaData); } catch(e) { console.error(e); @@ -111,6 +111,16 @@ function storeMetaData(metaData, file, descriptions) { } } +function cleanArray(actual) { + var newArray = new Array(); + for (var i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]); + } + } + return newArray; +} + /** Function: gatherDescriptions * Traverses the parent suites of a test spec recursivly and gathers all * descriptions. Finally returns them as an array. diff --git a/package.json b/package.json index a82e93a..bd87444 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "protractor-beautiful-reporter", - "version": "0.5.1", + "version": "0.5.2", "description": "An npm module and which generates your Protractor test reports in HTML (angular) with screenshots", "main": "index.js", "repository": {