build(): First version of using SauceLabs and BrowserStack on travis.

This commit is contained in:
Hans Larsen
2016-01-14 11:29:50 -08:00
parent 63d27a31e5
commit 015f8f63c6
15 changed files with 751 additions and 54 deletions
+352
View File
@@ -0,0 +1,352 @@
type ContextConfigurationInfo = { target: string, required: boolean };
export interface ConfigurationInfo {
unitTest: ContextConfigurationInfo;
e2e: ContextConfigurationInfo;
};
export interface BrowserLauncherInfo {
base: string;
flags?: string[];
version?: string;
platform?: string;
device?: string;
browser?: string;
browser_version?: string;
os?: string;
os_version?: string;
};
export type AliasMap = { [name: string]: string[] };
// Unique place to configure the browsers which are used in the different CI jobs in Sauce Labs (SL)
// and BrowserStack (BS).
// If the target is set to null, then the browser is not run anywhere during CI.
// If a category becomes empty (e.g. BS and required), then the corresponding job must be commented
// out in Travis configuration.
const configuration: { [name: string]: ConfigurationInfo } = {
'Chrome': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Firefox': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'ChromeBeta': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'FirefoxBeta': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'ChromeDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'FirefoxDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'IE9': { unitTest: {target: null, required: false}, e2e: {target: null, required: true}},
'IE10': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'IE11': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Edge': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Android4.1': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.2': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.3': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.4': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android5': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Safari7': { unitTest: {target: null, required: false}, e2e: {target: null, required: true}},
'Safari8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Safari9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS7': { unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
'iOS8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
// TODO(mlaval): iOS9 deactivated as not reliable, reactivate after
// https://github.com/angular/angular/issues/5408
'iOS9': { unitTest: {target: null, required: false}, e2e: {target: null, required: true}},
'WindowsPhone': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
};
export const customLaunchers: { [name: string]: BrowserLauncherInfo } = {
'DartiumWithWebPlatform': {
base: 'Dartium',
flags: ['--enable-experimental-web-platform-features'] },
'ChromeNoSandbox': {
base: 'Chrome',
flags: ['--no-sandbox'] },
'SL_CHROME': {
base: 'SauceLabs',
browserName: 'chrome',
version: '46'
},
'SL_CHROMEBETA': {
base: 'SauceLabs',
browserName: 'chrome',
version: 'beta'
},
'SL_CHROMEDEV': {
base: 'SauceLabs',
browserName: 'chrome',
version: 'dev'
},
'SL_FIREFOX': {
base: 'SauceLabs',
browserName: 'firefox',
version: '42'
},
'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_SAFARI9': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.11',
version: '9.0'
},
'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.4'
},
'SL_IOS9': {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '9.1'
},
'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_EDGE': {
base: 'SauceLabs',
browserName: 'microsoftedge',
platform: 'Windows 10',
version: '20.10240'
},
'SL_ANDROID4.1': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.1'
},
'SL_ANDROID4.2': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.2'
},
'SL_ANDROID4.3': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.3'
},
'SL_ANDROID4.4': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '4.4'
},
'SL_ANDROID5': {
base: 'SauceLabs',
browserName: 'android',
platform: 'Linux',
version: '5.1'
},
'BS_CHROME': {
base: 'BrowserStack',
browser: 'chrome',
os: 'OS X',
os_version: 'Yosemite'
},
'BS_FIREFOX': {
base: 'BrowserStack',
browser: 'firefox',
os: 'Windows',
os_version: '10'
},
'BS_SAFARI7': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Mavericks'
},
'BS_SAFARI8': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'Yosemite'
},
'BS_SAFARI9': {
base: 'BrowserStack',
browser: 'safari',
os: 'OS X',
os_version: 'El Capitan'
},
'BS_IOS7': {
base: 'BrowserStack',
device: 'iPhone 5S',
os: 'ios',
os_version: '7.0'
},
'BS_IOS8': {
base: 'BrowserStack',
device: 'iPhone 6',
os: 'ios',
os_version: '8.3'
},
'BS_IOS9': {
base: 'BrowserStack',
device: 'iPhone 6S',
os: 'ios',
os_version: '9.0'
},
'BS_IE9': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '9.0',
os: 'Windows',
os_version: '7'
},
'BS_IE10': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '10.0',
os: 'Windows',
os_version: '8'
},
'BS_IE11': {
base: 'BrowserStack',
browser: 'ie',
browser_version: '11.0',
os: 'Windows',
os_version: '10'
},
'BS_EDGE': {
base: 'BrowserStack',
browser: 'edge',
os: 'Windows',
os_version: '10'
},
'BS_WINDOWSPHONE' : {
base: 'BrowserStack',
device: 'Nokia Lumia 930',
os: 'winphone',
os_version: '8.1'
},
'BS_ANDROID5': {
base: 'BrowserStack',
device: 'Google Nexus 5',
os: 'android',
os_version: '5.0'
},
'BS_ANDROID4.4': {
base: 'BrowserStack',
device: 'HTC One M8',
os: 'android',
os_version: '4.4'
},
'BS_ANDROID4.3': {
base: 'BrowserStack',
device: 'Samsung Galaxy S4',
os: 'android',
os_version: '4.3'
},
'BS_ANDROID4.2': {
base: 'BrowserStack',
device: 'Google Nexus 4',
os: 'android',
os_version: '4.2'
},
'BS_ANDROID4.1': {
base: 'BrowserStack',
device: 'Google Nexus 7',
os: 'android',
os_version: '4.1'
}
};
const sauceAliases: AliasMap = {
'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'],
'MOBILE': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5',
'SL_IOS7', 'SL_IOS8', 'SL_IOS9'],
'ANDROID': ['SL_ANDROID4.1', 'SL_ANDROID4.2', 'SL_ANDROID4.3', 'SL_ANDROID4.4', 'SL_ANDROID5'],
'IE': ['SL_IE9', 'SL_IE10', 'SL_IE11'],
'IOS': ['SL_IOS7', 'SL_IOS8', 'SL_IOS9'],
'SAFARI': ['SL_SAFARI7', 'SL_SAFARI8', 'SL_SAFARI9'],
'BETA': ['SL_CHROMEBETA', 'SL_FIREFOXBETA'],
'DEV': ['SL_CHROMEDEV', 'SL_FIREFOXDEV'],
'REQUIRED': buildConfiguration('unitTest', 'SL', true),
'OPTIONAL': buildConfiguration('unitTest', 'SL', false)
};
const browserstackAliases: AliasMap = {
'ALL': Object.keys(customLaunchers).filter(function(item) {
return customLaunchers[item].base == 'BrowserStack';
}),
'DESKTOP': ['BS_CHROME', 'BS_FIREFOX', 'BS_IE9', 'BS_IE10', 'BS_IE11', 'BS_EDGE', 'BS_SAFARI7',
'BS_SAFARI8', 'BS_SAFARI9'],
'MOBILE': ['BS_ANDROID4.3', 'BS_ANDROID4.4', 'BS_IOS7', 'BS_IOS8', 'BS_IOS9', 'BS_WINDOWSPHONE'],
'ANDROID': ['BS_ANDROID4.3', 'BS_ANDROID4.4'],
'IE': ['BS_IE9', 'BS_IE10', 'BS_IE11'],
'IOS': ['BS_IOS7', 'BS_IOS8', 'BS_IOS9'],
'SAFARI': ['BS_SAFARI7', 'BS_SAFARI8', 'BS_SAFARI9'],
'REQUIRED': buildConfiguration('unitTest', 'BS', true),
'OPTIONAL': buildConfiguration('unitTest', 'BS', false)
};
export const platformMap: { [name: string]: AliasMap } = {
'saucelabs': sauceAliases,
'browserstack': browserstackAliases,
};
/** Decode the token for Travis to use. */
function decode(str: string): string {
return (str || '').split('').reverse().join('');
}
/** Setup the access keys */
if (process.env.TRAVIS) {
process.env.SAUCE_ACCESS_KEY = decode(process.env.SAUCE_ACCESS_KEY);
process.env.BROWSER_STACK_ACCESS_KEY = decode(process.env.BROWSER_STACK_ACCESS_KEY);
}
/** Build a list of configuration (custom launcher names). */
function buildConfiguration(type: string, target: string, required: boolean): string[] {
return Object.keys(configuration)
.map(item => [item, configuration[item][type]])
.filter(([item, conf]) => conf.required == required && conf.target == target)
.map(([item, conf]) => `${target}_${item.toUpperCase()}`);
}
+55
View File
@@ -0,0 +1,55 @@
Error.stackTraceLimit = Infinity;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
__karma__.loaded = function() {};
/**
* Gets map of module alias to location or package.
* @param dir Directory name under `src/` for create a map for.
*/
function getPathsMap(dir) {
return Object.keys(window.__karma__.files)
.filter(isComponentsFile)
.reduce(function(pathsMapping, appPath) {
var pathToReplace = new RegExp('^/base/dist/' + dir + '/');
var moduleName = appPath.replace(pathToReplace, './').replace(/\.js$/, '');
pathsMapping[moduleName] = appPath + '?' + window.__karma__.files[appPath];
return pathsMapping;
}, {});
}
System.config({
packages: {
'base/dist/components': {
defaultExtension: false,
format: 'register',
map: getPathsMap('components')
}
}
});
System.import('angular2/platform/browser').then(function(browser_adapter) {
// TODO: once beta is out we should change this code to use a "test platform"
browser_adapter.BrowserDomAdapter.makeCurrent();
}).then(function() {
return Promise.all(
Object.keys(window.__karma__.files)
.filter(onlySpecFiles)
.map(function(moduleName) {
return System.import(moduleName);
}));
}).then(function() {
__karma__.start();
}, function(error) {
__karma__.error(error.stack || error);
});
function isComponentsFile(filePath) {
return /^\/base\/dist\/components\/(?!spec)([a-z0-9-_\/]+)\.js$/.test(filePath);
}
function onlySpecFiles(path) {
return /\.spec\.js$/.test(path);
}
+32
View File
@@ -0,0 +1,32 @@
// This file only hook up on require calls to transpile the TypeScript.
// If you're looking at this file to see Karma configuration, you should look at
// karma.config.ts instead.
const fs = require('fs');
const ts = require('typescript');
const old = require.extensions['.ts'];
require.extensions['.ts'] = function(m, filename) {
// If we're in node module, either call the old hook or simply compile the
// file without transpilation. We do not touch node_modules/**.
if (filename.match(/node_modules/)) {
if (old) {
return old(m, filename);
}
return m._compile(fs.readFileSync(filename), filename);
}
// Node requires all require hooks to be sync.
const source = fs.readFileSync(filename).toString();
const result = ts.transpile(source, {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJs,
});
// Send it to node to execute.
return m._compile(result, filename);
};
// Import the TS once we know it's safe to require.
module.exports = require('./karma.config.ts').config;
+114
View File
@@ -0,0 +1,114 @@
// This file is named differently than its JS bootstrapper to avoid the ts compiler to overwrite it.
import path = require('path');
import {
customLaunchers,
platformMap,
} from './browser-providers.ts';
export function config(config) {
config.set({
basePath: path.join(__dirname, '..'),
frameworks: ['jasmine'],
plugins: [
require('karma-jasmine'),
require('karma-browserstack-launcher'),
require('karma-chrome-launcher'),
require('karma-firefox-launcher'),
require('karma-sauce-launcher'),
],
files: [
{pattern: 'node_modules/es6-shim/es6-shim.js', included: true, watched: true},
{pattern: 'node_modules/angular2/bundles/angular2-polyfills.js', included: true, watched: true},
{pattern: 'node_modules/systemjs/dist/system-polyfills.js', included: true, watched: true},
{pattern: 'node_modules/systemjs/dist/system.src.js', included: true, watched: true},
{pattern: 'node_modules/rxjs/bundles/Rx.js', included: true, watched: true},
{pattern: 'node_modules/angular2/bundles/angular2.js', included: true, watched: true},
{pattern: 'node_modules/angular2/bundles/testing.dev.js', included: true, watched: true},
{pattern: 'test/karma-test-shim.js', included: true, watched: true},
// paths loaded via module imports
{pattern: 'dist/**/*.js', included: false, watched: true},
// paths loaded via Angular's component compiler
// (these paths need to be rewritten, see proxies section)
{pattern: 'dist/**/*.html', included: false, watched: true},
{pattern: 'dist/**/*.css', included: false, watched: true},
// paths to support debugging with source maps in dev tools
{pattern: 'dist/**/*.ts', included: false, watched: false},
{pattern: 'dist/**/*.js.map', included: false, watched: false}
],
proxies: {
// required for component assests fetched by Angular's compiler
"/demo-app/": "/base/dist/demo-app/",
"/components/": "/base/dist/components/",
},
customLaunchers: customLaunchers,
exclude: [],
preprocessors: {},
reporters: ['dots'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
sauceLabs: {
testName: 'material2',
startConnect: false,
recordVideo: false,
recordScreenshots: false,
options: {
'selenium-version': '2.48.2',
'command-timeout': 600,
'idle-timeout': 600,
'max-duration': 5400
}
},
browserStack: {
project: 'material2',
startTunnel: false,
retryLimit: 1,
timeout: 600,
pollingTimeout: 10000
},
browsers: ['Chrome'],
singleRun: false
});
if (process.env['TRAVIS']) {
var buildId = `TRAVIS #${process.env.TRAVIS_BUILD_NUMBER} (${process.env.TRAVIS_BUILD_ID})`;
// The MODE variable is the indicator of what row in the test matrix we're running.
// It will look like <platform>_<alias>, where platform is one of 'saucelabs' or 'browserstack',
// and alias is one of the keys in the CIconfiguration variable declared in
// browser-providers.ts.
let [platform, alias] = process.env.MODE.split('_');
if (platform == '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'];
}
else if (platform == 'browserstack') {
config.browserStack.build = buildId;
config.browserStack.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
}
else {
throw new Error(`Platform "${platform}" unknown, but Travis specified. Exiting.`);
}
config.browsers = platformMap[platform][alias.toUpperCase()];
}
};