Skip to content

Commit

Permalink
feat(launcher): Add and options
Browse files Browse the repository at this point in the history
Closes #49
  • Loading branch information
dignifiedquire committed Oct 20, 2015
1 parent 6ab38f8 commit 6a15162
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 115 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
19 changes: 6 additions & 13 deletions examples/karma.conf-ci.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}
}

Expand All @@ -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,
Expand Down
249 changes: 150 additions & 99 deletions lib/sauce_launcher.js
Original file line number Diff line number Diff line change
@@ -1,159 +1,210 @@
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,
logger, helper,
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()
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
],
"author": "Vojta Jina <vojta.jina@gmail.com>",
"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": {
Expand Down

0 comments on commit 6a15162

Please sign in to comment.