Socket
Socket
Sign inDemoInstall

grunt-saucelabs

Package Overview
Dependencies
Maintainers
4
Versions
56
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

grunt-saucelabs - npm Package Compare versions

Comparing version 7.0.0 to 8.0.0

.editorconfig

6

CONTRIBUTING.md

@@ -12,7 +12,7 @@ # Please send all pull requests to the __incoming-pr__ branch.

2. Travis runs only jslint on your pull request.
* If the pull request tests fail, please correct the lint errors
* If the pull request tests fail, please correct the lint errors
3. Once the pull request passes the lint test, you pull request is merged into `incoming-pr` branch.
4. Travis runs *ALL* tests on `incoming-pr` branch. Since `incoming-pr` has access to the secure environment variables, it runs the saucelabs tests also.
* If the saucelabs tests fail, `incoming-pr` is reverted to its original state.
* Your changes are preserved in a separate branch. Please take a look at this branch and fix any failing tests
* If the saucelabs tests fail, `incoming-pr` is reverted to its original state.
* Your changes are preserved in a separate branch. Please take a look at this branch and fix any failing tests
5. Once travis on the `incoming-pr` branch succeeds, your commits are automatically merged into `master`.

@@ -19,0 +19,0 @@

@@ -1,135 +0,63 @@

module.exports = function(grunt) {
var browsers = [{
browserName: 'firefox',
version: '19',
platform: 'XP'
}, {
browserName: 'googlechrome',
platform: 'XP'
}, {
browserName: 'googlechrome',
platform: 'linux'
}, {
browserName: 'internet explorer',
platform: 'WIN8',
version: '10'
}, {
browserName: 'internet explorer',
platform: 'VISTA',
version: '9'
}];
'use strict';
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
options: {
jshintrc: __dirname + '/.jshintrc'
},
files: ['bin/grunt-saucelabs-qunit',
'tasks/**/*.js',
'test/qunit/grunt-saucelabs-inject.js',
'Gruntfile.js'],
},
connect: {
server: {
options: {
base: 'test',
port: 9999
}
}
},
module.exports = function (grunt) {
var Q = require('q');
var request = require('request');
var tunnelId = Math.floor((new Date()).getTime() / 1000 - 1230768000).toString();
'saucelabs-yui': {
all: {
//username: '',
//key: '',
options: {
urls: ['http://127.0.0.1:9999/yui/index.html'],
build: process.env.TRAVIS_JOB_ID,
browsers: browsers,
testname: "yui tests",
sauceConfig: {
'video-upload-on-pass': false
}
}
}
},
'saucelabs-mocha': {
all: {
//username: '',
//key: '',
options: {
urls: ['http://127.0.0.1:9999/mocha/test/browser/index.html'],
build: process.env.TRAVIS_JOB_ID,
browsers: browsers,
testname: "mocha tests",
sauceConfig: {
'video-upload-on-pass': false
}
}
}
},
'saucelabs-custom': {
all: {
//username: '',
//key: '',
options: {
urls: ['http://127.0.0.1:9999/custom/custom.html'],
build: process.env.TRAVIS_JOB_ID,
browsers: browsers,
testname: "custom tests",
sauceConfig: {
'video-upload-on-pass': false
}
}
}
},
'saucelabs-qunit': {
all: {
//username: '',
//key: '',
options: {
urls: ['http://127.0.0.1:9999/qunit/index.html'],
build: process.env.TRAVIS_JOB_ID,
browsers: browsers,
testname: "qunit tests",
sauceConfig: {
'video-upload-on-pass': false
}
}
}
},
'saucelabs-jasmine': {
all: {
//username: 'parashu',
//key: '',
options: {
urls: ['http://127.0.0.1:9999/jasmine/SpecRunner.html', 'http://127.0.0.1:9999/jasmine/SpecRunnerDos.html'],
build: process.env.TRAVIS_JOB_ID,
browsers: browsers,
testname: "jasmine tests",
throttled: 3,
sauceConfig: {
'video-upload-on-pass': false
}
}
}
},
watch: {}
});
var negateResult = function (result, callback) {
// Reverses the job's passed status. Can be used as the onTestComplete callback for
//the negative tests.
return Q.nfcall(request.put, {
url: [
'https://saucelabs.com/rest/v1',
process.env.SAUCE_USERNAME,
'jobs',
result.job_id
].join('/'),
auth: { user: process.env.SAUCE_USERNAME, pass: process.env.SAUCE_ACCESS_KEY },
json: { passed: !result.passed }
})
.then(function (resp) {
var response = resp[0];
var body = resp[1];
var error;
grunt.loadTasks('tasks');
if (response.statusCode !== 200) {
error = [
'Unexpected response from the Sauce Labs API.',
request.method + ' ' + request.url,
'Response status: ' + response.statusCode,
'Response body: ' + JSON.stringify(body)
].join('\n');
grunt.log.error(error);
throw error;
}
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-contrib-watch');
return !result.passed;
})
.nodeify(callback);
};
var testjobs = ['jshint', 'connect'];
if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined'){
testjobs = testjobs.concat(['saucelabs-qunit', 'saucelabs-jasmine', 'saucelabs-yui', 'saucelabs-mocha', 'saucelabs-custom']);
}
grunt.task.loadTasks('tasks');
grunt.registerTask("dev", ["connect", "watch"]);
grunt.registerTask('test', testjobs);
grunt.registerTask('default', ['test']);
require('load-grunt-config')(grunt, {
data: {
srcFiles: ['tasks/**/*.js', 'src/**/*.js', 'Gruntfile.js'],
negateResult: negateResult,
tunnelId: tunnelId,
baseSaucelabsTaskOptions: {
build: process.env.TRAVIS_JOB_ID,
browsers: [{
browserName: 'googlechrome',
platform: 'XP'
}],
tunneled: false,
sauceConfig: {
'video-upload-on-pass': false,
'tunnel-identifier': tunnelId
}
}
}
});
};
{
"name": "grunt-saucelabs",
"description": "Grunt task running tests using Sauce Labs. Supports QUnit, Jasmine, Mocha and YUI tests",
"version": "7.0.0",
"version": "8.0.0",
"homepage": "https://github.com/axemclion/grunt-saucelabs",

@@ -43,5 +43,10 @@ "author": {

"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-connect": "~0.7.1",
"grunt-contrib-jshint": "~0.10.0",
"grunt-contrib-connect": "~0.7.1",
"grunt-contrib-watch": "~0.6.1",
"grunt-jscs-checker": "^0.4.4",
"grunt-sauce-tunnel": "^0.2.1",
"load-grunt-config": "^0.9.2",
"merge": "^1.1.3",
"publish": "~0.3.2"

@@ -48,0 +53,0 @@ },

@@ -44,3 +44,3 @@ grunt-saucelabs

all: {
options: {
options: {
username: 'saucelabs-user-name', // if not provided it'll default to ENV SAUCE_USERNAME (if applicable)

@@ -53,4 +53,4 @@ key: 'saucelabs-key', // if not provided it'll default to ENV SAUCE_ACCESS_KEY (if applicable)

browserName: 'firefox',
version: '19',
platform: 'XP'
version: '19',
platform: 'XP'
}],

@@ -84,3 +84,3 @@ onTestComplete: function(result){

* __username__ : The Sauce Labs username that will be used to connect to the servers. If not provided, uses the value of SAUCE_USERNAME environment variable. _Optional_
* __key__ : The Sauce Labs secret key. Since this is a secret, this should not be checked into the source code and may be available as an environment variable. Grunt can access this using `process.env.saucekey`. Will also default to SAUCE_ACCESS_KEY environment variable. _Optional_
* __key__ : The Sauce Labs secret key. Since this is a secret, this should not be checked into the source code and may be available as an environment variable. Grunt can access this using `process.env.saucekey`. Will also default to SAUCE_ACCESS_KEY environment variable. _Optional_
* __urls__: An array or URLs that will be loaded in the browsers, one after another. Since SauceConnect is used, these URLs can also be localhost URLs that are available using the `server` task from grunt. _Required_

@@ -93,6 +93,6 @@ * __build__: The build number for this test. _Optional_

* __testInterval__ : Number of milliseconds between each retry to see if a test is completed or not (default: 2000). _Optional_
* __thottled__: Maximum number of unit test pages which will be sent to Sauce Labs concurrently. The maximum number of jobs you may have outstanding is this times the number of browsers, can be used to mitigate concurrency failures if you have a lot of unit test pages. _Optional_
* __throttled__: Maximum number of unit test pages which will be sent to Sauce Labs concurrently. The maximum number of jobs you may have outstanding is this times the number of browsers, can be used to mitigate concurrency failures if you have a lot of unit test pages. _Optional_
* __max-duration__: Maximum duration of a test, this is actually a Selenium Capability. Sauce Labs defaults to 180 seconds for js unit tests. _Optional_
* __browsers__: An array of objects representing the [various browsers](https://saucelabs.com/docs/platforms) on which this test should run. _Optional_
* __onTestComplete__ : A callback that is called every time a unit test for a page is complete. Runs per page, per browser configuration. Receives a 'result' argument which is the javascript object exposed to sauce labs. A true or false return value passes or fails the test, undefined return value does not alter the result of the test. For async results, call `this.async()` in the function. The return of `this.async()` is a function that should be called once the async action is completed. _Optional_
* __browsers__: An array of objects representing the [various browsers](https://saucelabs.com/docs/platforms) on which this test should run. _Optional_
* __onTestComplete__ : A callback that is called every time a unit test for a page is complete. Runs per page, per browser configuration. Receives two arguments `(result, callback)`. `result` is the javascript object exposed to sauce labs as the results of the test. `callback` must be called, node-style (having arguments `err`, `result` where result is a true/false boolean which sets the test result reported to the command line) _Optional_

@@ -218,2 +218,7 @@ A typical `test` task running from Grunt could look like `grunt.registerTask('test', ['server', 'qunit', 'saucelabs-qunit']);` This starts a server and then runs the QUnit tests first on PhantomJS and then using the Sauce Labs browsers.

---------
####8.0.0####
* Major refactor, thanks to all the work done by @gvas
* async `onTestComplete` callback fixed. Now the callback is passed two args: result, callback. `callback` is a node style callback (err, result);
* `/examples` directory added, while the actual tests and Gruntfile are now more complicated (and useful)
####7.0.0####

@@ -220,0 +225,0 @@ * `throttled` parameter now represents the max number of jobs sent concurrently. Previously was `throttled * browsers.length`

@@ -1,278 +0,135 @@

module.exports = function(grunt) {
var _ = require('lodash'),
rqst = require('request'),
SauceTunnel = require('sauce-tunnel'),
Q = require('q'),
scheduler = require('./promise-scheduler');
'use strict';
//these result parsers return true if the tests all passed
var resultParsers = {
jasmine: function(result){
return result.passed;
},
qunit: function(result){
if (result.passed === undefined){ return undefined; }
return result.passed == result.total;
},
mocha: function(result){
if (result.passes === undefined){ return undefined; }
return result.failures === 0;
},
'YUI Test': function(result){
if (result.passed === undefined){ return undefined; }
return result.passed == result.total;
},
custom: function(result){
if (result.passed === undefined){ return undefined; }
return result.failed === 0;
}
};
module.exports = function (grunt) {
var Q = require('q');
var SauceTunnel = require('sauce-tunnel');
var TestRunner = require('../src/TestRunner');
var TestResult = function(jobId, user, key, framework, testInterval){
var url = 'https://saucelabs.com/rest/v1/' + user + '/js-tests/status';
var deferred = Q.defer();
function reportProgress(notification) {
switch (notification.type) {
case 'tunnelOpen':
grunt.log.writeln('=> Starting Tunnel to Sauce Labs'.inverse.bold);
break;
case 'tunnelOpened':
grunt.log.ok('Connected to Saucelabs');
break;
case 'tunnelClose':
grunt.log.writeln('=> Stopping Tunnel to Sauce Labs'.inverse.bold);
break;
case 'tunnelEvent':
if (notification.verbose) {
grunt.verbose[notification.method](notification.text);
} else {
grunt.log[notification.method](notification.text);
}
break;
case 'jobStarted':
grunt.log.writeln('\n', notification.startedJobs, '/', notification.numberOfJobs, 'tests started');
break;
case 'jobCompleted':
grunt.log.subhead('\nTested %s', notification.url);
grunt.log.writeln('Platform: %s', notification.platform);
var requestParams = {
method: 'post',
url: url,
auth: {
user: user,
pass: key
},
json: true,
body: {
"js tests": [jobId]
if (notification.tunnelId && unsupportedPort(notification.url)) {
grunt.log.writeln('Warning: This url might use a port that is not proxied by Sauce Connect.'.yellow);
}
};
var checkStatus = function(){
grunt.log.writeln('Passed: %s', notification.passed);
grunt.log.writeln('Url %s', notification.url);
break;
case 'testCompleted':
grunt.log[notification.passed ? 'ok' : 'error']('All tests completed with status %s', notification.passed);
break;
default:
grunt.log.error('Unexpected notification type');
}
}
rqst(requestParams, function(error, response, body){
function createTunnel(arg) {
var tunnel;
if (error){
deferred.resolve({
passed: undefined,
result: {
message: "Error connecting to api to get test status: " + error.toString()
}
});
return;
}
reportProgress({
type: 'tunnelOpen'
});
var testInfo = body['js tests'][0];
tunnel = new SauceTunnel(arg.username, arg.key, arg.identifier, true, ['-P', '0'].concat(arg.tunnelArgs));
if (testInfo.status == "test error"){
deferred.resolve({
passed: undefined,
result: {
message: "Test Error"
}
});
return;
}
if (!body.completed){
setTimeout(checkStatus ,testInterval);
} else {
testInfo.passed = testInfo.result ? resultParsers[framework](testInfo.result) : false;
deferred.resolve(testInfo);
}
['write', 'writeln', 'error', 'ok', 'debug'].forEach(function (method) {
tunnel.on('log:' + method, function (text) {
reportProgress({
type: 'tunnelEvent',
verbose: false,
method: method,
text: text
});
});
};
tunnel.on('verbose:' + method, function (text) {
reportProgress({
type: 'tunnelEvent',
verbose: true,
method: method,
text: text
});
});
});
checkStatus();
return tunnel;
}
return deferred.promise;
};
function runTask(arg, framework, callback) {
var tunnel;
var TestRunner = function(user, key, testInterval) {
this.user = user;
this.key = key;
this.url = 'https://saucelabs.com/rest/v1/' + this.user + '/js-tests';
this.testInterval = testInterval;
};
Q
.fcall(function () {
var deferred;
TestRunner.prototype.runTests = function(browsers, urls, framework, tunnelIdentifier, testname, tags, build, onTestComplete, throttled, callback){
if (arg.tunneled) {
deferred = Q.defer();
var me = this;
var numberOfJobs = browsers.length * urls.length;
var startedJobs = 0;
tunnel = createTunnel(arg);
tunnel.start(function (succeeded) {
if (!succeeded) {
deferred.reject('Could not create tunnel to Sauce Labs');
} else {
reportProgress({
type: 'tunnelOpened'
});
function take(url, browser) {
return me.runTest(browser, url, framework, tunnelIdentifier, testname, tags, build)
.then(function (taskId) {
deferred.resolve();
}
});
return deferred.promise;
}
})
.then(function () {
var testRunner = new TestRunner(arg, framework, reportProgress);
return testRunner.runTests();
})
.fin(function () {
var deferred;
startedJobs += 1;
grunt.log.writeln("\n",startedJobs, "/", numberOfJobs, 'tests started');
if (tunnel) {
deferred = Q.defer();
reportProgress({
type: 'tunnelClose'
});
return TestResult(taskId, me.user, me.key, framework, me.testInterval)
.then(function (result) {
var alteredResult = onTestComplete(result);
if (alteredResult !== undefined) {
result.passed = alteredResult;
}
tunnel.stop(function () {
deferred.resolve();
});
grunt.log.subhead("\nTested %s", url);
grunt.log.writeln("Platform: %s", result.platform);
if (tunnelIdentifier && unsupportedPort(url)) {
grunt.log.writeln("Warning: This url might use a port that is not proxied by Sauce Connect.".yellow);
}
if (result.passed === undefined) {
grunt.log.error(result.result.message);
} else {
grunt.log.writeln("Passed: %s", result.passed);
}
grunt.log.writeln("Url %s", result.url);
return result;
}, function (e) {
grunt.log.error('some error? %s', e);
});
});
}
var throttledTake = scheduler.limitConcurrency(take, throttled || Number.MAX_VALUE);
var promises = urls
.map(function (url) {
return browsers.map(function (browser) {
return throttledTake(url, browser);
});
})
.reduce(function (acc, promisesForUrl) {
return acc.concat(promisesForUrl);
}, []);
Q.all(promises)
.then(function (results) {
results = results.map(function (result) {
return result.passed;
});
callback(results);
})
.done();
};
TestRunner.prototype.runTest = function(browser, url, framework, tunnelIdentifier, build, testname, sauceConfig){
var requestParams = {
method: 'post',
url: this.url,
auth: {
user: this.user,
pass: this.key
},
json: true,
body: {
platforms: [[browser.platform || "", browser.browserName || "", browser.version || ""]],
url: url,
framework: framework,
build: build,
name: testname
}
};
_.merge(requestParams.body, sauceConfig);
if (tunnelIdentifier){
requestParams.body['tunnel-identifier'] = tunnelIdentifier;
}
return Q.nfcall(rqst, requestParams)
return deferred.promise;
}
})
.then(
function (result) {
var body = result[1],
taskIds = body['js tests'];
if (!taskIds || !taskIds.length){
grunt.log.error('Error starting tests through Sauce API: %s', JSON.stringify(body));
throw new Error('Could not start tests through Sauce API');
}
return taskIds[0];
function (passed) {
callback(passed);
},
function (error) {
grunt.log.error("Could not connect to Sauce Labs api: %s", error);
throw error;
}
);
};
var defaultsObj = {
username: process.env.SAUCE_USERNAME,
key: process.env.SAUCE_ACCESS_KEY,
identifier: Math.floor((new Date()).getTime() / 1000 - 1230768000).toString(),
tunneled: true,
testInterval: 1000 * 2,
testReadyTimeout: 1000 * 5,
onTestComplete: _.noop,
testname: "",
browsers: [{}],
tunnelArgs: [],
sauceConfig: {}
};
function defaults(data) {
var result = data;
result.pages = result.url || result.urls;
if (!_.isArray(result.pages)) {
result.pages = [result.pages];
}
return result;
}
function configureLogEvents(tunnel) {
var methods = ['write', 'writeln', 'error', 'ok', 'debug'];
methods.forEach(function (method) {
tunnel.on('log:'+method, function (text) {
grunt.log[method](text);
});
tunnel.on('verbose:'+method, function (text) {
grunt.verbose[method](text);
});
});
}
function runTask(arg, framework, callback){
var test = new TestRunner(arg.username, arg.key, arg.testInterval);
//max-duration is actually a sauce selenium capability
if (arg['max-duration']){
arg.sauceConfig['max-duration'] = arg['max-duration'];
}
if (arg.tunneled){
var tunnel = new SauceTunnel(arg.username, arg.key, arg.identifier, arg.tunneled, ['-P', '0'].concat(arg.tunnelArgs));
grunt.log.writeln("=> Starting Tunnel to Sauce Labs".inverse.bold);
configureLogEvents(tunnel);
tunnel.start(function(isCreated) {
if (!isCreated) {
grunt.log.error("Could not create tunnel to Sauce Labs");
grunt.log.error(error.toString());
callback(false);
return;
}
grunt.log.ok("Connected to Saucelabs");
test.runTests(arg.browsers, arg.pages, framework, arg.identifier, arg.build, arg.testname, arg.sauceConfig, arg.onTestComplete, arg.throttled, function (status){
status = status.every(function(passed){ return passed; });
grunt.log[status ? 'ok' : 'error']("All tests completed with status %s", status);
grunt.log.writeln("=> Stopping Tunnel to Sauce Labs".inverse.bold);
tunnel.stop(function() {
callback(status);
});
});
});
} else {
test.runTests(arg.browsers, arg.pages, framework, null, arg.build, arg.testname, arg.sauceConfig, arg.onTestComplete, arg.throttled, function(status){
status = status.every(function(passed){ return passed; });
grunt.log[status ? 'ok' : 'error']("All tests completed with status %s", status);
callback(status);
});
}
)
.done();
}

@@ -286,6 +143,8 @@

var port = matches ? parseInt(matches[1], 10) : null;
var supportedPorts = [80, 443, 888, 2000, 2001, 2020, 2109, 2222, 2310, 3000, 3001, 3030,
3210, 3333, 4000, 4001, 4040, 4321, 4502, 4503, 4567, 5000, 5001, 5050, 5555, 5432, 6000,
6001, 6060, 6666, 6543, 7000, 7070, 7774, 7777, 8000, 8001, 8003, 8031, 8080, 8081, 8765,
8888, 9000, 9001, 9080, 9090, 9876, 9877, 9999, 49221, 55001];
var supportedPorts = [
80, 443, 888, 2000, 2001, 2020, 2109, 2222, 2310, 3000, 3001, 3030,
3210, 3333, 4000, 4001, 4040, 4321, 4502, 4503, 4567, 5000, 5001, 5050, 5555, 5432, 6000,
6001, 6060, 6666, 6543, 7000, 7070, 7774, 7777, 8000, 8001, 8003, 8031, 8080, 8081, 8765,
8888, 9000, 9001, 9080, 9090, 9876, 9877, 9999, 49221, 55001
];

@@ -299,41 +158,48 @@ if (port) {

grunt.registerMultiTask('saucelabs-jasmine', 'Run Jasmine test cases using Sauce Labs browsers', function() {
var done = this.async(),
arg = defaults(this.options(defaultsObj));
var defaults = {
username: process.env.SAUCE_USERNAME,
key: process.env.SAUCE_ACCESS_KEY,
tunneled: true,
identifier: Math.floor((new Date()).getTime() / 1000 - 1230768000).toString(),
pollInterval: 1000 * 2,
testname: '',
browsers: [{}],
tunnelArgs: [],
sauceConfig: {}
};
grunt.registerMultiTask('saucelabs-jasmine', 'Run Jasmine test cases using Sauce Labs browsers', function () {
var done = this.async();
var arg = this.options(defaults);
runTask(arg, 'jasmine', done);
});
grunt.registerMultiTask('saucelabs-qunit', 'Run Qunit test cases using Sauce Labs browsers', function() {
var done = this.async(),
arg = defaults(this.options(defaultsObj));
grunt.registerMultiTask('saucelabs-qunit', 'Run Qunit test cases using Sauce Labs browsers', function () {
var done = this.async();
var arg = this.options(defaults);
runTask(arg, 'qunit', done);
});
grunt.registerMultiTask('saucelabs-yui', 'Run YUI test cases using Sauce Labs browsers', function() {
var done = this.async(),
arg = defaults(this.options(defaultsObj));
grunt.registerMultiTask('saucelabs-yui', 'Run YUI test cases using Sauce Labs browsers', function () {
var done = this.async();
var arg = this.options(defaults);
runTask(arg, 'YUI Test', done);
});
grunt.registerMultiTask('saucelabs-mocha', 'Run Mocha test cases using Sauce Labs browsers', function() {
var done = this.async(),
arg = defaults(this.options(defaultsObj));
grunt.registerMultiTask('saucelabs-mocha', 'Run Mocha test cases using Sauce Labs browsers', function () {
var done = this.async();
var arg = this.options(defaults);
runTask(arg, 'mocha', done);
});
grunt.registerMultiTask('saucelabs-custom', 'Run custom test cases using Sauce Labs browsers', function() {
var done = this.async(),
arg = defaults(this.options(defaultsObj));
grunt.registerMultiTask('saucelabs-custom', 'Run custom test cases using Sauce Labs browsers', function () {
var done = this.async();
var arg = this.options(defaults);
runTask(arg, 'custom', done);
});
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc