From 13447e3198abcacef6e5fe5fcb77dd755f56ad4d Mon Sep 17 00:00:00 2001 From: mlaval Date: Tue, 27 Oct 2015 16:09:09 +0100 Subject: [PATCH] build(browserstack): initial setup Closes #4941 --- .travis.yml | 4 ++ sauce.conf.js => browser-providers.conf.js | 19 +++++++- gulpfile.js | 25 +++++++--- karma-dart.conf.js | 4 +- karma-js.conf.js | 33 +++++++++---- modules/angular1_router/karma-router.conf.js | 4 +- scripts/browserstack/start_tunnel.js | 50 ++++++++++++++++++++ scripts/browserstack/start_tunnel.sh | 3 ++ scripts/browserstack/teardown_tunnel.sh | 8 ++++ scripts/browserstack/waitfor_tunnel.sh | 19 ++++++++ scripts/ci/after-script.sh | 3 ++ scripts/ci/build_and_test.sh | 2 +- scripts/ci/test_browserstack.sh | 12 +++++ 13 files changed, 164 insertions(+), 22 deletions(-) rename sauce.conf.js => browser-providers.conf.js (87%) create mode 100644 scripts/browserstack/start_tunnel.js create mode 100755 scripts/browserstack/start_tunnel.sh create mode 100755 scripts/browserstack/teardown_tunnel.sh create mode 100755 scripts/browserstack/waitfor_tunnel.sh create mode 100755 scripts/ci/test_browserstack.sh diff --git a/.travis.yml b/.travis.yml index 1ca0ac25aa..579cbfaefb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,8 @@ env: - LOGS_DIR=/tmp/angular-build/logs - SAUCE_USERNAME=angular-ci - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987 + - BROWSER_STACK_USERNAME=angularteam1 + - BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB - ARCH=linux-x64 - DART_DEV_VERSION=latest - DART_STABLE_VERSION=latest @@ -36,6 +38,7 @@ env: - MODE=dart DART_CHANNEL=stable DART_VERSION=$DART_STABLE_VERSION - MODE=dart DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION - MODE=saucelabs DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION + - MODE=browserstack DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION - MODE=dart_experimental DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION - MODE=js DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION - MODE=router DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION @@ -45,6 +48,7 @@ env: matrix: allow_failures: - env: "MODE=saucelabs DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION" + - env: "MODE=browserstack DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION" - env: "MODE=dart_experimental DART_CHANNEL=dev DART_VERSION=$DART_DEV_VERSION" addons: diff --git a/sauce.conf.js b/browser-providers.conf.js similarity index 87% rename from sauce.conf.js rename to browser-providers.conf.js index 16fa46ccec..00775a9a2b 100644 --- a/sauce.conf.js +++ b/browser-providers.conf.js @@ -124,10 +124,17 @@ var customLaunchers = { browserName: 'android', platform: 'Linux', version: '5.1' + }, + + 'BS_Chrome': { + base: 'BrowserStack', + browser: 'chrome', + os: 'OS X', + os_version: 'Yosemite' } }; -var aliases = { +var sauceAliases = { 'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'SauceLabs';}), 'DESKTOP': ['SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_EDGE', 'SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9.0'], 'MOBILE': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5.1', 'SL_IOS7', 'SL_IOS8', 'SL_IOS9'], @@ -142,11 +149,19 @@ var aliases = { 'SL_CHROMEDEV', 'SL_FIREFOXBETA'] }; +var browserstackAliases = { + 'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'BrowserStack';}), + 'DESKTOP': ['BS_Chrome'], + 'CI': ['BS_Chrome'], +}; + module.exports = { customLaunchers: customLaunchers, - aliases: aliases + sauceAliases: sauceAliases, + browserstackAliases: browserstackAliases } if (process.env.TRAVIS) { process.env.SAUCE_ACCESS_KEY = process.env.SAUCE_ACCESS_KEY.split('').reverse().join(''); + process.env.BROWSER_STACK_ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY.split('').reverse().join(''); } diff --git a/gulpfile.js b/gulpfile.js index facf378e01..e4ae591634 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -43,7 +43,7 @@ var buildRouter = require('./modules/angular1_router/build'); var uglify = require('gulp-uglify'); var shouldLog = require('./tools/build/logging'); var dartSdk = require('./tools/build/dart'); -var sauceConf = require('./sauce.conf'); +var browserProvidersConf = require('./browser-providers.conf.js'); var os = require('os'); require('./tools/check-environment')({ @@ -463,17 +463,17 @@ function getBrowsersFromCLI() { for (var i = 0; i < inputList.length; i++) { var input = inputList[i]; var karmaChromeLauncher = require('karma-chrome-launcher'); - if (sauceConf.customLaunchers.hasOwnProperty(input) || karmaChromeLauncher.hasOwnProperty("launcher:" + input)) { + if (browserProvidersConf.customLaunchers.hasOwnProperty(input) || karmaChromeLauncher.hasOwnProperty("launcher:" + input)) { // In case of non-sauce browsers, or browsers defined in karma-chrome-launcher (Chrome, ChromeCanary and Dartium): // overrides everything, ignoring other options outputList = [input]; isSauce = false; break; - } else if (sauceConf.customLaunchers.hasOwnProperty("SL_" + input.toUpperCase())) { + } else if (browserProvidersConf.customLaunchers.hasOwnProperty("SL_" + input.toUpperCase())) { isSauce = true; outputList.push("SL_" + input.toUpperCase()); - } else if (sauceConf.aliases.hasOwnProperty(input.toUpperCase())) { - outputList = outputList.concat(sauceConf.aliases[input]); + } else if (browserProvidersConf.sauceAliases.hasOwnProperty(input.toUpperCase())) { + outputList = outputList.concat(browserProvidersConf.sauceAliases[input]); isSauce = true; } else { throw new Error('ERROR: unknown browser found in getBrowsersFromCLI()'); @@ -661,12 +661,25 @@ gulp.task('test.unit.js.sauce/ci', function (done) { browserNoActivityTimeout: 240000, captureTimeout: 120000, reporters: ['dots', 'saucelabs'], - browsers: sauceConf.aliases.CI + browsers: browserProvidersConf.sauceAliases.CI }, function(err) {done(); process.exit(err ? 1 : 0);} ).start(); }); +gulp.task('test.unit.js.browserstack/ci', function (done) { + new karma.Server({ + configFile: __dirname + '/karma-js.conf.js', + singleRun: true, + browserNoActivityTimeout: 240000, + captureTimeout: 120000, + reporters: ['dots'], + browsers: browserProvidersConf.browserstackAliases.CI + }, + function(err) {done(); process.exit(err ? 1 : 0);} + ).start(); +}); + gulp.task('test.unit.dart/ci', function (done) { var browserConf = getBrowsersFromCLI(); new karma.Server({ diff --git a/karma-dart.conf.js b/karma-dart.conf.js index 28951466aa..858c10e446 100644 --- a/karma-dart.conf.js +++ b/karma-dart.conf.js @@ -1,4 +1,4 @@ -var sauceConf = require('./sauce.conf'); +var browserProvidersConf = require('./browser-providers.conf.js'); var packageSources = { // Dependencies installed with `pub install`. @@ -67,7 +67,7 @@ module.exports = function(config) { // Map packages to the correct urls where Karma serves them. proxies: proxyPaths, - customLaunchers: sauceConf.customLaunchers, + customLaunchers: browserProvidersConf.customLaunchers, browsers: ['DartiumWithWebPlatform'], port: 9877, diff --git a/karma-js.conf.js b/karma-js.conf.js index b054246e39..435dc1a8ea 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -1,4 +1,4 @@ -var sauceConf = require('./sauce.conf'); +var browserProvidersConf = require('./browser-providers.conf.js'); // Karma configuration // Generated on Thu Sep 25 2014 11:52:02 GMT-0700 (PDT) @@ -33,7 +33,7 @@ module.exports = function(config) { exclude: ['dist/js/dev/es5/**/e2e_test/**', 'dist/js/dev/es5/angular2/examples/**', 'dist/angular1_router.js'], - customLaunchers: sauceConf.customLaunchers, + customLaunchers: browserProvidersConf.customLaunchers, sauceLabs: { testName: 'Angular2', @@ -48,18 +48,33 @@ module.exports = function(config) { } }, + browserStack: { + project: 'Angular2', + startTunnel: false, + retryLimit: 1, + timeout: 600 + }, + browsers: ['Chrome'], port: 9876 }); - if (process.env.TRAVIS && process.env.MODE === 'saucelabs') { - config.sauceLabs.build = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')'; - config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; + if (process.env.TRAVIS) { + var buildId = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')'; + if (process.env.MODE === 'saucelabs') { + config.sauceLabs.build = buildId; + config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; - // TODO(mlaval): remove once SauceLabs supports websockets. - // This speeds up the capturing a bit, as browsers don't even try to use websocket. - console.log('>>>> setting socket.io transport to polling <<<<'); - config.transports = ['polling']; + // TODO(mlaval): remove once SauceLabs supports websockets. + // This speeds up the capturing a bit, as browsers don't even try to use websocket. + console.log('>>>> setting socket.io transport to polling <<<<'); + config.transports = ['polling']; + } + + if (process.env.MODE === 'browserstack') { + config.browserStack.build = buildId; + config.browserStack.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; + } } }; diff --git a/modules/angular1_router/karma-router.conf.js b/modules/angular1_router/karma-router.conf.js index 1bfd17d380..e9980a724f 100644 --- a/modules/angular1_router/karma-router.conf.js +++ b/modules/angular1_router/karma-router.conf.js @@ -1,6 +1,6 @@ 'use strict'; -var sauceConf = require('../../sauce.conf'); +var browserProvidersConf = require('../../browser-providers.conf.js'); // This runs the tests for the router in Angular 1.x @@ -20,7 +20,7 @@ module.exports = function (config) { 'test/**/*_spec.js' ], - customLaunchers: sauceConf.customLaunchers, + customLaunchers: browserProvidersConf.customLaunchers, browsers: ['ChromeCanary'] }; diff --git a/scripts/browserstack/start_tunnel.js b/scripts/browserstack/start_tunnel.js new file mode 100644 index 0000000000..f4ebe0fa8f --- /dev/null +++ b/scripts/browserstack/start_tunnel.js @@ -0,0 +1,50 @@ +'use strict'; + +var fs = require('fs'); +var http = require('http'); +var BrowserStackTunnel = require('browserstacktunnel-wrapper'); + +var HOSTNAME = 'localhost'; +var PORTS = [9876, 9877]; +var ACCESS_KEY = process.env.BROWSER_STACK_ACCESS_KEY; +var READY_FILE = process.env.BROWSER_PROVIDER_READY_FILE; +var TUNNEL_IDENTIFIER = process.env.TRAVIS_JOB_NUMBER; + +// We need to start fake servers, otherwise the tunnel does not start. +var fakeServers = []; +var hosts = []; + +PORTS.forEach(function(port) { + fakeServers.push(http.createServer(function() {}).listen(port)); + hosts.push({ + name: HOSTNAME, + port: port, + sslFlag: 0 + }); +}); + +var tunnel = new BrowserStackTunnel({ + key: ACCESS_KEY, + localIdentifier: TUNNEL_IDENTIFIER, + hosts: hosts +}); + +console.log('Starting tunnel on ports', PORTS.join(', ')); +tunnel.start(function(error) { + if (error) { + console.error('Can not establish the tunnel', error); + } else { + console.log('Tunnel established.'); + fakeServers.forEach(function(server) { + server.close(); + }); + + if (READY_FILE) { + fs.writeFile(READY_FILE, ''); + } + } +}); + +tunnel.on('error', function(error) { + console.error(error); +}); diff --git a/scripts/browserstack/start_tunnel.sh b/scripts/browserstack/start_tunnel.sh new file mode 100755 index 0000000000..da351371da --- /dev/null +++ b/scripts/browserstack/start_tunnel.sh @@ -0,0 +1,3 @@ +export BROWSER_STACK_ACCESS_KEY=`echo $BROWSER_STACK_ACCESS_KEY | rev` + +node ./scripts/browserstack/start_tunnel.js & diff --git a/scripts/browserstack/teardown_tunnel.sh b/scripts/browserstack/teardown_tunnel.sh new file mode 100755 index 0000000000..86fd334284 --- /dev/null +++ b/scripts/browserstack/teardown_tunnel.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +set -e -o pipefail + + +echo "Shutting down Browserstack tunnel" +echo "TODO: implement me" +exit 1 \ No newline at end of file diff --git a/scripts/browserstack/waitfor_tunnel.sh b/scripts/browserstack/waitfor_tunnel.sh new file mode 100755 index 0000000000..a603a317b0 --- /dev/null +++ b/scripts/browserstack/waitfor_tunnel.sh @@ -0,0 +1,19 @@ +#!/bin/bash + + +# Wait for Connect to be ready before exiting +# Time out if we wait for more than 2 minutes, so that we can print logs. +let "counter=0" + +while [ ! -f $BROWSER_PROVIDER_READY_FILE ]; do + let "counter++" + if [ $counter -gt 240 ]; then + echo "Timed out after 2 minutes waiting for browser provider ready file" + # We must manually print logs here because travis will not run + # after_script commands if the failure occurs before the script + # phase. + ./scripts/ci/print-logs.sh + exit 5 + fi + sleep .5 +done diff --git a/scripts/ci/after-script.sh b/scripts/ci/after-script.sh index 8c444e964f..998073c7cc 100755 --- a/scripts/ci/after-script.sh +++ b/scripts/ci/after-script.sh @@ -8,6 +8,9 @@ echo '*******************' if [ "$MODE" = "saucelabs" ]; then ./scripts/sauce/sauce_connect_teardown.sh fi +if [ "$MODE" = "browserstack" ]; then + ./scripts/browserstack/teardown_tunnel.sh +fi echo '---------------------' diff --git a/scripts/ci/build_and_test.sh b/scripts/ci/build_and_test.sh index e5d684cb35..3318ea2821 100755 --- a/scripts/ci/build_and_test.sh +++ b/scripts/ci/build_and_test.sh @@ -10,7 +10,7 @@ cd $SCRIPT_DIR/../.. if [ "$MODE" = "dart_experimental" ]; then ${SCRIPT_DIR}/build_$MODE.sh -elif [ "$MODE" = "saucelabs" ]; then +elif [ "$MODE" = "saucelabs" ] || [ "$MODE" = "browserstack" ] ; then ${SCRIPT_DIR}/test_$MODE.sh elif [ "$MODE" = "lint" ]; then ./node_modules/.bin/gulp static-checks diff --git a/scripts/ci/test_browserstack.sh b/scripts/ci/test_browserstack.sh new file mode 100755 index 0000000000..9f6c3a834b --- /dev/null +++ b/scripts/ci/test_browserstack.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +echo ============================================================================= +# go to project dir +SCRIPT_DIR=$(dirname $0) +cd $SCRIPT_DIR/../.. + +./scripts/browserstack/start_tunnel.sh +./scripts/browserstack/waitfor_tunnel.sh +./node_modules/.bin/gulp build.js.dev +./node_modules/.bin/gulp test.unit.js.browserstack/ci