├── .github └── CODEOWNERS ├── .gitignore ├── .jshintrc ├── CHANGELOG.md ├── README.md ├── default-sauce-browsers.json ├── gulpfile.js ├── lib ├── browsers.js ├── plugin.js └── sauce.js ├── package.json ├── scripts └── postinstall.js ├── test └── browsers.js └── travis-browsers.json /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @azakus @usergenic 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "expr": true, 4 | "indent": 2, 5 | "lastsemic": true, 6 | "newcap": false, 7 | "node": true, 8 | "onecase": true, 9 | "quotmark": "single", 10 | "unused": "vars", 11 | "validthis": true, 12 | "esnext": true 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | 5 | ## 2.0.1 - 2018-04-27 6 | 7 | - Update dependency on `request` to lastest version due to reported security vulnerability in earlier version. 8 | 9 | ## 2.0.0 - 2018-01-09 10 | 11 | - Remove hardcoded Sauce Connect version 12 | 13 | ## 2.0.0-pre.4 - 2018-01-09 14 | 15 | - Removed the Linux Chrome 'dev' version browser, which is not a thing that Sauce does. 16 | 17 | ## 2.0.0-pre.3 - 2017-11-27 18 | 19 | - Added Edge 15 to Travis browser list. 20 | - Added Safari 11 to default and Travis browser lists. 21 | 22 | ## 2.0.0-pre.2 - 2017-11-16 23 | 24 | - Update to cleankill@^2.0.0 25 | 26 | ## 2.0.0-pre.1 27 | 28 | - Updated default browsers list: 29 | 30 | - Added Edge 14 and Safari 10 31 | - Removed Safari 8, 9 and IE 10 32 | 33 | See default-sauce-browser.json for the current list. 34 | 35 | - Added SKIP_WCT_SAUCE_POSTINSTALL_DOWNLOAD environment variable to skip 36 | downloading the sauce connect binary at install time. 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 🚨 Moved to [`Polymer/tools/packages/wct-sauce`][1] 🚨 2 | 3 | The [`Polymer/wct-sauce`][2] repo has been migrated to [`packages/wct-sauce`][1] folder of the [`Polymer/tools`][3] 🚝 *monorepo*. 4 | 5 | We are *actively* working on migrating open Issues and PRs to the new repo. New Issues and PRs should be filed at [`Polymer/tools`][3]. 6 | 7 | [1]: https://github.com/Polymer/tools/tree/master/packages/wct-sauce 8 | [2]: https://github.com/Polymer/wct-sauce 9 | [3]: https://github.com/Polymer/tools 10 | 11 | -------------------------------------------------------------------------------- /default-sauce-browsers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "browserName": "microsoftedge", 4 | "platform": "Windows 10", 5 | "version": "" 6 | }, 7 | { 8 | "browserName": "internet explorer", 9 | "platform": "Windows 8.1", 10 | "version": "11" 11 | }, 12 | 13 | { 14 | "browserName": "safari", 15 | "platform": "OS X 10.12", 16 | "version": "11" 17 | }, 18 | { 19 | "browserName": "safari", 20 | "platform": "OS X 10.11", 21 | "version": "10" 22 | }, 23 | { 24 | "browserName": "safari", 25 | "platform": "OS X 10.11", 26 | "version": "9" 27 | }, 28 | 29 | { 30 | "browserName": "chrome", 31 | "platform": "Linux", 32 | "version": "" 33 | }, 34 | { 35 | "browserName": "chrome", 36 | "platform": "Windows 10", 37 | "version": "beta" 38 | }, 39 | { 40 | "browserName": "chrome", 41 | "platform": "Windows 10", 42 | "version": "" 43 | }, 44 | 45 | { 46 | "browserName": "firefox", 47 | "platform": "Windows 10", 48 | "version": "" 49 | } 50 | ] 51 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | var gulp = require('gulp'); 11 | var jshint = require('gulp-jshint'); 12 | 13 | var MAIN_SOURCES = ['gulpfile.js', '{lib,test}/**/*.js', 'bin/*']; 14 | 15 | gulp.task('default', ['test']); 16 | 17 | gulp.task('test', ['test:style']); 18 | 19 | gulp.task('test:style', function() { 20 | return gulp.src(MAIN_SOURCES) 21 | .pipe(jshint()) 22 | .pipe(jshint.reporter('jshint-stylish')) 23 | .pipe(jshint.reporter('fail')); 24 | }); 25 | -------------------------------------------------------------------------------- /lib/browsers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | var _ = require('lodash'); 11 | 12 | var ALL_BROWSERS = require('../default-sauce-browsers.json'); 13 | 14 | var TRAVIS_BROWSERS = require('../travis-browsers.json'); 15 | 16 | // if running under travis, ENV{TRAVIS} = true, use the travis browser set 17 | var DEFAULT_BROWSERS = process.env.TRAVIS ? TRAVIS_BROWSERS : ALL_BROWSERS; 18 | 19 | // "/[@]" 20 | var BROWSER_SPEC = /^([^\/@]+)\/([^\/@]+)(?:@(.*))?$/; 21 | 22 | var PROPERTY_BLACKLIST = ['accessKey', 'browsers', 'disabled', 'username']; 23 | 24 | /** 25 | * Expands an array of browser identifiers for sauce browsers into their 26 | * webdriver capabilities objects. 27 | * 28 | * @param {!Object} pluginOptions 29 | * @param {function(*, Array)} done 30 | */ 31 | function expand(pluginOptions, done) { 32 | var browsers = pluginOptions.browsers; 33 | // 'all' is really 'default', just to be consistent with wct-local. 34 | if (browsers.indexOf('default') !== -1 || browsers.indexOf('all') !== -1) { 35 | // TODO(nevir): Figure out the latest version of each browser and pick 36 | // appropriate spreads of versions & OSes. 37 | browsers = browsers.concat(_.cloneDeep(DEFAULT_BROWSERS)); 38 | browsers = _.difference(browsers, ['default', 'all']); 39 | } 40 | 41 | done(null, _.compact(browsers.map(_expandBrowser.bind( 42 | null, 43 | pluginOptions, 44 | _.omit(pluginOptions, PROPERTY_BLACKLIST) 45 | )))); 46 | } 47 | 48 | /** 49 | * @param {string} username 50 | * @param {string} accessKey 51 | * @param {!Object} options 52 | * @param {string|!Object} browser 53 | * @return {Object} 54 | */ 55 | function _expandBrowser(options, extend, browser) { 56 | if (!_.isObject(browser)) { 57 | var match = _.isString(browser) && browser.match(BROWSER_SPEC); 58 | 59 | if (!match) { 60 | console.log('Invalid sauce browser spec:', browser); 61 | return null; 62 | } 63 | else { 64 | browser = { 65 | browserName: match[2], 66 | platform: match[1], 67 | version: match[3] || '', 68 | }; 69 | } 70 | } 71 | 72 | return _.extend(browser, { 73 | url: { 74 | accessKey: options.accessKey, 75 | hostname: 'ondemand.saucelabs.com', 76 | port: 80, 77 | username: options.username, 78 | }, 79 | }, extend); 80 | } 81 | 82 | module.exports = { 83 | expand: expand, 84 | }; 85 | -------------------------------------------------------------------------------- /lib/plugin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | var _ = require('lodash'); 11 | var request = require('request'); 12 | 13 | var browsers = require('./browsers'); 14 | var sauce = require('./sauce'); 15 | 16 | /** WCT plugin that enables support for remote browsers via Sauce Labs. */ 17 | module.exports = function(wct, pluginOptions) { 18 | 19 | // The capabilities objects for browsers to run. We don't know the tunnel id 20 | // until `prepare`, so we've gotta hang onto them. 21 | var eachCapabilities = []; 22 | 23 | wct.hook('configure', function(done) { 24 | if (!pluginOptions.browsers || pluginOptions.browsers.length === 0) return done(); 25 | 26 | expandOptions(pluginOptions); 27 | 28 | browsers.expand(pluginOptions, function(error, expanded) { 29 | if (error) return done(error); 30 | wct.emit('log:debug', 'Using sauce browsers:', expanded); 31 | // We are careful to append these to the configuration object, even though 32 | // we don't know the tunnel id yet. This allows WCT to give a useful error 33 | // if no browsers were configured. 34 | var activeBrowsers = wct.options.activeBrowsers; 35 | activeBrowsers.push.apply(activeBrowsers, expanded); 36 | // But we still need to inject the sauce tunnel ID once we know it. 37 | eachCapabilities = expanded; 38 | 39 | done(); 40 | }); 41 | }); 42 | 43 | wct.hook('prepare', function(done) { 44 | // Don't bother spinning up the tunnel if we don't have any browsers talking 45 | // over it. 46 | if (eachCapabilities.length === 0) return done(); 47 | 48 | // Is there already an active sauce tunnel? 49 | if (pluginOptions.tunnelId) { 50 | _injectTunnelId(eachCapabilities, pluginOptions.tunnelId); 51 | return done(); 52 | } 53 | 54 | // Let anyone know, and give them a chance to modify the options prior to 55 | // booting up the Sauce Connect tunnel. 56 | wct.emitHook('prepare:sauce-tunnel', function(error) { 57 | if (error) return done(error); 58 | sauce.startTunnel(pluginOptions, wct, function(error, tunnelId) { 59 | if (error) return done(error); 60 | _injectTunnelId(eachCapabilities, tunnelId); 61 | done(); 62 | }); 63 | }); 64 | }); 65 | 66 | wct.on('browser-start', function(def, data, stats, browser) { 67 | if (!browser) return; 68 | // Bump the connection periodically to advance Sauce's remote timeout. 69 | browser._keepalive = setInterval(function(){ 70 | browser.title(function() {}); 71 | }, (def.testTimeout / 2) || 45 * 1000); 72 | // do not let the keepalive hang node 73 | browser._keepalive.unref(); 74 | }); 75 | 76 | wct.on('browser-end', function(def, error, stats, sessionId, browser) { 77 | if (eachCapabilities.length === 0 || !sessionId) return; 78 | 79 | if (browser._keepalive) { 80 | clearInterval(browser._keepalive); 81 | } 82 | 83 | var payload = { 84 | passed: (stats.status === 'complete' && stats.failing === 0), 85 | 'public': pluginOptions.visibility, 86 | build: parseInt(pluginOptions.buildNumber, 10), 87 | name: pluginOptions.jobName 88 | }; 89 | wct.emit('log:debug', 'Updating sauce job', sessionId, payload); 90 | 91 | // Send the pass/fail info to sauce-labs if we are testing remotely. 92 | var username = wct.options.plugins.sauce.username; 93 | var accessKey = wct.options.plugins.sauce.accessKey; 94 | request.put({ 95 | url: 'https://saucelabs.com/rest/v1/' + encodeURIComponent(username) + '/jobs/' + encodeURIComponent(sessionId), 96 | auth: {user: username, pass: accessKey}, 97 | json: true, 98 | body: payload, 99 | }); 100 | }); 101 | 102 | }; 103 | 104 | function expandOptions(options) { 105 | _.defaults(options, { 106 | username: process.env.SAUCE_USERNAME, 107 | accessKey: process.env.SAUCE_ACCESS_KEY, 108 | tunnelId: process.env.SAUCE_TUNNEL_ID, 109 | // export the travis build number (integer) and repo slug (user/repo) to 110 | // sauce dashboard 111 | buildNumber: process.env.TRAVIS_BUILD_NUMBER, 112 | jobName: process.env.TRAVIS_REPO_SLUG, 113 | visibility: 'public' 114 | }); 115 | if (sauce.isTravisSauceConnectRunning()) { 116 | _.defaults(options, { 117 | // Under Travis CI, the tunnel id is $TRAVIS_JOB_NUMBER: https://docs.travis-ci.com/user/sauce-connect 118 | tunnelId: process.env.TRAVIS_JOB_NUMBER 119 | }); 120 | } 121 | } 122 | 123 | /** 124 | * @param {!Array} eachCapabilities 125 | * @param {string} tunnelId 126 | */ 127 | function _injectTunnelId(eachCapabilities, tunnelId) { 128 | eachCapabilities.forEach(function(browser) { 129 | browser['tunnel-identifier'] = tunnelId; 130 | }); 131 | } 132 | 133 | // Hacks for the wct-st binary. 134 | module.exports.expandOptions = expandOptions; 135 | module.exports.startTunnel = sauce.startTunnel; 136 | -------------------------------------------------------------------------------- /lib/sauce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | var _ = require('lodash'); 11 | var chalk = require('chalk'); 12 | var cleankill = require('cleankill'); 13 | var fs = require('fs'); 14 | var path = require('path'); 15 | var sauceConnect = require('sauce-connect-launcher'); 16 | var temp = require('temp'); 17 | var uuid = require('uuid'); 18 | 19 | /** 20 | * @param {!Object} config 21 | * @param {!EventEmitter} emitter 22 | * @param {function(*, string)} done 23 | */ 24 | function startTunnel(config, emitter, done) { 25 | if (!config.username || !config.accessKey) { 26 | return done('Missing Sauce credentials. Did you forget to set SAUCE_USERNAME and/or SAUCE_ACCESS_KEY?'); 27 | } 28 | 29 | // If anything goes wrong, sc tends to have a bit more detail in its log, so 30 | // let's make that easy(ish) to get at: 31 | temp.mkdir('wct', function(error, logDir) { 32 | if (error) return done(error); 33 | var logPath = path.join(logDir, 'sc.log'); 34 | 35 | var connectOptions = { 36 | username: config.username, 37 | accessKey: config.accessKey, 38 | tunnelIdentifier: uuid.v4(), 39 | logger: emitter.emit.bind(emitter, 'log:debug'), 40 | logfile: logPath, 41 | port: config.port 42 | }; 43 | _.assign(connectOptions, config.tunnelOptions); 44 | var tunnelId = connectOptions.tunnelIdentifier; 45 | 46 | emitter.emit('log:info', 'Creating Sauce Connect tunnel'); 47 | emitter.emit('log:info', 'Sauce Connect log:', chalk.magenta(logPath)); 48 | 49 | sauceConnect(connectOptions, function(error, tunnel) { 50 | if (error) { 51 | emitter.emit('log:error', 'Sauce tunnel failed:'); 52 | } else { 53 | emitter.emit('log:info', 'Sauce tunnel active:', chalk.yellow(tunnelId)); 54 | emitter.emit('sauce:tunnel-active', tunnelId); 55 | } 56 | done(error, tunnelId); 57 | }); 58 | // SauceConnectLauncher only supports one tunnel at a time; this allows us 59 | // to kill it before we've gotten our callback. 60 | cleankill.onInterrupt(function () { 61 | sauceConnect.kill(); 62 | return Promise.resolve(); 63 | }); 64 | }); 65 | } 66 | 67 | function isTravisSauceConnectRunning() { 68 | // https://docs.travis-ci.com/user/environment-variables/#Default-Environment-Variables 69 | if (!process.env.TRAVIS) { 70 | return false; 71 | } 72 | 73 | try { 74 | // when using the travis sauce_connect addon, the file 75 | // /home/travis/sauce-connect.log is written to with the sauce logs. 76 | // If this file exists, then the sauce_connect addon is in use 77 | // If fs.statSync throws, then the file does not exist 78 | var travisScLog = path.join(process.env.HOME, 'sauce-connect.log'); 79 | if (fs.statSync(travisScLog)) { 80 | return true; 81 | } 82 | return false; 83 | } catch (e) { 84 | return false; 85 | } 86 | } 87 | 88 | module.exports = { 89 | startTunnel: startTunnel, 90 | isTravisSauceConnectRunning: isTravisSauceConnectRunning 91 | }; 92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wct-sauce", 3 | "version": "2.0.1", 4 | "description": "WCT plugin that enables support for sauce browsers via Sauce Labs", 5 | "keywords": [ 6 | "wct", 7 | "web-component-tester", 8 | "plugin" 9 | ], 10 | "homepage": "https://github.com/Polymer/wct-sauce", 11 | "bugs": "https://github.com/Polymer/wct-sauce/issues", 12 | "license": "BSD-3-Clause", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/Polymer/wct-sauce.git" 16 | }, 17 | "scripts": { 18 | "postinstall": "node scripts/postinstall.js", 19 | "test": "mocha" 20 | }, 21 | "main": "lib/plugin.js", 22 | "wct-plugin": { 23 | "cli-options": { 24 | "browsers": { 25 | "help": "Remote Sauce Labs browsers to run tests on, or 'default'.", 26 | "full": "sauce", 27 | "metavar": "NAME", 28 | "abbr": "s", 29 | "list": true 30 | }, 31 | "username": { 32 | "help": "Sauce Labs username.", 33 | "full": "sauce-username" 34 | }, 35 | "accessKey": { 36 | "help": "Sauce Labs access key.", 37 | "full": "sauce-access-key" 38 | }, 39 | "tunnelId": { 40 | "help": "Sauce Connect tunnel identifier.", 41 | "full": "sauce-tunnel-id" 42 | }, 43 | "buildNumber": { 44 | "help": "The build number tested by this test for the sauce labs REST API.", 45 | "full": "build-number" 46 | }, 47 | "jobName": { 48 | "help": "Job name for the sauce labs REST API.", 49 | "full": "job-name" 50 | }, 51 | "visibility": { 52 | "help": "Set job visibility to 'public', 'public restricted', 'share', 'team' or 'private'", 53 | "full": "visibility" 54 | }, 55 | "port": { 56 | "help": "Select an alternative port for Sauce Connect (default is 4445)", 57 | "full": "port" 58 | } 59 | } 60 | }, 61 | "dependencies": { 62 | "chalk": "^1.1.1", 63 | "cleankill": "^2.0.0", 64 | "lodash": "^3.0.1", 65 | "request": "^2.85.0", 66 | "sauce-connect-launcher": "^1.0.0", 67 | "temp": "^0.8.1", 68 | "uuid": "^2.0.1" 69 | }, 70 | "devDependencies": { 71 | "chai": "^3.3.0", 72 | "gulp": "^3.8.10", 73 | "gulp-jshint": "^2.0.0", 74 | "jshint": "^2.8.0", 75 | "jshint-stylish": "^2.0.1", 76 | "mocha": "^2.2.4" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /scripts/postinstall.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4 | * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5 | * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6 | * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7 | * Code distributed by Google as part of the polymer project is also 8 | * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9 | */ 10 | // People frequently sudo install web-component-tester, and we have to be a 11 | // little careful about file permissions. 12 | // 13 | // sauce-connect-launcher downloads and caches the sc binary into its package 14 | // directory the first time you try to connect. If WCT is installed via sudo, 15 | // sauce-connect-launcher will be unable to write to its directory, and fail. 16 | // 17 | // So, we prefetch it during install ourselves. 18 | 19 | // Unfortunately, this process runs up against a npm race condition: 20 | // https://github.com/npm/npm/issues/6624 21 | // 22 | // As a workaround, our best bet is to retry with backoff. 23 | function requireSauceConnectLauncher(done, attempt) { 24 | attempt = attempt || 0; 25 | var sauceConnectLauncher; 26 | try { 27 | sauceConnectLauncher = require('sauce-connect-launcher'); 28 | } catch (error) { 29 | if (attempt > 3) { throw error; } 30 | setTimeout( 31 | requireSauceConnectLauncher.bind(null, done, attempt + 1), 32 | Math.pow(2, attempt) // Exponential backoff to play it safe. 33 | ); 34 | } 35 | // All is well. 36 | done(sauceConnectLauncher); 37 | } 38 | 39 | var sauce = require('../lib/sauce'); 40 | 41 | // don't download our own sauce connect binary if travis is running the 42 | // sauce_connect addon, or they explicitly opt out via an environment variable 43 | if (!(sauce.isTravisSauceConnectRunning() || process.env.SKIP_WCT_SAUCE_POSTINSTALL_DOWNLOAD)) { 44 | console.log('Prefetching the Sauce Connect binary.'); 45 | 46 | requireSauceConnectLauncher(function(sauceConnectLauncher) { 47 | sauceConnectLauncher.download({ 48 | logger: console.log.bind(console), 49 | }, function(error) { 50 | if (error) { 51 | console.log('Failed to download sauce connect binary:', error); 52 | console.log('sauce-connect-launcher will attempt to re-download next time it is run.'); 53 | // We explicitly do not fail the install process if this happens; the user 54 | // can still recover, unless their permissions are completely screwey. 55 | } 56 | }); 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /test/browsers.js: -------------------------------------------------------------------------------- 1 | var 2 | expand = require('../lib/browsers').expand, 3 | expect = require('chai').expect, 4 | slice = Array.prototype.slice, 5 | _ = require('lodash') 6 | ; 7 | 8 | const DEFAULT_BROWSERS = require('../default-sauce-browsers.json'); 9 | const DEFAULT_OPTIONS = { 10 | accessKey: 'access-key-' + Date.now(), 11 | browsers: ['default'], 12 | username: 'catpants-' + Date.now() 13 | }; 14 | const DEFAULT_URL = { 15 | accessKey: DEFAULT_OPTIONS.accessKey, 16 | hostname: 'ondemand.saucelabs.com', 17 | port: 80, 18 | username: DEFAULT_OPTIONS.username 19 | }; 20 | 21 | function browserOptions() { 22 | var options = _.cloneDeep(DEFAULT_OPTIONS); 23 | options.browsers = slice.call(arguments); 24 | 25 | return options; 26 | } 27 | 28 | describe('expand', function() { 29 | ['all', 'default'].forEach(function(keyword) { 30 | it('should respect browser keyword: ' + keyword, function(done) { 31 | expand(browserOptions(keyword), function(error, browsers) { 32 | expect(browsers).to.have.length(DEFAULT_BROWSERS.length); 33 | 34 | browsers.forEach(function(browser) { 35 | expect(browser).to.have.all.keys('browserName', 'platform', 'url', 'version'); 36 | 37 | expect(browser.url).to.deep.equal(DEFAULT_URL); 38 | }); 39 | 40 | done(); 41 | }); 42 | }); 43 | }); 44 | 45 | it('should remove bad browser instances', function(done) { 46 | expand(browserOptions('default', null, 'all', 0, false, 'What?'), function(error, browsers) { 47 | expect(browsers).to.have.length(DEFAULT_BROWSERS.length); 48 | 49 | browsers.forEach(function(browser) { 50 | expect(browser).to.have.all.keys('browserName', 'platform', 'url', 'version'); 51 | 52 | expect(browser.url).to.deep.equal(DEFAULT_URL); 53 | }); 54 | 55 | done(); 56 | }); 57 | }); 58 | 59 | it('should allow objects to pass through', function(done) { 60 | var browser = {foo: 'bar', baz: 1}; 61 | 62 | expand(browserOptions(browser), function(error, browsers) { 63 | expect(browsers).to.have.length(1); 64 | 65 | expect(browsers[0]).to.deep.equal(browser); 66 | 67 | done(); 68 | }); 69 | }); 70 | 71 | it('should pass custom browsers', function(done) { 72 | var 73 | browser = 'browers name here', 74 | platform = 'os or platform', 75 | version = String(Date.now()) 76 | ; 77 | 78 | expand(browserOptions(platform + '/' + browser + '@' + version), function(error, browsers) { 79 | var result; 80 | 81 | expect(browsers).to.have.length(1); 82 | 83 | result = browsers[0]; 84 | 85 | expect(result).to.have.property('browserName', browser); 86 | expect(result).to.have.property('platform', platform); 87 | expect(result).to.have.property('version', version); 88 | expect(result.url).to.deep.equal(DEFAULT_URL); 89 | 90 | done(); 91 | }); 92 | }); 93 | 94 | it('should extend browsers with custom metadata', function(done) { 95 | var 96 | buildNumber = Date.now(), 97 | customName = 'custom name ' + Date.now(), 98 | options = browserOptions('platform/browser@eleventy', 'default'), 99 | tags = ['custom', 'tags', 'here'] 100 | ; 101 | 102 | _.extend(options, { 103 | build: buildNumber, 104 | disabled: true, 105 | name: customName, 106 | tags: tags 107 | }); 108 | 109 | expand(options, function(error, browsers) { 110 | expect(browsers).to.have.length(DEFAULT_BROWSERS.length + 1); 111 | 112 | browsers.forEach(function(browser) { 113 | expect(browser).to.contain.all.keys('browserName', 'platform', 'url', 'version'); 114 | 115 | expect(browser).to.have.property('build', buildNumber); 116 | expect(browser).to.have.property('name', customName); 117 | expect(browser).to.have.property('tags', tags); 118 | expect(browser.url).to.deep.equal(DEFAULT_URL); 119 | 120 | expect(browser).to.not.have.keys('browsers', 'disabled'); 121 | }); 122 | 123 | done(); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /travis-browsers.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "browserName": "microsoftedge", 4 | "platform": "Windows 10", 5 | "version": "14" 6 | }, 7 | { 8 | "browserName": "microsoftedge", 9 | "platform": "Windows 10", 10 | "version": "15" 11 | }, 12 | { 13 | "browserName": "internet explorer", 14 | "platform": "Windows 8.1", 15 | "version": "11" 16 | }, 17 | { 18 | "browserName": "safari", 19 | "platform": "OS X 10.12", 20 | "version": "11" 21 | }, 22 | { 23 | "browserName": "safari", 24 | "platform": "OS X 10.11", 25 | "version": "10" 26 | }, 27 | { 28 | "browserName": "safari", 29 | "platform": "OS X 10.11", 30 | "version": "9" 31 | } 32 | ] 33 | --------------------------------------------------------------------------------