Files
cypress/packages/server/lib/cypress.js
T
Brian Mann 29841f32b9 feat: redesign server errors (#20072)
* chore: rename errors.js -> errors.ts

* refactor: type safety on errors

* refactor: add err_template for consistent error formatting

* fix a few system tests

* fix tests; update snapshots

* Fix types

* normalize snapshot - remove chalk ansi colors

* more unit test fixes

* more system test fixes

* circleci build

* backtick always in stdout, fix error formatting and failing snapshots

* refactor: create @packages/errors

* fix import

* fix import

* fixing build / tests

* remove extraneous file

* move warnIfExplicitCiBuildId

* fix build / tests

* Fix

* error, type fixes, documentation, standardize child process error serialization

* fix import

* build errors on install

* wrote specs generating visual images of all errors

* remove unused dep

* sanitize stack traces

* add image diffing

- if base images don't exist, create them
- if base images don't match and local, overwrite them, if in CI throw
- if base images are stale and local, delete them, if in CI throw

* remove Courier New + MesloLGS NF font

* type fixes, remove Bluebird, general cleanup

* TS Cleanup

* skip typecheck on tests for now

* yarn.lock

* fix @types/chai version

* fix yarn.lock

* Different version of mocha types so it isnt patched

* errors spec snapshot

* CI fix

* fixes

* store snapshot images in circle artifacts

* dont change artifact destination prefix

* use Courier Prime

* antialias the text

* decrease pixelmatch threshold, fail in CI only when changed pixels > 100

* increase timeout

* overflow: hidden, remove new Promise, add debug logging

Co-Authored-By: Tim Griesser <tgriesser@gmail.com>

* run unit tests w/ concurrency=1

* unique window per file

* disable app hardware acceleration + use in process gpu + single process

* do not do image diffing

- conditionally convert html to images
- store html snapshots
- do not store images in git

* store snapshot html

* Merge branch 'tgriesser/chore/refactor-errors' of https://github.com/cypress-io/cypress into tgriesser/chore/refactor-errors

* remove concurrency

* fix assertion

* fixing ci

* Link in readme

* pass the browsers to listItems

* fix: build @packages/errors in CI, defer import to prevent errors locally

* Merge branch 'develop' into tgriesser/chore/refactor-errors

* develop:
  chore: fix cypress npm package artifact upload path (#20023)
  chore(driver): move cy.within logic into it's own file (#20036)
  chore: update automerge workflows (#19982)
  fix(selectFile): use target window's File/DataTransfer classes (#20003)
  chore: Update Chrome (stable) to 98.0.4758.80 and Chrome (beta) to 98.0.4758.80 (#19995)
  fix: Adjust ffmpeg CLI args for performance (#19983)
  build: allow unified to run cypress on Apple Silicon (arm64) (backport #19067 to 9.x) (#19968)

* fix run-if-ci.sh

* remove dead code

* Mark the .html files as generated in gitattributes

* fix running single error case, slice out more of the brittle stack

* remove additional brittle stack line

* firefox web security error

* nest inside of describe

* reformat and redesign errors

* more error cleanup and standardization

* additional formatting of errors, code cleanup, refactoring

* update ansi colors to match terminal colors

* cleanup remaining loose ends, update several errors, compact excess formatters

* fix types

* additional formatting, remove TODO's, ensure no [object Object] in output

* add test for 412 server response on invalid schema

* update unknown dashboard error on creating run

* use fs.access instead of fs.stat for perf

* added PLUGINS_FILE_NOT_FOUND error

- separated out from PLUGINS_FILE_ERROR
- add system tests for both cases
- update snapshots
- remove stack trace from PLUGINS_FILE_NOT_FOUND fka PLUGINS_FILE_ERROR

* add plugins system test around plugins synchronously throwing on require

* remove forcing process.cwd() to be packages/server, update affected code

- this was a long needed hangover from very old code that was doing unnecessary things due to respawning electron from node and handling various entrypoints into booting cypress
- this also makes the root yarn dev and dev-debug work correctly because options.cwd is set correctly now

* perf: lazy load chalk

* remove excessive line since the file exists but is invalid

* fix types

* add system test when plugins function throws a synchronous error

* create new PLUGINS_INVALID_EVENT_ERROR, wire it up correctly for error template use

- properly pass error instance from child to ensure proper user stack frames
- move error display code into the right place

* only show a single stack trace, either originalError or internal cypressError

* push error html snapshots

* fix tests, types

* fix test

* fix failing tests

* fix tests

* fixes lots of broken tests

* more test fixes

* fixes more tests

* fix type checking

* wip: consistent handling of interpolated values

* feat: fixing up errors, added simple error comparison tool

* wrapping up error formatting

* Fixes for unit tests

* fix PLUGINS_VALIDATION_ERROR

* fix fs.readdir bug, show rows even if there's only markdown formatting [SKIP CI]

* when in base-list, show full width of errors

* Fix type errors

* added searching and filtering for files based on error name

* fix: system tests

* updated NO_SPECS_FOUND error to properly join searched folder + pattern

- join patterns off of process.cwd, not projectRoot
- highlight original specPattern in yellow, baseDir in blue
- add tests

* fixes failing tests

* fix test

* preserve original spec pattern, display relative to projectRoot for terminal banner

* make the nodeVersion path display in gray, not white

* fix tests, pass right variables

* fix chrome:canary snapshots

* update snapshots

* update snapshot

* strip newlines caused by "Still waiting to connect to {browser}..."

* don't remove the snapshotHtmlFolder in CI, add additional verification snapshots match to error keys symmetrically

* update snapshot

* update snapshot

* update snapshot

* update snapshot

* update snapshot

* update snapshot

* update gitignore

* fix snapshot

* update snapshot html

* update logic for parsing the resolve pattern matching, add tests

* update snapshots

* update snapshot

* update snapshot

* update snapshot

* fix failing test

* fix: error_message_spec

* fix snapshot

* run each variant through an it(...) so multiple failures are received

* add newlines to multiline formatters, add fmt.stringify, allow format overrides

* stringify invalid return values from plugins

* move config validation errors into packages/errors, properly highlight and stringify values

* add component testing yarn commands

* fix the arrow not showing on details

* fix typescript error

* fixed lots of poorly written tests that weren't actually testing anything. created settings validation error when given a string validation result.

* fixes tests

* fixes tests, adds new error template for validating within an array list (for browser validation)

* remove dupe line

* fix copy for consistency, update snapshots

* remove redundant errors, standardize formatting and phrasing

* more formatting

* remove excess snapshots

* prune out excessive error snapshot html files when not in CI

* add missing tests, add case for when config validation fails without a fileType

* fixes test

* update snapshot

* update snapshot

* update snapshot

* sort uniqErrors + errorKeys prior to assertion - assert one by one

* add system test for binding to an event with the wrong handler

* fixes tests

* set more descriptive errors when setting invalid plugin events, or during plugin event validation

* remove duplicate PLUGINS_EVENT_ERROR, collapse into existing error

* use the same multiline formatting as @packages/errors

* standardize verbiage and highlighting for consistency

* fix incorrect error path

* fixes tests, standardized and condensed more language

* Update packages/errors/src/errors.ts

Co-authored-by: Ryan Manuel <ryanm@cypress.io>

* Update guides/error-handling.md

Co-authored-by: Ryan Manuel <ryanm@cypress.io>

* Update guides/error-handling.md

Co-authored-by: Ryan Manuel <ryanm@cypress.io>

* added some final todo's

* fix types

Co-authored-by: Tim Griesser <tgriesser10@gmail.com>
Co-authored-by: Tim Griesser <tgriesser@gmail.com>
Co-authored-by: Ryan Manuel <ryanm@cypress.io>
2022-02-11 02:06:07 -05:00

283 lines
7.6 KiB
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 Promise = require('bluebird')
const debug = require('debug')('cypress:server:cypress')
const { getPublicConfigKeys } = require('@packages/config')
const argsUtils = require('./util/args')
const { openProject } = require('../lib/open_project')
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 publicConfigKeys = getPublicConfigKeys()
const invalidConfigOptions = require('lodash').keys(options.config).reduce((invalid, option) => {
if (!publicConfigKeys.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)
})
}
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 (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')
}
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)
// const mainEntryFile = require.main.filename
const serverMain = require('./cwd')()
return cypressElectron.open(serverMain, args, fn)
})
})
},
openProject (options) {
// this code actually starts a project
// and is spawned from nodemon
openProject.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 = argv.filter((val) => val !== '--')
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)
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.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 '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(require('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}'`)
}
},
}