UNPKG

macedonia-protractor-reporter

Version:

an easy to use html page for looking at protractor test results with option to combine results when reruning failed tests

292 lines (243 loc) 11 kB
'use strict'; const fs = require('fs'); const path = require('path'); //path setup const templatePath = path.join(__dirname, 'report.html'); const fileContents = fs.readFileSync(templatePath).toString(); /** A jasmine reporter that produces an html report **/ class Reporter { /** * @constructor * @param {Object} options - options for the reporter * @param {String} options.path - Path the report.html will be written to * @param {Boolean} options.screenshotOnPassed=false - take screenshots for passing tests too * @param {Boolean} options.writeReportEachSpec=true - write the report between each spec, recommended for long running tests * @param {Boolean} options.showBrowser=true - show browser icon on the overview * @param {Boolean} options.highlightSuspectLine=true - highlight the "suspect line" in the detail dialog * @param {Boolean} options.isSharded=false - use if using shardOnSpec of multiCapabilities options in protractor */ constructor(options) { this.sequence = []; this.counts = {specs: 0}; this.timer = {}; this.currentSpec = null; this.browserLogs = []; this.options = Reporter.getDefaultOptions(); this.setOptions(options); if (!this.options.path) { throw new Error('Please provide options.path') } this.imageLocation = path.join(this.options.path, 'img'); this.dataLocation = path.join(this.options.path, 'data'); this.destination = path.join(this.options.path, 'report.html'); this.dataFile = path.join(this.dataLocation, `${process.pid}.js`); this.hasWrittenReportFile = false; } startReporter() { Reporter.cleanDirectory(this.options.path); Reporter.makeDirectoryIfNeeded(this.options.path); Reporter.makeDirectoryIfNeeded(this.imageLocation); Reporter.makeDirectoryIfNeeded(this.dataLocation); this.timer.started = Reporter.nowString(); } stopReporter() { this.writeDataFile(); if (this.hasWrittenReportFile) { return; } let resultContents = fs.readdirSync(this.dataLocation).map(file => { return `<script src="data/${file}"></script>`; }).join('\n'); let results = fileContents.replace('<!-- inject::scripts -->', resultContents); fs.writeFileSync(this.destination, results, 'utf8'); this.hasWrittenReportFile = true; } jasmineStarted(suiteInfo) { if (!this.options.isSharded) { this.startReporter(); } afterEach((next) => { this.currentSpec.stopped = Reporter.nowString(); this.currentSpec.duration = new Date(this.currentSpec.stopped) - new Date(this.currentSpec.started); this.currentSpec.prefix = this.currentSpec.fullName.replace(this.currentSpec.description, ''); browser.takeScreenshot() .then((png) => { this.currentSpec.base64screenshot = png; }) .then(browser.getCapabilities) .then((capabilities) => { this.currentSpec.browserName = capabilities.get('browserName'); return browser.manage().logs().get('browser'); }) .then((browserLogs) => { this.currentSpec.browserLogs = browserLogs; this.browserLogs.concat(browserLogs); }) .then(next, next); }); }; suiteStarted(result) { }; specStarted(result) { this.counts.specs++; this.currentSpec = result; this.currentSpec.started = Reporter.nowString(); }; specDone(result) { this.counts[this.currentSpec.status] = (this.counts[this.currentSpec.status] || 0) + 1; this.sequence.push(this.currentSpec); // Handle screenshot saving if (this.currentSpec.status !== "disabled" && this.currentSpec.status !== "pending" && (this.currentSpec.status !== 'passed' || this.options.screenshotOnPassed)) { this.currentSpec.screenshotPath = `img/${process.pid}-${this.counts.specs}.png`; this.writeImage(this.currentSpec.base64screenshot); } // remove this from the payload that is written to report.html; delete this.currentSpec.base64screenshot; // suspectLine result.failedExpectations.forEach(failure => { failure.hasSuspectLine = failure.stack.split('\n').some(function (line) { let match = line.indexOf('Error:') === -1 && line.indexOf('node_modules') === -1; if (match) { failure.suspectLine = line; } return match; }); }); if (this.options.writeReportEachSpec) { this.jasmineDone(); } }; suiteDone(result) { }; jasmineDone() { this.timer.stopped = Reporter.nowString(); this.timer.duration = new Date(this.timer.stopped) - new Date(this.timer.started); this.stopReporter(); }; setOptions(options) { this.options = Object.assign(this.options, options); }; writeDataFile() { let logEntry = { options: this.options, timer: this.timer, counts: this.counts, sequence: this.sequence }; let json = JSON.stringify(logEntry, null, !this.options.debugData ? null : 4); fs.writeFileSync(this.dataFile, `window.RESULTS.push(${json});`, 'utf8'); } writeImage(img) { let stream = fs.createWriteStream(path.join(this.options.path, this.currentSpec.screenshotPath)); stream.write(new Buffer(img, 'base64')); stream.end(); } static getDefaultOptions() { return { screenshotOnPassed: false, writeReportEachSpec: true, showBrowser: true, highlightSuspectLine: true }; } static cleanDirectory(dirPath) { let files = []; try { files = fs.readdirSync(dirPath); } catch (e) { return; } if (files.length > 0) for (let i = 0; i < files.length; i++) { let filePath = dirPath + '/' + files[i]; if (fs.statSync(filePath).isFile()) { fs.unlinkSync(filePath); } else { Reporter.cleanDirectory(filePath); } } fs.rmdirSync(dirPath); } static makeDirectoryIfNeeded(path) { if (!fs.existsSync(path)) { fs.mkdirSync(path); } } static nowString() { return (new Date()).toISOString(); } consolidateReports() { let parts = this.options.path.split('/'); let targetDirectory = this.options.path.split(parts[parts.length - 1])[0]; let reporterDirectory = parts[parts.length - 1].split(/[^a-zA-Z]+/g)[0]; let fs = require('fs'); let output = null; let times = 0; let data; let img; let allData = null; let firstjs = null; let allSequences = null; if (!fs.existsSync(targetDirectory + 'data')) { fs.mkdirSync(targetDirectory + 'data'); } if (!fs.existsSync(targetDirectory + 'img')) { fs.mkdirSync(targetDirectory + 'img'); } fs.readdirSync(targetDirectory).forEach(function (file) { if (file.includes(reporterDirectory)) { if (!(fs.lstatSync(targetDirectory + file + '/report.html').isDirectory())) { fs.readdirSync(targetDirectory + file + '/data').forEach(function (filejs) { let currentDataBuffer = fs.readFileSync(targetDirectory + file + '/data/' + filejs, 'utf8'); let currentData = JSON.parse(currentDataBuffer.slice(20, (currentDataBuffer.length - 2))); if (allData == null) { allData = currentData; firstjs = filejs; allData.sequence.forEach(data => { data.times = 1; data.successTimes = 0; if (data.status === 'passed') { data.successTimes++; } let deepCopySpecs = JSON.parse(JSON.stringify(data)); data.allSpecs = [deepCopySpecs]; }); } else { allData.timer.duration += currentData.timer.duration; allData.counts.specs += currentData.counts.specs; allData.counts.passed += currentData.counts.passed; allData.counts.failed += currentData.counts.failed; allData.counts.pending += currentData.counts.pending; allData.sequence.forEach(function (allDataOneSequence) { currentData.sequence.forEach(function (currentDataOneSequence) { if (allDataOneSequence.description === currentDataOneSequence.description && allDataOneSequence.status != 'disabled') { allDataOneSequence.times++; if (currentDataOneSequence.status === 'passed') { allDataOneSequence.successTimes++; allDataOneSequence.status = 'passed'; } allDataOneSequence.allSpecs = allDataOneSequence.allSpecs.concat(currentDataOneSequence); return; } }); }); } }); fs.readdirSync(targetDirectory + file + '/img').forEach(function (img) { let readStream = fs.createReadStream(targetDirectory + file + '/img/' + img); readStream.pipe(fs.createWriteStream(targetDirectory + 'img/' + img)); }); } if (output == null) { output = fs.readFileSync('node_modules/macedonia-protractor-reporter/consolidatedreport.html'); } times++; } }); fs.writeFileSync(targetDirectory + 'ConsolidatedReport.html', output, 'utf8'); var dataInString = 'window.RESULTS.push(' + JSON.stringify(allData) + ');'; fs.writeFileSync(targetDirectory + 'data/1.js', dataInString, 'utf8'); } } module.exports = Reporter;