Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature bundle #29

Merged
merged 16 commits into from
Dec 22, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,14 @@ Filename for html report.

Default is <code>report.html</code>

### Use External CSS (optional)

Array of filenames that specifies extra css files to include in the html report.

<pre><code>jasmine.getEnv().addReporter(new HtmlScreenshotReporter({
userCss: 'my-report-styles.css'
}));</code></pre>

### Ignore pending specs (optional)

When this option is enabled, reporter will not create screenshots for pending / disabled specs. Only executed specs will be captured.
Expand Down Expand Up @@ -76,6 +84,58 @@ This option is __enabled by default__ - in combination with <code>captureOnlyFai
captureOnlyFailedSpecs: true
}));</code></pre>

### Display summary in report (optional)

This option is __enabled by default__ - it will display the total number of specs and the number of failed specs in a short summary at the beginnning of the report.

<pre><code>jasmine.getEnv().addReporter(new HtmlScreenshotReporter({
showSummary: true
}));</code></pre>

Default is <code>true</code>

### Display links to failed specs in report summary (optional)

If this option is enabled with the report summary, it will display a link to each failed spec as a part of the short summary at the beginnning of the report.

<pre><code>jasmine.getEnv().addReporter(new HtmlScreenshotReporter({
showSummary: true,
showQuickLinks: true
}));</code></pre>

Default is <code>false</code>

### Display configuration summary in report (optional)

This option is __enabled by default__ - it will display a summary of the test configuration details at the end of the report.

<pre><code>jasmine.getEnv().addReporter(new HtmlScreenshotReporter({
showConfiguration: true
}));</code></pre>

Default is <code>true</code>

### Report title (optional)

This option will add a title to the report.

<pre><code>jasmine.getEnv().addReporter(new HtmlScreenshotReporter({
reportTitle: "Report Title"
}));</code></pre>

Default is <code>'Report'</code>

### Extra configuration summary items (optional)

The user may specify a set of key/value pairs that are appended to the configuration report.

<pre><code>jasmine.getEnv().addReporter(new HtmlScreenshotReporter({
configurationStrings: {
"My 1st Param": firstParam,
"My 2nd Param": secondParam
}
}));</code></pre>

### Path Builder (optional)

Function used to build custom paths for screenshots. For example:
Expand Down
192 changes: 168 additions & 24 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var DEFAULT_DESTINATION = 'target/screenshots';

var fs = require('fs'),
mkdirp = require('mkdirp'),
rimraf = require('rimraf'),
_ = require('lodash'),
path = require('path'),
hat = require('hat');
Expand All @@ -22,6 +23,19 @@ function Jasmine2ScreenShotReporter(opts) {
failed: '<span class="failed">&#10007;</span>',
passed: '<span class="passed">&#10003;</span>'
},

statusCssClass = {
pending: 'pending',
failed: 'failed',
passed: 'passed'
},

// store extra css files.
cssLinks = [],

// monitor failed specs for quick links
failedSpecIds = [],

// when use use fit, jasmine never calls suiteStarted / suiteDone, so make a fake one to use
fakeFocusedSuite = {
id: 'focused',
Expand All @@ -30,7 +44,7 @@ function Jasmine2ScreenShotReporter(opts) {
};

var linkTemplate = _.template(
'<li>' +
'<li id="<%= id %>" class="<%= cssClass %>">' +
'<%= mark %>' +
'<a href="<%= filename %>"><%= name %></a> ' +
'(<%= duration %> s)' +
Expand All @@ -39,7 +53,7 @@ function Jasmine2ScreenShotReporter(opts) {
);

var nonLinkTemplate = _.template(
'<li title="No screenshot was created for this test case.">' +
'<li title="No screenshot was created for this test case." id="<%= id %>" class="<%= cssClass %>">' +
'<%= mark %>' +
'<%= name %> ' +
'(<%= duration %> s)' +
Expand All @@ -54,12 +68,16 @@ function Jasmine2ScreenShotReporter(opts) {
'<style>' +
'body { font-family: Arial; }' +
'ul { list-style-position: inside; }' +
'.passed { padding: 0 1em; color: green; }' +
'.failed { padding: 0 1em; color: red; }' +
'.pending { padding: 0 1em; color: orange; }' +
'span.passed { padding: 0 1em; color: green; }' +
'span.failed { padding: 0 1em; color: red; }' +
'span.pending { padding: 0 1em; color: orange; }' +
'</style>' +
'<%= userCss %>' +
'</head>' +
'<body><%= report %></body>' +
'<body>' +
'<h1><%= title %></h1>' +
'<%= report %>' +
'</body>' +
'</html>'
);

Expand All @@ -71,6 +89,37 @@ function Jasmine2ScreenShotReporter(opts) {
'</ul>'
);

var summaryTemplate = _.template(
'<div id="summary" class="<%= cssClass %>">' +
'<h4>Summary</h4>' +
'<%= summaryBody %>' +
'<%= quickLinks %>' +
'</div>'
);

var configurationTemplate = _.template(
'<div id="config">' +
'<h4>Configuration</h4>' +
'<%= configBody %>' +
'</div>'
);

var objectToItemTemplate = _.template(
'<li>' +
'<%= key %>: <%= value %>' +
'</li>'
);

var quickLinksTemplate = _.template(
'<ul id="quickLinks"><%= quickLinks %></ul>'
);

var quickLinkListItemTemplate = _.template(
'<li>' +
'<a href="#<%= specId %>"><%= specId %></a>' +
'</li>'
);

// write data into opts.dest as filename
var writeScreenshot = function (data, filename) {
var stream = fs.createWriteStream(opts.dest + filename);
Expand Down Expand Up @@ -160,6 +209,16 @@ function Jasmine2ScreenShotReporter(opts) {
return getDestination() + hat() + '/';
};

var getCssLinks = function(cssFiles) {
var cssLinks = '';

_.each(cssFiles, function(file) {
cssLinks +='<link type="text/css" rel="stylesheet" href="' + file + '">';
});

return cssLinks;
};

// TODO: more options
opts = opts || {};
opts.preserveDirectory = opts.preserveDirectory || false;
Expand All @@ -170,23 +229,38 @@ function Jasmine2ScreenShotReporter(opts) {
opts.captureOnlyFailedSpecs = opts.captureOnlyFailedSpecs || false;
opts.pathBuilder = opts.pathBuilder || pathBuilder;
opts.metadataBuilder = opts.metadataBuilder || metadataBuilder;

this.jasmineStarted = function() {
mkdirp(opts.dest, function(err) {
var files;

opts.userCss = Array.isArray(opts.userCss) ? opts.userCss : opts.userCss ? [ opts.userCss ] : [];
opts.totalSpecsDefined = null;
opts.failedSpecs = 0;
opts.showSummary = opts.showSummary || true;
opts.showQuickLinks = opts.showQuickLinks || false;
opts.browserCaps = {};
opts.configurationStrings = opts.configurationStrings || {};
opts.showConfiguration = opts.showConfiguration || true;
opts.reportTitle = opts.reportTitle || 'Report';


this.jasmineStarted = function(suiteInfo) {
opts.totalSpecsDefined = suiteInfo.totalSpecsDefined;

rimraf(opts.dest, function(err) {
if(err) {
throw new Error('Could not remove previous destination directory ' + opts.dest);
}

mkdirp(opts.dest, function(err) {
if(err) {
throw new Error('Could not create directory ' + opts.dest);
throw new Error('Could not create directory ' + opts.dest);
}
});
});

files = fs.readdirSync(opts.dest);

_.each(files, function(file) {
var filepath = opts.dest + file;
if (fs.statSync(filepath).isFile()) {
fs.unlinkSync(filepath);
}
});
browser.getCapabilities().then(function (capabilities) {
opts.browserCaps.browserName = capabilities.get('browserName');
opts.browserCaps.browserVersion = capabilities.get('version');
opts.browserCaps.platform = capabilities.get('platform');
opts.browserCaps.javascriptEnabled = capabilities.get('javascriptEnabled');
opts.browserCaps.cssSelectorsEnabled = capabilities.get('cssSelectorsEnabled');
});
};

Expand Down Expand Up @@ -235,15 +309,20 @@ function Jasmine2ScreenShotReporter(opts) {
return;
}

file = opts.pathBuilder(spec, suites);
spec.filename = file + '.png';
if (spec.status === 'failed') {
opts.failedSpecs += 1;
failedSpecIds.push(spec.id);
}

browser.takeScreenshot().then(function (png) {
browser.getCapabilities().then(function (capabilities) {
var screenshotPath,
metadataPath,
metadata;

file = opts.pathBuilder(spec, suites, capabilities);
spec.filename = file + '.png';

screenshotPath = path.join(opts.dest, spec.filename);
metadata = opts.metadataBuilder(spec, suites, capabilities);

Expand Down Expand Up @@ -274,6 +353,11 @@ function Jasmine2ScreenShotReporter(opts) {
// focused spec (fit) -- suiteDone was never called
self.suiteDone(fakeFocusedSuite);
}

if (opts.showSummary) {
output += printTestSummary();
}

_.each(suites, function(suite) {
output += printResults(suite);
});
Expand All @@ -283,9 +367,17 @@ function Jasmine2ScreenShotReporter(opts) {
output += printSpec(spec);
});

if (opts.showConfiguration) {
output += printTestConfiguration();
}

var cssLinks = getCssLinks(opts.userCss);

fs.appendFileSync(
opts.dest + opts.filename,
reportTemplate({ report: output}),
reportTemplate({ report: output,
title: opts.reportTitle,
userCss: cssLinks}),
{ encoding: 'utf8' },
function(err) {
if(err) {
Expand All @@ -308,6 +400,8 @@ function Jasmine2ScreenShotReporter(opts) {

return template({
mark: marks[spec.status],
cssClass: statusCssClass[spec.status],
id: spec.id,
name: spec.fullName.replace(suiteName, '').trim(),
reason: printReasonsForFailure(spec),
filename: encodeURIComponent(spec.filename),
Expand All @@ -326,7 +420,7 @@ function Jasmine2ScreenShotReporter(opts) {
suite.isPrinted = true;

output += '<ul style="list-style-type:none">';
output += '<h4>' + suite.fullName + ' (' + getDuration(suite) + ' s)</h4>';
output += '<h4>' + suite.description + ' (' + getDuration(suite) + ' s)</h4>';

_.each(suite._specs, function(spec) {
spec = specs[spec.id];
Expand All @@ -352,6 +446,56 @@ function Jasmine2ScreenShotReporter(opts) {
return reasonsTemplate({ reasons: spec.failedExpectations });
}

function printTestSummary() {
var summary = {
"Total specs": opts.totalSpecsDefined,
"Failed specs": opts.failedSpecs
};

var cssClass = opts.failedSpecs > 0 ? statusCssClass["failed"] : statusCssClass["passed"];
var keys = Object.keys(summary);

var summaryOutput = "";
_.each(keys, function(key) {
summaryOutput += objectToItemTemplate({"key": key, "value": summary[key]});
});

var quickLinks = opts.showQuickLinks ? printFailedSpecQuickLinks() : '';

return summaryTemplate({"summaryBody": summaryOutput, "cssClass": cssClass, "quickLinks": quickLinks});
}

function printFailedSpecQuickLinks() {
var quickLinksOutput = "";
_.each(failedSpecIds, function(id) {
quickLinksOutput += quickLinkListItemTemplate({specId: id});
});

return quickLinksTemplate({quickLinks: quickLinksOutput});
}

function printTestConfiguration() {
var testConfiguration = {
"Jasmine version": jasmine.version,
"Browser name": opts.browserCaps.browserName,
"Browser version": opts.browserCaps.browserVersion,
"Platform": opts.browserCaps.platform,
"Javascript enabled": opts.browserCaps.javascriptEnabled,
"Css selectors enabled": opts.browserCaps.cssSelectorsEnabled
};

testConfiguration = _.assign(testConfiguration, opts.configurationStrings);

var keys = Object.keys(testConfiguration);

var configOutput = "";
_.each(keys, function(key) {
configOutput += objectToItemTemplate({"key": key, "value": testConfiguration[key]});
});

return configurationTemplate({"configBody": configOutput});
}

return this;
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"hat": "0.0.3",
"lodash": "^3.0.0",
"mkdirp": "^0.5.0",
"rimraf": "^2.4.3",
"string.prototype.startswith": "^0.2.0"
},
"keywords": [
Expand Down