How to take sceenshots only when expect fails, using browser.takeScreenshot() - protractor

I want to capture screenshot whenever Spec fails here is the screenshot helper function I am using:
var fs = require('fs');
function screenshot(filename, path) {
// within a test:
browser.takeScreenshot().then(function (png) {
writeScreenShot(png, filename, path);
});
}
function writeScreenShot(data, filename, path) {
var stream = fs.createWriteStream( path+ filename);
stream.write(new Buffer(data, 'base64'));
stream.end();
}
my Test:
describe('this is a sample test',function(){
var testCaseName = this.getFullName();
it('this is sample spec1',function(){
// Test Steps
expect(A).toBe(B);
});
it('this is sample spec2',function(){
// Test Steps
expect(A).toBe(B);
});
});
I want to take screenshot when ever my test Spec fails, how can I do that ? any suggestions ?

Edit: For Jasmine 2, you can try https://www.npmjs.com/package/protractor-jasmine2-screenshot-reporter. This should be compatible with the latest version of Jasmine.
To install - npm install protractor-jasmine2-screenshot-reporter --save-dev and register the report in your config file.
Then the option for capturing screenshots is:
var reporter = new HtmlScreenshotReporter({
captureOnlyFailedSpecs: true
});
This may be the best option as I believe currentSpec() is no longer available for Jasmine 2.
For Jasmine 1, you have two options you could try. You can do something like this:
function screenshot(filename, path) {
afterEach(function() {
var passed = jasmine.getEnv().currentSpec.results().passed();
if (!passed) {
browser.takeScreenshot().then(function(png) {
writeScreenShot(png, filename, path);
});
}
});
}
Then in your spec:
describe('this is a sample test',function(){
screenshot(filename, path);
Another option is https://github.com/jintoppy/protractor-html-screenshot-reporter. It handles taking screenshots for you and generates an html report. For taking screenshots on failures for that, you would do
new HtmlReporter({
baseDirectory: '/tmp/screenshots'
, takeScreenShotsOnlyForFailedSpecs: true
});

If you want to capture screenshots when expects fail, try this one: https://www.npmjs.com/package/jasmine2-protractor-utils

Related

executeAsyncScript timing out

I am trying to execute executeAsyncScript using the following code:
function get(url) {
var callback = function(args) {
console.log(args);
};
var defer = protractor.promise.defer();
browser.executeAsyncScript(function (url, callback) {
console.log("url" + url);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
console.log(xhr.responseText);
callback(xhr.responseText);
defer.fulfill(xhr);
}
}
xhr.open('GET', url , true);
xhr.send();
}, url);
return defer.promise;
};
function setupCommon() {
return get('https://example.com/rest/api/getsomething');
}
var flow = protractor.promise.controlFlow();
flow.execute(setupCommon);
If I execute the code that is passed to executeAsyncScript directly in the browser console then it works. I get the expected output.
console.log("url" + url);
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == XMLHttpRequest.DONE) {
console.log(xhr.responseText);
callback(xhr.responseText);
defer.fulfill(xhr);
}
}
xhr.open('GET', 'https://example.com/rest/api/getsomething', true);
xhr.send();
But when I execute it using executeAsyncScript, it times out saying:
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
The restapi shouldn't have taken much time. I am new to all this. I am not sure what I am doing wrong. Can someone please help me with this.
The default timeout for Jasmine is 2000 milli-seconds which looks inadequate in your case as it looks like you indeed have lot of steps
Check the config file reference doc here for different timeout configurations from the protractor.conf.js
You can either increase the timeout at config level as in below
defaultTimeoutInterval: 60000,
allScriptsTimeout:90000
Or increase it for this test case alone
this.timeout(60000)
The default timeout for a script to be executed is 0ms. In most cases, including the examples below, one must set the script timeout WebDriver.Timeouts.setScriptTimeout(long, java.util.concurrent.TimeUnit) beforehand to a value sufficiently large enough.
Here is link for Java API that provides above information
https://seleniumhq.github.io/selenium/docs/api/java/org/openqa/selenium/JavascriptExecutor.html#executeAsyncScript-java.lang.String-java.lang.Object...-

How do I send a PDF to Watson's Document Conversion service without writing it to disk first?

I am trying to convert this document (http://www.redbooks.ibm.com/redbooks/pdfs/ga195486.pdf) to answer units in Watson's Document Conversion service using the watson-developer-cloud node.js library.
In the actual program (not this test program), I am retrieving the document and converting it on-the-fly, without writing it to disk first. I have done this before with other documents, but the latest version of the library (v 1.7.0) seems to have changed and it no longer works the way I was using it. But even before I started using the latest version, this particular document would not convert.
The annotated test code that I am using is below. I have tried several ways to get this to work, the variations of which are all commented out under var opts={ below. You have to uncomment one of them at a time to see the results.
'use strict';
var bluemix = require('./bluemix');
var extend=require('util')._extend;
var fs=require('fs');
var watson=require('watson-developer-cloud');
var streams = require('memory-streams');
var dcCredentials = extend({
url: '<url>',
version: 'v1',
username: '<username>',
password: '<password>'
}, bluemix.getServiceCreds('document_conversion')); // VCAP_SERVICES
var document_conversion = watson.document_conversion(dcCredentials);
var bookpdf=getBook('ga195486.pdf');
convert(bookpdf);
function getBook(filename)
{
var bl=fs.readFileSync(filename,'utf8');
return bl;
}
function convert(content)
{
var opts={ //uncomment ONE of these
// file: new Buffer(content), //See message #1 below
// file: {value: new Buffer(content), options: {}}, //see message #2 below
// file: {value: new Buffer(content), options: {contentType: "application/pdf"}}, //This used to work. See message #2 (again) below
// file: new streams.ReadableStream(content),//see message #3 below
conversion_target: "ANSWER_UNITS",
content_type:'application/pdf'
};
document_conversion.convert(opts,
function (err, response)
{
if (err)
{
console.log("Error converting doc: ", err);
}
else if (response.answer_units.length==0)
{
var msg="No answer units";
console.log(msg,response);
}
else
{
console.log('Works!');
console.dir(response);
}
}
);
}
//Message #1: This returns:
// No answer units { source_document_id: '',
// timestamp: '2016-05-23T16:18:23.825Z',
// media_type_detected: 'application/pdf',
// metadata: [],
// answer_units: [],
// warnings:
// [ { phase: 'pdf',
// warning_id: 'empty_input_to_converter',
// description: 'The input provided to the converter phase is empty or doesn\'t contain text that can be converted.' },
// { phase: 'normalized_html',
// warning_id: 'empty_input_to_converter',
// description: 'The input HTML document has no body content.' },
// { phase: 'answer_units',
// warning_id: 'empty_input_to_converter',
// description: 'The input provided to the converter phase is empty or doesn\'t contain text that can be converted.' } ] }
//Message #2: These return:
///home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/node_modules/request/node_modules/combined-stream/node_modules/delayed-stream/lib/delayed_stream.js:33
// source.on('error', function() {});
//
//TypeError: source.on is not a function
// at Function.DelayedStream.create (/home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/node_modules/request/node_modules/combined-stream/node_modules/delayed-stream/lib/delayed_stream.js:33:10)
// at FormData.CombinedStream.append (/home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/node_modules/request/node_modules/combined-stream/lib/combined_stream.js:43:37)
// at FormData.append (/home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/node_modules/request/node_modules/form-data/lib/form_data.js:68:3)
// at appendFormValue (/home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/node_modules/request/request.js:339:21)
// at Request.init (/home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/node_modules/request/request.js:352:11)
// at new Request (/home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/node_modules/request/request.js:142:8)
// at request (/home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/node_modules/request/index.js:55:10)
// at createRequest (/home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/lib/requestwrapper.js:134:10)
// at DocumentConversion.convert (/home/david/git/ccb-contentbridge/node_modules/watson-developer-cloud/services/document_conversion/v1.js:134:10)
// at convert (/home/david/git/ccb-contentbridge/testRedbooks.js:35:24)
//Message #3: This returns and then it hangs there:
//Error converting doc: { code: 400, error: 'Error in the web application' }
Can someone please tell me what I am doing wrong?
That particular file is larger than what the Document Conversion service can currently handle. Unfortunately I don't have very good info on exactly what the limits are right now, but the team is aware of this and looking into making improvements.
If you can provide an example that worked previously but broke with the v1.7.0 of the node.js library, I'll take a look at that and hopefully be able to provide better info.
Oh, and specifying 'utf8' on your fs.readfileSync() call may be causing some of the trouble you're experiencing.
Looks like the limit for Doc Con is 50 MB per this and our documents are smaller than that... must be some other problem.

Karma run single test

I use karma to run tests. I have many tests and running all tests is a very slow process. I want to run only a single test in order to spend less time, because all tests are run about 10 minutes.
Is it possible?
If you are using the Karma/Jasmine stack, use:
fdescribe("when ...", function () { // to [f]ocus on a single group of tests
fit("should ...", function () {...}); // to [f]ocus on a single test case
});
... and:
xdescribe("when ...", function () { // to e[x]clude a group of tests
xit("should ...", function () {...}); // to e[x]clude a test case
});
When you're on Karma/Mocha:
describe.only("when ...", function () { // to run [only] this group of tests
it.only("should ...", function () {...}); // to run [only] this test case
});
... and:
describe.skip("when ...", function () { // to [skip] running this group of tests
it.skip("should ...", function () {...}); // to [skip] running this test case
});
Update: karma has changed.
Now use fit() and fdescribe()
f stands for focused!
For Angular users, try the following ways:
1. Visual Studio Code Extension
The easiest way is to use the vscode-test-explorer extension along with its child angular-karma-test-explorer and jasmine-test-adapter, you'll get a list of current test to run one by one if you want:
UPDATE DEC/2021: The extension has been deprecated, please consider using Karma Test Explorer instead.
2. Karma runner improvement
Currently there's an open issue to improve their current behaviour, you can follow their progress at their github page.
3. Directly modify test.ts
In my case, I wasn't able to use the extension way because of this bug, and so, as stated in this answer; I ended up modifying the test.ts file. For example if you want to test a single file named my.file.name.spec.ts:
// By default context looks like this
const context = require.context('./', true, /\.spec\.ts$/);
// Modify it, so it's RegExp matches the files that you're willing to test.
const context = require.context('./', true, /my\.file\.name\.spec\.ts$/);
For more details about require parameters you may find it here at their wiki.
a) You can pass a pattern that describes your single file as command line argument to the karma start command:
# build and run all tests
$ karma start
# build and run only those tests that are in this dir
$ karma start --grep app/modules/sidebar/tests
# build and run only this test file
$ karma start --grep app/modules/sidebar/tests/animation_test.js
Source: https://gist.github.com/KidkArolis/fd5c0da60a5b748d54b2
b) You can use a Gulp (or Grunt ect.) task that starts Karma for you. This gives you more flexibility on how to execute Karma. You are for example able to pass custom command line arguments to those tasks. This strategy is also useful if you want to implement a watch mode that only executes the changed tests. (The Karma watch mode would execute all tests.) Another use case would be to only execute tests for files with local changes before you do a commit.
Also see Gulp examples below.
c) If you use VisualStudio, you might want to add an external tool command to the context menu of the solution explorer. This way, you can start the test from that context menu instead of using the console. Also see
How to execute custom file specific command / task in Visual Studio?
Example Gulp file
//This gulp file is used to execute the Karma test runner
//Several tasks are available, providing different work flows
//for using Karma.
var gulp = require('gulp');
var karma = require('karma');
var KarmaServerConstructor = karma.Server;
var karmaStopper = karma.stopper;
var watch = require('gulp-watch');
var commandLineArguments = require('yargs').argv;
var svn = require('gulp-svn');
var exec = require('child_process').exec;
var fs = require('fs');
//Executes all tests, based on the specifications in karma.conf.js
//Example usage: gulp all
gulp.task('all', function (done) {
var karmaOptions = { configFile: __dirname + '/karma.conf.js' };
var karmaServer = new KarmaServerConstructor(karmaOptions, done);
karmaServer.on('browsers_change', stopServerIfAllBrowsersAreClosed); //for a full list of events see http://karma-runner.github.io/1.0/dev/public-api.html
karmaServer.start();
});
//Executes only one test which has to be passed as command line argument --filePath
//The option --browser also has to be passed as command line argument.
//Example usage: gulp single --browser="Chrome_With_Saved_DevTools_Settings" --filePath="C:\myTest.spec.js"
gulp.task('single', function (done) {
var filePath = commandLineArguments.filePath.replace(/\\/g, "/");
var karmaOptions = {
configFile: __dirname + '/karma.conf.js',
action: 'start',
browsers: [commandLineArguments.browser],
files: [
'./Leen.Managementsystem/bower_components/jquery/dist/jquery.js',
'./Leen.Managementsystem/bower_components/globalize/lib/globalize.js',
{ pattern: './Leen.Managementsystem/bower_components/**/*.js', included: false },
{ pattern: './Leen.Managementsystem.Tests/App/test/mockFactory.js', included: false },
{ pattern: './Leen.Managementsystem/App/**/*.js', included: false },
{ pattern: './Leen.Managementsystem.Tests/App/test/*.js', included: false },
{ pattern: filePath, included: false },
'./Leen.Managementsystem.Tests/App/test-main.js',
'./switchKarmaToDebugTab.js' //also see https://stackoverflow.com/questions/33023535/open-karma-debug-html-page-on-startup
]
};
var karmaServer = new KarmaServerConstructor(karmaOptions, done);
karmaServer.on('browsers_change', stopServerIfAllBrowsersAreClosed);
karmaServer.start();
});
//Starts a watch mode for all *.spec.js files. Executes a test whenever it is saved with changes.
//The original Karma watch mode would execute all tests. This watch mode only executes the changed test.
//Example usage: gulp watch
gulp.task('watch', function () {
return gulp //
.watch('Leen.Managementsystem.Tests/App/**/*.spec.js', handleFileChanged)
.on('error', handleGulpError);
function handleFileChange(vinyl) {
var pathForChangedFile = "./" + vinyl.replace(/\\/g, "/");
var karmaOptions = {
configFile: __dirname + '/karma.conf.js',
action: 'start',
browsers: ['PhantomJS'],
singleRun: true,
files: [
'./Leen.Managementsystem/bower_components/jquery/dist/jquery.js',
'./Leen.Managementsystem/bower_components/globalize/lib/globalize.js',
{ pattern: './Leen.Managementsystem/bower_components/**/*.js', included: false },
{ pattern: './Leen.Managementsystem.Tests/App/test/mockFactory.js', included: false },
{ pattern: './Leen.Managementsystem/App/**/*.js', included: false },
{ pattern: './Leen.Managementsystem.Tests/App/test/*.js', included: false },
{ pattern: pathForChangedFile, included: false },
'./Leen.Managementsystem.Tests/App/test-main.js'
]
};
var karmaServer = new KarmaServerConstructor(karmaOptions);
karmaServer.start();
}
});
//Executes only tests for files that have local changes
//The option --browser has to be passed as command line arguments.
//Example usage: gulp localChanges --browser="Chrome_With_Saved_DevTools_Settings"
gulp.task('localChanges', function (done) {
exec('svn status -u --quiet --xml', handleSvnStatusOutput);
function handleSvnStatusOutput(error, stdout, stderr) {
if (error) {
throw error;
}
var changedJsFiles = getJavaScriptFiles(stdout);
var specFiles = getSpecFiles(changedJsFiles);
if(specFiles.length>0){
console.log('--- Following tests need to be executed for changed files: ---');
specFiles.forEach(function (file) {
console.log(file);
});
console.log('--------------------------------------------------------------');
} else{
console.log('Finsihed: No modified files need to be tested.');
return;
}
var files = [
'./Leen.Managementsystem/bower_components/jquery/dist/jquery.js',
'./Leen.Managementsystem/bower_components/globalize/lib/globalize.js',
{ pattern: './Leen.Managementsystem/bower_components/**/*.js', included: false },
{ pattern: './Leen.Managementsystem.Tests/App/test/mockFactory.js', included: false },
{ pattern: './Leen.Managementsystem/App/**/*.js', included: false },
{ pattern: './Leen.Managementsystem.Tests/App/test/*.js', included: false }];
specFiles.forEach(function (file) {
var pathForChangedFile = "./" + file.replace(/\\/g, "/");
files = files.concat([{ pattern: pathForChangedFile, included: false }]);
});
files = files.concat([ //
'./Leen.Managementsystem.Tests/App/test-main.js', //
'./switchKarmaToDebugTab.js'
]);
var karmaOptions = {
configFile: __dirname + '/karma.conf.js',
action: 'start',
singleRun: false,
browsers: [commandLineArguments.browser],
files: files
};
var karmaServer = new KarmaServerConstructor(karmaOptions, done);
karmaServer.on('browsers_change', stopServerIfAllBrowsersAreClosed);
karmaServer.start();
}
});
function getJavaScriptFiles(stdout) {
var jsFiles = [];
var lines = stdout.toString().split('\n');
lines.forEach(function (line) {
if (line.includes('js">')) {
var filePath = line.substring(9, line.length - 3);
jsFiles.push(filePath);
}
});
return jsFiles;
}
function getSpecFiles(jsFiles) {
var specFiles = [];
jsFiles.forEach(function (file) {
if (file.endsWith('.spec.js')) {
specFiles.push(file);
} else {
if (file.startsWith('Leen\.Managementsystem')) {
var specFile = file.replace('Leen\.Managementsystem\\', 'Leen.Managementsystem.Tests\\').replace('\.js', '.spec.js');
if (fs.existsSync(specFile)) {
specFiles.push(specFile);
} else {
console.error('Missing test: ' + specFile);
}
}
}
});
return specFiles;
}
function stopServerIfAllBrowsersAreClosed(browsers) {
if (browsers.length === 0) {
karmaStopper.stop();
}
}
function handleGulpError(error) {
throw error;
}
Example settings for ExternalToolCommand in VisualStudio:
Title: Run Karma using Chrome
Command: cmd.exe
Arguments: /c gulp single
--browser="Chrome_With_Saved_DevTools_Settings" --filePath=$(ItemPath)
Initial directory: $(SolutionDir)
Use Output window: true
If you want to run karma test with angular, You just need to modify your test.ts file.
Find line const context = require.context('./', true, /\.spec\.ts$/);
If you want to run your.component.spec.ts modify line to: const context = require.context('./', true, /your\.component\.spec\.ts$/);
Changing it() to iit() should work for running single test.
Also, similar, for describe() block we can use ddescribe()
Change your karma conf to only include the test you want to run instead of a full directory.
Inside the files : [...]
You might want to comment the preprocessors if you need/want to debug your test in chrome to avoid having your js minified.
Yes, this is an old thread.
The following situation has occurred on me 2 - 3 times now in the past few years. More so when I haven't done much unit testing and have come back to it.
I started up my Karma and found the tests, after initial start up, should have completed within 1 second to now take 20 seconds. Additionally, attempting to debug the unit tests within Chrome became tediously slow. The network tab showed all the files taking 2 - 3 seconds per file.
Solution: I didn't realize Fiddler was open. Close it and restart your tests.
Answer proposal for special Angular/IE case: The only thing that worked so far for me using "karma-ie-launcher", in order to run IE as browser, was modifying "include" property of tsconfig.spec.json to explicitly reference target test file using universal qualified path and not globs e.g. "C:\filepath\my-test.spec.ts", for compilation purposes. "In addition" the test.ts file should be appropriately amended to target said file for test file limitation purposes. Be aware that the cache will need to be initially deleted in IE for this scheme to take effect.
(For Angular/Chrome case modification of test.ts alone would be sufficient.)

Is there a way to create and run a dynamic script from karma.conf.js

I'm using karma to run tests on an angularjs application.
There are a couple JavaScript functions that I would like to run at start-up, but they need to be dynamically created based on some system data. When running the app, this is handled with node.
Is there any way to create a script as a var and pass it to the files: [] rather than just using a pattern to load an existing file?
I can make this work by creating the file, saving it to disk then loading it normally, but that's messy.
You can create your own karma preprocessor script.
For a starting point use the following as example:
var fs = require('fs'),
path = require('path');
var createCustomPreprocessor = function (config, helper, logger) {
var log = logger.create('custom'),
// get here the configuration set in the karma.conf.js file
customConfig = config.customConfig || {};
// read more config here in case needed
...
// a preprocessor has to return a function that when completed will call
// the done callback with the preprocessed content
return function (content, file, done) {
log.debug('custom: processing "%s"\n', file.originalPath);
// your crazy code here
fs.writeFile(path.join(outputDirectory, name), ... , function (err) {
if (err) {
log.error(err);
}
done(content);
});
}
};
createCustomPreprocessor.$inject = ['config', 'helper', 'logger'];
module.exports = {
'preprocessor:custom': ['factory', createCustomPreprocessor]
};
Add a package.json with the dependencies and serve it as a module. ;)
For more examples have a look to more modules here: https://www.npmjs.org/search?q=karma%20preprocessor

require.js synchronous loading

I'd like to define a module which computes a new dependancy, fetches it and then returns the result. Like so:
define(['defaults', 'get_config_name'], function(defaults, get_config_name) {
var name = get_config_name();
var config;
require.synchronous([configs / '+name'], function(a) {
config = defaults.extend(a);
});
return config;
});
Is there a way to do this or a better way to attack this problem?
You may try to use synchronous RequireJS call require('configs/'+get_config_name()), but it will load a module synchronously only if it is already loaded, otherwise it will throw an exception. Loading module/JavaScript file synchronously is technically impossible.
UPD: It's possible (see Henrique's answer) but highly unrecommended. It blocks JavaScript execution that causes to freezing of the entire page. So, RequireJS doesn't support it.
From your use case it seems that you don't need synchronous RequireJS, you need to return result asynchronously.
AMD pattern allows to define dependencies and load them asynchronously, but module's factory function must return result synchronously. The solution may be in using loader plugin (details here and here):
// config_loader.js
define(['defaults', 'get_config_name'], function(defaults, get_config_name) {
return {
load: function (resourceId, require, load) {
var config_name = 'configs/' + get_config_name();
require([config_name], function(config) {
load(defaults.extend(config));
})
}
}
});
// application.js
define(['config_loader!'], function(config) {
// code using config
});
If get_config_name() contains simple logic and doesn't depend on another modules, the better and simpler is calculating on the fly paths configuration option, or in case your config depends on context - map configuration option.
function get_config_name() {
// do something
}
require.config({
paths: {
'config': 'configs/' + get_config_name()
}
});
require(['application', 'defaults', 'config'], function(application, defaults, config) {
config = defaults.extend(config);
application.start(config);
});
Loading JavaScript synchronously is NOT technically impossible.
function loadJS(file){
var js = $.ajax({ type: "GET", url: file, async: false }).responseText; //No need to append
}
console.log('Test is loading...');
loadJS('test.js');
console.log('Test was loaded:', window.loadedModule); //loadedModule come from test.js