protractor-beautiful-reporter
Version:
An npm module and which generates your Protractor test reports in HTML (angular) with screenshots
513 lines (441 loc) • 16.2 kB
JavaScript
var app = angular.module('reportingApp', []);
//<editor-fold desc="global helpers">
var isValueAnArray = function (val) {
return Array.isArray(val);
};
var getSpec = function (str) {
var describes = str.split('|');
return describes[describes.length - 1];
};
var checkIfShouldDisplaySpecName = function (prevItem, item) {
if (!prevItem) {
item.displaySpecName = true;
} else if (getSpec(item.description) !== getSpec(prevItem.description)) {
item.displaySpecName = true;
}
};
var getParent = function (str) {
var arr = str.split('|');
str = "";
for (var i = arr.length - 2; i > 0; i--) {
str += arr[i] + " > ";
}
return str.slice(0, -3);
};
var getShortDescription = function (str) {
return str.split('|')[0];
};
var countLogMessages = function (item) {
if ((!item.logWarnings || !item.logErrors) && item.browserLogs && item.browserLogs.length > 0) {
item.logWarnings = 0;
item.logErrors = 0;
for (var logNumber = 0; logNumber < item.browserLogs.length; logNumber++) {
var logEntry = item.browserLogs[logNumber];
if (logEntry.level === 'SEVERE') {
item.logErrors++;
}
if (logEntry.level === 'WARNING') {
item.logWarnings++;
}
}
}
};
var convertTimestamp = function (timestamp) {
var d = new Date(timestamp),
yyyy = d.getFullYear(),
mm = ('0' + (d.getMonth() + 1)).slice(-2),
dd = ('0' + d.getDate()).slice(-2),
hh = d.getHours(),
h = hh,
min = ('0' + d.getMinutes()).slice(-2),
ampm = 'AM',
time;
if (hh > 12) {
h = hh - 12;
ampm = 'PM';
} else if (hh === 12) {
h = 12;
ampm = 'PM';
} else if (hh === 0) {
h = 12;
}
// ie: 2013-02-18, 8:35 AM
time = yyyy + '-' + mm + '-' + dd + ', ' + h + ':' + min + ' ' + ampm;
return time;
};
var defaultSortFunction = function sortFunction(a, b) {
if (a.sessionId < b.sessionId) {
return -1;
} else if (a.sessionId > b.sessionId) {
return 1;
}
if (a.timestamp < b.timestamp) {
return -1;
} else if (a.timestamp > b.timestamp) {
return 1;
}
return 0;
};
//</editor-fold>
app.controller('ScreenshotReportController', ['$scope', '$http', 'TitleService', function ($scope, $http, titleService) {
var that = this;
var clientDefaults = {};//'<Client Defaults Replacement>';
$scope.searchSettings = Object.assign({
description: '',
allselected: true,
passed: true,
failed: true,
pending: true,
withLog: true
}, clientDefaults.searchSettings || {}); // enable customisation of search settings on first page hit
this.warningTime = 1400;
this.dangerTime = 1900;
this.totalDurationFormat = clientDefaults.totalDurationFormat;
this.showTotalDurationIn = clientDefaults.showTotalDurationIn;
var initialColumnSettings = clientDefaults.columnSettings; // enable customisation of visible columns on first page hit
if (initialColumnSettings) {
if (initialColumnSettings.displayTime !== undefined) {
// initial settings have be inverted because the html bindings are inverted (e.g. !ctrl.displayTime)
this.displayTime = !initialColumnSettings.displayTime;
}
if (initialColumnSettings.displayBrowser !== undefined) {
this.displayBrowser = !initialColumnSettings.displayBrowser; // same as above
}
if (initialColumnSettings.displaySessionId !== undefined) {
this.displaySessionId = !initialColumnSettings.displaySessionId; // same as above
}
if (initialColumnSettings.displayOS !== undefined) {
this.displayOS = !initialColumnSettings.displayOS; // same as above
}
if (initialColumnSettings.inlineScreenshots !== undefined) {
this.inlineScreenshots = initialColumnSettings.inlineScreenshots; // this setting does not have to be inverted
} else {
this.inlineScreenshots = false;
}
if (initialColumnSettings.warningTime) {
this.warningTime = initialColumnSettings.warningTime;
}
if (initialColumnSettings.dangerTime) {
this.dangerTime = initialColumnSettings.dangerTime;
}
}
this.chooseAllTypes = function () {
var value = true;
$scope.searchSettings.allselected = !$scope.searchSettings.allselected;
if (!$scope.searchSettings.allselected) {
value = false;
}
$scope.searchSettings.passed = value;
$scope.searchSettings.failed = value;
$scope.searchSettings.pending = value;
$scope.searchSettings.withLog = value;
};
this.isValueAnArray = function (val) {
return isValueAnArray(val);
};
this.getParent = function (str) {
return getParent(str);
};
this.getSpec = function (str) {
return getSpec(str);
};
this.getShortDescription = function (str) {
return getShortDescription(str);
};
this.hasNextScreenshot = function (index) {
var old = index;
return old !== this.getNextScreenshotIdx(index);
};
this.hasPreviousScreenshot = function (index) {
var old = index;
return old !== this.getPreviousScreenshotIdx(index);
};
this.getNextScreenshotIdx = function (index) {
var next = index;
var hit = false;
while (next + 2 < this.results.length) {
next++;
if (this.results[next].screenShotFile && !this.results[next].pending) {
hit = true;
break;
}
}
return hit ? next : index;
};
this.getPreviousScreenshotIdx = function (index) {
var prev = index;
var hit = false;
while (prev > 0) {
prev--;
if (this.results[prev].screenShotFile && !this.results[prev].pending) {
hit = true;
break;
}
}
return hit ? prev : index;
};
this.convertTimestamp = convertTimestamp;
this.round = function (number, roundVal) {
return (parseFloat(number) / 1000).toFixed(roundVal);
};
this.passCount = function () {
var passCount = 0;
for (var i in this.results) {
var result = this.results[i];
if (result.passed) {
passCount++;
}
}
return passCount;
};
this.pendingCount = function () {
var pendingCount = 0;
for (var i in this.results) {
var result = this.results[i];
if (result.pending) {
pendingCount++;
}
}
return pendingCount;
};
this.failCount = function () {
var failCount = 0;
for (var i in this.results) {
var result = this.results[i];
if (!result.passed && !result.pending) {
failCount++;
}
}
return failCount;
};
this.totalDuration = function () {
var sum = 0;
for (var i in this.results) {
var result = this.results[i];
if (result.duration) {
sum += result.duration;
}
}
return sum;
};
this.passPerc = function () {
return (this.passCount() / this.totalCount()) * 100;
};
this.pendingPerc = function () {
return (this.pendingCount() / this.totalCount()) * 100;
};
this.failPerc = function () {
return (this.failCount() / this.totalCount()) * 100;
};
this.totalCount = function () {
return this.passCount() + this.failCount() + this.pendingCount();
};
var results = [];//'<Results Replacement>';
this.sortSpecs = function () {
this.results = results.sort(defaultSortFunction/*<Sort Function Replacement>*/);
};
this.setTitle = function () {
var title = $('.report-title').text();
titleService.setTitle(title);
};
// is run after all test data has been prepared/loaded
this.afterLoadingJobs = function () {
this.sortSpecs();
this.setTitle();
};
this.loadResultsViaAjax = function () {
$http({
url: './combined.json',
method: 'GET'
}).then(function (response) {
var data = null;
if (response && response.data) {
if (typeof response.data === 'object') {
data = response.data;
} else if (response.data[0] === '"') { //detect super escaped file (from circular json)
data = CircularJSON.parse(response.data); //the file is escaped in a weird way (with circular json)
} else {
data = JSON.parse(response.data);
}
}
if (data) {
results = data;
that.afterLoadingJobs();
}
},
function (error) {
console.error(error);
});
};
if (clientDefaults.useAjax) {
this.loadResultsViaAjax();
} else {
this.afterLoadingJobs();
}
}]);
app.filter('bySearchSettings', function () {
return function (items, searchSettings) {
var filtered = [];
if (!items) {
return filtered; // to avoid crashing in where results might be empty
}
var prevItem = null;
for (var i = 0; i < items.length; i++) {
var item = items[i];
item.displaySpecName = false;
var isHit = false; //is set to true if any of the search criteria matched
countLogMessages(item); // modifies item contents
var hasLog = searchSettings.withLog && item.browserLogs && item.browserLogs.length > 0;
if (searchSettings.description === '' ||
(item.description && item.description.toLowerCase().indexOf(searchSettings.description.toLowerCase()) > -1)) {
if (searchSettings.passed && item.passed || hasLog) {
isHit = true;
} else if (searchSettings.failed && !item.passed && !item.pending || hasLog) {
isHit = true;
} else if (searchSettings.pending && item.pending || hasLog) {
isHit = true;
}
}
if (isHit) {
checkIfShouldDisplaySpecName(prevItem, item);
filtered.push(item);
prevItem = item;
}
}
return filtered;
};
});
//formats millseconds to h m s
app.filter('timeFormat', function () {
return function (tr, fmt) {
if(tr == null){
return "NaN";
}
switch (fmt) {
case 'h':
var h = tr / 1000 / 60 / 60;
return "".concat(h.toFixed(2)).concat("h");
case 'm':
var m = tr / 1000 / 60;
return "".concat(m.toFixed(2)).concat("min");
case 's' :
var s = tr / 1000;
return "".concat(s.toFixed(2)).concat("s");
case 'hm':
case 'h:m':
var hmMt = tr / 1000 / 60;
var hmHr = Math.trunc(hmMt / 60);
var hmMr = hmMt - (hmHr * 60);
if (fmt === 'h:m') {
return "".concat(hmHr).concat(":").concat(hmMr < 10 ? "0" : "").concat(Math.round(hmMr));
}
return "".concat(hmHr).concat("h ").concat(hmMr.toFixed(2)).concat("min");
case 'hms':
case 'h:m:s':
var hmsS = tr / 1000;
var hmsHr = Math.trunc(hmsS / 60 / 60);
var hmsM = hmsS / 60;
var hmsMr = Math.trunc(hmsM - hmsHr * 60);
var hmsSo = hmsS - (hmsHr * 60 * 60) - (hmsMr*60);
if (fmt === 'h:m:s') {
return "".concat(hmsHr).concat(":").concat(hmsMr < 10 ? "0" : "").concat(hmsMr).concat(":").concat(hmsSo < 10 ? "0" : "").concat(Math.round(hmsSo));
}
return "".concat(hmsHr).concat("h ").concat(hmsMr).concat("min ").concat(hmsSo.toFixed(2)).concat("s");
case 'ms':
var msS = tr / 1000;
var msMr = Math.trunc(msS / 60);
var msMs = msS - (msMr * 60);
return "".concat(msMr).concat("min ").concat(msMs.toFixed(2)).concat("s");
}
return tr;
};
});
function PbrStackModalController($scope, $rootScope) {
var ctrl = this;
ctrl.rootScope = $rootScope;
ctrl.getParent = getParent;
ctrl.getShortDescription = getShortDescription;
ctrl.convertTimestamp = convertTimestamp;
ctrl.isValueAnArray = isValueAnArray;
ctrl.toggleSmartStackTraceHighlight = function () {
var inv = !ctrl.rootScope.showSmartStackTraceHighlight;
ctrl.rootScope.showSmartStackTraceHighlight = inv;
};
ctrl.applySmartHighlight = function (line) {
if ($rootScope.showSmartStackTraceHighlight) {
if (line.indexOf('node_modules') > -1) {
return 'greyout';
}
if (line.indexOf(' at ') === -1) {
return '';
}
return 'highlight';
}
return '';
};
}
app.component('pbrStackModal', {
templateUrl: "pbr-stack-modal.html",
bindings: {
index: '=',
data: '='
},
controller: PbrStackModalController
});
function PbrScreenshotModalController($scope, $rootScope) {
var ctrl = this;
ctrl.rootScope = $rootScope;
ctrl.getParent = getParent;
ctrl.getShortDescription = getShortDescription;
/**
* Updates which modal is selected.
*/
this.updateSelectedModal = function (event, index) {
var key = event.key; //try to use non-deprecated key first https://developer.mozilla.org/de/docs/Web/API/KeyboardEvent/keyCode
if (key == null) {
var keyMap = {
37: 'ArrowLeft',
39: 'ArrowRight'
};
key = keyMap[event.keyCode]; //fallback to keycode
}
if (key === "ArrowLeft" && this.hasPrevious) {
this.showHideModal(index, this.previous);
} else if (key === "ArrowRight" && this.hasNext) {
this.showHideModal(index, this.next);
}
};
/**
* Hides the modal with the #oldIndex and shows the modal with the #newIndex.
*/
this.showHideModal = function (oldIndex, newIndex) {
const modalName = '#imageModal';
$(modalName + oldIndex).modal("hide");
$(modalName + newIndex).modal("show");
};
}
app.component('pbrScreenshotModal', {
templateUrl: "pbr-screenshot-modal.html",
bindings: {
index: '=',
data: '=',
next: '=',
previous: '=',
hasNext: '=',
hasPrevious: '='
},
controller: PbrScreenshotModalController
});
app.factory('TitleService', ['$document', function ($document) {
return {
setTitle: function (title) {
$document[0].title = title;
}
};
}]);
app.run(
function ($rootScope, $templateCache) {
//make sure this option is on by default
$rootScope.showSmartStackTraceHighlight = true;
//'<templates replacement>';
});