integration/script/run-test.js (83 lines of code) (raw):

#!/usr/bin/env node const { argv, env, kill, exit } = require('process'); const { spawnSync, spawn } = require('child_process'); const args = require('minimist')(argv.slice(2)); const path = require('path'); const pathToIntegrationFolder = path.resolve(__dirname, '..'); const pathToTestDemoFolder = path.resolve(__dirname, '../app/test-demo'); const green = '\x1b[32m%s\x1b[0m'; const red = '\x1b[31m%s\x1b[0m'; let testTarget = 'test/*'; const usage = () => { console.log( `Usage: run-test -- [-t test] [-h host] [-b browser-version] [-p platform-name]` ); console.log( ` -t, --test Target test suite [default: all]` ); console.log( ` -h, --host Target browser and WebDriver server [default: sauce-chrome]` ); console.log( ` -v, --browser-version Version of the browser to use [default: latest]` ); console.log( ` -p, --platform-name Name of the operating system the browser should be running on [default: Windows 10]\n` ); console.log(`Values:`); console.log(` -t, --test`); console.log(` all: ../test/*`); console.log(` roster: ../test/roster-test.js\n`); console.log(` -h, --host`); console.log(` chrome: Run tests locally with Chrome`); console.log(` firefox: Run tests locally with Firefox`); console.log(` safari: Run tests locally with Safari`); console.log(` sauce-chrome: Run tests on Sauce Labs with Chrome`); console.log(` sauce-firefox: Run tests on Sauce Labs with Firefox`); console.log(` sauce-safari: Run tests on Sauce Labs with Safari\n`); console.log( `Below options are only available when executing tests on Sauce Labs` ); console.log(` -v, --browser-version`); console.log( ` check example here: https://saucelabs.com/platform/platform-configurator#/` ); console.log(` -p, --platform-name`); console.log( ` check example here: https://saucelabs.com/platform/platform-configurator#/\n` ); }; const parseArgs = () => { for (const [key, value] of Object.entries(args)) { if (key === '_') continue; switch (key) { case 'help': usage(); exit(0); case 't': case 'test': setTestTarget(value); break; case 'h': case 'host': env.HOST = value; break; case 'v': case 'browser-version': env.BROWSER_VERSION = value; break; case 'p': case 'platform-name': env.PLATFORM_NAME = value; break; default: console.log(red, `Invalid argument ${key}`); usage(); exit(1); } } }; const setTestTarget = (target) => { switch (target) { case 'all': testTarget = 'test/*'; break; case 'roster': testTarget = 'test/roster_test.js'; break; default: testTarget = 'test/*'; break; } }; // Run the command asynchronously without blocking the Node.js event loop. const runAsync = (command, args, options) => { options = { ...options, shell: true, }; const child = spawn(command, args, options); child.stdout.setEncoding('utf8'); child.stdout.on('data', (data) => { console.log(data); }); child.stderr.setEncoding('utf8'); child.stderr.on('data', (error) => { console.log(red, error); }); child.on('close', (code) => { console.log(red, `Command ${command} exited with ${code}`); }); return child.pid; }; // Run the command synchronously with blocking the Node.js event loop // until the spawned process either exits or is terminated. const runSync = (command, args, options, printOutput = true) => { options = { ...options, shell: true, }; const child = spawnSync(command, args, options); const output = child.stdout.toString(); if (printOutput) { console.log(output); } if (child.error) { console.log(red, `Command ${command} failed with ${child.error.code}`); } if (child.status !== 0) { console.log( red, `Command ${command} failed with exit code ${child.status} and signal ${child.signal}` ); console.log(red, child.stderr.toString()); } return output; }; const checkIfPortIsInUse = async (port) => new Promise((resolve) => { const server = require('http') .createServer() .once('error', (err) => { if (err && err.code === 'EADDRINUSE') { resolve(true); } }) .once('listening', () => { server.once('close', () => resolve(false)).close(); }) .listen(port, '127.0.0.1'); }); const startTestDemo = () => { console.log(green, 'Installing dependencies in test demo'); runSync('npm', ['install'], { cwd: pathToTestDemoFolder }); console.log(green, ' Starting the test demo'); // The test demo will keep running until the process is terminated, // so we should execute this command asynchronously without blocking other commands. runAsync('npm', ['run', 'start:fast'], { cwd: pathToTestDemoFolder }); }; const waitUntilTestDemoStarts = async () => { console.log(green, ' Waiting for test demo to start'); count = 0; threshold = 60; while (count < threshold) { const isInUse = await checkIfPortIsInUse(8080); if (isInUse === true) { console.log(green, ' Test demo has started successfully'); return; } count += 1; await new Promise((resolve) => setTimeout(resolve, 1000)); } console.log(red, ' Test demo did not start successfully'); terminateTestDemo(); exit(1); }; const startTesting = () => { console.log(green, ' Running test'); const testResult = runSync('mocha', [testTarget], { cwd: pathToIntegrationFolder, }); return testResult; }; const terminateTestDemo = () => { const demoPid = runSync( 'lsof', ['-i', ':9000', '-t'], null, (printOutput = false) ); if (demoPid) kill(demoPid, 'SIGKILL'); const serverPid = runSync( 'lsof', ['-i', ':8080', '-t'], null, (printOutput = false) ); if (serverPid) kill(serverPid, 'SIGKILL'); console.log(green, 'Terminated the test demo'); }; const checkTestResult = (result) => { if (!result || result.includes('failing')) { console.log(red, 'Did not pass all tests, failed'); exit(1); } else { console.log(green, 'Passed all tests, succeeded'); exit(0); } }; (async () => { parseArgs(); startTestDemo(); await waitUntilTestDemoStarts(); const testResult = startTesting(); terminateTestDemo(); checkTestResult(testResult); })();