Files
onearmy-community-platform/packages/cypress/scripts/start.ts
2024-05-31 07:45:14 +01:00

139 lines
5.2 KiB
TypeScript

#!/usr/bin/env ts-node
import { spawn, spawnSync } from 'child_process'
import * as dotenv from 'dotenv'
import fs from 'fs-extra'
import waitOn from 'wait-on'
import { generateAlphaNumeric } from '../src/utils/TestUtils'
import PATHS from './paths'
const e2eEnv = dotenv.config()
const isCi = process.argv.includes('ci')
const isProduction = process.argv.includes('prod')
// Prevent unhandled errors being silently ignored
process.on('unhandledRejection', (err) => {
console.error('There was an uncaught error', err)
process.exitCode = 1
})
/**
* When running e2e tests with cypress we need to first get the server up and running
* before launching the test suite. We will seed the DB from within the test suite
*
* @argument ci - specify if running in ci (e.g. circleci) to run and record
* @argument prod - specify to use a production build instead of local development server
* @example npm run test ci prod
*
* TODO: CC - 2021-02-24
* - DB seeding happens inbetween test suites, but really should happen before/after test
* scripts start and end (particularly teardown, as it won't be called if tests fail).
* Possibly could be done with a Cypress.task or similar
* Temp cli function to wipe hanging db: `firebase use ci; firebase firestore:delete --all-collections`
*/
main()
.then(() => process.exit(0))
.catch((err) => {
console.error(err)
process.exit(1)
})
async function main() {
// copy endpoints for use in testing
fs.copyFileSync(PATHS.SRC_DB_ENDPOINTS, PATHS.WORKSPACE_DB_ENDPOINTS)
await startAppServer()
runTests()
}
/** We need to ensure the platform is up and running before starting tests
* There are npm packages like start-server-and-test but they seem to have flaky
* performance in some environments (https://github.com/bahmutov/start-server-and-test/issues/250).
* Instead manually track via child spawns
*
*/
async function startAppServer() {
const { CROSSENV_BIN, BUILD_SERVE_JSON } = PATHS
// by default spawns will not respect colours used in stdio, so try to force
const crossEnvArgs = `FORCE_COLOR=1 VITE_APP_SITE_VARIANT=test-ci`
// run local debug server for testing unless production build specified
let serverCmd = `${CROSSENV_BIN} ${crossEnvArgs} BROWSER=none PORT=3456 yarn start`
// for production will instead serve from production build folder
if (isProduction) {
// create local build if not running on ci (which will have build already generated)
if (!isCi) {
// specify CI=false to prevent throwing lint warnings as errors
spawnSync(`${CROSSENV_BIN} ${crossEnvArgs} CI=false yarn build`, {
shell: true,
stdio: ['inherit', 'inherit', 'pipe'],
})
}
// create a rewrites file for handling local server behaviour
const opts = { rewrites: [{ source: '/**', destination: '/index.html' }] }
fs.writeFileSync(BUILD_SERVE_JSON, JSON.stringify(opts))
serverCmd = `npx serve build -l 3456`
}
/******************* Run the main commands ******************* */
// as the spawn will not terminate create non-async, and just listen to and handle messages
// from the methods
const child = spawn(serverCmd, {
shell: true,
stdio: ['pipe', 'pipe', 'inherit'],
cwd: PATHS.PLATFORM_ROOT_DIR,
})
child.stdout.on('data', (d) => {
const msg = d.toString('utf8')
console.log(msg)
// throw typescript build errors
if (msg.includes('Failed to compile')) {
// the server will still be running after compile failure (waiting for changes),
// so give time for any other messages to come through before exiting manually
setTimeout(() => {
process.exit(1)
}, 2000)
}
})
// do not end function until server responsive on port 3456
// give up if not reponsive after 5 minutes (assume uncaught error somewhere)
const timeout = 5 * 60 * 1000
await waitOn({ resources: ['http-get://127.0.0.1:3456'], timeout })
}
function runTests() {
console.log(isCi ? 'Start tests' : 'Opening cypress for manual testing')
const e = process.env
const { CYPRESS_KEY } = e2eEnv.parsed
const CI_BROWSER = e.CI_BROWSER || 'chrome'
const CI_GROUP = e.CI_GROUP || '1x-chrome'
// not currently used, but can pass variables accessed by Cypress.env()
const CYPRESS_ENV = `DUMMY_VAR=1`
// use workflow ID so that jobs running in parallel can be assigned to same cypress build
// cypress will use this to split tests between parallel runs
const buildId = e.CIRCLE_WORKFLOW_ID || generateAlphaNumeric(8)
// main testing command, depending on whether running on ci machine or interactive local
// call with path to bin as to ensure locally installed used
const { CY_BIN, CROSSENV_BIN } = PATHS
const testCMD = isCi
? `${CY_BIN} run --record --env ${CYPRESS_ENV} --key=${CYPRESS_KEY} --parallel --headless --browser ${CI_BROWSER} --group ${CI_GROUP} --ci-build-id ${buildId}`
: `${CY_BIN} open --browser chrome --env ${CYPRESS_ENV}`
console.log(`Running cypress with cmd: ${testCMD}`)
const spawn = spawnSync(`${CROSSENV_BIN} FORCE_COLOR=1 ${testCMD}`, {
shell: true,
stdio: ['inherit', 'inherit', 'pipe'],
cwd: PATHS.WORKSPACE_DIR,
})
console.log('testing complete with exit code', spawn.status)
if (spawn.status === 1) {
console.error('error', spawn.stderr.toString())
}
process.exit(spawn.status)
}