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

Take a screenshot on each failed expect #154

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,17 @@ Default is `report.html`.
, cssOverrideFile: 'css/style.css'
});
```

### Option to add inline CSS in reporter (optional)
If you want to tweak some details you can add/overide css rules into the page.

This example will make the image/browserlogs modal window to occupy more screen real-estate
```javascript
new HtmlReporter({
baseDirectory: 'tmp/screenshots'
, customCssInline: `.modal-lg { width: 88% }`,
});
```

### Preserve base directory (optional)
You can preserve (or clear) the base directory using `preserveDirectory:` option:
Expand Down
69 changes: 60 additions & 9 deletions app/reporter.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
var util = require('./util'),
_ = require('underscore'),
path = require('path');
const os = require('os');
var fs = require('fs');
const fse = require('fs-extra');

/** Function: defaultPathBuilder
* This function builds paths for a screenshot file. It is appended to the
Expand Down Expand Up @@ -175,6 +178,7 @@ function ScreenshotReporter(options) {
this.pathBuilder = options.pathBuilder || defaultPathBuilder;
this.docTitle = options.docTitle || 'Test Results';
this.docName = options.docName || 'report.html';
this.screenshotOnFailure = typeof options.screenshotOnFailure !== 'undefined' ? options.screenshotOnFailure : false;
this.metaDataBuilder = options.metaDataBuilder || defaultMetaDataBuilder;
this.jasmine2MetaDataBuilder = options.jasmine2MetaDataBuilder || jasmine2MetaDataBuilder;
this.sortFunction = options.sortFunction || sortFunction;
Expand Down Expand Up @@ -214,16 +218,47 @@ function ScreenshotReporter(options) {
if (!this.preserveDirectory) {
util.removeDirectory(this.finalOptions.baseDirectory);
}
this.screenshotArray = [];
}

function expectFailed(rep) {
var originalAddExpectationResult = jasmine.Spec.prototype.addExpectationResult;
jasmine.Spec.prototype.addExpectationResult = function (passed, expectation) {
var self = rep;

if (!passed && self._screenshotReporter.screenshotOnFailure) {
let baseName = os.tmpdir();
let gUid = util.generateGuid();
let screenShotFileName = path.basename(gUid + '.png');
let screenShotFilePath = path.join(baseName, self._screenshotReporter.screenshotsSubfolder);
let screenShotPath = path.join(screenShotFilePath, screenShotFileName);
self._screenshotReporter.screenshotArray.push(screenShotPath);
try {
browser.takeScreenshot().then(png => {
util.storeScreenShot(png, screenShotPath);
});
}
catch (ex) {
if (ex['name'] === 'NoSuchWindowError') {
console.warn('Protractor-beautiful-reporter could not take the screenshot because target window is already closed');
} else {
console.error(ex);
console.error('Protractor-beautiful-reporter could not take the screenshot');
}
metaData.screenShotFile = void 0;
}
}
return originalAddExpectationResult.apply(this, arguments);
}
};
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 `_awaitAsyncFlow`
* function will wait for the flow to finish before running the next spec. */
* It is a "flow" that we create in `specDone`.
* `suiteDone`, `suiteStarted` and `specStarted` will then add their steps to the flow and the `_awaitAsyncFlow`
* function will wait for the flow to finish before running the next spec. */
this._asyncFlow = null;

this._screenshotReporter = screenshotReporter;
Expand Down Expand Up @@ -304,7 +339,7 @@ class Jasmine2Reporter {
result.browserLogs = await browser.manage().logs().get('browser');

}

async _takeScreenShotAndAddMetaData(result) {

const capabilities = await browser.getCapabilities();
Expand Down Expand Up @@ -342,7 +377,23 @@ class Jasmine2Reporter {
let considerScreenshot = !(this._screenshotReporter.takeScreenShotsOnlyForFailedSpecs && result.status === 'passed')

if (considerScreenshot) {
metaData.screenShotFile = path.join(this._screenshotReporter.screenshotsSubfolder, screenShotFileName);
fse.ensureDir(path.join(this._screenshotReporter.baseDirectory, screenShotFilePath)).then(()=>{
for(let i = 0; i<this._screenshotReporter.screenshotArray.length;i++){
let tmpFilePath = this._screenshotReporter.screenshotArray.pop();
let fileName = tmpFilePath.replace(/^.*[\\\/]/, '');
let newScreenshotFilePath = path.join(this._screenshotReporter.baseDirectory, screenShotFilePath, fileName);
let newFilePath = path.join(this._screenshotReporter.screenshotsSubfolder, fileName);
fs.rename(tmpFilePath,newScreenshotFilePath, err => {
if(err){throw err};
});
this._screenshotReporter.screenshotArray.unshift(newFilePath);
}
this._screenshotReporter.screenshotArray.push(path.join(this._screenshotReporter.screenshotsSubfolder, screenShotFileName));
metaData.screenShotFile = [...this._screenshotReporter.screenshotArray];
this._screenshotReporter.screenshotArray.length = 0;
}).catch(err=>{
console.log(err);
});
}

if (result.browserLogs) {
Expand All @@ -352,7 +403,7 @@ class Jasmine2Reporter {
metaData.timestamp = new Date(result.started).getTime();
metaData.duration = new Date(result.stopped) - new Date(result.started);

let testWasExecuted = ! (['pending','disabled','excluded'].includes(result.status));
let testWasExecuted = ! (['pending', 'disabled', 'excluded'].includes(result.status));
if (testWasExecuted && considerScreenshot) {
try {
const png = await browser.takeScreenshot();
Expand Down Expand Up @@ -399,9 +450,9 @@ class Jasmine2Reporter {
* http://jasmine.github.io/2.1/custom_reporter.html
*/
ScreenshotReporter.prototype.getJasmine2Reporter = function () {

return new Jasmine2Reporter({screenshotReporter: this});

let reporter = new Jasmine2Reporter({ screenshotReporter: this });
expectFailed(reporter);
return reporter;
};


Expand Down
224 changes: 112 additions & 112 deletions examples/protractor.jasmine2.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,118 +4,118 @@ var path = require('path');
// ----- Config example for Jasmine 2 -----

exports.config = {
// ----- How to setup Selenium -----
//
// There are three ways to specify how to use Selenium. Specify one of the
// following:
//
// 1. seleniumServerJar - to start Selenium Standalone locally.
// 2. seleniumAddress - to connect to a Selenium server which is already
// running.
// 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs.

// The location of the selenium standalone server .jar file.
//seleniumServerJar: 'node_modules/protractor/selenium/selenium-server-standalone-2.37.0.jar',

// Address of selenium server (before running protractor, run "webdriver-manager start" to start the Selenium server)
seleniumAddress: process.env.SELENIUMSERVER ? process.env.SELENIUMSERVER : 'http://localhost:4444/wd/hub',

// The port to start the selenium server on, or null if the server should
// find its own unused port.
seleniumPort: null,

// Chromedriver location is used to help the selenium standalone server
// find chromedriver. This will be passed to the selenium jar as
// the system property webdriver.chrome.driver. If null, selenium will
// attempt to find chromedriver using PATH.
//chromeDriver: 'node_modules/protractor/selenium/chromedriver',

// Additional command line options to pass to selenium. For example,
// if you need to change the browser timeout, use
// seleniumArgs: ['-browserTimeout=60'],
seleniumArgs: [],

// If sauceUser and sauceKey are specified, seleniumServerJar will be ignored.
// The tests will be run remotely using SauceLabs.
sauceUser: null,
sauceKey: null,

// ----- What tests to run -----
//
// Spec patterns are relative to the location of this config.
specs: [
'./specs/*.js'
],

// ----- Capabilities to be passed to the webdriver instance ----
//
// For a full list of available capabilities, see
// https://code.google.com/p/selenium/wiki/DesiredCapabilities
// and
// https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
capabilities: {
browserName: 'chrome',
logName: 'Chrome - English',
version: '',
platform: 'ANY',
shardTestFiles: false,
maxInstances: 2,

chromeOptions: {
args: ["--window-size=1680,1000"]
// commented out but needed to keep it for local testing
//, binary: process.env.CHROMIUM_BIN
//
// args:["--headless","--disable-gpu","--window-size=1680,1680"]
// args:["--headless","--disable-gpu","--force-device-scale-factor=1.75","--high-dpi-support=1.75","--window-size=1400,1680"]
}
},

// A base URL for your application under test. Calls to protractor.get()
// with relative paths will be prepended with this.
baseUrl: 'http://localhost:9999',

// Set the framework
framework: 'jasmine',

// Selector for the element housing the angular app - this defaults to
// body, but is necessary if ng-app is on a descendant of <body>
rootElement: 'body',

onPrepare: function () {
// Add a screenshot reporter:
jasmine.getEnv().addReporter(new HtmlReporter({
preserveDirectory: false,
takeScreenShotsOnlyForFailedSpecs: true,
screenshotsSubfolder: 'images',
jsonsSubfolder: 'jsons',
baseDirectory: 'reports-tmp',

pathBuilder: function pathBuilder(spec, descriptions, results, capabilities) {
// Return '<30-12-2016>/<browser>/<specname>' as path for screenshots:
// Example: '30-12-2016/firefox/list-should work'.
var currentDate = new Date(),
day = currentDate.getDate(),
month = currentDate.getMonth() + 1,
year = currentDate.getFullYear();

var validDescriptions = descriptions.map(function (description) {
return description.replace('/', '@');
});

return path.join(
day + "-" + month + "-" + year,
// capabilities.get('browserName'),
validDescriptions.join('-'));
}
}).getJasmine2Reporter());
},

jasmineNodeOpts: {
// If true, print colors to the terminal.
showColors: true,
// Default time to wait in ms before a test fails.
defaultTimeoutInterval: 30000,
// ----- How to setup Selenium -----
//
// There are three ways to specify how to use Selenium. Specify one of the
// following:
//
// 1. seleniumServerJar - to start Selenium Standalone locally.
// 2. seleniumAddress - to connect to a Selenium server which is already
// running.
// 3. sauceUser/sauceKey - to use remote Selenium servers via SauceLabs.

// The location of the selenium standalone server .jar file.
//seleniumServerJar: 'node_modules/protractor/selenium/selenium-server-standalone-2.37.0.jar',

// Address of selenium server (before running protractor, run "webdriver-manager start" to start the Selenium server)
seleniumAddress: process.env.SELENIUMSERVER ? process.env.SELENIUMSERVER : 'http://localhost:4444/wd/hub',

// The port to start the selenium server on, or null if the server should
// find its own unused port.
seleniumPort: null,

// Chromedriver location is used to help the selenium standalone server
// find chromedriver. This will be passed to the selenium jar as
// the system property webdriver.chrome.driver. If null, selenium will
// attempt to find chromedriver using PATH.
//chromeDriver: 'node_modules/protractor/selenium/chromedriver',

// Additional command line options to pass to selenium. For example,
// if you need to change the browser timeout, use
// seleniumArgs: ['-browserTimeout=60'],
seleniumArgs: [],

// If sauceUser and sauceKey are specified, seleniumServerJar will be ignored.
// The tests will be run remotely using SauceLabs.
sauceUser: null,
sauceKey: null,

// ----- What tests to run -----
//
// Spec patterns are relative to the location of this config.
specs: [
'./specs/*.js'
],

// ----- Capabilities to be passed to the webdriver instance ----
//
// For a full list of available capabilities, see
// https://code.google.com/p/selenium/wiki/DesiredCapabilities
// and
// https://code.google.com/p/selenium/source/browse/javascript/webdriver/capabilities.js
capabilities: {
browserName: 'chrome',
logName: 'Chrome - English',
version: '',
platform: 'ANY',
shardTestFiles: false,
maxInstances: 2,

chromeOptions: {
args: ["--window-size=1680,1000"]
// commented out but needed to keep it for local testing
//, binary: process.env.CHROMIUM_BIN
//
// args:["--headless","--disable-gpu","--window-size=1680,1680"]
// args:["--headless","--disable-gpu","--force-device-scale-factor=1.75","--high-dpi-support=1.75","--window-size=1400,1680"]
}
},

// A base URL for your application under test. Calls to protractor.get()
// with relative paths will be prepended with this.
baseUrl: 'http://localhost:9999',

// Set the framework
framework: 'jasmine',

// Selector for the element housing the angular app - this defaults to
// body, but is necessary if ng-app is on a descendant of <body>
rootElement: 'body',

onPrepare: function () {
// Add a screenshot reporter:
jasmine.getEnv().addReporter(new HtmlReporter({
preserveDirectory: false,
takeScreenShotsOnlyForFailedSpecs: true,
screenshotsSubfolder: 'images',
jsonsSubfolder: 'jsons',
baseDirectory: 'reports-tmp',

pathBuilder: function pathBuilder(spec, descriptions, results, capabilities) {
// Return '<30-12-2016>/<browser>/<specname>' as path for screenshots:
// Example: '30-12-2016/firefox/list-should work'.
var currentDate = new Date(),
day = currentDate.getDate(),
month = currentDate.getMonth() + 1,
year = currentDate.getFullYear();

var validDescriptions = descriptions.map(function (description) {
return description.replace('/', '@');
});

return path.join(
day + "-" + month + "-" + year,
// capabilities.get('browserName'),
validDescriptions.join('-'));
}
}).getJasmine2Reporter());
},

jasmineNodeOpts: {
// If true, print colors to the terminal.
showColors: true,
// Default time to wait in ms before a test fails.
defaultTimeoutInterval: 30000,
}
};

Loading