Skip to content

Commit

Permalink
Merge pull request #28 from developit/wip
Browse files Browse the repository at this point in the history
SauceLabs, Custom Browsers & Modern JS
  • Loading branch information
developit committed Apr 18, 2019
2 parents a391c9b + c802b6d commit 277f019
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 31 deletions.
22 changes: 12 additions & 10 deletions package.json
Expand Up @@ -7,7 +7,7 @@
"bin": "dist/cli.js",
"scripts": {
"prepare": "npm t",
"build": "microbundle --target node --external all -f cjs --no-compress src/*.js",
"build": "microbundle --target node -f cjs --no-compress src/*.js",
"test:build": "node ./dist/cli.js run",
"test:watch": "node ./dist/cli.js watch --headless false",
"test": "eslint src test && npm run -s build && npm run -s test:build",
Expand All @@ -30,26 +30,28 @@
"devDependencies": {
"eslint": "^4.16.0",
"eslint-config-developit": "^1.1.1",
"microbundle": "^0.4.3",
"microbundle": "^0.11.0",
"webpack": "^4.14.0",
"workerize-loader": "^1.0.1"
"workerize-loader": "^1.0.4"
},
"dependencies": {
"babel-core": "^6.26.0",
"babel-loader": "^7.1.2",
"@babel/core": "^7.4.3",
"@babel/plugin-proposal-object-rest-spread": "^7.4.3",
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@babel/polyfill": "^7.4.3",
"@babel/preset-env": "^7.4.3",
"@babel/preset-stage-0": "^7.0.0",
"babel-loader": "^8.0.5",
"babel-plugin-istanbul": "^5.1.0",
"babel-plugin-transform-object-rest-spread": "^6.26.0",
"babel-plugin-transform-react-jsx": "^6.24.1",
"babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.1",
"babel-preset-stage-0": "^6.24.1",
"chalk": "^2.3.0",
"dlv": "^1.1.1",
"jasmine-core": "^3.3.0",
"karma": "^3.1.1",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "^1.1.2",
"karma-firefox-launcher": "^1.1.0",
"karma-jasmine": "^2.0.1",
"karma-sauce-launcher": "^2.0.2",
"karma-sourcemap-loader": "^0.3.7",
"karma-spec-reporter": "0.0.32",
"karma-webpack": "2.0.7",
Expand Down
9 changes: 7 additions & 2 deletions src/cli.js
Expand Up @@ -6,17 +6,19 @@ import './lib/patch';
import karmatic from '.';
import { cleanStack } from './lib/util';

// @ts-ignore
const { version } = require('../package.json');

let toArray = val => Array.isArray(val) ? val : val == null ? [] : [val];
let toArray = val => typeof val === 'string' ? val.split(/\s*,\s*/) : val == null ? [] : [].concat(val);

let prog = sade('karmatic');

prog
.version(version)
.option('--files', 'Minimatch pattern for test files')
.option('--headless', 'Run using Chrome Headless', true)
.option('--coverage', 'Report code coverage of tests', true);
.option('--coverage', 'Report code coverage of tests', true)
.option('--downlevel', 'Downlevel syntax to ES5');

prog
.command('run [...files]', '', { default: true })
Expand All @@ -32,6 +34,7 @@ prog
.command('debug [...files]')
.describe('Open a headful Puppeteer instance for debugging your tests')
.option('--headless', 'Run using Chrome Headless', false) // Override default to false
.option('--browsers', 'Run in specific browsers', null)
.option('--coverage', 'Report code coverage of tests', false) // Override default to false
.action( (str, opts) => run(str, opts, true) );

Expand All @@ -40,6 +43,8 @@ prog.parse(process.argv);
function run(str, opts, isWatch) {
opts.watch = !!isWatch;
opts.files = toArray(str || opts.files).concat(opts._);
const b = opts.browsers || opts.browser;
opts.browsers = b ? toArray(b) : null;
karmatic(opts)
.then( output => {
if (output!=null) process.stdout.write(output + '\n');
Expand Down
90 changes: 80 additions & 10 deletions src/configure.js
@@ -1,13 +1,24 @@
import path from 'path';
import puppeteer from 'puppeteer';
import chalk from 'chalk';
import delve from 'dlv';
import { moduleDir, tryRequire, dedupe, cleanStack, readFile, readDir } from './lib/util';
import { tryRequire, dedupe, cleanStack, readFile, readDir } from './lib/util';
import babelLoader from './lib/babel-loader';
import cssLoader from './lib/css-loader';

const WEBPACK_VERSION = String(require('webpack').version || '3.0.0');
const WEBPACK_MAJOR = WEBPACK_VERSION.split('.')[0]|0;
const WEBPACK_MAJOR = parseInt(WEBPACK_VERSION.split('.')[0], 10);

/**
* @param {Object} options
* @param {Array} options.files - Test files to run
* @param {Array} [options.browsers] - Custom list of browsers to run in
* @param {Boolean} [options.headless=false] - Run in Headless Chrome?
* @param {Boolean} [options.watch=false] - Start a continuous test server and retest when files change
* @param {Boolean} [options.coverage=false] - Instrument and collect code coverage statistics
* @param {Object} [options.webpackConfig] - Custom webpack configuration
* @param {Boolean} [options.downlevel=false] - Downlevel/transpile syntax to ES5
*/
export default function configure(options) {
let cwd = process.cwd(),
res = file => path.resolve(cwd, file);
Expand All @@ -17,7 +28,9 @@ export default function configure(options) {

process.env.CHROME_BIN = puppeteer.executablePath();

let gitignore = (readFile(path.resolve(cwd, '.gitignore'), 'utf8') || '').replace(/(^\s*|\s*$|#.*$)/g, '').split('\n').filter(Boolean);
let gitignore = (
readFile(path.resolve(cwd, '.gitignore')) || ''
).replace(/(^\s*|\s*$|#.*$)/g, '').split('\n').filter(Boolean);
let repoRoot = (readDir(cwd) || []).filter( c => c[0]!=='.' && c!=='node_modules' && gitignore.indexOf(c)===-1 );
let rootFiles = '{' + repoRoot.join(',') + '}';

Expand All @@ -31,6 +44,56 @@ export default function configure(options) {
options.coverage ? 'karma-coverage' : []
);

// Custom launchers to be injected:
const launchers = {};
let useSauceLabs = false;

let browsers;
if (options.browsers) {
browsers = options.browsers.map(browser => {
if (/^chrome$/i.test(browser)) {
return 'Chrome';
}
if (/^firefox$/i.test(browser)) {
PLUGINS.push('karma-firefox-launcher');
return 'Firefox';
}
if (/^sauce-/.test(browser)) {
if (!useSauceLabs) {
useSauceLabs = true;
PLUGINS.push('karma-sauce-launcher');
}
const parts = browser.toLowerCase().split('-');
const name = parts.join('_');
launchers[name] = {
base: 'SauceLabs',
browserName: parts[1].replace(/^ie$/gi, 'Internet Explorer'),
version: parts[2] || undefined,
platform: parts[3] ? parts[3].replace(/^win(dows)?[ -]+/gi, 'Windows ').replace(/^(macos|mac ?os ?x|os ?x)[ -]+/gi, 'OS X ') : undefined
};
return name;
}
return browser;
});
}
else {
browsers = [options.headless===false ? 'KarmaticChrome' : 'KarmaticChromeHeadless'];
}

if (useSauceLabs) {
let missing = ['SAUCE_USERNAME', 'SAUCE_ACCESS_KEY'].filter(x => !process.env[x])[0];
if (missing) {
throw (
'\n' +
chalk.bold.bgRed.white('Error:') + ' Missing SauceLabs auth configuration.' +
'\n ' + chalk.white(`A SauceLabs browser was requested, but no ${chalk.magentaBright(missing)} environment variable provided.`) +
'\n ' + chalk.white('Try prepending it to your test command:') +
' ' + chalk.greenBright(missing + '=... npm test') +
'\n'
);
}
}

const WEBPACK_CONFIGS = [
'webpack.config.babel.js',
'webpack.config.js'
Expand Down Expand Up @@ -109,22 +172,27 @@ export default function configure(options) {

let generatedConfig = {
basePath: cwd,
plugins: PLUGINS.map(require.resolve),
plugins: PLUGINS.map(req => require.resolve(req)),
frameworks: ['jasmine'],
reporters: ['spec'].concat(
options.coverage ? 'coverage' : []
options.coverage ? 'coverage' : [],
useSauceLabs ? 'saucelabs' : []
),
browsers: [options.headless===false ? 'KarmaticChrome' : 'KarmaticChromeHeadless'],
browsers,
sauceLabs: {
testName: pkg && pkg.name || undefined
},

customLaunchers: {
customLaunchers: Object.assign({
KarmaticChrome: {
base: 'Chrome'
base: 'Chrome',
flags: ['--no-sandbox']
},
KarmaticChromeHeadless: {
base: 'ChromeHeadless',
flags: ['--no-sandbox']
}
},
}, launchers),

coverageReporter: {
reporters: [
Expand All @@ -149,7 +217,8 @@ export default function configure(options) {
}],

files: [
{ pattern: moduleDir('babel-polyfill')+'/dist/polyfill.js', watched: false, included: true, served: true }
// @TODO remove me
// { pattern: moduleDir('babel-polyfill')+'/dist/polyfill.js', watched: false, included: true, served: true }
].concat( ...files.map( pattern => {
// Expand '**/xx' patterns but exempt node_modules and gitignored directories
let matches = pattern.match(/^\*\*\/(.+)$/);
Expand All @@ -167,6 +236,7 @@ export default function configure(options) {

webpack: {
devtool: 'cheap-module-eval-source-map',
// devtool: 'module-source-map',
mode: webpackConfig.mode || 'development',
module: {
// @TODO check webpack version and use loaders VS rules as the key here appropriately:
Expand Down
26 changes: 18 additions & 8 deletions src/lib/babel-loader.js
Expand Up @@ -2,23 +2,33 @@ export default function babelLoader(options) {
return {
test: /\.jsx?$/,
exclude: /node_modules/,
loader: 'babel-loader',
loader: require.resolve('babel-loader'),
query: {
presets: [
[require.resolve('babel-preset-env'), {
[require.resolve('@babel/preset-env'), {
targets: {
browsers: 'last 2 Chrome versions'
browsers: [
'last 2 Chrome versions',
'last 2 Firefox versions',
(options.downlevel || options.browsers && String(options.browsers).match(/(\bie(\b|\d)|internet.explorer)/gi)) && 'ie>=9'
].filter(Boolean)
},
corejs: 2,
useBuiltIns: 'usage',
modules: false,
loose: true
}],
require.resolve('babel-preset-stage-0')
}]
],
plugins: [
[require.resolve('babel-plugin-transform-object-rest-spread')],
[require.resolve('babel-plugin-transform-react-jsx'), { pragma: options.pragma || 'h' }]
[require.resolve('@babel/plugin-proposal-object-rest-spread'), {
loose: true,
useBuiltIns: true
}],
[require.resolve('@babel/plugin-transform-react-jsx'), {
pragma: options.pragma || 'h'
}]
].concat(
options.coverage ? require.resolve('babel-plugin-istanbul') : []
options.coverage ? [require.resolve('babel-plugin-istanbul')] : []
)
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/lib/css-loader.js
@@ -1,4 +1,4 @@
export default function cssLoader() {
export default function cssLoader(options) {
return {
test: /\.css$/,
loader: 'style-loader!css-loader'
Expand Down

0 comments on commit 277f019

Please sign in to comment.