This repository has been archived by the owner on Oct 10, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 87
add basic browserstack support #126
Open
yanatan16
wants to merge
1
commit into
defunctzombie:master
Choose a base branch
from
yanatan16:browserstack
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ program | |
.option('--local [port]', 'port for manual testing in a local browser') | ||
.option('--tunnel', 'establish a tunnel for outside access. only used when --local is specified') | ||
.option('--phantom', 'run tests in phantomjs. PhantomJS must be installed separately.') | ||
.option('--browserstack', 'run tests in BrowserStack instead of Sauce Labs.') | ||
.option('--tunnel-host <host url>', 'specify a localtunnel server to use for forwarding') | ||
.option('--sauce-connect [tunnel-identifier]', 'use saucelabs with sauce connect instead of localtunnel. Optionally specify the tunnel-identifier') | ||
.option('--server <the server script>', 'specify a server script to be run') | ||
|
@@ -39,6 +40,7 @@ var config = { | |
ui: program.ui, | ||
tunnel: program.tunnel, | ||
phantom: program.phantom, | ||
selenium_runner: program.browserstack ? 'BrowserStack' : 'SauceLabs', | ||
prj_dir: process.cwd(), | ||
tunnel_host: program.tunnelHost, | ||
sauce_connect: program.sauceConnect, | ||
|
@@ -67,10 +69,23 @@ if(!process.stdout.isTTY){ | |
}); | ||
} | ||
|
||
// optional additional local config or from $HOME/.zuulrc | ||
var local_config = find_nearest_file('.zuulrc') || path.join(osenv.home(), '.zuulrc'); | ||
if (fs.existsSync(local_config)) { | ||
var zuulrc = yaml.parse(fs.readFileSync(local_config, 'utf-8')); | ||
config = xtend(zuulrc, config); | ||
config.tunnel_host = config.tunnel_host || zuulrc.tunnel_host; | ||
} | ||
|
||
config.username = {'SauceLabs': process.env.SAUCE_USERNAME || config.sauce_username, | ||
'BrowserStack': process.env.BROWSERSTACK_USERNAME || config.browserstack_username}[config.selenium_runner] | ||
config.key = {'SauceLabs': process.env.SAUCE_ACCESS_KEY || config.sauce_key, | ||
'BrowserStack': process.env.BROWSERSTACK_ACCESS_KEY || config.browserstack_key}[config.selenium_runner] | ||
|
||
if (program.listAvailableBrowsers) { | ||
scout_browser(function(err, all_browsers) { | ||
scout_browser(config, function(err, all_browsers) { | ||
if (err) { | ||
console.error('Unable to get available browsers for saucelabs'.red); | ||
console.error('Unable to get available browsers for '+config.selenium_runner+''.red); | ||
console.error(err.stack); | ||
return process.exit(1); | ||
} | ||
|
@@ -106,20 +121,6 @@ if (program.browserName) { | |
config = xtend(config, { browsers: [{ name: program.browserName, version: program.browserVersion, platform: program.browserPlatform }] }); | ||
} | ||
|
||
// optional additional local config or from $HOME/.zuulrc | ||
var local_config = find_nearest_file('.zuulrc') || path.join(osenv.home(), '.zuulrc'); | ||
if (fs.existsSync(local_config)) { | ||
var zuulrc = yaml.parse(fs.readFileSync(local_config, 'utf-8')); | ||
config = xtend(zuulrc, config); | ||
config.tunnel_host = config.tunnel_host || zuulrc.tunnel_host; | ||
} | ||
|
||
var sauce_username = process.env.SAUCE_USERNAME; | ||
var sauce_key = process.env.SAUCE_ACCESS_KEY; | ||
|
||
config.username = sauce_username || config.sauce_username; | ||
config.key = sauce_key || config.sauce_key; | ||
|
||
if (!config.ui) { | ||
console.error('Error: `ui` must be configured in .zuul.yml or specified with the --ui flag'); | ||
return process.exit(1); | ||
|
@@ -155,15 +156,15 @@ else if (config.phantom) { | |
|
||
if (!config.username || !config.key) { | ||
console.error('Error:'); | ||
console.error('Zuul tried to run tests in saucelabs, however no saucelabs credentials were provided.'); | ||
console.error('Zuul tried to run tests in '+config.selenium_runner+', however no '+config.selenium_runner+' credentials were provided.'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use string formatting: console.error('Zuul tried %s however no %s, config.selenium_runner, config.selenium_runner). Makes it easier to grok the string. |
||
console.error('See the zuul wiki (https://github.com/defunctzombie/zuul/wiki/Cloud-testing) for info on how to setup cloud testing.'); | ||
process.exit(1); | ||
return; | ||
} | ||
|
||
scout_browser(function(err, all_browsers) { | ||
scout_browser(config, function(err, all_browsers) { | ||
if (err) { | ||
console.error('Unable to get available browsers for saucelabs'.red); | ||
console.error('Unable to get available browsers for '+config.selenium_runner+''.red); | ||
console.error(err.stack); | ||
return process.exit(1); | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
var wd = require('wd'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var FirefoxProfile = require('firefox-profile'); | ||
var debug = require('debug')('zuul:browserstackbrowser'); | ||
var xtend = require('xtend'); | ||
|
||
var setup_test_instance = require('./setup'); | ||
|
||
function BrowserStackBrowser(conf, opt) { | ||
if (!(this instanceof BrowserStackBrowser)) { | ||
return new BrowserStackBrowser(conf, opt); | ||
} | ||
|
||
var self = this; | ||
self._conf = conf; | ||
self._opt = opt; | ||
self._opt.tunnel = !opt.sauce_connect; // TODO: BrowserStack local testing | ||
self.stats = { | ||
passed: 0, | ||
failed: 0 | ||
}; | ||
} | ||
|
||
BrowserStackBrowser.prototype.__proto__ = EventEmitter.prototype; | ||
|
||
BrowserStackBrowser.prototype.toString = function() { | ||
var self = this; | ||
var conf = self._conf; | ||
return '<' + conf.browser + ' ' + conf.version + ' on ' + conf.platform + '>'; | ||
}; | ||
|
||
BrowserStackBrowser.prototype.start = function() { | ||
var self = this; | ||
var conf = self._conf; | ||
|
||
self.stopped = false; | ||
self.stats = { | ||
passed: 0, | ||
failed: 0 | ||
}; | ||
|
||
debug('running %s %s %s', conf.browser, conf.version, conf.platform); | ||
var browser = self.browser = wd.remote('hub.browserstack.com', 80, conf.username, conf.key); | ||
|
||
browser.configureHttp({ | ||
timeout: undefined, | ||
retries: 1, | ||
retryDelay: 1000 | ||
}); | ||
|
||
self.controller = setup_test_instance(self._opt, function(err, url) { | ||
if (err) { | ||
return self.shutdown(err); | ||
} | ||
|
||
self.emit('init', conf); | ||
|
||
var init_conf = xtend({ | ||
build: conf.build, | ||
name: conf.name, | ||
tags: conf.tags || [], | ||
browserName: conf.browser, | ||
version: conf.version, | ||
platform: 'ANY' | ||
}, conf.capabilities); | ||
|
||
if (conf.firefox_profile) { | ||
var fp = new FirefoxProfile(); | ||
var extensions = conf.firefox_profile.extensions; | ||
for (var preference in conf.firefox_profile) { | ||
if (preference !== 'extensions') { | ||
fp.setPreference(preference, conf.firefox_profile[preference]); | ||
} | ||
} | ||
extensions = extensions ? extensions : []; | ||
fp.addExtensions(extensions, function () { | ||
fp.encoded(function(zippedProfile) { | ||
init_conf.firefox_profile = zippedProfile; | ||
init(); | ||
}); | ||
}); | ||
} else { | ||
init(); | ||
} | ||
|
||
function init() { | ||
debug('queuing %s %s %s', conf.browser, conf.version, conf.platform); | ||
|
||
browser.init(init_conf, function(err) { | ||
if (err) { | ||
if (err.data) { | ||
err.message += ': ' + err.data.split('\n').slice(0, 1); | ||
} | ||
return self.shutdown(err); | ||
} | ||
|
||
var reporter = new EventEmitter(); | ||
|
||
reporter.on('test_end', function(test) { | ||
if (!test.passed) { | ||
return self.stats.failed++; | ||
} | ||
self.stats.passed++; | ||
}); | ||
|
||
reporter.on('done', function(results) { | ||
debug('done %s %s %s', conf.browser, conf.version, conf.platform); | ||
var passed = results.passed; | ||
var called = false; | ||
browser.sauceJobStatus(passed, function(err) { | ||
if (called) { | ||
return; | ||
} | ||
|
||
called = true; | ||
self.shutdown(); | ||
|
||
if (err) { | ||
return; | ||
// don't let this error fail us | ||
} | ||
}); | ||
|
||
reporter.removeAllListeners(); | ||
}); | ||
|
||
debug('open %s', url); | ||
self.emit('start', reporter); | ||
|
||
var timeout = false; | ||
var get_timeout = setTimeout(function() { | ||
debug('timed out waiting for open %s', url); | ||
timeout = true; | ||
self.shutdown(new Error('Timeout opening url')); | ||
}, 60 * 1000); | ||
|
||
browser.get(url, function(err) { | ||
if (timeout) { | ||
return; | ||
} | ||
|
||
clearTimeout(get_timeout); | ||
if (err) { | ||
return self.shutdown(err); | ||
} | ||
|
||
(function wait() { | ||
if (self.stopped) { | ||
return; | ||
} | ||
|
||
debug('waiting for test results from %s', url); | ||
var js = '(window.zuul_msg_bus ? window.zuul_msg_bus.splice(0, 10) : []);' | ||
browser.eval(js, function(err, res) { | ||
if (err) { | ||
debug('err: %s', err.message); | ||
} | ||
|
||
debug('res.length: %s', res.length); | ||
|
||
if (err) { | ||
return self.shutdown(err); | ||
} | ||
|
||
var has_done = false; | ||
res = res || []; | ||
res.filter(Boolean).forEach(function(msg) { | ||
if (msg.type === 'done') { | ||
has_done = true; | ||
} | ||
|
||
reporter.emit(msg.type, msg); | ||
}); | ||
|
||
if (has_done) { | ||
debug('finished tests for %s', url); | ||
return; | ||
} | ||
|
||
debug('fetching more results'); | ||
setTimeout(wait, 1000); | ||
}); | ||
})(); | ||
}); | ||
}); | ||
} | ||
}); | ||
}; | ||
|
||
BrowserStackBrowser.prototype.shutdown = function(err) { | ||
var self = this; | ||
|
||
self.stopped = true; | ||
|
||
finish_shutdown = function() { | ||
debug('shutdown'); | ||
|
||
if (self.controller) { | ||
try { self.controller.shutdown(); } catch (e) {} | ||
} | ||
|
||
if (err) { | ||
self.emit('error', err); | ||
return; | ||
} | ||
|
||
self.emit('done', self.stats); | ||
self.removeAllListeners(); | ||
} | ||
|
||
// make sure the browser shuts down before continuing | ||
if (self.browser) { | ||
debug('quitting browser'); | ||
|
||
var timeout = false; | ||
var quit_timeout = setTimeout(function() { | ||
debug('timed out waiting for browser to quit'); | ||
timeout = true; | ||
finish_shutdown(); | ||
}, 10 * 1000); | ||
|
||
self.browser.quit(function(err) { | ||
if (timeout) { | ||
return; | ||
} | ||
|
||
clearTimeout(quit_timeout); | ||
finish_shutdown(); | ||
}); | ||
} | ||
else { | ||
finish_shutdown(); | ||
} | ||
}; | ||
|
||
module.exports = BrowserStackBrowser; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,6 +40,8 @@ function flatten(request, all_browsers) { | |
return a.version - b.version; | ||
}); | ||
|
||
var beta_available = avail[avail.length - 1].version === 'beta'; | ||
|
||
// remove duplicate version entries | ||
// because we are not interested in testing on all platforms | ||
avail.reduce(function(prev, curr, idx, arr) { | ||
|
@@ -68,6 +70,11 @@ function flatten(request, all_browsers) { | |
// or ##..latest | ||
function process_version_str(version) { | ||
version = String(version); | ||
|
||
if (version == 'beta' && !beta_available) { | ||
console.log('Couldnt find beta version, using latest.'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we want to console log here. Just throw or do nothing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe Zuul skips those browsers in general. Consistency would be cool. |
||
version = 'latest'; | ||
} | ||
if (version === 'latest') { | ||
return get_numeric_versions(avail).slice(-1).map(addProfile); | ||
} | ||
|
@@ -94,6 +101,10 @@ function flatten(request, all_browsers) { | |
start_idx = v_map.indexOf(start); | ||
} | ||
|
||
if (end == 'beta' && !beta_available) { | ||
console.log('Couldnt find beta version, using latest.'); | ||
end = 'latest'; | ||
} | ||
if (end === 'latest') { | ||
end_idx = get_numeric_versions(avail).length - 1; | ||
} | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wondering if
--platform <sauce | browserstack | phantom >
wouldn't be a better flagThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the
--platform
flag. Unlikely confused with--browser-platform
.