diff --git a/.travis.yml b/.travis.yml index ea7b18bccc..fe78fec7e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,8 @@ env: - KARMA_BROWSERS=DartiumWithWebPlatform - E2E_BROWSERS=Dartium - LOGS_DIR=/tmp/angular-build/logs + - SAUCE_USERNAME=angular-ci + - SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987 - ARCH=linux-x64 # Token for tsd to increase github rate limit # See https://github.com/DefinitelyTyped/tsd#tsdrc @@ -26,10 +28,15 @@ env: # (password is in Valentine) - TSDRC='{"token":"ef474500309daea53d5991b3079159a29520a40b"}' matrix: - - MODE=js DART_CHANNEL=dev - - MODE=dart DART_CHANNEL=stable - - MODE=dart DART_CHANNEL=dev - - MODE=dart_experimental DART_CHANNEL=dev + - MODE=js DART_CHANNEL=dev + - MODE=dart DART_CHANNEL=stable + - MODE=dart DART_CHANNEL=dev + - MODE=dart_experimental DART_CHANNEL=dev + - MODE=saucelabs DART_CHANNEL=dev + +matrix: + allow_failures: + - env: "MODE=saucelabs DART_CHANNEL=dev" addons: firefox: "38.0" diff --git a/gulpfile.js b/gulpfile.js index 7dc12c1dc7..b622c1e8ba 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -40,6 +40,7 @@ var uglify = require('gulp-uglify'); var shouldLog = require('./tools/build/logging'); var tslint = require('gulp-tslint'); var dartSdk = require('./tools/build/dart'); +var sauceConf = require('./sauce.conf'); require('./tools/check-environment')({ requiredNpmVersion: '>=2.9.0', @@ -530,13 +531,35 @@ gulp.task('test.all.dart', shell.task(['./scripts/ci/test_dart.sh'])) // These tests run in the browser and are allowed to access // HTML DOM APIs. function getBrowsersFromCLI() { + var isSauce = false; var args = minimist(process.argv.slice(2)); - return [args.browsers?args.browsers:'DartiumWithWebPlatform'] + var rawInput = args.browsers?args.browsers:'DartiumWithWebPlatform'; + var inputList = rawInput.replace(' ', '').split(','); + var outputList = []; + for (var i = 0; i < inputList.length; i++) { + var input = inputList[i]; + if (sauceConf.customLaunchers.hasOwnProperty(input)) { + //Non-sauce browsers case: overrides everything, ignoring other options + outputList = [input]; + isSauce = false; + break; + } else if (sauceConf.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]); + isSauce = true; + } else { + throw new Error('ERROR: unknown browser found in getBrowsersFromCLI()'); + } + } + return { + browsersToRun: outputList.filter(function(item, pos, self) {return self.indexOf(item) == pos;}), + isSauce: isSauce + } } - -gulp.task('test.unit.js', ['build.js.dev'], function (neverDone) { - +gulp.task('test.unit.js', ['build.js.dev'], function (done) { runSequence( '!test.unit.js/karma-server', function() { @@ -548,6 +571,16 @@ gulp.task('test.unit.js', ['build.js.dev'], function (neverDone) { ); }); +gulp.task('test.unit.js.sauce', ['build.js.dev'], function (done) { + var browserConf = getBrowsersFromCLI(); + if (browserConf.isSauce) { + karma.server.start({configFile: __dirname + '/karma-js.conf.js', + singleRun: true, browserNoActivityTimeout: 240000, captureTimeout: 120000, reporters: ['dots'], browsers: browserConf.browsersToRun}, + function(err) {done(); process.exit(err ? 1 : 0)}); + } else { + throw new Error('ERROR: no Saucelabs browsers provided, add them with the --browsers option'); + } +}); gulp.task('!test.unit.js/karma-server', function() { karma.server.start({configFile: __dirname + '/karma-js.conf.js', reporters: 'dots'}); @@ -598,13 +631,21 @@ gulp.task('!test.unit.dart/karma-server', function() { gulp.task('test.unit.js/ci', function (done) { + var browserConf = getBrowsersFromCLI(); karma.server.start({configFile: __dirname + '/karma-js.conf.js', - singleRun: true, reporters: ['dots'], browsers: getBrowsersFromCLI()}, done); + singleRun: true, reporters: ['dots'], browsers: browserConf.browsersToRun}, done); +}); + +gulp.task('test.unit.js.sauce/ci', function (done) { + karma.server.start({configFile: __dirname + '/karma-js.conf.js', + singleRun: true, browserNoActivityTimeout: 240000, captureTimeout: 120000, reporters: ['dots', 'saucelabs'], browsers: sauceConf.aliases.CI}, + function(err) {done(); process.exit(err ? 1 : 0)}); }); gulp.task('test.unit.dart/ci', function (done) { + var browserConf = getBrowsersFromCLI(); karma.server.start({configFile: __dirname + '/karma-dart.conf.js', - singleRun: true, reporters: ['dots'], browsers: getBrowsersFromCLI()}, done); + singleRun: true, reporters: ['dots'], browsers: browserConf.browsersToRun}, done); }); diff --git a/karma-dart.conf.js b/karma-dart.conf.js index d7e21b3d5a..cc84598efc 100644 --- a/karma-dart.conf.js +++ b/karma-dart.conf.js @@ -1,3 +1,5 @@ +var sauceConf = require('./sauce.conf'); + // Karma configuration // Generated on Thu Sep 25 2014 11:52:02 GMT-0700 (PDT) module.exports = function(config) { @@ -48,11 +50,7 @@ module.exports = function(config) { '/packages/examples': '/base/dist/dart/examples/lib' }, - customLaunchers: { - DartiumWithWebPlatform: { - base: 'Dartium', - flags: ['--enable-experimental-web-platform-features'] } - }, + customLaunchers: sauceConf.customLaunchers, browsers: ['DartiumWithWebPlatform'], port: 9877 diff --git a/karma-js.conf.js b/karma-js.conf.js index 5fd438f3d8..158acd8b1b 100644 --- a/karma-js.conf.js +++ b/karma-js.conf.js @@ -1,3 +1,5 @@ +var sauceConf = require('./sauce.conf'); + // Karma configuration // Generated on Thu Sep 25 2014 11:52:02 GMT-0700 (PDT) module.exports = function(config) { @@ -25,23 +27,42 @@ module.exports = function(config) { 'node_modules/reflect-metadata/Reflect.js', 'tools/build/file2modulename.js', 'test-main.js', - {pattern: 'modules/**/test/**/static_assets/**', included: false, watched: false} + {pattern: 'modules/**/test/**/static_assets/**', included: false, watched: false}, + 'modules/angular2/src/test_lib/shims_for_IE.ts' ], exclude: [ 'dist/js/dev/es5/**/e2e_test/**', ], - customLaunchers: { - DartiumWithWebPlatform: { - base: 'Dartium', - flags: ['--enable-experimental-web-platform-features'] }, - ChromeNoSandbox: { - base: 'Chrome', - flags: ['--no-sandbox'] } + customLaunchers: sauceConf.customLaunchers, + + sauceLabs: { + testName: 'Angular2', + startConnect: false, + recordVideo: false, + recordScreenshots: false, + options: { + 'selenium-version': '2.45.0', + 'command-timeout': 600, + 'idle-timeout': 600, + 'max-duration': 5400 + } }, + browsers: ['ChromeCanary'], port: 9876 }); + + if (process.env.TRAVIS) { + config.sauceLabs.build = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')'; + 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. + config.transports = ['xhr-polling']; + } }; + + diff --git a/modules/angular2/src/test_lib/test_lib.dart b/modules/angular2/src/test_lib/test_lib.dart index 25ab62156d..bb3821e891 100644 --- a/modules/angular2/src/test_lib/test_lib.dart +++ b/modules/angular2/src/test_lib/test_lib.dart @@ -177,15 +177,15 @@ void _it(gnsFn, name, fn) { }); } -void it(name, fn) { +void it(name, fn, [timeOut = null]) { _it(gns.it, name, fn); } -void iit(name, fn) { +void iit(name, fn, [timeOut = null]) { _it(gns.iit, name, fn); } -void xit(name, fn) { +void xit(name, fn, [timeOut = null]) { _it(gns.xit, name, fn); } diff --git a/modules/angular2/src/test_lib/test_lib.ts b/modules/angular2/src/test_lib/test_lib.ts index e2417410a3..3233964299 100644 --- a/modules/angular2/src/test_lib/test_lib.ts +++ b/modules/angular2/src/test_lib/test_lib.ts @@ -129,7 +129,7 @@ export function beforeEachBindings(fn) { }); } -function _it(jsmFn, name, fn) { +function _it(jsmFn, name, fn, timeOut) { var runner = runnerStack[runnerStack.length - 1]; jsmFn(name, function(done) { @@ -156,19 +156,19 @@ function _it(jsmFn, name, fn) { inIt = false; if (!async) done(); - }); + }, timeOut); } -export function it(name, fn) { - return _it(jsmIt, name, fn); +export function it(name, fn, timeOut = null) { + return _it(jsmIt, name, fn, timeOut); } -export function xit(name, fn) { - return _it(jsmXIt, name, fn); +export function xit(name, fn, timeOut = null) { + return _it(jsmXIt, name, fn, timeOut); } -export function iit(name, fn) { - return _it(jsmIIt, name, fn); +export function iit(name, fn, timeOut = null) { + return _it(jsmIIt, name, fn, timeOut); } // Some Map polyfills don't polyfill Map.toString correctly, which diff --git a/modules/angular2/test/render/xhr_impl_spec.ts b/modules/angular2/test/render/xhr_impl_spec.ts index 90d6840ad4..b28c29f569 100644 --- a/modules/angular2/test/render/xhr_impl_spec.ts +++ b/modules/angular2/test/render/xhr_impl_spec.ts @@ -27,7 +27,7 @@ export function main() { expect(text.trim()).toEqual('
hey
'); async.done(); }); - })); + }), 10000); it('should reject the Promise on failure', inject([AsyncTestCompleter], (async) => { PromiseWrapper.catchError(xhr.get(url404), (e) => { @@ -35,6 +35,6 @@ export function main() { async.done(); return null; }); - })); + }), 10000); }); } diff --git a/sauce.conf.js b/sauce.conf.js new file mode 100644 index 0000000000..f19ef937f6 --- /dev/null +++ b/sauce.conf.js @@ -0,0 +1,138 @@ +var customLaunchers = { + 'DartiumWithWebPlatform': { + base: 'Dartium', + flags: ['--enable-experimental-web-platform-features'] }, + 'ChromeNoSandbox': { + base: 'Chrome', + flags: ['--no-sandbox'] }, + 'SL_CHROME': { + base: 'SauceLabs', + browserName: 'chrome', + version: '43' + }, + 'SL_CHROMEBETA': { + base: 'SauceLabs', + browserName: 'chrome', + version: 'beta' + }, + 'SL_CHROMEDEV': { + base: 'SauceLabs', + browserName: 'chrome', + version: 'dev' + }, + 'SL_FIREFOX': { + base: 'SauceLabs', + browserName: 'firefox', + version: '37' + }, + 'SL_FIREFOXBETA': { + base: 'SauceLabs', + browserName: 'firefox', + version: 'beta' + }, + 'SL_FIREFOXDEV': { + base: 'SauceLabs', + browserName: 'firefox', + version: 'dev' + }, + 'SL_SAFARI7': { + base: 'SauceLabs', + browserName: 'safari', + platform: 'OS X 10.9', + version: '7' + }, + 'SL_SAFARI8': { + base: 'SauceLabs', + browserName: 'safari', + platform: 'OS X 10.10', + version: '8' + }, + 'SL_IOS7': { + base: 'SauceLabs', + browserName: 'iphone', + platform: 'OS X 10.10', + version: '7.1' + }, + 'SL_IOS8': { + base: 'SauceLabs', + browserName: 'iphone', + platform: 'OS X 10.10', + version: '8.2' + }, + 'SL_IE9': { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 2008', + version: '9' + }, + 'SL_IE10': { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 2012', + version: '10' + }, + 'SL_IE11': { + base: 'SauceLabs', + browserName: 'internet explorer', + platform: 'Windows 8.1', + version: '11' + }, + 'SL_ANDROID5.1': { + base: 'SauceLabs', + browserName: 'android', + platform: 'Linux', + version: '5.1' + }, + 'SL_ANDROID4.4': { + base: 'SauceLabs', + browserName: 'android', + platform: 'Linux', + version: '4.4' + }, + 'SL_ANDROID4.3': { + base: 'SauceLabs', + browserName: 'android', + platform: 'Linux', + version: '4.3' + }, + 'SL_ANDROID4.2': { + base: 'SauceLabs', + browserName: 'android', + platform: 'Linux', + version: '4.2' + }, + 'SL_ANDROID4.1': { + base: 'SauceLabs', + browserName: 'android', + platform: 'Linux', + version: '4.1' + }, + 'SL_ANDROID4.0': { + base: 'SauceLabs', + browserName: 'android', + platform: 'Linux', + version: '4.0' + } +}; + +var aliases = { + 'ALL': Object.keys(customLaunchers).filter(function(item) {return customLaunchers[item].base == 'SauceLabs';}), + 'DESKTOP': ['SL_CHROME', 'SL_FIREFOX', 'SL_IE9', 'SL_IE10', 'SL_IE11', 'SL_SAFARI7', 'SL_SAFARI8'], + 'MOBILE': ['SL_ANDROID4.0', 'SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5.1', 'SL_IOS7', 'SL_IOS8'], + 'ANDROID': ['SL_ANDROID4.0', 'SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5.1'], + 'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'], + 'IOS': ['SL_IOS7', 'SL_IOS8'], + 'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8'], + 'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'], + 'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'], + 'CI': ['SL_CHROME', 'SL_FIREFOX'] +}; + +module.exports = { + customLaunchers: customLaunchers, + aliases: aliases +} + +if (process.env.TRAVIS) { + process.env.SAUCE_ACCESS_KEY = process.env.SAUCE_ACCESS_KEY.split('').reverse().join(''); +} \ No newline at end of file diff --git a/scripts/ci/build_and_test.sh b/scripts/ci/build_and_test.sh index 444f06a84d..4c00c92d77 100755 --- a/scripts/ci/build_and_test.sh +++ b/scripts/ci/build_and_test.sh @@ -12,7 +12,12 @@ if [ "$MODE" = "dart_experimental" ] then ${SCRIPT_DIR}/build_$MODE.sh else - ${SCRIPT_DIR}/build_$MODE.sh - mkdir deploy; tar -czpf deploy/dist.tgz -C dist . - ${SCRIPT_DIR}/test_$MODE.sh + if [ "$MODE" = "saucelabs" ] + then + ${SCRIPT_DIR}/test_$MODE.sh + else + ${SCRIPT_DIR}/build_$MODE.sh + mkdir deploy; tar -czpf deploy/dist.tgz -C dist . + ${SCRIPT_DIR}/test_$MODE.sh + fi fi diff --git a/scripts/ci/test_saucelabs.sh b/scripts/ci/test_saucelabs.sh new file mode 100755 index 0000000000..e57c378209 --- /dev/null +++ b/scripts/ci/test_saucelabs.sh @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +echo ============================================================================= +# go to project dir +SCRIPT_DIR=$(dirname $0) +cd $SCRIPT_DIR/../.. + +./scripts/sauce/sauce_connect_setup.sh +./scripts/sauce/sauce_connect_block.sh +./node_modules/.bin/gulp build.js.dev +./node_modules/.bin/gulp test.unit.js.sauce/ci \ No newline at end of file diff --git a/scripts/sauce/sauce_connect_block.sh b/scripts/sauce/sauce_connect_block.sh new file mode 100755 index 0000000000..ebda1fccb0 --- /dev/null +++ b/scripts/sauce/sauce_connect_block.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Wait for Connect to be ready before exiting +printf "Connecting to Sauce." +while [ ! -f $BROWSER_PROVIDER_READY_FILE ]; do + printf "." + #dart2js takes longer than the travis 10 min timeout to complete + sleep .5 +done +echo "Connected" \ No newline at end of file diff --git a/scripts/sauce/sauce_connect_setup.sh b/scripts/sauce/sauce_connect_setup.sh new file mode 100755 index 0000000000..db7839605c --- /dev/null +++ b/scripts/sauce/sauce_connect_setup.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +set -e -o pipefail + +# Setup and start Sauce Connect for your TravisCI build +# This script requires your .travis.yml to include the following two private env variables: +# SAUCE_USERNAME +# SAUCE_ACCESS_KEY +# Follow the steps at https://saucelabs.com/opensource/travis to set that up. +# +# Curl and run this script as part of your .travis.yml before_script section: +# before_script: +# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash + +CONNECT_URL="https://saucelabs.com/downloads/sc-4.3.8-linux.tar.gz" +CONNECT_DIR="/tmp/sauce-connect-$RANDOM" +CONNECT_DOWNLOAD="sc-latest-linux.tar.gz" + +CONNECT_LOG="$LOGS_DIR/sauce-connect" +CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout" +CONNECT_STDERR="$LOGS_DIR/sauce-connect.stderr" + +# Get Connect and start it +mkdir -p $CONNECT_DIR +cd $CONNECT_DIR +curl $CONNECT_URL -o $CONNECT_DOWNLOAD 2> /dev/null 1> /dev/null +mkdir sauce-connect +tar --extract --file=$CONNECT_DOWNLOAD --strip-components=1 --directory=sauce-connect > /dev/null +rm $CONNECT_DOWNLOAD + +SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev` + +ARGS="" + +# Set tunnel-id only on Travis, to make local testing easier. +if [ ! -z "$TRAVIS_JOB_NUMBER" ]; then + ARGS="$ARGS --tunnel-identifier $TRAVIS_JOB_NUMBER" +fi +if [ ! -z "$BROWSER_PROVIDER_READY_FILE" ]; then + ARGS="$ARGS --readyfile $BROWSER_PROVIDER_READY_FILE" +fi + + +echo "Starting Sauce Connect in the background, logging into:" +echo " $CONNECT_LOG" +echo " $CONNECT_STDOUT" +echo " $CONNECT_STDERR" +sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY $ARGS \ + --logfile $CONNECT_LOG 2> $CONNECT_STDERR 1> $CONNECT_STDOUT &