From 2f05c55fa3d016476450eaa766766e1d87fc50c5 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 22 Dec 2021 16:07:20 -0500 Subject: [PATCH] Run DevTools e2e tests on Circle CI --- .circleci/config.yml | 25 ++++ scripts/circleci/run_devtools_e2e_tests.js | 165 +++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100755 scripts/circleci/run_devtools_e2e_tests.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 86f54450e51a..a9905a53b7a9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -200,6 +200,28 @@ jobs: - store_artifacts: path: ./build/devtools.tgz + run_devtools_e2e_tests: + docker: *docker + environment: *environment + steps: + - checkout + - attach_workspace: + at: . + - run: yarn workspaces info | head -n -1 > workspace_info.txt + - *restore_node_modules + - run: + name: Install Packages + command: yarn --frozen-lockfile --cache-folder ~/.cache/yarn + - run: + name: Playwright install deps + command: | + npx playwright install + sudo npx playwright install-deps + - run: + environment: + RELEASE_CHANNEL: experimental + command: ./scripts/circleci/run_devtools_e2e_tests.js + yarn_lint_build: docker: *docker environment: *environment @@ -452,6 +474,9 @@ workflows: - build_devtools_and_process_artifacts: requires: - yarn_build_combined + - run_devtools_e2e_tests: + requires: + - build_devtools_and_process_artifacts fuzz_tests: unless: << pipeline.parameters.prerelease_commit_sha >> diff --git a/scripts/circleci/run_devtools_e2e_tests.js b/scripts/circleci/run_devtools_e2e_tests.js new file mode 100755 index 000000000000..fbd8baf3c64c --- /dev/null +++ b/scripts/circleci/run_devtools_e2e_tests.js @@ -0,0 +1,165 @@ +#!/usr/bin/env node + +'use strict'; + +const {spawn} = require('child_process'); +const {join} = require('path'); + +const ROOT_PATH = join(__dirname, '..', '..'); + +const inlinePackagePath = join(ROOT_PATH, 'packages', 'react-devtools-inline'); +const shellPackagePath = join(ROOT_PATH, 'packages', 'react-devtools-shell'); + +let buildProcess = null; +let serverProcess = null; +let testProcess = null; + +function format(loggable) { + return `${loggable}` + .split('\n') + .filter(line => { + return line.trim() !== ''; + }) + .map(line => ` ${line}`) + .join('\n'); +} + +function logBright(loggable) { + console.log(`\x1b[1m${loggable}\x1b[0m`); +} + +function logDim(loggable) { + const formatted = format(loggable, 2); + if (formatted !== '') { + console.log(`\x1b[2m${formatted}\x1b[0m`); + } +} + +function logError(loggable) { + const formatted = format(loggable, 2); + if (formatted !== '') { + console.error(`\x1b[31m${formatted}\x1b[0m`); + } +} + +function buildInlinePackage() { + logBright('Building inline packages'); + + buildProcess = spawn('yarn', ['build'], {cwd: inlinePackagePath}); + buildProcess.stdout.on('data', data => { + logDim(data); + }); + buildProcess.stderr.on('data', data => { + if (`${data}`.includes('Warning')) { + logDim(data); + } else { + logError(`Error:\n${data}`); + + exitWithCode(1); + } + }); + buildProcess.on('close', code => { + logBright('Inline package built'); + + runTestShell(); + }); +} + +function runTestShell() { + const timeoutID = setTimeout(() => { + // Assume the test shell server failed to start. + logError('Testing shell server failed to start'); + exitWithCode(1); + }, 30000); + + logBright('Starting testing shell server'); + + serverProcess = spawn('yarn', ['start'], {cwd: shellPackagePath}); + serverProcess.stdout.on('data', data => { + if (`${data}`.includes('Compiled successfully.')) { + logBright('Testing shell server running'); + + clearTimeout(timeoutID); + + runEndToEndTests(); + } + }); + serverProcess.stderr.on('data', data => { + if (`${data}`.includes('EADDRINUSE')) { + // Something is occuprying this port; + // We could kill the process and restart but probably better to prompt the user to do this. + + logError('Free up the port and re-run tests:'); + logBright(' kill -9 $(lsof -ti:8080)'); + + exitWithCode(1); + } else if (`${data}`.includes('ERROR')) { + logError(`Error:\n${data}`); + + exitWithCode(1); + } else { + // Non-fatal stuff like Babel optimization warnings etc. + logDim(data); + } + }); +} + +async function runEndToEndTests() { + logBright('Running e2e tests'); + + testProcess = spawn('yarn', ['test:e2e'], {cwd: inlinePackagePath}); + testProcess.stdout.on('data', data => { + // Log without formatting because Playwright applies its own formatting. + const formatted = format(data); + if (formatted !== '') { + console.log(formatted); + } + }); + testProcess.stderr.on('data', data => { + // Log without formatting because Playwright applies its own formatting. + const formatted = format(data); + if (formatted !== '') { + console.error(formatted); + } + + exitWithCode(1); + }); + testProcess.on('close', code => { + logBright(`Tests completed with code: ${code}`); + + exitWithCode(code); + }); +} + +function exitWithCode(code) { + if (buildProcess !== null) { + try { + logBright('Shutting down build process'); + buildProcess.kill(); + } catch (error) { + logError(error); + } + } + + if (serverProcess !== null) { + try { + logBright('Shutting down shell server process'); + serverProcess.kill(); + } catch (error) { + logError(error); + } + } + + if (testProcess !== null) { + try { + logBright('Shutting down test process'); + testProcess.kill(); + } catch (error) { + logError(error); + } + } + + process.exit(code); +} + +buildInlinePackage();