From aaa09bb96e24396c9577011df49dd79583864f83 Mon Sep 17 00:00:00 2001 From: Dan Bucholtz Date: Tue, 13 Sep 2016 15:10:24 -0500 Subject: [PATCH] chore(karma): update karma scripts --- scripts/karma/browser-providers.ts | 359 +++++++++++++++++++++++++++++ scripts/karma/karma-watch.conf.js | 8 - scripts/karma/karma.conf.js | 55 ++--- scripts/karma/karma.config.ts | 93 ++++++++ scripts/karma/system.config.js | 88 ++++--- scripts/karma/test-main.js | 41 ---- 6 files changed, 526 insertions(+), 118 deletions(-) create mode 100644 scripts/karma/browser-providers.ts delete mode 100644 scripts/karma/karma-watch.conf.js create mode 100644 scripts/karma/karma.config.ts delete mode 100644 scripts/karma/test-main.js diff --git a/scripts/karma/browser-providers.ts b/scripts/karma/browser-providers.ts new file mode 100644 index 0000000000..b4cc1fe767 --- /dev/null +++ b/scripts/karma/browser-providers.ts @@ -0,0 +1,359 @@ +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: false}, e2e: {target: null, required: true}}, + 'FirefoxBeta': { unitTest: {target: null, required: false}, 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: false}, e2e: {target: null, required: true}}, + 'iOS8': { unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}}, + // TODO(mlaval): iOS9 deactivated as not reliable, reactivate after + // https://github.com/angular/angular/issues/5408 + 'iOS9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}, + 'WindowsPhone': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}} +}; + +export const customLaunchers: { [name: string]: BrowserLauncherInfo } = { + 'ChromeNoSandbox': { + base: 'Chrome', + flags: ['--no-sandbox'] + }, + // Chrome set to 1024x768 resolution for *local testing only*. + // On CI, both SauceLabs and Browserstack already default all browser window sizes to 1024x768. + 'Chrome_1024x768': { + base : 'Chrome', + flags: ['--window-size=1024,768'] + }, + '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', + resolution: '1024x768' + }, + 'BS_IOS8': { + base: 'BrowserStack', + device: 'iPhone 6', + os: 'ios', + os_version: '8.3', + resolution: '1024x768' + }, + 'BS_IOS9': { + base: 'BrowserStack', + device: 'iPhone 6S', + os: 'ios', + os_version: '9.0', + resolution: '1024x768' + }, + '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()}`); +} diff --git a/scripts/karma/karma-watch.conf.js b/scripts/karma/karma-watch.conf.js deleted file mode 100644 index 063ab1405b..0000000000 --- a/scripts/karma/karma-watch.conf.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = function(config) { - require('./karma.conf.js')(config); - config.set({ - singleRun: false, - }); -}; - - diff --git a/scripts/karma/karma.conf.js b/scripts/karma/karma.conf.js index 9adc295991..9286034ca2 100644 --- a/scripts/karma/karma.conf.js +++ b/scripts/karma/karma.conf.js @@ -1,37 +1,28 @@ -var buildConfig = require('../build/config'); +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); + } -module.exports = function(config) { - config.set({ - singleRun: true, - basePath: '../../', - - frameworks: ['jasmine'], - - files: [ - 'node_modules/es6-shim/es6-shim.min.js', - - 'node_modules/systemjs/node_modules/es6-module-loader/dist/es6-module-loader.js', //npm2 - 'node_modules/es6-module-loader/dist/es6-module-loader.js', //npm3 - 'node_modules/reflect-metadata/Reflect.js', - 'node_modules/zone.js/dist/zone.js', - 'node_modules/systemjs/dist/system.js', - 'scripts/karma/system.config.js', - 'node_modules/rxjs/bundles/Rx.min.js', - 'dist/bundles/ionic.system.js', - { pattern: 'node_modules/@angular/**/*.js', included: false}, - { pattern: 'dist/tests/**/*.spec.js', included: false }, - // { pattern: 'dist/tests/components/nav/**/*.spec.js', included: false }, - { pattern: 'src/**/*.js', included: false }, - 'scripts/karma/test-main.js' - ], - - exclude: ['src/components/*/test/*/**/*'], - - logLevel: 'warn', - - browsers: ['Chrome'], - port: 9876 + // 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; diff --git a/scripts/karma/karma.config.ts b/scripts/karma/karma.config.ts new file mode 100644 index 0000000000..0c02b8d6f8 --- /dev/null +++ b/scripts/karma/karma.config.ts @@ -0,0 +1,93 @@ +// This file is named differently than its JS bootstrapper to avoid the ts compiler to overwrite it. + +import path = require('path'); +import { customLaunchers } from './browser-providers.ts'; + + +export function config(config) { + config.set({ + basePath: path.join(__dirname, '../..'), + frameworks: ['jasmine'], + plugins: [ + require('karma-jasmine'), + require('karma-coverage'), + require('karma-browserstack-launcher'), + require('karma-sauce-launcher'), + require('karma-sourcemap-loader'), + require('karma-chrome-launcher'), + require('karma-firefox-launcher'), + ], + files: [ + {pattern: 'dist/vendor/core-js/client/core.js', included: true, watched: false}, + {pattern: 'dist/vendor/systemjs/dist/system-polyfills.js', included: true, watched: false}, + {pattern: 'dist/vendor/systemjs/dist/system.src.js', included: true, watched: false}, + {pattern: 'dist/vendor/zone.js/dist/zone.js', included: true, watched: false}, + {pattern: 'dist/vendor/zone.js/dist/proxy.js', included: true, watched: false}, + {pattern: 'dist/vendor/zone.js/dist/async-test.js', included: true, watched: false}, + {pattern: 'dist/vendor/zone.js/dist/fake-async-test.js', included: true, watched: false}, + + {pattern: 'scripts/karma/system.config.js', included: true, watched: false}, + + // paths loaded via module imports + {pattern: 'dist/**/*.js', 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: {}, + + customLaunchers: customLaunchers, + + exclude: [ + 'dist/e2e/**/*', + 'dist/ionic-angular/components/slides/swiper-widget*' + ], + // Source files that you wanna generate coverage for. + // Do not include tests or libraries (these files will be instrumented by Istanbul) + preprocessors: { + 'dist/ionic-angular/**/!(*spec).js': ['coverage'], + 'dist/ionic-angular/**/*.js': ['sourcemap'] + }, + reporters: ['dots', 'coverage'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + + sauceLabs: { + testName: 'ionic', + startConnect: false, + recordVideo: false, + recordScreenshots: false, + options: { + 'selenium-version': '2.48.2', + 'command-timeout': 600, + 'idle-timeout': 600, + 'max-duration': 5400 + } + }, + + browserStack: { + project: 'ionic', + startTunnel: false, + retryLimit: 1, + timeout: 600, + pollingTimeout: 20000 + }, + + browserDisconnectTimeout: 20000, + browserNoActivityTimeout: 240000, + captureTimeout: 120000, + browsers: ['Chrome_1024x768'], + + coverageReporter: { + reporters: [ + {type: 'json', subdir: '.', file: 'coverage-final.json'} + ] + }, + + singleRun: true + }); + +}; diff --git a/scripts/karma/system.config.js b/scripts/karma/system.config.js index 0c485eeb42..ade90b16fb 100644 --- a/scripts/karma/system.config.js +++ b/scripts/karma/system.config.js @@ -1,48 +1,62 @@ +/*global jasmine, __karma__, window*/ +Error.stackTraceLimit = Infinity; +jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000; + +// disable console debugs/errors/warns from printing out +console.debug = () => {}; +console.error = () => {}; +console.warn = () => {}; + +__karma__.loaded = function () {}; + + +var distPath = '/base/dist/'; + System.config({ - baseURL: '/base', + baseURL: distPath, map: { - 'ionic-angular': 'src', - '@angular': 'node_modules/@angular', + '@angular': 'vendor/@angular', + '@angular/core': 'vendor/@angular/core/bundles/core.umd.js', + '@angular/common': 'vendor/@angular/common/bundles/common.umd.js', + '@angular/compiler': 'vendor/@angular/compiler/bundles/compiler.umd.js', + '@angular/platform-browser': 'vendor/@angular/platform-browser/bundles/platform-browser.umd.js', + '@angular/platform-browser-dynamic': 'vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', + '@angular/http': 'vendor/@angular/http/bundles/http.umd.js', + '@angular/forms': 'vendor/@angular/forms/bundles/forms.umd.js', + 'rxjs': 'vendor/rxjs', + 'ionic-angular': 'ionic-angular/commonjs' }, packages: { 'ionic-angular': { + format: 'cjs', + defaultExtension: 'js', main: 'index' }, - 'rxjs': { + rxjs: { defaultExtension: 'js' }, - '@angular/core': { - main: 'index.js', - defaultExtension: 'js' - }, - '@angular/compiler': { - main: 'index.js', - defaultExtension: 'js' - }, - '@angular/common': { - main: 'index.js', - defaultExtension: 'js' - }, - // remove after all tests imports are fixed - '@angular/facade': { - main: 'index.js', - defaultExtension: 'js' - }, - '@angular/forms': { - main: 'index.js', - defaultExtension: 'js' - }, - '@angular/http': { - main: 'index.js', - defaultExtension: 'js' - }, - '@angular/platform-browser': { - main: 'index.js', - defaultExtension: 'js' - }, - '@angular/platform-browser-dynamic': { - main: 'index.js', - defaultExtension: 'js' - } } }); + +var allSpecFiles = Object.keys(window.__karma__.files).filter(isSpecFile).filter(isIonicFile); + +Promise.all( + allSpecFiles.map((moduleName) => { + return System.import(moduleName).then(function(m) { + return m; + }); + }) +).then(__karma__.start, __karma__.error).catch(__karma__.error); + + +function isJsFile(path) { + return path.slice(-3) == '.js'; +} + +function isSpecFile(path) { + return path.slice(-8) == '.spec.js'; +} + +function isIonicFile(path) { + return isJsFile(path) && path.indexOf('vendor') == -1; +} diff --git a/scripts/karma/test-main.js b/scripts/karma/test-main.js deleted file mode 100644 index b88aa8afc1..0000000000 --- a/scripts/karma/test-main.js +++ /dev/null @@ -1,41 +0,0 @@ -jasmine.DEFAULT_TIMEOUT_INTERVAL = 50; - -// Cancel Karma's synchronous start, -// we will call `__karma__.start()` later, once all the specs are loaded. -__karma__.loaded = function() {}; - -System.import('@angular/core/testing').then(function(coreTesting) { - return System.import('@angular/platform-browser-dynamic/testing').then(function(browserTesting) { - coreTesting.setBaseTestProviders( - browserTesting.TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS, - browserTesting.TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS - ); - }); -}).then(function() { - return Promise.all( - Object.keys(window.__karma__.files) // All files served by Karma. - .filter(onlySpecFiles) - .map(window.file2moduleName) // Normalize paths to module names. - .map(function(path) { - return System.import(path).then(function(module) { - if (module.hasOwnProperty('run')) { - module.run(); - } else { - console.warn('WARNING: Module ' + path + ' does not implement a run() method. No tests run.'); - } - }); - })) -}) -.then(function() { - __karma__.start(); -}, function(error) { - console.error(error.stack || error); - __karma__.start(); -}); - -function onlySpecFiles(path) { - return /\.spec\.js$/.test(path); -} -function file2moduleName(filePath) { - return filePath.replace(/\\/g, '/') -}