|
| 1 | +/** |
| 2 | + * Mocha's Karma config for modern browsers. |
| 3 | + * |
| 4 | + * Copy of "./karma.config.js" |
| 5 | + * |
| 6 | + * Changelog: |
| 7 | + * - remove IE11 out of SAUCE_BROWSER_PLATFORM_MAP |
| 8 | + * - no sourcemap |
| 9 | + * - configFile: 'rollup_no-ie11.config.js' |
| 10 | + */ |
| 11 | + |
| 12 | +'use strict'; |
| 13 | +const fs = require('fs'); |
| 14 | +const path = require('path'); |
| 15 | +const os = require('os'); |
| 16 | +const rollupPlugin = require('./scripts/karma-rollup-plugin'); |
| 17 | +const BASE_BUNDLE_DIR_PATH = path.join(__dirname, '.karma'); |
| 18 | +const env = process.env; |
| 19 | +const hostname = os.hostname(); |
| 20 | + |
| 21 | +const SAUCE_BROWSER_PLATFORM_MAP = { |
| 22 | + 'chrome@latest': 'Windows 10', |
| 23 | + 'MicrosoftEdge@latest': 'Windows 10', |
| 24 | + 'firefox@latest': 'Windows 10', |
| 25 | + 'safari@latest': 'macOS 10.13' |
| 26 | +}; |
| 27 | + |
| 28 | +const baseConfig = { |
| 29 | + frameworks: ['rollup', 'mocha'], |
| 30 | + files: [ |
| 31 | + // we use the BDD interface for all of the tests that |
| 32 | + // aren't interface-specific. |
| 33 | + 'test/unit/*.spec.js' |
| 34 | + ], |
| 35 | + plugins: [ |
| 36 | + 'karma-mocha', |
| 37 | + 'karma-mocha-reporter', |
| 38 | + 'karma-sauce-launcher', |
| 39 | + 'karma-chrome-launcher', |
| 40 | + rollupPlugin |
| 41 | + ], |
| 42 | + rollup: { |
| 43 | + configFile: 'rollup_no-ie11.config.js', |
| 44 | + include: ['test/**'] |
| 45 | + }, |
| 46 | + reporters: ['mocha'], |
| 47 | + colors: true, |
| 48 | + browsers: ['ChromeHeadless'], |
| 49 | + client: { |
| 50 | + mocha: { |
| 51 | + // this helps debug |
| 52 | + reporter: 'html' |
| 53 | + } |
| 54 | + }, |
| 55 | + mochaReporter: { |
| 56 | + showDiff: true |
| 57 | + }, |
| 58 | + customLaunchers: { |
| 59 | + ChromeDebug: { |
| 60 | + base: 'Chrome', |
| 61 | + flags: ['--remote-debugging-port=9333'] |
| 62 | + } |
| 63 | + } |
| 64 | +}; |
| 65 | + |
| 66 | +module.exports = config => { |
| 67 | + let bundleDirPath = path.join(BASE_BUNDLE_DIR_PATH, hostname); |
| 68 | + let cfg = {...baseConfig}; |
| 69 | + |
| 70 | + // TO RUN AGAINST SAUCELABS LOCALLY, execute: |
| 71 | + // `CI=1 SAUCE_USERNAME=<user> SAUCE_ACCESS_KEY=<key> npm start test.browser` |
| 72 | + let sauceConfig; |
| 73 | + |
| 74 | + // configuration for CI mode |
| 75 | + if (env.CI) { |
| 76 | + console.error('CI mode enabled'); |
| 77 | + if (env.GITHUB_RUN_ID) { |
| 78 | + console.error('Github Actions detected'); |
| 79 | + const buildId = `github-${env.GITHUB_RUN_ID}_${env.GITHUB_RUN_NUMBER}`; |
| 80 | + bundleDirPath = path.join(BASE_BUNDLE_DIR_PATH, buildId); |
| 81 | + sauceConfig = { |
| 82 | + build: buildId |
| 83 | + }; |
| 84 | + } else { |
| 85 | + console.error(`Local environment (${hostname}) detected`); |
| 86 | + // don't need to run sauce from Windows CI b/c travis does it. |
| 87 | + if (env.SAUCE_USERNAME || env.SAUCE_ACCESS_KEY) { |
| 88 | + const id = `${hostname} (${Date.now()})`; |
| 89 | + sauceConfig = { |
| 90 | + build: id, |
| 91 | + tunnelIdentifier: id |
| 92 | + }; |
| 93 | + console.error('Configured SauceLabs'); |
| 94 | + } else { |
| 95 | + console.error( |
| 96 | + 'No SauceLabs credentials present; set SAUCE_USERNAME and SAUCE_ACCESS_KEY env vars' |
| 97 | + ); |
| 98 | + } |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + cfg = createBundleDir(cfg, bundleDirPath); |
| 103 | + cfg = addSauceTests(cfg, sauceConfig); |
| 104 | + cfg = chooseTestSuite(cfg, env.MOCHA_TEST); |
| 105 | + |
| 106 | + config.set(cfg); |
| 107 | +}; |
| 108 | + |
| 109 | +/** |
| 110 | + * Creates dir `bundleDirPath` if it does not exist; returns new Karma config |
| 111 | + * containing `bundleDirPath` for rollup plugin. |
| 112 | + * |
| 113 | + * If this fails, the rollup plugin will use a temp dir. |
| 114 | + * @param {object} cfg - Karma config. |
| 115 | + * @param {string} [bundleDirPath] - Path where the output bundle should live |
| 116 | + * @returns {object} - New Karma config |
| 117 | + */ |
| 118 | +const createBundleDir = (cfg, bundleDirPath) => { |
| 119 | + if (bundleDirPath) { |
| 120 | + try { |
| 121 | + fs.mkdirSync(bundleDirPath, {recursive: true}); |
| 122 | + cfg = { |
| 123 | + ...cfg, |
| 124 | + rollup: { |
| 125 | + ...cfg.rollup, |
| 126 | + bundleDirPath |
| 127 | + } |
| 128 | + }; |
| 129 | + } catch (ignored) { |
| 130 | + console.error( |
| 131 | + `Failed to create ${bundleDirPath}; using temp directory instead` |
| 132 | + ); |
| 133 | + } |
| 134 | + } |
| 135 | + return {...cfg}; |
| 136 | +}; |
| 137 | + |
| 138 | +/** |
| 139 | + * Adds Saucelabs-specific config to a Karma config object. |
| 140 | + * |
| 141 | + * If `sauceLabs` parameter is falsy, just return a clone of the `cfg` parameter. |
| 142 | + * |
| 143 | + * @see https://github.com/karma-runner/karma-sauce-launcher |
| 144 | + * @see https://github.com/bermi/sauce-connect-launcher#advanced-usage |
| 145 | + * @param {object} cfg - Karma config |
| 146 | + * @param {object} [sauceLabs] - SauceLabs config |
| 147 | + * @returns {object} Karma config |
| 148 | + */ |
| 149 | +const addSauceTests = (cfg, sauceLabs) => { |
| 150 | + if (sauceLabs) { |
| 151 | + const sauceBrowsers = Object.keys(SAUCE_BROWSER_PLATFORM_MAP); |
| 152 | + |
| 153 | + // creates Karma `customLauncher` configs from `SAUCE_BROWSER_PLATFORM_MAP` |
| 154 | + const customLaunchers = sauceBrowsers.reduce((acc, sauceBrowser) => { |
| 155 | + const platformName = SAUCE_BROWSER_PLATFORM_MAP[sauceBrowser]; |
| 156 | + const [browserName, browserVersion] = sauceBrowser.split('@'); |
| 157 | + return { |
| 158 | + ...acc, |
| 159 | + [sauceBrowser]: { |
| 160 | + base: 'SauceLabs', |
| 161 | + browserName, |
| 162 | + browserVersion, |
| 163 | + platformName, |
| 164 | + 'sauce:options': sauceLabs |
| 165 | + } |
| 166 | + }; |
| 167 | + }, {}); |
| 168 | + |
| 169 | + return { |
| 170 | + ...cfg, |
| 171 | + reporters: [...cfg.reporters, 'saucelabs'], |
| 172 | + browsers: [...cfg.browsers, ...sauceBrowsers], |
| 173 | + customLaunchers: { |
| 174 | + ...cfg.customLaunchers, |
| 175 | + ...customLaunchers |
| 176 | + }, |
| 177 | + sauceLabs, |
| 178 | + concurrency: Infinity, |
| 179 | + retryLimit: 1, |
| 180 | + captureTimeout: 120000, |
| 181 | + browserNoActivityTimeout: 20000 |
| 182 | + }; |
| 183 | + } |
| 184 | + return {...cfg}; |
| 185 | +}; |
| 186 | + |
| 187 | +/** |
| 188 | + * Returns a new Karma config containing standard dependencies for our tests. |
| 189 | + * |
| 190 | + * Most suites use this. |
| 191 | + * @param {object} cfg - Karma config |
| 192 | + * @returns {object} New Karma config |
| 193 | + */ |
| 194 | +const addStandardDependencies = cfg => ({ |
| 195 | + ...cfg, |
| 196 | + files: [ |
| 197 | + require.resolve('sinon/pkg/sinon.js'), |
| 198 | + require.resolve('unexpected/unexpected'), |
| 199 | + { |
| 200 | + pattern: require.resolve('unexpected/unexpected.js.map'), |
| 201 | + included: false |
| 202 | + }, |
| 203 | + require.resolve('unexpected-sinon'), |
| 204 | + require.resolve('unexpected-eventemitter/dist/unexpected-eventemitter.js'), |
| 205 | + require.resolve('./test/browser-specific/setup'), |
| 206 | + ...cfg.files |
| 207 | + ], |
| 208 | + rollup: { |
| 209 | + ...cfg.rollup, |
| 210 | + external: [ |
| 211 | + 'sinon', |
| 212 | + 'unexpected', |
| 213 | + 'unexpected-eventemitter', |
| 214 | + 'unexpected-sinon' |
| 215 | + ], |
| 216 | + globals: { |
| 217 | + sinon: 'sinon', |
| 218 | + unexpected: 'weknowhow.expect', |
| 219 | + 'unexpected-sinon': 'weknowhow.unexpectedSinon', |
| 220 | + 'unexpected-eventemitter': 'unexpectedEventEmitter' |
| 221 | + } |
| 222 | + } |
| 223 | +}); |
| 224 | + |
| 225 | +/** |
| 226 | + * Adds a name for the tests, reflected in SauceLabs' UI. Returns new Karma |
| 227 | + * config. |
| 228 | + * |
| 229 | + * Does not add a test name if the `sauceLabs` prop of `cfg` is falsy (which |
| 230 | + * would imply that we're not running tests on SauceLabs). |
| 231 | + * |
| 232 | + * @param {string} testName - SauceLabs test name |
| 233 | + * @param {object} cfg - Karma config. |
| 234 | + * @returns {object} New Karma config |
| 235 | + */ |
| 236 | +const addSauceLabsTestName = (testName, cfg) => |
| 237 | + cfg.sauceLabs |
| 238 | + ? { |
| 239 | + ...cfg, |
| 240 | + sauceLabs: { |
| 241 | + ...cfg.sauceLabs, |
| 242 | + testName |
| 243 | + } |
| 244 | + } |
| 245 | + : {...cfg}; |
| 246 | + |
| 247 | +/** |
| 248 | + * Returns a new Karma config to run with specific configuration (which cannot |
| 249 | + * be run with other configurations) as specified by `value`. Known values: |
| 250 | + * |
| 251 | + * - `bdd` - `bdd`-specific tests |
| 252 | + * - `tdd` - `tdd`-specific tests |
| 253 | + * - `qunit` - `qunit`-specific tests |
| 254 | + * - `esm` - ESM-specific tests |
| 255 | + * - `requirejs` - RequireJS-specific tests |
| 256 | + * |
| 257 | + * Since we can't change Mocha's interface on-the-fly, tests for specific interfaces |
| 258 | + * must be run in isolation. |
| 259 | + * @param {object} cfg - Karma config |
| 260 | + * @param {string} [value] - Configuration identifier, if any |
| 261 | + * @returns {object} New Karma config |
| 262 | + */ |
| 263 | +const chooseTestSuite = (cfg, value) => { |
| 264 | + switch (value) { |
| 265 | + case 'bdd': |
| 266 | + case 'tdd': |
| 267 | + case 'qunit': |
| 268 | + return addStandardDependencies({ |
| 269 | + ...cfg, |
| 270 | + ...addSauceLabsTestName(`Interface "${value}" Integration Tests`, cfg), |
| 271 | + files: [`test/interfaces/${value}.spec.js`], |
| 272 | + client: { |
| 273 | + ...cfg.client, |
| 274 | + mocha: { |
| 275 | + ...cfg.client.mocha, |
| 276 | + ui: value |
| 277 | + } |
| 278 | + } |
| 279 | + }); |
| 280 | + case 'esm': |
| 281 | + return addStandardDependencies({ |
| 282 | + ...addSauceLabsTestName('ESM Integration Tests', cfg), |
| 283 | + // just run against ChromeHeadless, since other browsers may not |
| 284 | + // support ESM. |
| 285 | + // XXX: remove following line when dropping IE11 |
| 286 | + browsers: ['ChromeHeadless'], |
| 287 | + files: [ |
| 288 | + { |
| 289 | + pattern: 'test/browser-specific/fixtures/esm.fixture.mjs', |
| 290 | + type: 'module' |
| 291 | + }, |
| 292 | + { |
| 293 | + pattern: 'test/browser-specific/esm.spec.mjs', |
| 294 | + type: 'module' |
| 295 | + } |
| 296 | + ] |
| 297 | + }); |
| 298 | + case 'requirejs': |
| 299 | + // no standard deps because I'm too lazy to figure out how to make |
| 300 | + // them work with RequireJS. not important anyway |
| 301 | + return { |
| 302 | + ...addSauceLabsTestName('RequireJS Tests', cfg), |
| 303 | + plugins: [...cfg.plugins, 'karma-requirejs'], |
| 304 | + frameworks: ['requirejs', ...cfg.frameworks], |
| 305 | + files: [ |
| 306 | + { |
| 307 | + pattern: 'test/browser-specific/fixtures/requirejs/*.fixture.js', |
| 308 | + included: false |
| 309 | + }, |
| 310 | + 'test/browser-specific/requirejs-setup.js' |
| 311 | + ], |
| 312 | + // this skips bundling the above tests & fixtures |
| 313 | + rollup: { |
| 314 | + ...cfg.rollup, |
| 315 | + include: [] |
| 316 | + } |
| 317 | + }; |
| 318 | + default: |
| 319 | + return addStandardDependencies({ |
| 320 | + ...addSauceLabsTestName('Unit Tests', cfg) |
| 321 | + }); |
| 322 | + } |
| 323 | +}; |
0 commit comments