diff --git a/README.md b/README.md index 26716f5..a8feab5 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,22 @@ Default: `true` Set to `false` if you don't want to record screenshots. +### public +Type: 'String' +Default: 'null' + +Control who can view job details. Available visibility levels are documented on +the [SauceLabs website](https://docs.saucelabs.com/reference/test-configuration/#job-visibility). + +### customData +Type: `Object` +Default: `{}` + +Send arbitrary data alongside your tests. See +the [SauceLabs documentation](https://docs.saucelabs.com/reference/test-configuration/#recording-custom-data) +for more details. + + ## `customLaunchers` config properties The `customLaunchers` object has browser names as keys and configs as values. Documented below are the different properties which you can configure for each browser/platform combo. diff --git a/examples/karma.conf-ci.js b/examples/karma.conf-ci.js index 18b5ea3..0afc16b 100644 --- a/examples/karma.conf-ci.js +++ b/examples/karma.conf-ci.js @@ -10,28 +10,20 @@ module.exports = function (config) { 'SL_Chrome': { base: 'SauceLabs', platform: 'OS X 10.11', - browserName: 'chrome' + browserName: 'chrome', + customData: { + awesome: true + } }, 'SL_Firefox': { base: 'SauceLabs', platform: 'OS X 10.11', browserName: 'firefox' }, - 'SL_Safari': { - base: 'SauceLabs', - platform: 'OS X 10.11', - browserName: 'safari' - }, 'SL_Edge': { base: 'SauceLabs', platform: 'Windows 10', browserName: 'microsoftedge' - }, - 'SL_IE11': { - base: 'SauceLabs', - platform: 'Windows 10', - browserName: 'internet explorer', - version: '11.0' } } @@ -52,7 +44,8 @@ module.exports = function (config) { connectOptions: { port: 5757, logfile: 'sauce_connect.log' - } + }, + public: 'public' }, // Increase timeout in case connection in CI is slow captureTimeout: 120000, diff --git a/lib/sauce_launcher.js b/lib/sauce_launcher.js index dd167d5..91209f0 100644 --- a/lib/sauce_launcher.js +++ b/lib/sauce_launcher.js @@ -1,5 +1,82 @@ var wd = require('wd') +function formatSauceError (err) { + return err.message + '\n' + (err.data ? ' ' + err.data : '') +} + +function processConfig (helper, config, args) { + config = config || {} + args = args || {} + + var username = args.username || config.username || process.env.SAUCE_USERNAME + var accessKey = args.accessKey || config.accessKey || process.env.SAUCE_ACCESS_KEY + var startConnect = config.startConnect !== false + var tunnelIdentifier = args.tunnelIdentifier || config.tunnelIdentifier + + if (startConnect && !tunnelIdentifier) { + tunnelIdentifier = 'karma' + Math.round(new Date().getTime() / 1000) + } + + var browserName = args.browserName + + (args.version ? ' ' + args.version : '') + + (args.platform ? ' (' + args.platform + ')' : '') + + var connectOptions = helper.merge(config.connectOptions, { + username: username, + accessKey: accessKey, + tunnelIdentifier: tunnelIdentifier + }) + + var build = process.env.TRAVIS_BUILD_NUMBER || + process.env.BUILD_NUMBER || + process.env.BUILD_TAG || + process.env.CIRCLE_BUILD_NUM + + var defaults = { + version: '', + platform: 'ANY', + tags: [], + name: 'Karma Test', + 'tunnel-identifier': tunnelIdentifier, + 'record-video': false, + 'record-screenshots': false, + 'device-orientation': null, + 'disable-popup-handler': true, + build: build || null, + public: null, + customData: {} + } + + var options = helper.merge( + // Legacy + config.options, + defaults, { + // Pull out all the properties from the config that + // we are interested in + name: config.testName, + build: config.build, + 'record-video': config.recordVideo, + 'record-screenshots': config.recordScreenshots, + public: config.public, + customData: config.customData + }, { + // Need to rename some properties from args + name: args.testName, + 'record-video': args.recordVideo, + 'record-screenshots': args.recordScreenshots + }, args + ) + + return { + options: options, + connectOptions: connectOptions, + browserName: browserName, + username: username, + accessKey: accessKey, + startConnect: startConnect + } +} + var SauceLauncher = function ( args, sauceConnect, /* config.sauceLabs */ config, @@ -7,153 +84,127 @@ var SauceLauncher = function ( baseLauncherDecorator, captureTimeoutLauncherDecorator, retryLauncherDecorator, /* sauce:jobMapping */ jobMapping ) { - baseLauncherDecorator(this) - captureTimeoutLauncherDecorator(this) - retryLauncherDecorator(this) + var self = this - config = config || {} + baseLauncherDecorator(self) + captureTimeoutLauncherDecorator(self) + retryLauncherDecorator(self) + + var pConfig = processConfig(helper, config, args) + var options = pConfig.options + var connectOptions = pConfig.connectOptions + var browserName = pConfig.browserName + var username = pConfig.username + var accessKey = pConfig.accessKey + var startConnect = pConfig.startConnect + + var pendingCancellations = 0 + var sessionIsReady = false + + self.name = browserName + ' on SauceLabs' + + var pendingHeartBeat - var username = args.username || config.username || process.env.SAUCE_USERNAME - var accessKey = args.accessKey || config.accessKey || process.env.SAUCE_ACCESS_KEY - var tunnelIdentifier = args.tunnelIdentifier || config.tunnelIdentifier - var browserName = args.browserName + (args.version ? ' ' + args.version : '') + - (args.platform ? ' (' + args.platform + ')' : '') - var startConnect = config.startConnect !== false var log = logger.create('launcher.sauce') var driverLog = logger.create('wd') - var self = this var driver = wd.promiseChainRemote('ondemand.saucelabs.com', 80, username, accessKey) + driver.on('status', function (info) { driverLog.debug(info.cyan) }) + driver.on('command', function (eventType, command, response) { driverLog.debug(' > ' + eventType.cyan, command, (response || '').grey) }) + driver.on('http', function (meth, path, data) { driverLog.debug(' > ' + meth.magenta, path, (data || '').grey) }) - var pendingCancellations = 0 - var sessionIsReady = false - - if (startConnect && !tunnelIdentifier) { - tunnelIdentifier = 'karma' + Math.round(new Date().getTime() / 1000) - } - - var connectOptions = config.connectOptions || {} - connectOptions = helper.merge(connectOptions, { - username: username, - accessKey: accessKey, - tunnelIdentifier: tunnelIdentifier - }) - - this.name = browserName + ' on SauceLabs' - - var formatSauceError = function (err) { - return err.message + '\n' + (err.data ? ' ' + err.data : '') - } - - var pendingHeartBeat var heartbeat = function () { pendingHeartBeat = setTimeout(function () { log.debug('Heartbeat to Sauce Labs (%s) - fetching title', browserName) - driver.title().then(null, function (err) { - log.error('Heartbeat to %s failed\n %s', browserName, formatSauceError(err)) - clearTimeout(pendingHeartBeat) - return self._done('failure') - }) + + driver.title() + .then(null, function (err) { + log.error('Heartbeat to %s failed\n %s', browserName, formatSauceError(err)) + + clearTimeout(pendingHeartBeat) + return self._done('failure') + }) + heartbeat() }, 60000) } var start = function (url) { - var options = helper.merge(config.options, args, { - browserName: args.browserName, - version: args.version || '', - platform: args.platform || 'ANY', - tags: args.tags || config.tags || [], - name: args.testName || config.testName || 'Karma test', - 'tunnel-identifier': tunnelIdentifier, - 'record-video': args.recordVideo || config.recordVideo || false, - 'record-screenshots': !(args.recordScreenshots === false || config.recordScreenshots === false), - 'build': args.build || config.build || process.env.TRAVIS_BUILD_NUMBER || - process.env.BUILD_NUMBER || process.env.BUILD_TAG || - process.env.CIRCLE_BUILD_NUM || null, - 'device-orientation': args.deviceOrientation || null, - 'disable-popup-handler': true - }) - - // Adding any other option that was specified in args, but not consumed from above - // Useful for supplying chromeOptions, firefoxProfile, etc. - for (var key in args) { - if (typeof options[key] === 'undefined') { - options[key] = args[key] - } - } - driver .init(options) - .then( - function () { - if (pendingCancellations > 0) { - pendingCancellations-- - return - } - // Record the job details, so we can access it later with the reporter - jobMapping[self.id] = { - jobId: driver.sessionID, - credentials: { - username: username, - password: accessKey - } + .then(function () { + if (pendingCancellations > 0) { + pendingCancellations-- + return + } + // Record the job details, so we can access it later with the reporter + jobMapping[self.id] = { + jobId: driver.sessionID, + credentials: { + username: username, + password: accessKey } + } - sessionIsReady = true + sessionIsReady = true - log.info('%s session at https://saucelabs.com/tests/%s', browserName, driver.sessionID) - log.debug('WebDriver channel for %s instantiated, opening %s', browserName, url) - return driver.get(url).then(heartbeat, function (err) { + log.info('%s session at https://saucelabs.com/tests/%s', browserName, driver.sessionID) + log.debug('WebDriver channel for %s instantiated, opening %s', browserName, url) + + return driver.get(url) + .then(heartbeat, function (err) { log.error('Can not start %s\n %s', browserName, formatSauceError(err)) return self._done('failure') }) - }, function (err) { - if (pendingCancellations > 0) { - pendingCancellations-- - return - } - log.error('Can not start %s\n %s', browserName, formatSauceError(err)) - return self._done('failure') + }, function (err) { + if (pendingCancellations > 0) { + pendingCancellations-- + return } - ).done() + + log.error('Can not start %s\n %s', browserName, formatSauceError(err)) + return self._done('failure') + }) + .done() } - this.on('start', function (url) { + self.on('start', function (url) { if (pendingCancellations > 0) { pendingCancellations-- return } if (startConnect) { - sauceConnect.start(connectOptions).then(function () { - if (pendingCancellations > 0) { + sauceConnect.start(connectOptions) + .then(function () { + if (pendingCancellations > 0) { + pendingCancellations-- + return + } + + start(url) + }, function (err) { pendingCancellations-- - return - } + log.error('Can not start %s\n Failed to start Sauce Connect:\n %s', browserName, err.message) - start(url) - }, function (err) { - pendingCancellations-- - log.error('Can not start %s\n Failed to start Sauce Connect:\n %s', browserName, err.message) - self._retryLimit = -1 // don't retry - self._done('failure') - }) + self._retryLimit = -1 // don't retry + self._done('failure') + }) } else { start(url) } }) - this.on('kill', function (done) { + self.on('kill', function (done) { var allDone = function () { self._done() done() diff --git a/package.json b/package.json index f026a1a..909960e 100644 --- a/package.json +++ b/package.json @@ -19,10 +19,10 @@ ], "author": "Vojta Jina ", "dependencies": { - "wd": "^0.3.4", - "sauce-connect-launcher": "^0.13.0", "q": "^1.4.1", - "saucelabs": "^1.0.1" + "sauce-connect-launcher": "^0.13.0", + "saucelabs": "^1.0.1", + "wd": "^0.3.4" }, "license": "MIT", "devDependencies": {