@revoloo/cypress6
Version:
Cypress.io end to end testing tool
316 lines (257 loc) • 8.95 kB
JavaScript
require('./environment')
// we are not requiring everything up front
// to optimize how quickly electron boots while
// in dev or linux production. the reasoning is
// that we likely may need to spawn a new child process
// and its a huge waste of time (about 1.5secs) of
// synchronous requires the first go around just to
// essentially do it all again when we boot the correct
// mode.
const R = require('ramda')
const Promise = require('bluebird')
const debug = require('debug')('cypress:server:cypress')
const argsUtils = require('./util/args')
const chalk = require('chalk')
const warning = (code, args) => {
return require('./errors').warning(code, args)
}
const exit = (code = 0) => {
// TODO: we shouldn't have to do this
// but cannot figure out how null is
// being passed into exit
debug('about to exit with code', code)
return process.exit(code)
}
const showWarningForInvalidConfig = (options) => {
const invalidConfigOptions = require('lodash').keys(options.config).reduce((invalid, option) => {
if (!require('./config').getConfigKeys().find((configKey) => configKey === option)) {
invalid.push(option)
}
return invalid
}, [])
if (invalidConfigOptions.length && options.invokedFromCli) {
return warning('INVALID_CONFIG_OPTION', invalidConfigOptions)
}
}
const exit0 = () => {
return exit(0)
}
const exitErr = (err) => {
// log errors to the console
// and potentially raygun
// and exit with 1
debug('exiting with err', err)
return require('./errors').logException(err)
.then(() => {
debug('calling exit 1')
return exit(1)
})
}
const isComponentTesting = (options) => {
return options.testingType === 'component'
}
module.exports = {
isCurrentlyRunningElectron () {
return require('./util/electron-app').isRunning()
},
runElectron (mode, options) {
// wrap all of this in a promise to force the
// promise interface - even if it doesn't matter
// in dev mode due to cp.spawn
return Promise.try(() => {
// if we have the electron property on versions
// that means we're already running in electron
// like in production and we shouldn't spawn a new
// process
if (isComponentTesting(options) || this.isCurrentlyRunningElectron()) {
// if we weren't invoked from the CLI
// then display a warning to the user
if (!options.invokedFromCli) {
warning('INVOKED_BINARY_OUTSIDE_NPM_MODULE')
}
// just run the gui code directly here
// and pass our options directly to main
if (isComponentTesting(options)) {
debug(`skipping running Electron when in ${mode} mode`)
} else {
debug('running Electron currently')
}
return require('./modes')(mode, options)
}
return new Promise((resolve) => {
debug('starting Electron')
const cypressElectron = require('@packages/electron')
const fn = (code) => {
// juggle up the totalFailed since our outer
// promise is expecting this object structure
debug('electron finished with', code)
if (mode === 'smokeTest') {
return resolve(code)
}
return resolve({ totalFailed: code })
}
const args = require('./util/args').toArray(options)
debug('electron open arguments %o', args)
return cypressElectron.open('.', args, fn)
})
})
},
openProject (options) {
// this code actually starts a project
// and is spawned from nodemon
return require('./open_project').open(options.project, options)
},
start (argv = []) {
debug('starting cypress with argv %o', argv)
// if the CLI passed "--" somewhere, we need to remove it
// for https://github.com/cypress-io/cypress/issues/5466
argv = R.without('--', argv)
let options
try {
options = argsUtils.toObject(argv)
showWarningForInvalidConfig(options)
} catch (argumentsError) {
debug('could not parse CLI arguments: %o', argv)
// note - this is promise-returned call
return exitErr(argumentsError)
}
debug('from argv %o got options %o', argv, options)
// Allow for Cypress to test locally, but do not allow users to access component testing
if (options.componentTesting && !process.env.CYPRESS_INTERNAL_ENV) {
throw new Error('Component testing mode is not implemented. But coming 🥳.')
}
if (options.headless) {
// --headless is same as --headed false
if (options.headed) {
throw new Error('Impossible options: both headless and headed are true')
}
options.headed = false
}
if (options.runProject && !options.headed) {
debug('scaling electron app in headless mode')
// scale the electron browser window
// to force retina screens to not
// upsample their images when offscreen
// rendering
require('./util/electron-app').scale()
}
// make sure we have the appData folder
return require('./util/app_data').ensure()
.then(() => {
// else determine the mode by
// the passed in arguments / options
// and normalize this mode
let mode = options.mode || 'interactive'
if (options.version) {
mode = 'version'
} else if (options.smokeTest) {
mode = 'smokeTest'
} else if (options.returnPkg) {
mode = 'returnPkg'
} else if (options.logs) {
mode = 'logs'
} else if (options.clearLogs) {
mode = 'clearLogs'
} else if (options.getKey) {
mode = 'getKey'
} else if (options.generateKey) {
mode = 'generateKey'
} else if (!(options.exitWithCode == null)) {
mode = 'exitWithCode'
} else if (options.runProject) {
// go into headless mode when running
// until completion + exit
mode = 'run'
}
return this.startInMode(mode, options)
})
},
startInMode (mode, options) {
debug('starting in mode %s with options %o', mode, options)
switch (mode) {
case 'version':
return require('./modes/pkg')(options)
.get('version')
.then((version) => {
return console.log(version) // eslint-disable-line no-console
}).then(exit0)
.catch(exitErr)
case 'info':
return require('./modes/info')(options)
.then(exit0)
.catch(exitErr)
case 'smokeTest':
return this.runElectron(mode, options)
.then((pong) => {
if (!this.isCurrentlyRunningElectron()) {
return pong
}
if (pong === options.ping) {
return 0
}
return 1
}).then(exit)
.catch(exitErr)
case 'returnPkg':
return require('./modes/pkg')(options)
.then((pkg) => {
return console.log(JSON.stringify(pkg)) // eslint-disable-line no-console
}).then(exit0)
.catch(exitErr)
case 'logs':
// print the logs + exit
return require('./gui/logs').print()
.then(exit0)
.catch(exitErr)
case 'clearLogs':
// clear the logs + exit
return require('./gui/logs').clear()
.then(exit0)
.catch(exitErr)
case 'getKey':
// print the key + exit
return require('./project-base').ProjectBase
.getSecretKeyByPath(options.projectRoot)
.then((key) => {
return console.log(key) // eslint-disable-line no-console
}).then(exit0)
.catch(exitErr)
case 'generateKey':
// generate + print the key + exit
return require('./project-base').ProjectBase
.generateSecretKeyByPath(options.projectRoot)
.then((key) => {
return console.log(key) // eslint-disable-line no-console
}).then(exit0)
.catch(exitErr)
case 'exitWithCode':
return require('./modes/exit')(options)
.then(exit)
.catch(exitErr)
case 'run':
// run headlessly and exit
// with num of totalFailed
return this.runElectron(mode, options)
.then((results) => {
if (results.runs) {
const isCanceled = results.runs.filter((run) => run.skippedSpec).length
if (isCanceled) {
// eslint-disable-next-line no-console
console.log(chalk.magenta('\n Exiting with non-zero exit code because the run was canceled.'))
return 1
}
}
return results.totalFailed
})
.then(exit)
.catch(exitErr)
case 'interactive':
return this.runElectron(mode, options)
case 'openProject':
// open + start the project
return this.openProject(options)
default:
throw new Error(`Cannot start. Invalid mode: '${mode}'`)
}
},
}