#!/usr/bin/env nodejs /* runTests.js builds and runs the spec files from the passed directory. */ const fs = require('fs'); const path = require('path'); const chromeLauncher = require('chrome-launcher'); const CDP = require('chrome-remote-interface'); const SPEC_DIR = 'spec'; const TEST_FILENAME = '.spec_runner.html'; const HOST = '127.0.0.1'; const PORT = 8090; const DEBUG = typeof process.env['DEBUG'] === 'string' ? process.env['DEBUG'].toLowerCase() == 'true' : !!process.env['DEBUG']; const PORTFOLIO_DIR = path.normalize(path.join(__dirname, '..')); const JASMINE_DIR = path.relative( PORTFOLIO_DIR, path.normalize(path.join(PORTFOLIO_DIR, 'vendor', 'jasmine')) ); if (process.argv.length <= 2) { console.log(`Usage: ${__filename} path/to/package/dir`); process.exit(-1); } const packageRoot = path.resolve(process.argv[2]); const testPath = path.join(packageRoot, TEST_FILENAME); const webRoot = path.relative(PORTFOLIO_DIR, testPath); const items = fs.readdirSync(packageRoot); let chrome, protocol; writeSpecRunner(); runTestsInChrome(); // ---------------------- function writeSpecRunner() { if (fs.existsSync(testPath)) { return; } let settings = {}; try { settings = JSON.parse(fs.readFileSync(path.join(packageRoot, 'test.json'), 'utf8')); } catch (e) {} const spec_dir = settings.spec_dir || SPEC_DIR; if (items.indexOf(spec_dir) === -1) { console.log(`ERROR: ${packageRoot} does not contain a "${SPEC_DIR}" folder.`); process.exit(-1); } const scripts = settings.lib_files || []; const specFilenames = fs .readdirSync(path.join(packageRoot, spec_dir)) .filter(fn => fn.endsWith('.spec.js')) .map(fn => path.join(spec_dir, fn)); runnerText = ` Jasmine Spec Runner v3.1.0 ${scripts.map(s => ``).join('\n\t ')} ${specFilenames.map(fn => ``).join('\n\t ')} `; fs.writeFileSync(testPath, runnerText); } async function runTestsInChrome() { chrome = await launchChrome(); protocol = await CDP({ port: chrome.port }); const { DOM, Network, Page, Emulation, Runtime, Console, Debugger } = protocol; await Promise.all([ Network.enable(), Page.enable(), DOM.enable(), Runtime.enable(), Console.enable(), DEBUG ? Debugger.enable() : Promise.resolve() ]); await Page.navigate({ url: `http://${HOST}:${PORT}/${webRoot}` }); let indentation = 0; const reqMap = new Map(); Network.requestWillBeSent(result => reqMap.set(result.requestId, result.request.url)); Network.loadingFailed(result => consolePrint( `\x1b[31mNetwork Error: ${result.errorText} for ${reqMap.get(result.requestId)}` ) ); Runtime.exceptionThrown(result => consolePrint(`\x1b[31m${result.exceptionDetails.exception.description}`) ); Console.messageAdded(result => { const msg = result.message; if (msg && msg.source === 'console-api') { try { const obj = JSON.parse(msg.text); if (obj.method === 'group') { indentation += 2; consoleNewline(); consolePrint(`\x1b[37m${obj.description}`, indentation, false); } else if (obj.method === 'groupEnd') { indentation -= 2; } else if (obj.status === 'failed') { obj.failedExpectations.forEach(fe => { consoleNewline(); // consolePrint(`\x1b[31m${fe.message}`); consolePrint(`\x1b[31m${fe.stack}`); }); } else { consolePrint('\x1b[32m.', 0, false); } // console[obj.method || 'log'](`${obj.description}`); } catch (e) { if ( msg.text.startsWith('failed after') || msg.text.startsWith('incomplete after') || msg.text.startsWith('passed after') ) { let color = 32; // green let exitCode = 0; if (!msg.text.startsWith('passed after')) { color = 31; exitCode = -1; } consoleNewline(); consoleNewline(); consolePrint(`\x1b[${color}m${msg.text}`, 0); exit(exitCode); } else { consolePrint(`\x1b[0m${msg.text}`, 0, true); } } } }); } function mime(filename, def = 'text/plain') { const MIMEMAP = { js: 'application/javascript', html: 'text/html; charset=utf-8', css: 'text/css' }; return MIMEMAP[path.extname(filename).substr(1)] || def; } function consolePrint(message, indent = 0, newline = true) { process.stdout.write(`${new Array(indent).join(' ')}${message}`); if (newline) { consoleNewline('\n'); } } function consoleNewline() { process.stdout.write('\x1b[0m\n'); } async function launchChrome() { return await chromeLauncher.launch({ chromeFlags: ['--disable-gpu', '--disable-web-security', '--user-data-dir'].concat( DEBUG ? [] : ['--headless'] ) }); } function exit(code) { if (DEBUG) { return; } fs.unlink(testPath); protocol.close(); chrome.kill(); process.exit(code); }