From 912369f5539d553dced196dc03befb8a2e7bf6df Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 27 Jan 2022 19:55:39 -0500 Subject: [PATCH 001/165] chore: rename errors.js -> errors.ts --- packages/server/lib/{errors.js => errors.ts} | 102 +++++++++---------- 1 file changed, 51 insertions(+), 51 deletions(-) rename packages/server/lib/{errors.js => errors.ts} (96%) diff --git a/packages/server/lib/errors.js b/packages/server/lib/errors.ts similarity index 96% rename from packages/server/lib/errors.js rename to packages/server/lib/errors.ts index 33c35fd1c723..ea89351398b7 100644 --- a/packages/server/lib/errors.js +++ b/packages/server/lib/errors.ts @@ -179,9 +179,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers. ${displayFlags(arg1.flags, { - group: '--group', - ciBuildId: '--ciBuildId', - })} + group: '--group', + ciBuildId: '--ciBuildId', + })} The server's response was: @@ -192,9 +192,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { We encountered an unexpected error talking to our servers. ${displayFlags(arg1.flags, { - group: '--group', - ciBuildId: '--ciBuildId', - })} + group: '--group', + ciBuildId: '--ciBuildId', + })} The server's response was: @@ -206,11 +206,11 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { There is likely something wrong with the request. ${displayFlags(arg1.flags, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} The server's response was: @@ -230,11 +230,11 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { You cannot parallelize a run that has been complete for that long. ${displayFlags(arg1, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} https://on.cypress.io/stale-run` case 'DASHBOARD_ALREADY_COMPLETE': @@ -246,11 +246,11 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { When a run finishes all of its groups, it waits for a configurable set of time before finally completing. You must add more groups during that time period. ${displayFlags(arg1, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} https://on.cypress.io/already-complete` case 'DASHBOARD_PARALLEL_REQUIRED': @@ -260,11 +260,11 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { The existing run is: ${arg1.runUrl} ${displayFlags(arg1, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} You must use the --parallel flag with this group. @@ -276,10 +276,10 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { The existing run is: ${arg1.runUrl} ${displayFlags(arg1, { - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} You can not use the --parallel flag with this group. @@ -295,12 +295,12 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { In order to run in parallel mode each machine must send identical environment parameters such as: ${listItems([ - 'specs', - 'osName', - 'osVersion', - 'browserName', - 'browserVersion (major)', - ])} + 'specs', + 'osName', + 'osVersion', + 'browserName', + 'browserVersion (major)', + ])} This machine sent the following parameters: @@ -314,10 +314,10 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { The existing run is: ${arg1.runUrl} ${displayFlags(arg1, { - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name. @@ -329,9 +329,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId. ${displayFlags(arg1, { - group: '--group', - parallel: '--parallel', - })} + group: '--group', + parallel: '--parallel', + })} In order to use either of these features a ciBuildId must be determined. @@ -347,11 +347,11 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag. ${displayFlags(arg1, { - ciBuildId: '--ci-build-id', - tags: '--tag', - group: '--group', - parallel: '--parallel', - })} + ciBuildId: '--ci-build-id', + tags: '--tag', + group: '--group', + parallel: '--parallel', + })} These flags can only be used when recording to the Cypress Dashboard service. @@ -361,8 +361,8 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { You passed the --ci-build-id flag but did not provide either a --group or --parallel flag. ${displayFlags(arg1, { - ciBuildId: '--ci-build-id', - })} + ciBuildId: '--ci-build-id', + })} The --ci-build-id flag is used to either group or parallelize multiple runs together. @@ -1120,7 +1120,7 @@ const logException = Promise.method(function (err) { } }) -module.exports = { +export = { get, log, From 311480738e62293ef4454ab7b22d702a9c86bd19 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 27 Jan 2022 23:25:24 -0500 Subject: [PATCH 002/165] refactor: type safety on errors --- packages/server/lib/browsers/utils.ts | 6 +- packages/server/lib/config.ts | 2 +- packages/server/lib/errors-child.js | 82 ++ packages/server/lib/errors.ts | 791 ++++++++++-------- packages/server/lib/modes/interactive-ct.ts | 2 +- .../lib/plugins/child/browser_launch.js | 6 +- packages/server/lib/plugins/child/task.js | 4 +- packages/server/lib/project-base.ts | 2 +- packages/server/lib/project_utils.ts | 4 +- packages/server/lib/server-e2e.ts | 2 +- packages/server/lib/util/ensure-url.ts | 2 +- packages/server/lib/util/require_async.ts | 2 +- packages/server/lib/util/settings.ts | 2 +- 13 files changed, 535 insertions(+), 372 deletions(-) create mode 100644 packages/server/lib/errors-child.js diff --git a/packages/server/lib/browsers/utils.ts b/packages/server/lib/browsers/utils.ts index 283f6a04b55e..d75e6447bbac 100644 --- a/packages/server/lib/browsers/utils.ts +++ b/packages/server/lib/browsers/utils.ts @@ -1,10 +1,12 @@ import _ from 'lodash' import type { FoundBrowser } from '@packages/launcher' -// @ts-ignore + import errors from '../errors' // @ts-ignore import plugins from '../plugins' +const errorsChild = require('../errors-child') + const path = require('path') const debug = require('debug')('cypress:server:browsers:utils') const Bluebird = require('bluebird') @@ -138,7 +140,7 @@ function extendLaunchOptionsFromPlugins (launchOptions, pluginConfigResult, opti // interface and we need to warn them // TODO: remove this logic in >= v5.0.0 if (pluginConfigResult[0]) { - options.onWarning(errors.get( + options.onWarning(errorsChild.get( 'DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS', )) diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index a02a0db4a581..ddbef9f25e09 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -535,7 +535,7 @@ export const setPluginsFile = Promise.method((obj, defaults) => { }) .then((result) => { if (result === null) { - return errors.throw('PLUGINS_FILE_ERROR', path.resolve(obj.projectRoot, pluginsFile)) + return errors.throw('PLUGINS_FILE_ERROR', path.resolve(obj.projectRoot, pluginsFile), new Error().stack ?? '') } debug('setting plugins file to %o', { result }) diff --git a/packages/server/lib/errors-child.js b/packages/server/lib/errors-child.js new file mode 100644 index 000000000000..4686b8807f52 --- /dev/null +++ b/packages/server/lib/errors-child.js @@ -0,0 +1,82 @@ +/* eslint-disable no-console */ +// Used in both the child process + +const chalk = require('chalk') +const { stripIndent } = require('common-tags') +const _ = require('lodash') + +const isCypressErr = (err) => { + return Boolean(err.isCypressErr) +} + +const log = function (err, color = 'red') { + console.log(chalk[color](err.message)) + + if (err.details) { + console.log('\n', chalk['yellow'](err.details)) + } + + // bail if this error came from known + // list of Cypress errors + if (isCypressErr(err)) { + return + } + + console.log(chalk[color](err.stack)) + + return err +} + +const get = (type, ...args) => { + let msg = trimMultipleNewLines(ErrorsUsedInChildProcess[type](...args)) + + const err = new Error(msg) + + err.isCypressErr = true + err.type = type + + return err +} + +const warning = function (type, ...args) { + const err = get(type, ...args) + + log(err, 'magenta') + + return null +} + +const twoOrMoreNewLinesRe = /\n{2,}/ + +const trimMultipleNewLines = (str) => { + return _ + .chain(str) + .split(twoOrMoreNewLinesRe) + .compact() + .join('\n\n') + .value() +} + +const ErrorsUsedInChildProcess = { + DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { + return stripIndent`\ + Deprecation Warning: The \`before:browser:launch\` plugin event changed its signature in version \`4.0.0\` + + The \`before:browser:launch\` plugin event switched from yielding the second argument as an \`array\` of browser arguments to an options \`object\` with an \`args\` property. + + We've detected that your code is still using the previous, deprecated interface signature. + + This code will not work in a future version of Cypress. Please see the upgrade guide: ${chalk.yellow('https://on.cypress.io/deprecated-before-browser-launch-args')}` + }, + + DUPLICATE_TASK_KEY: (arg1) => { + return `Warning: Multiple attempts to register the following task(s): ${chalk.blue(arg1)}. Only the last attempt will be registered.` + }, +} + +module.exports = { + log, + warning, + trimMultipleNewLines, + get, +} diff --git a/packages/server/lib/errors.ts b/packages/server/lib/errors.ts index ea89351398b7..fae5226b9984 100644 --- a/packages/server/lib/errors.ts +++ b/packages/server/lib/errors.ts @@ -5,13 +5,12 @@ const chalk = require('chalk') const AU = require('ansi_up') const Promise = require('bluebird') const { stripIndent } = require('./util/strip_indent') +const { log, trimMultipleNewLines } = require('./errors-child') const ansi_up = new AU.default ansi_up.use_classes = true -const twoOrMoreNewLinesRe = /\n{2,}/ - const isProduction = () => { return process.env['CYPRESS_INTERNAL_ENV'] === 'production' } @@ -25,7 +24,7 @@ const listItems = (paths) => { .value() } -const displayFlags = (obj, mapper) => { +const displayFlags = (obj, mapper: Record) => { return _ .chain(mapper) .map((flag, key) => { @@ -36,6 +35,8 @@ const displayFlags = (obj, mapper) => { if (v) { return `The ${flag} flag you passed was: ${chalk.blue(v)}` } + + return undefined }).compact() .join('\n') .value() @@ -65,59 +66,52 @@ This flag must be unique for each new run, but must also be identical for each m ` } -const trimMultipleNewLines = (str) => { - return _ - .chain(str) - .split(twoOrMoreNewLinesRe) - .compact() - .join('\n\n') - .value() -} - const isCypressErr = (err) => { return Boolean(err.isCypressErr) } -const getMsgByType = function (type, arg1 = {}, arg2, arg3) { - // NOTE: declarations in case blocks are forbidden so we declare them up front - let filePath; let err; let msg; let str - - switch (type) { - case 'CANNOT_TRASH_ASSETS': - return stripIndent`\ +// Note: Returning as a variable to make the git history better, will remove this in a future commit +const AllCypressErrors = { + CANNOT_TRASH_ASSETS: (arg1: string) => { + return stripIndent`\ Warning: We failed to trash the existing run results. This error will not alter the exit code. ${arg1}` - case 'CANNOT_REMOVE_OLD_BROWSER_PROFILES': - return stripIndent`\ + }, + CANNOT_REMOVE_OLD_BROWSER_PROFILES: (arg1: string) => { + return stripIndent`\ Warning: We failed to remove old browser profiles from previous runs. This error will not alter the exit code. ${arg1}` - case 'VIDEO_RECORDING_FAILED': - return stripIndent`\ + }, + VIDEO_RECORDING_FAILED: (arg1: string) => { + return stripIndent`\ Warning: We failed to record the video. This error will not alter the exit code. ${arg1}` - case 'VIDEO_POST_PROCESSING_FAILED': - return stripIndent`\ + }, + VIDEO_POST_PROCESSING_FAILED: (arg1: string) => { + return stripIndent`\ Warning: We failed processing this video. This error will not alter the exit code. ${arg1}` - case 'CHROME_WEB_SECURITY_NOT_SUPPORTED': - return stripIndent`\ + }, + CHROME_WEB_SECURITY_NOT_SUPPORTED: (arg1: string) => { + return stripIndent`\ Your project has set the configuration option: \`chromeWebSecurity: false\` This option will not have an effect in ${_.capitalize(arg1)}. Tests that rely on web security being disabled will not run as expected.` - case 'BROWSER_NOT_FOUND_BY_NAME': - str = stripIndent`\ + }, + BROWSER_NOT_FOUND_BY_NAME: (arg1: string, arg2: string) => { + let str = stripIndent`\ Can't run because you've entered an invalid browser name. Browser: '${arg1}' was not found on your system or is not supported by Cypress. @@ -134,35 +128,41 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Available browsers found on your system are: ${arg2}` - if (arg1 === 'canary') { - str += '\n\n' - str += stripIndent`\ + if (arg1 === 'canary') { + str += '\n\n' + str += stripIndent`\ Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.` - } + } - return str - case 'BROWSER_NOT_FOUND_BY_PATH': - msg = stripIndent`\ + return str + }, + BROWSER_NOT_FOUND_BY_PATH: (arg1: string, arg2: string) => { + let msg = stripIndent`\ We could not identify a known browser at the path you provided: \`${arg1}\` The output from the command we ran was:` - return { msg, details: arg2 } - case 'NOT_LOGGED_IN': - return stripIndent`\ + return { msg, details: arg2 } + }, + NOT_LOGGED_IN: () => { + return stripIndent`\ You're not logged in. Run \`cypress open\` to open the Desktop App and log in.` - case 'TESTS_DID_NOT_START_RETRYING': - return `Timed out waiting for the browser to connect. ${arg1}` - case 'TESTS_DID_NOT_START_FAILED': - return 'The browser never connected. Something is wrong. The tests cannot run. Aborting...' - case 'DASHBOARD_CANCEL_SKIPPED_SPEC': - return '\n This spec and its tests were skipped because the run has been canceled.' - case 'DASHBOARD_API_RESPONSE_FAILED_RETRYING': - return stripIndent`\ + }, + TESTS_DID_NOT_START_RETRYING: (arg1: string) => { + return `Timed out waiting for the browser to connect. ${arg1}` + }, + TESTS_DID_NOT_START_FAILED: () => { + return 'The browser never connected. Something is wrong. The tests cannot run. Aborting...' + }, + DASHBOARD_CANCEL_SKIPPED_SPEC: () => { + return '\n This spec and its tests were skipped because the run has been canceled.' + }, + DASHBOARD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delay: number, response: string}) => { + return stripIndent`\ We encountered an unexpected error talking to our servers. We will retry ${arg1.tries} more ${arg1.tries === 1 ? 'time' : 'times'} in ${arg1.delay}... @@ -172,57 +172,61 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { ${arg1.response}` /* Because of displayFlags() and listItems() */ /* eslint-disable indent */ - case 'DASHBOARD_CANNOT_PROCEED_IN_PARALLEL': - return stripIndent`\ + }, + DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: (arg1: {flags: any, response: string}) => { + return stripIndent`\ We encountered an unexpected error talking to our servers. Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers. ${displayFlags(arg1.flags, { - group: '--group', - ciBuildId: '--ciBuildId', - })} + group: '--group', + ciBuildId: '--ciBuildId', + })} The server's response was: ${arg1.response}` - - case 'DASHBOARD_CANNOT_PROCEED_IN_SERIAL': - return stripIndent`\ + }, + DASHBOARD_CANNOT_PROCEED_IN_SERIAL: (arg1: {flags: any, response: string}) => { + return stripIndent`\ We encountered an unexpected error talking to our servers. ${displayFlags(arg1.flags, { - group: '--group', - ciBuildId: '--ciBuildId', - })} + group: '--group', + ciBuildId: '--ciBuildId', + })} The server's response was: ${arg1.response}` - case 'DASHBOARD_UNKNOWN_INVALID_REQUEST': - return stripIndent`\ + }, + DASHBOARD_UNKNOWN_INVALID_REQUEST: (arg1: {flags: any, response: string}) => { + return stripIndent`\ We encountered an unexpected error talking to our servers. There is likely something wrong with the request. ${displayFlags(arg1.flags, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} The server's response was: ${arg1.response}` - case 'DASHBOARD_UNKNOWN_CREATE_RUN_WARNING': - return stripIndent`\ + }, + DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: (arg1: {props: any, message: string}) => { + return stripIndent`\ Warning from Cypress Dashboard: ${arg1.message} Details: ${JSON.stringify(arg1.props, null, 2)}` - case 'DASHBOARD_STALE_RUN': - return stripIndent`\ + }, + DASHBOARD_STALE_RUN: (arg1: {runUrl: string}) => { + return stripIndent`\ You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago. The existing run is: ${arg1.runUrl} @@ -230,15 +234,16 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { You cannot parallelize a run that has been complete for that long. ${displayFlags(arg1, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} https://on.cypress.io/stale-run` - case 'DASHBOARD_ALREADY_COMPLETE': - return stripIndent`\ + }, + DASHBOARD_ALREADY_COMPLETE: (arg1: {runUrl: string}) => { + return stripIndent`\ The run you are attempting to access is already complete and will not accept new groups. The existing run is: ${arg1.runUrl} @@ -246,46 +251,49 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { When a run finishes all of its groups, it waits for a configurable set of time before finally completing. You must add more groups during that time period. ${displayFlags(arg1, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} https://on.cypress.io/already-complete` - case 'DASHBOARD_PARALLEL_REQUIRED': - return stripIndent`\ + }, + DASHBOARD_PARALLEL_REQUIRED: (arg1: {runUrl: string}) => { + return stripIndent`\ You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag. The existing run is: ${arg1.runUrl} ${displayFlags(arg1, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} You must use the --parallel flag with this group. https://on.cypress.io/parallel-required` - case 'DASHBOARD_PARALLEL_DISALLOWED': - return stripIndent`\ + }, + DASHBOARD_PARALLEL_DISALLOWED: (arg1: {runUrl: string}) => { + return stripIndent`\ You passed the --parallel flag, but this run group was originally created without the --parallel flag. The existing run is: ${arg1.runUrl} ${displayFlags(arg1, { - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} You can not use the --parallel flag with this group. https://on.cypress.io/parallel-disallowed` - case 'DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH': - return stripIndent`\ + }, + DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: (arg1: {runUrl: string, parameters: any}) => { + return stripIndent`\ You passed the --parallel flag, but we do not parallelize tests across different environments. This machine is sending different environment parameters than the first machine that started this parallel run. @@ -295,43 +303,45 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { In order to run in parallel mode each machine must send identical environment parameters such as: ${listItems([ - 'specs', - 'osName', - 'osVersion', - 'browserName', - 'browserVersion (major)', - ])} + 'specs', + 'osName', + 'osVersion', + 'browserName', + 'browserVersion (major)', + ])} This machine sent the following parameters: ${JSON.stringify(arg1.parameters, null, 2)} https://on.cypress.io/parallel-group-params-mismatch` - case 'DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE': - return stripIndent`\ + }, + DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: (arg1: {runUrl: string, ciBuildId?: string | null}) => { + return stripIndent`\ You passed the --group flag, but this group name has already been used for this run. The existing run is: ${arg1.runUrl} ${displayFlags(arg1, { - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name. ${warnIfExplicitCiBuildId(arg1.ciBuildId)} https://on.cypress.io/run-group-name-not-unique` - case 'INDETERMINATE_CI_BUILD_ID': - return stripIndent`\ + }, + INDETERMINATE_CI_BUILD_ID: (arg1: object, arg2: string[]) => { + return stripIndent`\ You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId. ${displayFlags(arg1, { - group: '--group', - parallel: '--parallel', - })} + group: '--group', + parallel: '--parallel', + })} In order to use either of these features a ciBuildId must be determined. @@ -342,34 +352,37 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually. https://on.cypress.io/indeterminate-ci-build-id` - case 'RECORD_PARAMS_WITHOUT_RECORDING': - return stripIndent`\ + }, + RECORD_PARAMS_WITHOUT_RECORDING: (arg1: object) => { + return stripIndent`\ You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag. ${displayFlags(arg1, { - ciBuildId: '--ci-build-id', - tags: '--tag', - group: '--group', - parallel: '--parallel', - })} + ciBuildId: '--ci-build-id', + tags: '--tag', + group: '--group', + parallel: '--parallel', + })} These flags can only be used when recording to the Cypress Dashboard service. https://on.cypress.io/record-params-without-recording` - case 'INCORRECT_CI_BUILD_ID_USAGE': - return stripIndent`\ + }, + INCORRECT_CI_BUILD_ID_USAGE: (arg1: object) => { + return stripIndent`\ You passed the --ci-build-id flag but did not provide either a --group or --parallel flag. ${displayFlags(arg1, { - ciBuildId: '--ci-build-id', - })} + ciBuildId: '--ci-build-id', + })} The --ci-build-id flag is used to either group or parallelize multiple runs together. https://on.cypress.io/incorrect-ci-build-id-usage` /* eslint-enable indent */ - case 'RECORD_KEY_MISSING': - return stripIndent`\ + }, + RECORD_KEY_MISSING: () => { + return stripIndent`\ You passed the --record flag but did not provide us your Record Key. You can pass us your Record Key like this: @@ -379,8 +392,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { You can also set the key as an environment variable with the name CYPRESS_RECORD_KEY. https://on.cypress.io/how-do-i-record-runs` - case 'CANNOT_RECORD_NO_PROJECT_ID': - return stripIndent`\ + }, + CANNOT_RECORD_NO_PROJECT_ID: (arg1: string) => { + return stripIndent`\ You passed the --record flag but this project has not been setup to record. This project is missing the 'projectId' inside of '${arg1}'. @@ -392,8 +406,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Alternatively if you omit the --record flag this project will run without recording. https://on.cypress.io/recording-project-runs` - case 'PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION': - return stripIndent`\ + }, + PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION: (arg1: string) => { + return stripIndent`\ This project has been configured to record runs on our Dashboard. It currently has the projectId: ${chalk.green(arg1)} @@ -411,8 +426,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { ${chalk.yellow('cypress run --record false')} https://on.cypress.io/recording-project-runs` - case 'DASHBOARD_INVALID_RUN_REQUEST': - return stripIndent`\ + }, + DASHBOARD_INVALID_RUN_REQUEST: (arg1: {message: string, errors: any, object: any}) => { + return stripIndent`\ Recording this run failed because the request was invalid. ${arg1.message} @@ -424,8 +440,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Request Sent: ${JSON.stringify(arg1.object, null, 2)}` - case 'RECORDING_FROM_FORK_PR': - return stripIndent`\ + }, + RECORDING_FROM_FORK_PR: () => { + return stripIndent`\ Warning: It looks like you are trying to record this run from a forked PR. The 'Record Key' is missing. Your CI provider is likely not passing private environment variables to builds from forks. @@ -433,8 +450,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { These results will not be recorded. This error will not alter the exit code.` - case 'DASHBOARD_CANNOT_UPLOAD_RESULTS': - return stripIndent`\ + }, + DASHBOARD_CANNOT_UPLOAD_RESULTS: (arg1: string) => { + return stripIndent`\ Warning: We encountered an error while uploading results from your run. These results will not be recorded. @@ -442,8 +460,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { This error will not alter the exit code. ${arg1}` - case 'DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE': - return stripIndent`\ + }, + DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: (arg1: string) => { + return stripIndent`\ Warning: We encountered an error talking to our servers. This run will not be recorded. @@ -451,8 +470,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { This error will not alter the exit code. ${arg1}` - case 'DASHBOARD_RECORD_KEY_NOT_VALID': - return stripIndent`\ + }, + DASHBOARD_RECORD_KEY_NOT_VALID: (arg1: string, arg2: string) => { + return stripIndent`\ Your Record Key ${chalk.yellow(arg1)} is not valid with this project: ${chalk.blue(arg2)} It may have been recently revoked by you or another user. @@ -460,8 +480,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Please log into the Dashboard to see the valid record keys. https://on.cypress.io/dashboard/projects/${arg2}` - case 'DASHBOARD_PROJECT_NOT_FOUND': - return stripIndent`\ + }, + DASHBOARD_PROJECT_NOT_FOUND: (arg1: string, arg2: string) => { + return stripIndent`\ We could not find a project with the ID: ${chalk.yellow(arg1)} This projectId came from your '${arg2}' file or an environment variable. @@ -473,50 +494,60 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Alternatively, you can create a new project using the Desktop Application. https://on.cypress.io/dashboard` - case 'NO_PROJECT_ID': - return `Can't find 'projectId' in the '${arg1}' file for this project: ${chalk.blue(arg2)}` - case 'NO_PROJECT_FOUND_AT_PROJECT_ROOT': - return `Can't find project at the path: ${chalk.blue(arg1)}` - case 'CANNOT_FETCH_PROJECT_TOKEN': - return 'Can\'t find project\'s secret key.' - case 'CANNOT_CREATE_PROJECT_TOKEN': - return 'Can\'t create project\'s secret key.' - case 'PORT_IN_USE_SHORT': - return `Port ${arg1} is already in use.` - case 'PORT_IN_USE_LONG': - return stripIndent`\ + }, + NO_PROJECT_ID: (arg1: string, arg2: string) => { + return `Can't find 'projectId' in the '${arg1}' file for this project: ${chalk.blue(arg2)}` + }, + NO_PROJECT_FOUND_AT_PROJECT_ROOT: (arg1: string) => { + return `Can't find project at the path: ${chalk.blue(arg1)}` + }, + CANNOT_FETCH_PROJECT_TOKEN: () => { + return 'Can\'t find project\'s secret key.' + }, + CANNOT_CREATE_PROJECT_TOKEN: () => { + return 'Can\'t create project\'s secret key.' + }, + PORT_IN_USE_SHORT: (arg1: string) => { + return `Port ${arg1} is already in use.` + }, + PORT_IN_USE_LONG: (arg1: string) => { + return stripIndent`\ Can't run project because port is currently in use: ${chalk.blue(arg1)} ${chalk.yellow('Assign a different port with the \'--port \' argument or shut down the other running process.')}` - case 'ERROR_READING_FILE': - filePath = `\`${arg1}\`` - err = `\`${arg2.type || arg2.code || arg2.name}: ${arg2.message}\`` + }, + ERROR_READING_FILE: (arg1: string, arg2: Record) => { + let filePath = `\`${arg1}\`` - return stripIndent`\ + let err = `\`${arg2.type || arg2.code || arg2.name}: ${arg2.message}\`` + + return stripIndent`\ Error reading from: ${chalk.blue(filePath)} ${chalk.yellow(err)}` - case 'ERROR_WRITING_FILE': - filePath = `\`${arg1}\`` - err = `\`${arg2}\`` + }, + ERROR_WRITING_FILE: (arg1: string, arg2: string) => { + let filePath = `\`${arg1}\`` - return stripIndent`\ + let err = `\`${arg2}\`` + + return stripIndent`\ Error writing to: ${chalk.blue(filePath)} ${chalk.yellow(err)}` - - case 'NO_SPECS_FOUND': - // no glob provided, searched all specs - if (!arg2) { - return stripIndent`\ + }, + NO_SPECS_FOUND: (arg1: string, arg2?: string | null) => { + // no glob provided, searched all specs + if (!arg2) { + return stripIndent`\ Can't run because no spec files were found. We searched for any files inside of this folder: ${chalk.blue(arg1)}` - } + } - return stripIndent`\ + return stripIndent`\ Can't run because no spec files were found. We searched for any files matching this glob pattern: @@ -526,9 +557,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Relative to the project root folder: ${chalk.blue(arg1)}` - - case 'RENDERER_CRASHED': - return stripIndent`\ + }, + RENDERER_CRASHED: () => { + return stripIndent`\ We detected that the Chromium Renderer process just crashed. This is the equivalent to seeing the 'sad face' when Chrome dies. @@ -546,10 +577,12 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { You can learn more including how to fix Docker here: https://on.cypress.io/renderer-process-crashed` - case 'AUTOMATION_SERVER_DISCONNECTED': - return 'The automation client disconnected. Cannot continue running tests.' - case 'SUPPORT_FILE_NOT_FOUND': - return stripIndent`\ + }, + AUTOMATION_SERVER_DISCONNECTED: () => { + return 'The automation client disconnected. Cannot continue running tests.' + }, + SUPPORT_FILE_NOT_FOUND: (arg1: string, arg2: string) => { + return stripIndent`\ The support file is missing or invalid. Your \`supportFile\` is set to \`${arg1}\`, but either the file is missing or it's invalid. The \`supportFile\` must be a \`.js\`, \`.ts\`, \`.coffee\` file or be supported by your preprocessor plugin (if configured). @@ -559,8 +592,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Or you might have renamed the extension of your \`supportFile\` to \`.ts\`. If that's the case, restart the test runner. Learn more at https://on.cypress.io/support-file-missing-or-invalid` - case 'PLUGINS_FILE_ERROR': - msg = stripIndent`\ + }, + PLUGINS_FILE_ERROR: (arg1: string, arg2: string) => { + let msg = stripIndent`\ The plugins file is missing or invalid. Your \`pluginsFile\` is set to \`${arg1}\`, but either the file is missing, it contains a syntax error, or threw an error when required. The \`pluginsFile\` must be a \`.js\`, \`.ts\`, or \`.coffee\` file. @@ -569,13 +603,14 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Please fix this, or set \`pluginsFile\` to \`false\` if a plugins file is not necessary for your project.`.trim() - if (arg2) { - return { msg, details: arg2 } - } + if (arg2) { + return { msg, details: arg2 } + } - return msg - case 'PLUGINS_DIDNT_EXPORT_FUNCTION': - msg = stripIndent`\ + return msg + }, + PLUGINS_DIDNT_EXPORT_FUNCTION: (arg1: string, arg2: any) => { + let msg = stripIndent`\ The \`pluginsFile\` must export a function with the following signature: \`\`\` @@ -590,26 +625,30 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { It exported:` - return { msg, details: JSON.stringify(arg2) } - case 'PLUGINS_FUNCTION_ERROR': - msg = stripIndent`\ + return { msg, details: JSON.stringify(arg2) } + }, + PLUGINS_FUNCTION_ERROR: (arg1: string, arg2: string) => { + let msg = stripIndent`\ The function exported by the plugins file threw an error. We invoked the function exported by \`${arg1}\`, but it threw an error.` - return { msg, details: arg2 } - case 'PLUGINS_UNEXPECTED_ERROR': - msg = `The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (\`${arg1}\`)` - - return { msg, details: arg2 } - case 'PLUGINS_VALIDATION_ERROR': - msg = `The following validation error was thrown by your plugins file (\`${arg1}\`).` - - return { msg, details: arg2 } - case 'BUNDLE_ERROR': - // IF YOU MODIFY THIS MAKE SURE TO UPDATE - // THE ERROR MESSAGE IN THE RUNNER TOO - return stripIndent`\ + return { msg, details: arg2 } + }, + PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string) => { + const msg = `The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (\`${arg1}\`)` + + return { msg, details: arg2 } + }, + PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string) => { + const msg = `The following validation error was thrown by your plugins file (\`${arg1}\`).` + + return { msg, details: arg2 } + }, + BUNDLE_ERROR: (arg1: string, arg2: string) => { + // IF YOU MODIFY THIS MAKE SURE TO UPDATE + // THE ERROR MESSAGE IN THE RUNNER TOO + return stripIndent`\ Oops...we found an error preparing this test file: ${chalk.blue(arg1)} @@ -626,49 +665,56 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Fix the error in your code and re-run your tests.` // happens when there is an error in configuration file like "cypress.json" - case 'SETTINGS_VALIDATION_ERROR': - filePath = `\`${arg1}\`` + }, + SETTINGS_VALIDATION_ERROR: (arg1: string, arg2: string) => { + let filePath = `\`${arg1}\`` - return stripIndent`\ + return stripIndent`\ We found an invalid value in the file: ${chalk.blue(filePath)} ${chalk.yellow(arg2)}` // happens when there is an invalid config value is returned from the // project's plugins file like "cypress/plugins.index.js" - case 'PLUGINS_CONFIG_VALIDATION_ERROR': - filePath = `\`${arg1}\`` + }, + PLUGINS_CONFIG_VALIDATION_ERROR: (arg1: string, arg2: string) => { + let filePath = `\`${arg1}\`` - return stripIndent`\ + return stripIndent`\ An invalid configuration value returned from the plugins file: ${chalk.blue(filePath)} ${chalk.yellow(arg2)}` // general configuration error not-specific to configuration or plugins files - case 'CONFIG_VALIDATION_ERROR': - return stripIndent`\ + }, + CONFIG_VALIDATION_ERROR: (arg1: string) => { + return stripIndent`\ We found an invalid configuration value: ${chalk.yellow(arg1)}` - case 'RENAMED_CONFIG_OPTION': - return stripIndent`\ + }, + RENAMED_CONFIG_OPTION: (arg1: {name: string, newName: string}) => { + return stripIndent`\ The ${chalk.yellow(arg1.name)} configuration option you have supplied has been renamed. Please rename ${chalk.yellow(arg1.name)} to ${chalk.blue(arg1.newName)}` - case 'CANNOT_CONNECT_BASE_URL': - return stripIndent`\ + }, + CANNOT_CONNECT_BASE_URL: () => { + return stripIndent`\ Cypress failed to verify that your server is running. Please start this server and then run Cypress again.` - case 'CANNOT_CONNECT_BASE_URL_WARNING': - return stripIndent`\ + }, + CANNOT_CONNECT_BASE_URL_WARNING: (arg1: string) => { + return stripIndent`\ Cypress could not verify that this server is running: > ${chalk.blue(arg1)} This server has been configured as your \`baseUrl\`, and tests will likely fail if it is not running.` - case 'CANNOT_CONNECT_BASE_URL_RETRYING': - switch (arg1.attempt) { - case 1: - return stripIndent`\ + }, + CANNOT_CONNECT_BASE_URL_RETRYING: (arg1: {attempt: number, baseUrl: string, remaining: number, delay: number}) => { + switch (arg1.attempt) { + case 1: + return stripIndent`\ Cypress could not verify that this server is running: > ${chalk.blue(arg1.baseUrl)} @@ -678,11 +724,12 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Cypress automatically waits until your server is accessible before running tests. ${displayRetriesRemaining(arg1.remaining)}` - default: - return `${displayRetriesRemaining(arg1.remaining)}` - } - case 'INVALID_REPORTER_NAME': - return stripIndent`\ + default: + return `${displayRetriesRemaining(arg1.remaining)}` + } + }, + INVALID_REPORTER_NAME: (arg1: {name: string, paths: string[], error: string}) => { + return stripIndent`\ Could not load reporter by name: ${chalk.yellow(arg1.name)} We searched for the reporter in these paths: @@ -694,27 +741,31 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { ${chalk.yellow(arg1.error)} Learn more at https://on.cypress.io/reporters` - // TODO: update with vetted cypress language - case 'NO_DEFAULT_CONFIG_FILE_FOUND': - return stripIndent`\ + // TODO: update with vetted cypress language + }, + NO_DEFAULT_CONFIG_FILE_FOUND: (arg1: string) => { + return stripIndent`\ Could not find a Cypress configuration file, exiting. We looked but did not find a default config file in this folder: ${chalk.blue(arg1)}` - // TODO: update with vetted cypress language - case 'CONFIG_FILES_LANGUAGE_CONFLICT': - return stripIndent` + // TODO: update with vetted cypress language + }, + CONFIG_FILES_LANGUAGE_CONFLICT: (arg1: string, arg2: string, arg3: string) => { + return stripIndent` There is both a \`${arg2}\` and a \`${arg3}\` at the location below: ${arg1} Cypress does not know which one to read for config. Please remove one of the two and try again. ` - case 'CONFIG_FILE_NOT_FOUND': - return stripIndent`\ + }, + CONFIG_FILE_NOT_FOUND: (arg1: string, arg2: string) => { + return stripIndent`\ Could not find a Cypress configuration file, exiting. We looked but did not find a ${chalk.blue(arg1)} file in this folder: ${chalk.blue(arg2)}` - case 'INVOKED_BINARY_OUTSIDE_NPM_MODULE': - return stripIndent`\ + }, + INVOKED_BINARY_OUTSIDE_NPM_MODULE: () => { + return stripIndent`\ It looks like you are running the Cypress binary directly. This is not the recommended approach, and Cypress may not work correctly. @@ -722,38 +773,41 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Please install the 'cypress' NPM package and follow the instructions here: https://on.cypress.io/installing-cypress` - case 'DUPLICATE_TASK_KEY': - return `Warning: Multiple attempts to register the following task(s): ${chalk.blue(arg1)}. Only the last attempt will be registered.` - case 'FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS': - return stripIndent`\ + }, + FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + return stripIndent`\ You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. ${arg1.link}` - case 'FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS': - return stripIndent`\ + }, + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { + return stripIndent`\ You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} Your plan is now in a grace period, which means your tests will still be recorded until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue recording tests on the Cypress Dashboard in the future. ${arg1.link}` - case 'PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS': - return stripIndent`\ + }, + PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + return stripIndent`\ You've exceeded the limit of private test results under your current billing plan this month. ${arg1.usedTestsMessage} To upgrade your account, please visit your billing to upgrade to another billing plan. ${arg1.link}` - case 'FREE_PLAN_EXCEEDS_MONTHLY_TESTS': - return stripIndent`\ + }, + FREE_PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + return stripIndent`\ You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. ${arg1.link}` - case 'FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS': - return stripIndent`\ + }, + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { + return stripIndent`\ You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} Your plan is now in a grace period, which means you will have the full benefits of your current plan until ${arg1.gracePeriodMessage}. @@ -761,43 +815,49 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Please visit your billing to upgrade your plan. ${arg1.link}` - case 'PLAN_EXCEEDS_MONTHLY_TESTS': - return stripIndent`\ + }, + PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + return stripIndent`\ You've exceeded the limit of test results under your ${arg1.planType} billing plan this month. ${arg1.usedTestsMessage} To continue getting the full benefits of your current plan, please visit your billing to upgrade. ${arg1.link}` - case 'FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE': - return stripIndent`\ + }, + FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: (arg1: {link: string, gracePeriodMessage: string}) => { + return stripIndent`\ Parallelization is not included under your free plan. Your plan is now in a grace period, which means your tests will still run in parallel until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests in parallel in the future. ${arg1.link}` - case 'PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN': - return stripIndent`\ + }, + PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { + return stripIndent`\ Parallelization is not included under your current billing plan. To run your tests in parallel, please visit your billing and upgrade to another plan with parallelization. ${arg1.link}` - case 'PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED': - return stripIndent`\ + }, + PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: (arg1: {link: string, gracePeriodMessage: string}) => { + return stripIndent`\ Grouping is not included under your free plan. Your plan is now in a grace period, which means your tests will still run with groups until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests with groups in the future. ${arg1.link}` - case 'RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN': - return stripIndent`\ + }, + RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { + return stripIndent`\ Grouping is not included under your current billing plan. To run your tests with groups, please visit your billing and upgrade to another plan with grouping. ${arg1.link}` - case 'FIXTURE_NOT_FOUND': - return stripIndent`\ + }, + FIXTURE_NOT_FOUND: (arg1: string, arg2: string[]) => { + return stripIndent`\ A fixture file could not be found at any of the following paths: > ${arg1} @@ -808,17 +868,20 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { > ${arg2.join(', ')} Provide a path to an existing fixture file.` - case 'AUTH_COULD_NOT_LAUNCH_BROWSER': - return stripIndent`\ + }, + AUTH_COULD_NOT_LAUNCH_BROWSER: (arg1: string) => { + return stripIndent`\ Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser: \`\`\` ${arg1} \`\`\`` - case 'AUTH_BROWSER_LAUNCHED': - return `Check your browser to continue logging in.` - case 'BAD_POLICY_WARNING': - return stripIndent`\ + }, + AUTH_BROWSER_LAUNCHED: () => { + return `Check your browser to continue logging in.` + }, + BAD_POLICY_WARNING: (arg1: string[]) => { + return stripIndent`\ Cypress detected policy settings on your computer that may cause issues. The following policies were detected that may prevent Cypress from automating Chrome: @@ -826,24 +889,28 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { ${arg1.map((line) => ` > ${line}`).join('\n')} For more information, see https://on.cypress.io/bad-browser-policy` - case 'BAD_POLICY_WARNING_TOOLTIP': - return `Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy` - case 'EXTENSION_NOT_LOADED': - return stripIndent`\ + }, + BAD_POLICY_WARNING_TOOLTIP: () => { + return `Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy` + }, + EXTENSION_NOT_LOADED: (arg1: string, arg2: string) => { + return stripIndent`\ ${arg1} could not install the extension at path: > ${arg2} Please verify that this is the path to a valid, unpacked WebExtension.` - case 'COULD_NOT_FIND_SYSTEM_NODE': - return stripIndent`\ + }, + COULD_NOT_FIND_SYSTEM_NODE: (arg1: string) => { + return stripIndent`\ \`nodeVersion\` is set to \`system\`, but Cypress could not find a usable Node executable on your PATH. Make sure that your Node executable exists and can be run by the current user. Cypress will use the built-in Node version (v${arg1}) instead.` - case 'INVALID_CYPRESS_INTERNAL_ENV': - return stripIndent`\ + }, + INVALID_CYPRESS_INTERNAL_ENV: (arg1: string) => { + return stripIndent`\ We have detected an unknown or unsupported "CYPRESS_INTERNAL_ENV" value ${chalk.yellow(arg1)} @@ -851,10 +918,12 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { "CYPRESS_INTERNAL_ENV" is reserved and should only be used internally. Do not modify the "CYPRESS_INTERNAL_ENV" value.` - case 'CDP_VERSION_TOO_OLD': - return `A minimum CDP version of v${arg1} is required, but the current browser has ${arg2.major !== 0 ? `v${arg2.major}.${arg2.minor}` : 'an older version'}.` - case 'CDP_COULD_NOT_CONNECT': - return stripIndent`\ + }, + CDP_VERSION_TOO_OLD: (arg1: string, arg2: {major: number, minor: string | number}) => { + return `A minimum CDP version of v${arg1} is required, but the current browser has ${arg2.major !== 0 ? `v${arg2.major}.${arg2.minor}` : 'an older version'}.` + }, + CDP_COULD_NOT_CONNECT: (arg1: string, arg2: Error, arg3: string) => { + return stripIndent`\ Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds. This usually indicates there was a problem opening the ${arg3} browser. @@ -864,8 +933,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Error details: ${arg2.stack}` - case 'FIREFOX_COULD_NOT_CONNECT': - return stripIndent`\ + }, + FIREFOX_COULD_NOT_CONNECT: (arg1: Error) => { + return stripIndent`\ Cypress failed to make a connection to Firefox. This usually indicates there was a problem opening the Firefox browser. @@ -873,24 +943,18 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Error details: ${arg1.stack}` - case 'CDP_COULD_NOT_RECONNECT': - return stripIndent`\ + }, + CDP_COULD_NOT_RECONNECT: (arg1: Error) => { + return stripIndent`\ There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser. ${arg1.stack}` - case 'CDP_RETRYING_CONNECTION': - return `Still waiting to connect to ${arg2}, retrying in 1 second (attempt ${chalk.yellow(arg1)}/62)` - case 'DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS': - return stripIndent`\ - Deprecation Warning: The \`before:browser:launch\` plugin event changed its signature in version \`4.0.0\` - - The \`before:browser:launch\` plugin event switched from yielding the second argument as an \`array\` of browser arguments to an options \`object\` with an \`args\` property. - - We've detected that your code is still using the previous, deprecated interface signature. - - This code will not work in a future version of Cypress. Please see the upgrade guide: ${chalk.yellow('https://on.cypress.io/deprecated-before-browser-launch-args')}` - case 'UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES': - return stripIndent`\ + }, + CDP_RETRYING_CONNECTION: (arg1: string, arg2: string) => { + return `Still waiting to connect to ${arg2}, retrying in 1 second (attempt ${chalk.yellow(arg1)}/62)` + }, + UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: (arg1: string[], arg2: string[]) => { + return stripIndent`\ The \`launchOptions\` object returned by your plugin's \`before:browser:launch\` handler contained unexpected properties: ${listItems(arg1)} @@ -900,15 +964,17 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { ${listItems(arg2)} https://on.cypress.io/browser-launch-api` - case 'COULD_NOT_PARSE_ARGUMENTS': - return stripIndent`\ + }, + COULD_NOT_PARSE_ARGUMENTS: (arg1: string, arg2: string, arg3: string) => { + return stripIndent`\ Cypress encountered an error while parsing the argument ${chalk.gray(arg1)} You passed: ${arg2} The error was: ${arg3}` - case 'FIREFOX_MARIONETTE_FAILURE': - return stripIndent`\ + }, + FIREFOX_MARIONETTE_FAILURE: (arg1: string, arg2: string) => { + return stripIndent`\ Cypress could not connect to Firefox. An unexpected error was received from Marionette ${arg1}: @@ -916,8 +982,9 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { ${arg2} To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running.` - case 'FOLDER_NOT_WRITABLE': - return stripIndent`\ + }, + FOLDER_NOT_WRITABLE: (arg1: string) => { + return stripIndent`\ Folder ${arg1} is not writable. Writing to this directory is required by Cypress in order to store screenshots and videos. @@ -925,13 +992,15 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { Enable write permissions to this directory to ensure screenshots and videos are stored. If you don't require screenshots or videos to be stored you can safely ignore this warning.` - case 'EXPERIMENTAL_SAMESITE_REMOVED': - return stripIndent`\ + }, + EXPERIMENTAL_SAMESITE_REMOVED: () => { + return stripIndent`\ The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress version \`5.0.0\`. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. You can safely remove this option from your config.` - case 'EXPERIMENTAL_COMPONENT_TESTING_REMOVED': - return stripIndent`\ + }, + EXPERIMENTAL_COMPONENT_TESTING_REMOVED: (arg1: {configFile: string}) => { + return stripIndent`\ The ${chalk.yellow(`\`experimentalComponentTesting\``)} configuration option was removed in Cypress version \`7.0.0\`. Please remove this flag from ${chalk.yellow(`\`${arg1.configFile}\``)}. Cypress Component Testing is now a standalone command. You can now run your component tests with: @@ -939,31 +1008,36 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { ${chalk.yellow(`\`cypress open-ct\``)} https://on.cypress.io/migration-guide` - case 'EXPERIMENTAL_SHADOW_DOM_REMOVED': - return stripIndent`\ + }, + EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { + return stripIndent`\ The \`experimentalShadowDomSupport\` configuration option was removed in Cypress version \`5.2.0\`. It is no longer necessary when utilizing the \`includeShadowDom\` option. You can safely remove this option from your config.` - case 'EXPERIMENTAL_NETWORK_STUBBING_REMOVED': - return stripIndent`\ + }, + EXPERIMENTAL_NETWORK_STUBBING_REMOVED: () => { + return stripIndent`\ The \`experimentalNetworkStubbing\` configuration option was removed in Cypress version \`6.0.0\`. It is no longer necessary for using \`cy.intercept()\` (formerly \`cy.route2()\`). You can safely remove this option from your config.` - case 'EXPERIMENTAL_RUN_EVENTS_REMOVED': - return stripIndent`\ + }, + EXPERIMENTAL_RUN_EVENTS_REMOVED: () => { + return stripIndent`\ The \`experimentalRunEvents\` configuration option was removed in Cypress version \`6.7.0\`. It is no longer necessary when listening to run events in the plugins file. You can safely remove this option from your config.` - case 'FIREFOX_GC_INTERVAL_REMOVED': - return stripIndent`\ + }, + FIREFOX_GC_INTERVAL_REMOVED: () => { + return stripIndent`\ The \`firefoxGcInterval\` configuration option was removed in Cypress version \`8.0.0\`. It was introduced to work around a bug in Firefox 79 and below. Since Cypress no longer supports Firefox 85 and below in Cypress 8, this option was removed. You can safely remove this option from your config.` - case 'INCOMPATIBLE_PLUGIN_RETRIES': - return stripIndent`\ + }, + INCOMPATIBLE_PLUGIN_RETRIES: (arg1: string) => { + return stripIndent`\ We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at \`${arg1}\`. Test retries is now supported in Cypress version \`5.0.0\`. @@ -972,27 +1046,30 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { https://on.cypress.io/test-retries ` - case 'INVALID_CONFIG_OPTION': - return stripIndent`\ + }, + INVALID_CONFIG_OPTION: (arg1: string[]) => { + return stripIndent`\ ${arg1.map((arg) => `\`${arg}\` is not a valid configuration option`)} https://on.cypress.io/configuration ` - case 'PLUGINS_RUN_EVENT_ERROR': - return stripIndent`\ + }, + PLUGINS_RUN_EVENT_ERROR: (arg1: string, arg2: string) => { + return stripIndent`\ An error was thrown in your plugins file while executing the handler for the '${chalk.blue(arg1)}' event. The error we received was: ${chalk.yellow(arg2)} ` - case 'CT_NO_DEV_START_EVENT': - return stripIndent`\ + }, + CT_NO_DEV_START_EVENT: (arg1: string) => { + return stripIndent`\ To run component-testing, cypress needs the \`dev-server:start\` event. Implement it by adding a \`on('dev-server:start', () => startDevServer())\` call in your pluginsFile. ${arg1 ? - stripIndent`\ + stripIndent`\ You can find the \'pluginsFile\' at the following path: ${arg1} @@ -1001,32 +1078,42 @@ const getMsgByType = function (type, arg1 = {}, arg2, arg3) { https://on.cypress.io/component-testing ` - case 'UNSUPPORTED_BROWSER_VERSION': - return arg1 - case 'WIN32_UNSUPPORTED': - return stripIndent`\ + }, + UNSUPPORTED_BROWSER_VERSION: (error: Error) => { + return error + }, + WIN32_UNSUPPORTED: (arg1: never) => { + return stripIndent`\ You are attempting to run Cypress on Windows 32-bit. Cypress has removed Windows 32-bit support. ${arg1 ? 'Try installing Node.js 64-bit and reinstalling Cypress to use the 64-bit build.' - : 'Consider upgrading to a 64-bit OS to continue using Cypress.'} + : 'Consider upgrading to a 64-bit OS to continue using Cypress.'} ` - case 'NODE_VERSION_DEPRECATION_SYSTEM': - return stripIndent`\ + }, + NODE_VERSION_DEPRECATION_SYSTEM: (arg1: {name: string, value: any, configFile: string}) => { + return stripIndent`\ Deprecation Warning: ${chalk.yellow(`\`${arg1.name}\``)} is currently set to ${chalk.yellow(`\`${arg1.value}\``)} in the ${chalk.yellow(`\`${arg1.configFile}\``)} configuration file. As of Cypress version \`9.0.0\` the default behavior of ${chalk.yellow(`\`${arg1.name}\``)} has changed to always use the version of Node used to start cypress via the cli. Please remove the ${chalk.yellow(`\`${arg1.name}\``)} configuration option from ${chalk.yellow(`\`${arg1.configFile}\``)}. ` - case 'NODE_VERSION_DEPRECATION_BUNDLED': - return stripIndent`\ + }, + NODE_VERSION_DEPRECATION_BUNDLED: (arg1: {name: string, value: any, configFile: string}) => { + return stripIndent`\ Deprecation Warning: ${chalk.yellow(`\`${arg1.name}\``)} is currently set to ${chalk.yellow(`\`${arg1.value}\``)} in the ${chalk.yellow(`\`${arg1.configFile}\``)} configuration file. As of Cypress version \`9.0.0\` the default behavior of ${chalk.yellow(`\`${arg1.name}\``)} has changed to always use the version of Node used to start cypress via the cli. When ${chalk.yellow(`\`${arg1.name}\``)} is set to ${chalk.yellow(`\`${arg1.value}\``)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. As the ${chalk.yellow(`\`${arg1.name}\``)} configuration option will be removed in a future release, it is recommended to remove the ${chalk.yellow(`\`${arg1.name}\``)} configuration option from ${chalk.yellow(`\`${arg1.configFile}\``)}. ` - default: - } + }, +} as const + +type AllCypressErrorObj = typeof AllCypressErrors + +function getMsgByType (type: Type, ...args: Parameters) { + // @ts-expect-error + return AllCypressErrors[type](...args) } -const get = function (type, arg1, arg2, arg3) { +const get = function (type: Type, ...args: Parameters) { let details - let msg = getMsgByType(type, arg1, arg2, arg3) + let msg = getMsgByType(type, ...args) if (_.isObject(msg)) { ({ @@ -1040,7 +1127,7 @@ const get = function (type, arg1, arg2, arg3) { msg = trimMultipleNewLines(msg) - const err = new Error(msg) + const err = new Error(msg) as CypressErr err.isCypressErr = true err.type = type @@ -1049,19 +1136,27 @@ const get = function (type, arg1, arg2, arg3) { return err } -const warning = function (type, arg1, arg2) { - const err = get(type, arg1, arg2) +interface CypressErr extends Error { + isCypressErr: boolean + type: keyof AllCypressErrorObj + details: string + code?: string | number + errno?: string | number +} + +const warning = function (type: Type, ...args: Parameters) { + const err = get(type, ...args) log(err, 'magenta') return null } -const throwErr = function (type, arg1, arg2, arg3) { - throw get(type, arg1, arg2, arg3) +const throwErr = function (type: Type, ...args: Parameters) { + throw get(type, ...args) } -const clone = function (err, options = {}) { +const clone = function (err, options: {html?: boolean} = {}) { _.defaults(options, { html: false, }) @@ -1091,25 +1186,7 @@ const clone = function (err, options = {}) { return obj } -const log = function (err, color = 'red') { - console.log(chalk[color](err.message)) - - if (err.details) { - console.log('\n', chalk['yellow'](err.details)) - } - - // bail if this error came from known - // list of Cypress errors - if (isCypressErr(err)) { - return - } - - console.log(chalk[color](err.stack)) - - return err -} - -const logException = Promise.method(function (err) { +const logException = Promise.method(function (this: any, err) { // TODO: remove context here if (this.log(err) && isProduction()) { // log this exception since diff --git a/packages/server/lib/modes/interactive-ct.ts b/packages/server/lib/modes/interactive-ct.ts index 67cce240f220..34605d885ac6 100644 --- a/packages/server/lib/modes/interactive-ct.ts +++ b/packages/server/lib/modes/interactive-ct.ts @@ -5,7 +5,7 @@ import human from 'human-interval' import browsers from '../browsers' import { LaunchArgs, openProject } from '../open_project' import * as Updater from '../updater' -import * as errors from '../errors' +import errors from '../errors' const debug = Debug('cypress:server:interactive-ct') diff --git a/packages/server/lib/plugins/child/browser_launch.js b/packages/server/lib/plugins/child/browser_launch.js index 91d402999859..a6904f04721f 100644 --- a/packages/server/lib/plugins/child/browser_launch.js +++ b/packages/server/lib/plugins/child/browser_launch.js @@ -1,5 +1,5 @@ const util = require('../util') -const errors = require('../../errors') +const errorsChild = require('../../errors-child') const ARRAY_METHODS = ['concat', 'push', 'unshift', 'slice', 'pop', 'shift', 'slice', 'splice', 'filter', 'map', 'forEach', 'reduce', 'reverse', 'splice', 'includes'] @@ -21,7 +21,9 @@ module.exports = { hasEmittedWarning = true - const warning = errors.get('DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS') + const warning = errorsChild.get( + 'DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS', + ) ipc.send('warning', util.serializeError(warning)) diff --git a/packages/server/lib/plugins/child/task.js b/packages/server/lib/plugins/child/task.js index 699ebc591288..7cb96426a34f 100644 --- a/packages/server/lib/plugins/child/task.js +++ b/packages/server/lib/plugins/child/task.js @@ -1,6 +1,6 @@ const _ = require('lodash') const util = require('../util') -const errors = require('../../errors') +const errorsChild = require('../../errors-child') const getBody = (ipc, events, ids, [event]) => { const taskEvent = _.find(events, { event: 'task' }).handler @@ -24,7 +24,7 @@ const merge = (prevEvents, events) => { const duplicates = _.intersection(_.keys(prevEvents), _.keys(events)) if (duplicates.length) { - errors.warning('DUPLICATE_TASK_KEY', duplicates.join(', ')) + errorsChild.warning('DUPLICATE_TASK_KEY', duplicates.join(', ')) } return _.extend(prevEvents, events) diff --git a/packages/server/lib/project-base.ts b/packages/server/lib/project-base.ts index 5175a515a936..722faa008bed 100644 --- a/packages/server/lib/project-base.ts +++ b/packages/server/lib/project-base.ts @@ -849,7 +849,7 @@ export class ProjectBase extends EE { return readSettings.projectId } - errors.throw('NO_PROJECT_ID', settings.configFile(this.options), this.projectRoot) + errors.throw('NO_PROJECT_ID', settings.configFile(this.options) || '', this.projectRoot) } async verifyExistence () { diff --git a/packages/server/lib/project_utils.ts b/packages/server/lib/project_utils.ts index 54c3cab34559..07ad6e08a0a7 100644 --- a/packages/server/lib/project_utils.ts +++ b/packages/server/lib/project_utils.ts @@ -126,7 +126,7 @@ export const checkSupportFile = async ({ const found = await fs.pathExists(supportFile) if (!found) { - errors.throw('SUPPORT_FILE_NOT_FOUND', supportFile, settings.configFile({ configFile })) + errors.throw('SUPPORT_FILE_NOT_FOUND', supportFile, settings.configFile({ configFile }) || '') } } @@ -145,7 +145,7 @@ export async function getDefaultConfigFilePath (projectRoot: string, returnDefau // if we found more than one, throw a language conflict if (foundConfigFiles.length > 1) { - throw errors.throw('CONFIG_FILES_LANGUAGE_CONFLICT', projectRoot, ...foundConfigFiles) + throw errors.throw('CONFIG_FILES_LANGUAGE_CONFLICT', projectRoot, foundConfigFiles[0], foundConfigFiles[1]) } if (returnDefaultValueIfNotFound) { diff --git a/packages/server/lib/server-e2e.ts b/packages/server/lib/server-e2e.ts index 531962cedc91..f37e8557c4f9 100644 --- a/packages/server/lib/server-e2e.ts +++ b/packages/server/lib/server-e2e.ts @@ -107,7 +107,7 @@ export class ServerE2E extends ServerBase { .catch((e) => { debug(e) - return reject(errors.get('CANNOT_CONNECT_BASE_URL', baseUrl)) + return reject(errors.get('CANNOT_CONNECT_BASE_URL')) }) } diff --git a/packages/server/lib/util/ensure-url.ts b/packages/server/lib/util/ensure-url.ts index 776d1e6ac54b..5b644218b67f 100644 --- a/packages/server/lib/util/ensure-url.ts +++ b/packages/server/lib/util/ensure-url.ts @@ -9,7 +9,7 @@ const debug = debugModule('cypress:server:ensure-url') type RetryOptions = { retryIntervals: number[] - onRetry: Function + onRetry(o: {delay: number, attempt: number, remaining: number}): void } export const retryIsListening = (urlStr: string, options: RetryOptions) => { diff --git a/packages/server/lib/util/require_async.ts b/packages/server/lib/util/require_async.ts index 77869b1466f2..518a4a8204ae 100644 --- a/packages/server/lib/util/require_async.ts +++ b/packages/server/lib/util/require_async.ts @@ -3,7 +3,7 @@ import * as path from 'path' import * as cp from 'child_process' import * as inspector from 'inspector' import * as util from '../plugins/util' -import * as errors from '../errors' +import errors from '../errors' import Debug from 'debug' const debug = Debug('cypress:server:require_async') diff --git a/packages/server/lib/util/settings.ts b/packages/server/lib/util/settings.ts index 3d690b693ea5..2368388ba434 100644 --- a/packages/server/lib/util/settings.ts +++ b/packages/server/lib/util/settings.ts @@ -156,7 +156,7 @@ export function read (projectRoot, options: SettingsOptions = {}) { .catch((err) => { if (err.type === 'MODULE_NOT_FOUND' || err.code === 'ENOENT') { if (options.args?.runProject) { - return Promise.reject(errors.get('CONFIG_FILE_NOT_FOUND', options.configFile, projectRoot)) + return Promise.reject(errors.get('CONFIG_FILE_NOT_FOUND', options.configFile || '', projectRoot)) } return _write(file, {}) From d9f4baf724fcd5512808ec377cd5c37935ccde8f Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 28 Jan 2022 16:34:11 -0500 Subject: [PATCH 003/165] refactor: add err_template for consistent error formatting --- packages/server/__snapshots__/args_spec.js | 2 +- .../server/__snapshots__/browsers_spec.js | 4 +- packages/server/__snapshots__/cypress_spec.js | 3 +- packages/server/lib/errors-child.js | 2 +- packages/server/lib/errors.ts | 522 ++++++++++-------- packages/server/lib/util/err_template.ts | 147 +++++ .../server/test/integration/cypress_spec.js | 7 +- .../test/unit/browsers/browsers_spec.js | 2 +- .../test/unit/browsers/protocol_spec.ts | 14 +- packages/server/test/unit/errors_spec.js | 2 +- .../test/unit/util/err_template_spec.ts | 104 ++++ .../{strip_indent.ts => strip_indent_spec.ts} | 0 packages/server/tsconfig.json | 8 +- packages/ts/tsconfig.json | 4 +- 14 files changed, 561 insertions(+), 260 deletions(-) create mode 100644 packages/server/lib/util/err_template.ts create mode 100644 packages/server/test/unit/util/err_template_spec.ts rename packages/server/test/unit/util/{strip_indent.ts => strip_indent_spec.ts} (100%) diff --git a/packages/server/__snapshots__/args_spec.js b/packages/server/__snapshots__/args_spec.js index 99434367f504..cdf8434f60eb 100644 --- a/packages/server/__snapshots__/args_spec.js +++ b/packages/server/__snapshots__/args_spec.js @@ -25,7 +25,7 @@ The error was: Cannot read property 'split' of undefined exports['invalid spec error'] = ` Cypress encountered an error while parsing the argument spec -You passed: [object Object] +You passed: {} The error was: spec must be a string or comma-separated list ` diff --git a/packages/server/__snapshots__/browsers_spec.js b/packages/server/__snapshots__/browsers_spec.js index 7f98cbef4175..6da3ba73f7fe 100644 --- a/packages/server/__snapshots__/browsers_spec.js +++ b/packages/server/__snapshots__/browsers_spec.js @@ -1,7 +1,7 @@ exports['lib/browsers/index .ensureAndGetByNameOrPath throws when no browser can be found 1'] = ` Can't run because you've entered an invalid browser name. -Browser: 'browserNotGonnaBeFound' was not found on your system or is not supported by Cypress. +Browser: browserNotGonnaBeFound was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - chrome @@ -21,7 +21,7 @@ Available browsers found on your system are: exports['lib/browsers/index .ensureAndGetByNameOrPath throws a special error when canary is passed 1'] = ` Can't run because you've entered an invalid browser name. -Browser: 'canary' was not found on your system or is not supported by Cypress. +Browser: canary was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - chrome diff --git a/packages/server/__snapshots__/cypress_spec.js b/packages/server/__snapshots__/cypress_spec.js index aac557ee7344..c5f11d360496 100644 --- a/packages/server/__snapshots__/cypress_spec.js +++ b/packages/server/__snapshots__/cypress_spec.js @@ -302,7 +302,8 @@ The error was: Cannot read property 'split' of undefined ` exports['INVALID_CONFIG_OPTION'] = ` -\`test\` is not a valid configuration option,\`foo\` is not a valid configuration option +\`test\` is not a valid configuration option +\`foo\` is not a valid configuration option https://on.cypress.io/configuration diff --git a/packages/server/lib/errors-child.js b/packages/server/lib/errors-child.js index 4686b8807f52..d3abf2065210 100644 --- a/packages/server/lib/errors-child.js +++ b/packages/server/lib/errors-child.js @@ -13,7 +13,7 @@ const log = function (err, color = 'red') { console.log(chalk[color](err.message)) if (err.details) { - console.log('\n', chalk['yellow'](err.details)) + console.log(`\n${chalk['yellow'](err.details)}`) } // bail if this error came from known diff --git a/packages/server/lib/errors.ts b/packages/server/lib/errors.ts index fae5226b9984..c957e7731818 100644 --- a/packages/server/lib/errors.ts +++ b/packages/server/lib/errors.ts @@ -1,11 +1,12 @@ /* eslint-disable no-console */ -const _ = require('lodash') +import chalk from 'chalk' +import _ from 'lodash' +import Bluebird from 'bluebird' +import { errTemplate, details, guard, ErrTemplateResult, backtick } from './util/err_template' const strip = require('strip-ansi') -const chalk = require('chalk') const AU = require('ansi_up') -const Promise = require('bluebird') const { stripIndent } = require('./util/strip_indent') -const { log, trimMultipleNewLines } = require('./errors-child') +const { log } = require('./errors-child') const ansi_up = new AU.default @@ -16,16 +17,16 @@ const isProduction = () => { } const listItems = (paths) => { - return _ + return guard(_ .chain(paths) .map((p) => { - return `- ${chalk.blue(p)}` + return stripIndent`- ${chalk.blue(p)}` }).join('\n') - .value() + .value()) } const displayFlags = (obj, mapper: Record) => { - return _ + return guard(_ .chain(mapper) .map((flag, key) => { let v @@ -39,7 +40,7 @@ const displayFlags = (obj, mapper: Record) => { return undefined }).compact() .join('\n') - .value() + .value()) } const displayRetriesRemaining = function (tries) { @@ -70,51 +71,65 @@ const isCypressErr = (err) => { return Boolean(err.isCypressErr) } -// Note: Returning as a variable to make the git history better, will remove this in a future commit +/** + * All Cypress Errors should be defined here: + * + * The errors must return an "errTemplate", this is processed by the + */ const AllCypressErrors = { CANNOT_TRASH_ASSETS: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Warning: We failed to trash the existing run results. This error will not alter the exit code. - ${arg1}` + ${details(arg1)}` }, CANNOT_REMOVE_OLD_BROWSER_PROFILES: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Warning: We failed to remove old browser profiles from previous runs. This error will not alter the exit code. - ${arg1}` + ${details(arg1)}` }, VIDEO_RECORDING_FAILED: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Warning: We failed to record the video. This error will not alter the exit code. - ${arg1}` + ${details(arg1)}` }, VIDEO_POST_PROCESSING_FAILED: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Warning: We failed processing this video. This error will not alter the exit code. - ${arg1}` + ${details(arg1)}` }, CHROME_WEB_SECURITY_NOT_SUPPORTED: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Your project has set the configuration option: \`chromeWebSecurity: false\` - This option will not have an effect in ${_.capitalize(arg1)}. Tests that rely on web security being disabled will not run as expected.` + This option will not have an effect in ${guard(_.capitalize(arg1))}. Tests that rely on web security being disabled will not run as expected.` }, BROWSER_NOT_FOUND_BY_NAME: (arg1: string, arg2: string) => { - let str = stripIndent`\ + let canarySuffix = '' + + if (arg1 === 'canary') { + canarySuffix += '\n\n' + canarySuffix += stripIndent`\ + Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. + + See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.` + } + + return errTemplate`\ Can't run because you've entered an invalid browser name. - Browser: '${arg1}' was not found on your system or is not supported by Cypress. + Browser: ${arg1} was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - chrome @@ -126,46 +141,36 @@ const AllCypressErrors = { You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: - ${arg2}` - - if (arg1 === 'canary') { - str += '\n\n' - str += stripIndent`\ - Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. - - See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.` - } - - return str + ${guard(arg2)}${guard(canarySuffix)}` }, BROWSER_NOT_FOUND_BY_PATH: (arg1: string, arg2: string) => { - let msg = stripIndent`\ - We could not identify a known browser at the path you provided: \`${arg1}\` - - The output from the command we ran was:` + return errTemplate`\ + We could not identify a known browser at the path you provided: ${arg1} - return { msg, details: arg2 } + The output from the command we ran was: + + ${details(arg2)}` }, NOT_LOGGED_IN: () => { - return stripIndent`\ + return errTemplate`\ You're not logged in. Run \`cypress open\` to open the Desktop App and log in.` }, TESTS_DID_NOT_START_RETRYING: (arg1: string) => { - return `Timed out waiting for the browser to connect. ${arg1}` + return errTemplate`Timed out waiting for the browser to connect. ${guard(arg1)}` }, TESTS_DID_NOT_START_FAILED: () => { - return 'The browser never connected. Something is wrong. The tests cannot run. Aborting...' + return errTemplate`The browser never connected. Something is wrong. The tests cannot run. Aborting...` }, DASHBOARD_CANCEL_SKIPPED_SPEC: () => { - return '\n This spec and its tests were skipped because the run has been canceled.' + return errTemplate`\n This spec and its tests were skipped because the run has been canceled.` }, DASHBOARD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delay: number, response: string}) => { - return stripIndent`\ + return errTemplate`\ We encountered an unexpected error talking to our servers. - We will retry ${arg1.tries} more ${arg1.tries === 1 ? 'time' : 'times'} in ${arg1.delay}... + We will retry ${arg1.tries} more ${guard(arg1.tries === 1 ? 'time' : 'times')} in ${guard(arg1.delay)}... The server's response was: @@ -174,7 +179,7 @@ const AllCypressErrors = { /* eslint-disable indent */ }, DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: (arg1: {flags: any, response: string}) => { - return stripIndent`\ + return errTemplate`\ We encountered an unexpected error talking to our servers. Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers. @@ -189,7 +194,7 @@ const AllCypressErrors = { ${arg1.response}` }, DASHBOARD_CANNOT_PROCEED_IN_SERIAL: (arg1: {flags: any, response: string}) => { - return stripIndent`\ + return errTemplate`\ We encountered an unexpected error talking to our servers. ${displayFlags(arg1.flags, { @@ -202,7 +207,7 @@ const AllCypressErrors = { ${arg1.response}` }, DASHBOARD_UNKNOWN_INVALID_REQUEST: (arg1: {flags: any, response: string}) => { - return stripIndent`\ + return errTemplate`\ We encountered an unexpected error talking to our servers. There is likely something wrong with the request. @@ -219,14 +224,14 @@ const AllCypressErrors = { ${arg1.response}` }, DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: (arg1: {props: any, message: string}) => { - return stripIndent`\ + return errTemplate`\ Warning from Cypress Dashboard: ${arg1.message} Details: ${JSON.stringify(arg1.props, null, 2)}` }, DASHBOARD_STALE_RUN: (arg1: {runUrl: string}) => { - return stripIndent`\ + return errTemplate`\ You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago. The existing run is: ${arg1.runUrl} @@ -243,7 +248,7 @@ const AllCypressErrors = { https://on.cypress.io/stale-run` }, DASHBOARD_ALREADY_COMPLETE: (arg1: {runUrl: string}) => { - return stripIndent`\ + return errTemplate`\ The run you are attempting to access is already complete and will not accept new groups. The existing run is: ${arg1.runUrl} @@ -260,7 +265,7 @@ const AllCypressErrors = { https://on.cypress.io/already-complete` }, DASHBOARD_PARALLEL_REQUIRED: (arg1: {runUrl: string}) => { - return stripIndent`\ + return errTemplate`\ You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag. The existing run is: ${arg1.runUrl} @@ -277,7 +282,7 @@ const AllCypressErrors = { https://on.cypress.io/parallel-required` }, DASHBOARD_PARALLEL_DISALLOWED: (arg1: {runUrl: string}) => { - return stripIndent`\ + return errTemplate`\ You passed the --parallel flag, but this run group was originally created without the --parallel flag. The existing run is: ${arg1.runUrl} @@ -293,7 +298,7 @@ const AllCypressErrors = { https://on.cypress.io/parallel-disallowed` }, DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: (arg1: {runUrl: string, parameters: any}) => { - return stripIndent`\ + return errTemplate`\ You passed the --parallel flag, but we do not parallelize tests across different environments. This machine is sending different environment parameters than the first machine that started this parallel run. @@ -317,7 +322,7 @@ const AllCypressErrors = { https://on.cypress.io/parallel-group-params-mismatch` }, DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: (arg1: {runUrl: string, ciBuildId?: string | null}) => { - return stripIndent`\ + return errTemplate`\ You passed the --group flag, but this group name has already been used for this run. The existing run is: ${arg1.runUrl} @@ -335,7 +340,7 @@ const AllCypressErrors = { https://on.cypress.io/run-group-name-not-unique` }, INDETERMINATE_CI_BUILD_ID: (arg1: object, arg2: string[]) => { - return stripIndent`\ + return errTemplate`\ You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId. ${displayFlags(arg1, { @@ -354,7 +359,7 @@ const AllCypressErrors = { https://on.cypress.io/indeterminate-ci-build-id` }, RECORD_PARAMS_WITHOUT_RECORDING: (arg1: object) => { - return stripIndent`\ + return errTemplate`\ You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag. ${displayFlags(arg1, { @@ -369,7 +374,7 @@ const AllCypressErrors = { https://on.cypress.io/record-params-without-recording` }, INCORRECT_CI_BUILD_ID_USAGE: (arg1: object) => { - return stripIndent`\ + return errTemplate`\ You passed the --ci-build-id flag but did not provide either a --group or --parallel flag. ${displayFlags(arg1, { @@ -382,7 +387,7 @@ const AllCypressErrors = { /* eslint-enable indent */ }, RECORD_KEY_MISSING: () => { - return stripIndent`\ + return errTemplate`\ You passed the --record flag but did not provide us your Record Key. You can pass us your Record Key like this: @@ -394,7 +399,7 @@ const AllCypressErrors = { https://on.cypress.io/how-do-i-record-runs` }, CANNOT_RECORD_NO_PROJECT_ID: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ You passed the --record flag but this project has not been setup to record. This project is missing the 'projectId' inside of '${arg1}'. @@ -408,7 +413,7 @@ const AllCypressErrors = { https://on.cypress.io/recording-project-runs` }, PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ This project has been configured to record runs on our Dashboard. It currently has the projectId: ${chalk.green(arg1)} @@ -419,7 +424,7 @@ const AllCypressErrors = { If you meant to have this run recorded please additionally pass this flag. - ${chalk.blue('cypress run --record')} + ${'cypress run --record'} If you don't want to record these runs, you can silence this warning: @@ -428,7 +433,7 @@ const AllCypressErrors = { https://on.cypress.io/recording-project-runs` }, DASHBOARD_INVALID_RUN_REQUEST: (arg1: {message: string, errors: any, object: any}) => { - return stripIndent`\ + return errTemplate`\ Recording this run failed because the request was invalid. ${arg1.message} @@ -442,7 +447,7 @@ const AllCypressErrors = { ${JSON.stringify(arg1.object, null, 2)}` }, RECORDING_FROM_FORK_PR: () => { - return stripIndent`\ + return errTemplate`\ Warning: It looks like you are trying to record this run from a forked PR. The 'Record Key' is missing. Your CI provider is likely not passing private environment variables to builds from forks. @@ -452,7 +457,7 @@ const AllCypressErrors = { This error will not alter the exit code.` }, DASHBOARD_CANNOT_UPLOAD_RESULTS: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Warning: We encountered an error while uploading results from your run. These results will not be recorded. @@ -462,7 +467,7 @@ const AllCypressErrors = { ${arg1}` }, DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Warning: We encountered an error talking to our servers. This run will not be recorded. @@ -472,7 +477,7 @@ const AllCypressErrors = { ${arg1}` }, DASHBOARD_RECORD_KEY_NOT_VALID: (arg1: string, arg2: string) => { - return stripIndent`\ + return errTemplate`\ Your Record Key ${chalk.yellow(arg1)} is not valid with this project: ${chalk.blue(arg2)} It may have been recently revoked by you or another user. @@ -482,7 +487,7 @@ const AllCypressErrors = { https://on.cypress.io/dashboard/projects/${arg2}` }, DASHBOARD_PROJECT_NOT_FOUND: (arg1: string, arg2: string) => { - return stripIndent`\ + return errTemplate`\ We could not find a project with the ID: ${chalk.yellow(arg1)} This projectId came from your '${arg2}' file or an environment variable. @@ -496,42 +501,42 @@ const AllCypressErrors = { https://on.cypress.io/dashboard` }, NO_PROJECT_ID: (arg1: string, arg2: string) => { - return `Can't find 'projectId' in the '${arg1}' file for this project: ${chalk.blue(arg2)}` + return errTemplate`Can't find 'projectId' in the '${arg1}' file for this project: ${chalk.blue(arg2)}` }, NO_PROJECT_FOUND_AT_PROJECT_ROOT: (arg1: string) => { - return `Can't find project at the path: ${chalk.blue(arg1)}` + return errTemplate`Can't find project at the path: ${chalk.blue(arg1)}` }, CANNOT_FETCH_PROJECT_TOKEN: () => { - return 'Can\'t find project\'s secret key.' + return errTemplate`Can't find project's secret key.` }, CANNOT_CREATE_PROJECT_TOKEN: () => { - return 'Can\'t create project\'s secret key.' + return errTemplate`Can't create project's secret key.` }, PORT_IN_USE_SHORT: (arg1: string) => { - return `Port ${arg1} is already in use.` + return errTemplate`Port ${arg1} is already in use.` }, PORT_IN_USE_LONG: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Can't run project because port is currently in use: ${chalk.blue(arg1)} ${chalk.yellow('Assign a different port with the \'--port \' argument or shut down the other running process.')}` }, ERROR_READING_FILE: (arg1: string, arg2: Record) => { - let filePath = `\`${arg1}\`` + let filePath = `${arg1}` let err = `\`${arg2.type || arg2.code || arg2.name}: ${arg2.message}\`` - return stripIndent`\ + return errTemplate`\ Error reading from: ${chalk.blue(filePath)} ${chalk.yellow(err)}` }, ERROR_WRITING_FILE: (arg1: string, arg2: string) => { - let filePath = `\`${arg1}\`` + let filePath = `${arg1}` let err = `\`${arg2}\`` - return stripIndent`\ + return errTemplate`\ Error writing to: ${chalk.blue(filePath)} ${chalk.yellow(err)}` @@ -539,7 +544,7 @@ const AllCypressErrors = { NO_SPECS_FOUND: (arg1: string, arg2?: string | null) => { // no glob provided, searched all specs if (!arg2) { - return stripIndent`\ + return errTemplate`\ Can't run because no spec files were found. We searched for any files inside of this folder: @@ -547,7 +552,7 @@ const AllCypressErrors = { ${chalk.blue(arg1)}` } - return stripIndent`\ + return errTemplate`\ Can't run because no spec files were found. We searched for any files matching this glob pattern: @@ -559,7 +564,7 @@ const AllCypressErrors = { ${chalk.blue(arg1)}` }, RENDERER_CRASHED: () => { - return stripIndent`\ + return errTemplate`\ We detected that the Chromium Renderer process just crashed. This is the equivalent to seeing the 'sad face' when Chrome dies. @@ -579,132 +584,133 @@ const AllCypressErrors = { https://on.cypress.io/renderer-process-crashed` }, AUTOMATION_SERVER_DISCONNECTED: () => { - return 'The automation client disconnected. Cannot continue running tests.' + return errTemplate`The automation client disconnected. Cannot continue running tests.` }, SUPPORT_FILE_NOT_FOUND: (arg1: string, arg2: string) => { - return stripIndent`\ + return errTemplate`\ The support file is missing or invalid. - Your \`supportFile\` is set to \`${arg1}\`, but either the file is missing or it's invalid. The \`supportFile\` must be a \`.js\`, \`.ts\`, \`.coffee\` file or be supported by your preprocessor plugin (if configured). + Your ${'supportFile'} is set to ${arg1}, but either the file is missing or it's invalid. The \`supportFile\` must be a \`.js\`, \`.ts\`, \`.coffee\` file or be supported by your preprocessor plugin (if configured). - Correct your \`${arg2}\`, create the appropriate file, or set \`supportFile\` to \`false\` if a support file is not necessary for your project. + Correct your ${backtick(arg2)}, create the appropriate file, or set \`supportFile\` to \`false\` if a support file is not necessary for your project. Or you might have renamed the extension of your \`supportFile\` to \`.ts\`. If that's the case, restart the test runner. Learn more at https://on.cypress.io/support-file-missing-or-invalid` }, PLUGINS_FILE_ERROR: (arg1: string, arg2: string) => { - let msg = stripIndent`\ + return errTemplate`\ The plugins file is missing or invalid. - Your \`pluginsFile\` is set to \`${arg1}\`, but either the file is missing, it contains a syntax error, or threw an error when required. The \`pluginsFile\` must be a \`.js\`, \`.ts\`, or \`.coffee\` file. + Your \`pluginsFile\` is set to ${arg1}, but either the file is missing, it contains a syntax error, or threw an error when required. The \`pluginsFile\` must be a \`.js\`, \`.ts\`, or \`.coffee\` file. Or you might have renamed the extension of your \`pluginsFile\`. If that's the case, restart the test runner. - Please fix this, or set \`pluginsFile\` to \`false\` if a plugins file is not necessary for your project.`.trim() + Please fix this, or set \`pluginsFile\` to \`false\` if a plugins file is not necessary for your project. - if (arg2) { - return { msg, details: arg2 } - } - - return msg + ${details(arg2)} + ` }, PLUGINS_DIDNT_EXPORT_FUNCTION: (arg1: string, arg2: any) => { - let msg = stripIndent`\ - The \`pluginsFile\` must export a function with the following signature: + return errTemplate`\ + The \`pluginsFile\` must export a function with the following signature: - \`\`\` - module.exports = function (on, config) { - // configure plugins here - } - \`\`\` + \`\`\` + module.exports = function (on, config) { + // configure plugins here + } + \`\`\` - Learn more: https://on.cypress.io/plugins-api + Learn more: https://on.cypress.io/plugins-api - We loaded the \`pluginsFile\` from: \`${arg1}\` + We loaded the \`pluginsFile\` from: ${arg1} - It exported:` - - return { msg, details: JSON.stringify(arg2) } + It exported: + + ${details(arg2)} + ` }, PLUGINS_FUNCTION_ERROR: (arg1: string, arg2: string) => { - let msg = stripIndent`\ - The function exported by the plugins file threw an error. - - We invoked the function exported by \`${arg1}\`, but it threw an error.` + return errTemplate`\ + The function exported by the plugins file threw an error. - return { msg, details: arg2 } + We invoked the function exported by ${arg1}, but it threw an error. + + ${details(arg2)} + ` }, PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string) => { - const msg = `The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (\`${arg1}\`)` + return errTemplate` + The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (${arg1}) - return { msg, details: arg2 } + ${details(arg2)} + ` }, PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string) => { - const msg = `The following validation error was thrown by your plugins file (\`${arg1}\`).` - - return { msg, details: arg2 } + return errTemplate` + The following validation error was thrown by your plugins file (${arg1}). + + ${details(arg2)} + ` }, BUNDLE_ERROR: (arg1: string, arg2: string) => { // IF YOU MODIFY THIS MAKE SURE TO UPDATE // THE ERROR MESSAGE IN THE RUNNER TOO - return stripIndent`\ - Oops...we found an error preparing this test file: - - ${chalk.blue(arg1)} + return errTemplate`\ + Oops...we found an error preparing this test file: - The error was: + ${chalk.blue(arg1)} - ${chalk.yellow(arg2)} + The error was: - This occurred while Cypress was compiling and bundling your test code. This is usually caused by: + ${chalk.yellow(arg2)} - - A missing file or dependency - - A syntax error in the file or one of its dependencies + This occurred while Cypress was compiling and bundling your test code. This is usually caused by: - Fix the error in your code and re-run your tests.` + - A missing file or dependency + - A syntax error in the file or one of its dependencies + Fix the error in your code and re-run your tests. + ` // happens when there is an error in configuration file like "cypress.json" }, SETTINGS_VALIDATION_ERROR: (arg1: string, arg2: string) => { - let filePath = `\`${arg1}\`` - - return stripIndent`\ - We found an invalid value in the file: ${chalk.blue(filePath)} + return errTemplate`\ + We found an invalid value in the file: ${arg1} ${chalk.yellow(arg2)}` // happens when there is an invalid config value is returned from the // project's plugins file like "cypress/plugins.index.js" }, PLUGINS_CONFIG_VALIDATION_ERROR: (arg1: string, arg2: string) => { - let filePath = `\`${arg1}\`` + let filePath = `${arg1}` - return stripIndent`\ + return errTemplate`\ An invalid configuration value returned from the plugins file: ${chalk.blue(filePath)} ${chalk.yellow(arg2)}` // general configuration error not-specific to configuration or plugins files }, CONFIG_VALIDATION_ERROR: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ We found an invalid configuration value: ${chalk.yellow(arg1)}` }, RENAMED_CONFIG_OPTION: (arg1: {name: string, newName: string}) => { - return stripIndent`\ + return errTemplate`\ The ${chalk.yellow(arg1.name)} configuration option you have supplied has been renamed. Please rename ${chalk.yellow(arg1.name)} to ${chalk.blue(arg1.newName)}` }, CANNOT_CONNECT_BASE_URL: () => { - return stripIndent`\ + return errTemplate`\ Cypress failed to verify that your server is running. Please start this server and then run Cypress again.` }, CANNOT_CONNECT_BASE_URL_WARNING: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Cypress could not verify that this server is running: > ${chalk.blue(arg1)} @@ -714,7 +720,7 @@ const AllCypressErrors = { CANNOT_CONNECT_BASE_URL_RETRYING: (arg1: {attempt: number, baseUrl: string, remaining: number, delay: number}) => { switch (arg1.attempt) { case 1: - return stripIndent`\ + return errTemplate`\ Cypress could not verify that this server is running: > ${chalk.blue(arg1.baseUrl)} @@ -725,11 +731,11 @@ const AllCypressErrors = { ${displayRetriesRemaining(arg1.remaining)}` default: - return `${displayRetriesRemaining(arg1.remaining)}` + return errTemplate`${guard(displayRetriesRemaining(arg1.remaining))}` } }, INVALID_REPORTER_NAME: (arg1: {name: string, paths: string[], error: string}) => { - return stripIndent`\ + return errTemplate`\ Could not load reporter by name: ${chalk.yellow(arg1.name)} We searched for the reporter in these paths: @@ -744,7 +750,7 @@ const AllCypressErrors = { // TODO: update with vetted cypress language }, NO_DEFAULT_CONFIG_FILE_FOUND: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Could not find a Cypress configuration file, exiting. We looked but did not find a default config file in this folder: ${chalk.blue(arg1)}` @@ -759,13 +765,13 @@ const AllCypressErrors = { ` }, CONFIG_FILE_NOT_FOUND: (arg1: string, arg2: string) => { - return stripIndent`\ + return errTemplate`\ Could not find a Cypress configuration file, exiting. We looked but did not find a ${chalk.blue(arg1)} file in this folder: ${chalk.blue(arg2)}` }, INVOKED_BINARY_OUTSIDE_NPM_MODULE: () => { - return stripIndent`\ + return errTemplate`\ It looks like you are running the Cypress binary directly. This is not the recommended approach, and Cypress may not work correctly. @@ -775,89 +781,89 @@ const AllCypressErrors = { https://on.cypress.io/installing-cypress` }, FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { - return stripIndent`\ + return errTemplate`\ You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. - ${arg1.link}` + ${guard(arg1.link)}` }, FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { - return stripIndent`\ + return errTemplate`\ You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} Your plan is now in a grace period, which means your tests will still be recorded until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue recording tests on the Cypress Dashboard in the future. - ${arg1.link}` + ${guard(arg1.link)}` }, PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { - return stripIndent`\ + return errTemplate`\ You've exceeded the limit of private test results under your current billing plan this month. ${arg1.usedTestsMessage} To upgrade your account, please visit your billing to upgrade to another billing plan. - ${arg1.link}` + ${guard(arg1.link)}` }, FREE_PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { - return stripIndent`\ + return errTemplate`\ You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. - ${arg1.link}` + ${guard(arg1.link)}` }, FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { - return stripIndent`\ + return errTemplate`\ You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} Your plan is now in a grace period, which means you will have the full benefits of your current plan until ${arg1.gracePeriodMessage}. Please visit your billing to upgrade your plan. - ${arg1.link}` + ${guard(arg1.link)}` }, PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { - return stripIndent`\ + return errTemplate`\ You've exceeded the limit of test results under your ${arg1.planType} billing plan this month. ${arg1.usedTestsMessage} To continue getting the full benefits of your current plan, please visit your billing to upgrade. - ${arg1.link}` + ${guard(arg1.link)}` }, FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: (arg1: {link: string, gracePeriodMessage: string}) => { - return stripIndent`\ + return errTemplate`\ Parallelization is not included under your free plan. Your plan is now in a grace period, which means your tests will still run in parallel until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests in parallel in the future. - ${arg1.link}` + ${guard(arg1.link)}` }, PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { - return stripIndent`\ + return errTemplate`\ Parallelization is not included under your current billing plan. To run your tests in parallel, please visit your billing and upgrade to another plan with parallelization. - ${arg1.link}` + ${guard(arg1.link)}` }, PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: (arg1: {link: string, gracePeriodMessage: string}) => { - return stripIndent`\ + return errTemplate`\ Grouping is not included under your free plan. Your plan is now in a grace period, which means your tests will still run with groups until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests with groups in the future. - ${arg1.link}` + ${guard(arg1.link)}` }, RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { - return stripIndent`\ + return errTemplate`\ Grouping is not included under your current billing plan. To run your tests with groups, please visit your billing and upgrade to another plan with grouping. - ${arg1.link}` + ${guard(arg1.link)}` }, FIXTURE_NOT_FOUND: (arg1: string, arg2: string[]) => { - return stripIndent`\ + return errTemplate`\ A fixture file could not be found at any of the following paths: > ${arg1} @@ -870,7 +876,7 @@ const AllCypressErrors = { Provide a path to an existing fixture file.` }, AUTH_COULD_NOT_LAUNCH_BROWSER: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser: \`\`\` @@ -878,31 +884,31 @@ const AllCypressErrors = { \`\`\`` }, AUTH_BROWSER_LAUNCHED: () => { - return `Check your browser to continue logging in.` + return errTemplate`Check your browser to continue logging in.` }, BAD_POLICY_WARNING: (arg1: string[]) => { - return stripIndent`\ + return errTemplate`\ Cypress detected policy settings on your computer that may cause issues. The following policies were detected that may prevent Cypress from automating Chrome: - ${arg1.map((line) => ` > ${line}`).join('\n')} + ${guard(arg1.map((line) => ` > ${line}`).join('\n'))} For more information, see https://on.cypress.io/bad-browser-policy` }, BAD_POLICY_WARNING_TOOLTIP: () => { - return `Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy` + return errTemplate`Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy` }, EXTENSION_NOT_LOADED: (arg1: string, arg2: string) => { - return stripIndent`\ - ${arg1} could not install the extension at path: + return errTemplate`\ + ${guard(arg1)} could not install the extension at path: > ${arg2} Please verify that this is the path to a valid, unpacked WebExtension.` }, COULD_NOT_FIND_SYSTEM_NODE: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ \`nodeVersion\` is set to \`system\`, but Cypress could not find a usable Node executable on your PATH. Make sure that your Node executable exists and can be run by the current user. @@ -910,7 +916,7 @@ const AllCypressErrors = { Cypress will use the built-in Node version (v${arg1}) instead.` }, INVALID_CYPRESS_INTERNAL_ENV: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ We have detected an unknown or unsupported "CYPRESS_INTERNAL_ENV" value ${chalk.yellow(arg1)} @@ -920,41 +926,41 @@ const AllCypressErrors = { Do not modify the "CYPRESS_INTERNAL_ENV" value.` }, CDP_VERSION_TOO_OLD: (arg1: string, arg2: {major: number, minor: string | number}) => { - return `A minimum CDP version of v${arg1} is required, but the current browser has ${arg2.major !== 0 ? `v${arg2.major}.${arg2.minor}` : 'an older version'}.` + return errTemplate`A minimum CDP version of v${guard(arg1)} is required, but the current browser has ${guard(arg2.major !== 0 ? `v${arg2.major}.${arg2.minor}` : 'an older version')}.` }, CDP_COULD_NOT_CONNECT: (arg1: string, arg2: Error, arg3: string) => { - return stripIndent`\ + return errTemplate`\ Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds. - This usually indicates there was a problem opening the ${arg3} browser. + This usually indicates there was a problem opening the ${guard(arg3)} browser. - The CDP port requested was ${chalk.yellow(arg1)}. + The CDP port requested was ${guard(chalk.yellow(arg1))}. Error details: - ${arg2.stack}` + ${details(arg2)}` }, FIREFOX_COULD_NOT_CONNECT: (arg1: Error) => { - return stripIndent`\ + return errTemplate`\ Cypress failed to make a connection to Firefox. This usually indicates there was a problem opening the Firefox browser. Error details: - ${arg1.stack}` + ${details(arg1)}` }, CDP_COULD_NOT_RECONNECT: (arg1: Error) => { - return stripIndent`\ + return errTemplate`\ There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser. - ${arg1.stack}` + ${details(arg1)}` }, - CDP_RETRYING_CONNECTION: (arg1: string, arg2: string) => { - return `Still waiting to connect to ${arg2}, retrying in 1 second (attempt ${chalk.yellow(arg1)}/62)` + CDP_RETRYING_CONNECTION: (attempt: string | number, browserType: string) => { + return errTemplate`Still waiting to connect to ${guard(browserType)}, retrying in 1 second (attempt ${chalk.yellow(`${attempt}`)}/62)` }, UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: (arg1: string[], arg2: string[]) => { - return stripIndent`\ + return errTemplate`\ The \`launchOptions\` object returned by your plugin's \`before:browser:launch\` handler contained unexpected properties: ${listItems(arg1)} @@ -966,7 +972,7 @@ const AllCypressErrors = { https://on.cypress.io/browser-launch-api` }, COULD_NOT_PARSE_ARGUMENTS: (arg1: string, arg2: string, arg3: string) => { - return stripIndent`\ + return errTemplate`\ Cypress encountered an error while parsing the argument ${chalk.gray(arg1)} You passed: ${arg2} @@ -974,17 +980,17 @@ const AllCypressErrors = { The error was: ${arg3}` }, FIREFOX_MARIONETTE_FAILURE: (arg1: string, arg2: string) => { - return stripIndent`\ + return errTemplate`\ Cypress could not connect to Firefox. - An unexpected error was received from Marionette ${arg1}: + An unexpected error was received from Marionette ${guard(arg1)}: - ${arg2} + ${guard(arg2)} To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running.` }, FOLDER_NOT_WRITABLE: (arg1: string) => { - return stripIndent`\ + return errTemplate`\ Folder ${arg1} is not writable. Writing to this directory is required by Cypress in order to store screenshots and videos. @@ -994,14 +1000,14 @@ const AllCypressErrors = { If you don't require screenshots or videos to be stored you can safely ignore this warning.` }, EXPERIMENTAL_SAMESITE_REMOVED: () => { - return stripIndent`\ + return errTemplate`\ The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress version \`5.0.0\`. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. You can safely remove this option from your config.` }, EXPERIMENTAL_COMPONENT_TESTING_REMOVED: (arg1: {configFile: string}) => { - return stripIndent`\ - The ${chalk.yellow(`\`experimentalComponentTesting\``)} configuration option was removed in Cypress version \`7.0.0\`. Please remove this flag from ${chalk.yellow(`\`${arg1.configFile}\``)}. + return errTemplate`\ + The ${'experimentalComponentTesting'} configuration option was removed in Cypress version \`7.0.0\`. Please remove this flag from ${arg1.configFile}. Cypress Component Testing is now a standalone command. You can now run your component tests with: @@ -1010,26 +1016,26 @@ const AllCypressErrors = { https://on.cypress.io/migration-guide` }, EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { - return stripIndent`\ + return errTemplate`\ The \`experimentalShadowDomSupport\` configuration option was removed in Cypress version \`5.2.0\`. It is no longer necessary when utilizing the \`includeShadowDom\` option. You can safely remove this option from your config.` }, EXPERIMENTAL_NETWORK_STUBBING_REMOVED: () => { - return stripIndent`\ + return errTemplate`\ The \`experimentalNetworkStubbing\` configuration option was removed in Cypress version \`6.0.0\`. It is no longer necessary for using \`cy.intercept()\` (formerly \`cy.route2()\`). You can safely remove this option from your config.` }, EXPERIMENTAL_RUN_EVENTS_REMOVED: () => { - return stripIndent`\ + return errTemplate`\ The \`experimentalRunEvents\` configuration option was removed in Cypress version \`6.7.0\`. It is no longer necessary when listening to run events in the plugins file. You can safely remove this option from your config.` }, FIREFOX_GC_INTERVAL_REMOVED: () => { - return stripIndent`\ + return errTemplate`\ The \`firefoxGcInterval\` configuration option was removed in Cypress version \`8.0.0\`. It was introduced to work around a bug in Firefox 79 and below. Since Cypress no longer supports Firefox 85 and below in Cypress 8, this option was removed. @@ -1037,8 +1043,8 @@ const AllCypressErrors = { You can safely remove this option from your config.` }, INCOMPATIBLE_PLUGIN_RETRIES: (arg1: string) => { - return stripIndent`\ - We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at \`${arg1}\`. + return errTemplate`\ + We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at ${arg1}. Test retries is now supported in Cypress version \`5.0.0\`. @@ -1048,14 +1054,14 @@ const AllCypressErrors = { ` }, INVALID_CONFIG_OPTION: (arg1: string[]) => { - return stripIndent`\ - ${arg1.map((arg) => `\`${arg}\` is not a valid configuration option`)} + return errTemplate`\ + ${arg1.map((arg) => `\`${arg}\` is not a valid configuration option`).join('\n')} https://on.cypress.io/configuration ` }, PLUGINS_RUN_EVENT_ERROR: (arg1: string, arg2: string) => { - return stripIndent`\ + return errTemplate`\ An error was thrown in your plugins file while executing the handler for the '${chalk.blue(arg1)}' event. The error we received was: @@ -1064,74 +1070,82 @@ const AllCypressErrors = { ` }, CT_NO_DEV_START_EVENT: (arg1: string) => { - return stripIndent`\ + const pluginsFilePath = arg1 ? + stripIndent`\ + You can find the \'pluginsFile\' at the following path: + + ${arg1} + ` : '' + + return errTemplate`\ To run component-testing, cypress needs the \`dev-server:start\` event. Implement it by adding a \`on('dev-server:start', () => startDevServer())\` call in your pluginsFile. - ${arg1 ? - stripIndent`\ - You can find the \'pluginsFile\' at the following path: - - ${arg1} - ` : ''} + ${pluginsFilePath} Learn how to set up component testing: https://on.cypress.io/component-testing ` }, - UNSUPPORTED_BROWSER_VERSION: (error: Error) => { - return error - }, - WIN32_UNSUPPORTED: (arg1: never) => { - return stripIndent`\ - You are attempting to run Cypress on Windows 32-bit. Cypress has removed Windows 32-bit support. - - ${arg1 ? 'Try installing Node.js 64-bit and reinstalling Cypress to use the 64-bit build.' - : 'Consider upgrading to a 64-bit OS to continue using Cypress.'} - ` + UNSUPPORTED_BROWSER_VERSION: (errorMsg: string) => { + return errTemplate`${guard(errorMsg)}` }, NODE_VERSION_DEPRECATION_SYSTEM: (arg1: {name: string, value: any, configFile: string}) => { - return stripIndent`\ + return errTemplate`\ Deprecation Warning: ${chalk.yellow(`\`${arg1.name}\``)} is currently set to ${chalk.yellow(`\`${arg1.value}\``)} in the ${chalk.yellow(`\`${arg1.configFile}\``)} configuration file. As of Cypress version \`9.0.0\` the default behavior of ${chalk.yellow(`\`${arg1.name}\``)} has changed to always use the version of Node used to start cypress via the cli. Please remove the ${chalk.yellow(`\`${arg1.name}\``)} configuration option from ${chalk.yellow(`\`${arg1.configFile}\``)}. ` }, NODE_VERSION_DEPRECATION_BUNDLED: (arg1: {name: string, value: any, configFile: string}) => { - return stripIndent`\ + return errTemplate`\ Deprecation Warning: ${chalk.yellow(`\`${arg1.name}\``)} is currently set to ${chalk.yellow(`\`${arg1.value}\``)} in the ${chalk.yellow(`\`${arg1.configFile}\``)} configuration file. As of Cypress version \`9.0.0\` the default behavior of ${chalk.yellow(`\`${arg1.name}\``)} has changed to always use the version of Node used to start cypress via the cli. When ${chalk.yellow(`\`${arg1.name}\``)} is set to ${chalk.yellow(`\`${arg1.value}\``)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. As the ${chalk.yellow(`\`${arg1.name}\``)} configuration option will be removed in a future release, it is recommended to remove the ${chalk.yellow(`\`${arg1.name}\``)} configuration option from ${chalk.yellow(`\`${arg1.configFile}\``)}. ` }, } as const +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const _typeCheck: Record ErrTemplateResult> = AllCypressErrors + type AllCypressErrorObj = typeof AllCypressErrors -function getMsgByType (type: Type, ...args: Parameters) { +function getMsgByType (type: Type, ...args: Parameters): string { // @ts-expect-error - return AllCypressErrors[type](...args) + const result = AllCypressErrors[type](...args) as ErrTemplateResult + + return result.message } +/** + * Given an error name & params for the error, returns a "CypressError", + * with a forBrowser property, used when we want to format the value for sending to + * the browser rather than the terminal. + * + * @param type + * @param args + * @returns + */ const get = function (type: Type, ...args: Parameters) { - let details - let msg = getMsgByType(type, ...args) - - if (_.isObject(msg)) { - ({ - details, - } = msg); - - ({ - msg, - } = msg) - } + // @ts-expect-error + const result = AllCypressErrors[type](...args) as ErrTemplateResult - msg = trimMultipleNewLines(msg) + const { message, details, originalError, forBrowser } = result - const err = new Error(msg) as CypressErr + const err = new Error(message) as CypressErr err.isCypressErr = true err.type = type err.details = details + err.forBrowser = forBrowser + + if (originalError) { + err.stack = originalError.stack + } else { + const newErr = new Error() + + Error.captureStackTrace(newErr, get) + err.errWithoutMessage = newErr + } return err } @@ -1139,9 +1153,12 @@ const get = function (type: Type, ...arg interface CypressErr extends Error { isCypressErr: boolean type: keyof AllCypressErrorObj - details: string + details?: string code?: string | number errno?: string | number + errWithoutMessage?: Error + stackWithoutMessage?: string + forBrowser: ErrTemplateResult['forBrowser'] } const warning = function (type: Type, ...args: Parameters) { @@ -1153,26 +1170,45 @@ const warning = function (type: Type, .. } const throwErr = function (type: Type, ...args: Parameters) { - throw get(type, ...args) + const err = get(type, ...args) + + if (err.errWithoutMessage) { + Error.captureStackTrace(err.errWithoutMessage, throwErr) + err.stackWithoutMessage = err.errWithoutMessage.stack?.split('\n').slice(1).join('\n') + } + + throw err +} + +interface ClonedError { + type: string + name: string + columnNumber?: string + lineNumber?: string + fileName?: String + stack?: string + message?: string } -const clone = function (err, options: {html?: boolean} = {}) { +const clone = function (err: CypressErr, options: {html?: boolean} = {}) { _.defaults(options, { html: false, }) + const message = _.isFunction(err.forBrowser) ? err.forBrowser().message : err.message + // pull off these properties - const obj = _.pick(err, 'type', 'name', 'stack', 'fileName', 'lineNumber', 'columnNumber') + const obj = _.pick(err, 'type', 'name', 'stack', 'fileName', 'lineNumber', 'columnNumber') as ClonedError if (options.html) { - obj.message = ansi_up.ansi_to_html(err.message) + obj.message = ansi_up.ansi_to_html(message) // revert back the distorted characters // in case there is an error in a child_process // that contains quotes .replace(/\&\#x27;/g, '\'') .replace(/\"\;/g, '"') } else { - obj.message = err.message + obj.message = message } // and any own (custom) properties @@ -1183,10 +1219,14 @@ const clone = function (err, options: {html?: boolean} = {}) { obj[prop] = val } + if (err.stackWithoutMessage) { + obj.stack = err.stackWithoutMessage + } + return obj } -const logException = Promise.method(function (this: any, err) { +const logException = Bluebird.method(function (this: any, err) { // TODO: remove context here if (this.log(err) && isProduction()) { // log this exception since diff --git a/packages/server/lib/util/err_template.ts b/packages/server/lib/util/err_template.ts new file mode 100644 index 000000000000..5f7e9f20674b --- /dev/null +++ b/packages/server/lib/util/err_template.ts @@ -0,0 +1,147 @@ +/** + * Guarding the value, involves + */ +import chalk from 'chalk' +import assert from 'assert' +import stripAnsi from 'strip-ansi' + +import { trimMultipleNewLines } from '../errors-child' + +const { stripIndent } = require('./strip_indent') + +export class Guard { + constructor (readonly val: string | number) {} +} + +/** + * Prevents a string from being colored "blue" when wrapped in the errTemplate + * tag template literal + */ +export function guard (val: string | number) { + return new Guard(val) +} + +export class Backtick { + constructor (readonly val: string | number) {} +} + +export function backtick (val) { + return new Backtick(val) +} + +/** + * Marks the value as "details". This is when we print out the stack trace to the console + * (if it's an error), or use the stack trace as the originalError + */ +export class Details { + /** + * @param {string | Error | object} details + */ + constructor (readonly val: string | Error | object) {} +} + +export function details (val: string | Error | object) { + return new Details(val) +} + +export interface ErrTemplateResult { + message: string + details?: string + originalError?: Error + forBrowser(): { + message: string + details?: string + } +} + +/** + * Creates a consistently formatted object to return from the error call. + * + * For the console: + * - By default, wrap every arg in chalk.blue, unless it's "guarded" or is a "details" + * - Details stack gets logged at the end of the message in yellow + * + * For the browser: + * - Wrap every arg in backticks, for better rendering in markdown + * - If details is an error, it gets provided as originalError + */ +export const errTemplate = (strings: TemplateStringsArray, ...args: Array): ErrTemplateResult => { + let originalError: Error | undefined = undefined + let messageDetails + + function prepMessage (forTerminal = true) { + function isScalar (val: any): val is string | number | null | boolean { + return typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || val == null + } + + function formatVal (val: string | number | Error | object | null) { + if (val instanceof Error) { + return `${val.name}: ${val.message}` + } + + if (isScalar(val)) { + return forTerminal ? chalk.blue(`${val}`) : `\`${val}\`` + } + + try { + const objJson = JSON.stringify(val, null, 2) + + return forTerminal ? objJson : stripIndent` + \`\`\` + ${objJson} + \`\`\` + ` + } catch { + return String(val) + } + } + + function formatMsgDetails (val: any) { + return isScalar(val) ? val : val instanceof Error ? val.stack || val.message : JSON.stringify(val, null, 2) + } + + let templateArgs: Array = [] + let detailsSeen = false + + for (const arg of args) { + if (arg instanceof Backtick) { + templateArgs.push(forTerminal ? arg.val : `\`${arg.val}\``) + } else if (arg instanceof Guard) { + templateArgs.push(arg.val) + } else if (arg instanceof Details) { + assert(!detailsSeen, `Cannot use details() multiple times in the same errTemplate`) + detailsSeen = true + const { val } = arg + + messageDetails = formatMsgDetails(val) + if (val instanceof Error) { + originalError = val + } + + templateArgs.push('') + } else if (arg instanceof Error) { + templateArgs.push(chalk.red(`${arg.name}: ${arg.message}`)) + } else { + templateArgs.push(formatVal(arg)) + } + } + + return stripIndent(strings, ...templateArgs) + } + + const msg = trimMultipleNewLines(prepMessage()) + + return { + message: msg, + details: messageDetails, + forBrowser () { + const msg = trimMultipleNewLines(prepMessage(false)) + + return { + originalError, + message: stripAnsi(msg), + details: messageDetails, + } + }, + } +} diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index dae47fc8a3e7..43cb67fa0f3c 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -1,6 +1,7 @@ /* eslint-disable no-restricted-properties */ require('../spec_helper') +const chalk = require('chalk') const _ = require('lodash') const path = require('path') const EE = require('events') @@ -684,7 +685,7 @@ describe('lib/cypress', () => { .then(() => { return cypress.start([`--run-project=${this.idsPath}`]) }).then(() => { - this.expectExitWithErr('SUPPORT_FILE_NOT_FOUND', 'Your `supportFile` is set to `/does/not/exist`,') + this.expectExitWithErr('SUPPORT_FILE_NOT_FOUND', `Your ${chalk.blue('supportFile')} is set to ${chalk.blue('/does/not/exist')}`) }) }) @@ -701,12 +702,12 @@ describe('lib/cypress', () => { const found1 = _.find(argsSet, (args) => { return _.find(args, (arg) => { return arg.message && arg.message.includes( - 'Browser: \'foo\' was not found on your system or is not supported by Cypress.', + `Browser: ${chalk.blue('foo')} was not found on your system or is not supported by Cypress.`, ) }) }) - expect(found1, 'foo should not be found').to.be.ok + expect(found1, `foo should not be found`).to.be.ok const found2 = _.find(argsSet, (args) => { return _.find(args, (arg) => { diff --git a/packages/server/test/unit/browsers/browsers_spec.js b/packages/server/test/unit/browsers/browsers_spec.js index f6fa070ba35b..03d6b8cccef0 100644 --- a/packages/server/test/unit/browsers/browsers_spec.js +++ b/packages/server/test/unit/browsers/browsers_spec.js @@ -98,7 +98,7 @@ describe('lib/browsers/index', () => { // we will get good error message that includes the "err" object expect(err).to.have.property('type').to.eq('BROWSER_NOT_FOUND_BY_NAME') - expect(err).to.have.property('message').to.contain('\'foo-bad-bang\' was not found on your system') + expect(err).to.have.property('message').to.contain('foo-bad-bang was not found on your system') }) }) }) diff --git a/packages/server/test/unit/browsers/protocol_spec.ts b/packages/server/test/unit/browsers/protocol_spec.ts index fe4694b3d9f8..f54b3e35d805 100644 --- a/packages/server/test/unit/browsers/protocol_spec.ts +++ b/packages/server/test/unit/browsers/protocol_spec.ts @@ -59,9 +59,10 @@ describe('lib/browsers/protocol', () => { sinon.stub(connect, 'createRetryingSocket').callsArgWith(1, innerErr) const p = protocol.getWsTargetFor(12345, 'FooBrowser') - return expect(p).to.eventually.be.rejected - .and.property('message').include(expectedCdpFailedError) - .and.include(innerErr.message) + return expect(p).to.eventually.be.rejected.then((val) => { + expect(val).property('message').include(expectedCdpFailedError) + expect(val).property('details').include(innerErr.message) + }) }) it('rejects if CRI.List fails', () => { @@ -79,9 +80,10 @@ describe('lib/browsers/protocol', () => { const p = protocol.getWsTargetFor(12345, 'FooBrowser') - return expect(p).to.eventually.be.rejected - .and.property('message').include(expectedCdpFailedError) - .and.include(innerErr.message) + return expect(p).to.eventually.be.rejected.then((val) => { + expect(val).property('message').include(expectedCdpFailedError) + expect(val).property('details').include(innerErr.message) + }) }) it('returns the debugger URL of the first about:blank tab', async () => { diff --git a/packages/server/test/unit/errors_spec.js b/packages/server/test/unit/errors_spec.js index f0e9620fdcaf..84be6f5fd62f 100644 --- a/packages/server/test/unit/errors_spec.js +++ b/packages/server/test/unit/errors_spec.js @@ -166,7 +166,7 @@ describe('lib/errors', () => { } const text = errors.displayFlags(options, mapping) - return snapshot('tags and name only', text) + return snapshot('tags and name only', text.val) }) }) }) diff --git a/packages/server/test/unit/util/err_template_spec.ts b/packages/server/test/unit/util/err_template_spec.ts new file mode 100644 index 000000000000..9d1a9335dbc1 --- /dev/null +++ b/packages/server/test/unit/util/err_template_spec.ts @@ -0,0 +1,104 @@ +/// +import { expect } from 'chai' +import chalk from 'chalk' + +import { details, errTemplate, guard } from '../../../lib/util/err_template' +import { stripIndent } from '../../../lib/util/strip_indent' + +describe('err_template', () => { + it('returns an object w/ basic props & forBrowser', () => { + const obj = errTemplate`Hello world` + + expect(obj).to.include({ message: 'Hello world' }) + expect(obj.forBrowser()).to.include({ message: 'Hello world' }) + }) + + it('colors blue by default for the console, backticks passed arguments for the browser,', () => { + const obj = errTemplate`Hello world ${'special'}` + + expect(obj).to.include({ message: `Hello world ${chalk.blue('special')}` }) + expect(obj.forBrowser()).to.include({ message: 'Hello world `special`' }) + }) + + it('uses guard to guard passed values', () => { + const obj = errTemplate`Hello world ${guard('special')}` + + expect(obj).to.include({ message: `Hello world special` }) + expect(obj.forBrowser()).to.include({ message: `Hello world special` }) + }) + + it('provides as details for toErrorProps', () => { + const errStack = new Error().stack + const obj = errTemplate` + This was an error + + ${details(errStack)} + ` + + expect(obj.forBrowser()).to.include({ message: `This was an error`, details: errStack }) + expect(obj).to.include({ message: `This was an error`, details: errStack }) + }) + + it('will stringify non scalar values', () => { + const someObj = { a: 1, b: 2, c: 3 } + const obj = errTemplate` + This was returned from the app: + + ${someObj} + ` + + expect(obj.forBrowser()).to.include({ + message: stripIndent` + This was returned from the app: + + \`\`\` + ${JSON.stringify(someObj, null, 2)} + \`\`\``, + }) + + expect(obj).to.include({ + message: stripIndent` + This was returned from the app: + + ${JSON.stringify(someObj, null, 2)} + `, + }) + }) + + it('will stringify details values', () => { + const someObj = { a: 1, b: 2, c: 3 } + const obj = errTemplate` + This was returned from the app: + + ${details(someObj)} + ` + + expect(obj.forBrowser()).to.include({ message: `This was returned from the app:`, details: JSON.stringify(someObj, null, 2) }) + expect(obj).to.include({ message: `This was returned from the app:`, details: JSON.stringify(someObj, null, 2) }) + }) + + it('uses details to set originalError, for toErrorProps, highlight stack for console', () => { + const specFile = 'specFile.js' + const err = new Error() + const obj = errTemplate` + This was an error in ${specFile} + + ${details(err)} + ` + + expect(obj.forBrowser()).to.include({ message: `This was an error in \`specFile.js\``, details: err.stack }) + expect(obj).to.include({ message: `This was an error in ${chalk.blue(specFile)}`, details: err.stack }) + }) + + it('throws if multiple details are used in the same template', () => { + expect(() => { + errTemplate` + Hello world + + ${details(new Error().stack)} + + ${details(new Error().stack)} + ` + }).to.throw(/Cannot use details\(\) multiple times in the same errTemplate/) + }) +}) diff --git a/packages/server/test/unit/util/strip_indent.ts b/packages/server/test/unit/util/strip_indent_spec.ts similarity index 100% rename from packages/server/test/unit/util/strip_indent.ts rename to packages/server/test/unit/util/strip_indent_spec.ts diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 7e1bbc315ba9..4f8f749850ff 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -8,7 +8,11 @@ "./../ts/index.d.ts" ], "compilerOptions": { - "types": ["mocha", "node"], + "types": [ + "mocha", + "node" + ], + "noUnusedLocals": false, "importHelpers": true } -} +} \ No newline at end of file diff --git a/packages/ts/tsconfig.json b/packages/ts/tsconfig.json index 045ac1bf0350..5644ad1319f8 100644 --- a/packages/ts/tsconfig.json +++ b/packages/ts/tsconfig.json @@ -26,7 +26,9 @@ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ /* Additional Checks */ - "noUnusedLocals": true, /* Report errors on unused locals. */ + + // noUnusedLocals should be linted, not type-check'ed + "noUnusedLocals": false, /* Report errors on unused locals. */ "noUnusedParameters": false, /* Report errors on unused parameters. */ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ From e0880a0303bc336a953ea3511619fbfacb8c2db2 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Mon, 31 Jan 2022 09:00:14 -0500 Subject: [PATCH 004/165] fix a few system tests --- packages/server/lib/errors.ts | 3 +-- system-tests/__snapshots__/plugins_spec.js | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/server/lib/errors.ts b/packages/server/lib/errors.ts index c957e7731818..af28f3aeee97 100644 --- a/packages/server/lib/errors.ts +++ b/packages/server/lib/errors.ts @@ -670,8 +670,7 @@ const AllCypressErrors = { - A missing file or dependency - A syntax error in the file or one of its dependencies - Fix the error in your code and re-run your tests. - ` + Fix the error in your code and re-run your tests.` // happens when there is an error in configuration file like "cypress.json" }, SETTINGS_VALIDATION_ERROR: (arg1: string, arg2: string) => { diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js index 248834c535d2..25203ec8c173 100644 --- a/system-tests/__snapshots__/plugins_spec.js +++ b/system-tests/__snapshots__/plugins_spec.js @@ -119,14 +119,14 @@ exports['e2e plugins can modify config from plugins 1'] = ` ` exports['e2e plugins catches invalid browsers list returned from plugins 1'] = ` -An invalid configuration value returned from the plugins file: \`cypress/plugins/index.js\` +An invalid configuration value returned from the plugins file: cypress/plugins/index.js Expected at least one browser ` exports['e2e plugins catches invalid browser returned from plugins 1'] = ` -An invalid configuration value returned from the plugins file: \`cypress/plugins/index.js\` +An invalid configuration value returned from the plugins file: cypress/plugins/index.js Found an error while validating the \`browsers\` list. Expected \`displayName\` to be a non-empty string. Instead the value was: \`{"name":"browser name","family":"chromium"}\` @@ -346,7 +346,7 @@ exports['e2e plugins calls after:screenshot for cy.screenshot() and failure scre ` exports['e2e plugins catches invalid viewportWidth returned from plugins 1'] = ` -An invalid configuration value returned from the plugins file: \`cypress/plugins/index.js\` +An invalid configuration value returned from the plugins file: cypress/plugins/index.js Expected \`viewportWidth\` to be a number. Instead the value was: \`"foo"\` @@ -370,9 +370,9 @@ exports['e2e plugins fails when there is an async error inside an event handler Running: app_spec.js (1 of 1) -The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (\`/foo/bar/.projects/plugins-async-error/cypress/plugins/index.js\`) +The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (/foo/bar/.projects/plugins-async-error/cypress/plugins/index.js) - Error: Async error from plugins file +Error: Async error from plugins file [stack trace lines] (Results) @@ -419,14 +419,14 @@ We loaded the \`pluginsFile\` from: \`/foo/bar/.projects/plugin-empty/cypress/pl It exported: - {} +{} ` exports['e2e plugins fails when invalid event is registered 1'] = ` -The following validation error was thrown by your plugins file (\`/foo/bar/.projects/plugin-validation-error/cypress/plugins/index.js\`). +The following validation error was thrown by your plugins file (/foo/bar/.projects/plugin-validation-error/cypress/plugins/index.js). - Error: You must pass a valid event name when registering a plugin. +Error: You must pass a valid event name when registering a plugin. You passed: \`invalid:event\` From dc99313f00f65688a0c643968fa89d8980e03548 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 31 Jan 2022 09:27:57 -0500 Subject: [PATCH 005/165] fix tests; update snapshots --- packages/server/__snapshots__/chrome_spec.js | 2 +- packages/server/__snapshots__/cri-client_spec.ts.js | 2 +- system-tests/__snapshots__/plugins_spec.js | 2 +- system-tests/__snapshots__/record_spec.js | 2 +- system-tests/__snapshots__/retries_spec.ts.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/server/__snapshots__/chrome_spec.js b/packages/server/__snapshots__/chrome_spec.js index c5ceaaf0a6ba..5968c8cd46fc 100644 --- a/packages/server/__snapshots__/chrome_spec.js +++ b/packages/server/__snapshots__/chrome_spec.js @@ -1,7 +1,7 @@ exports['lib/browsers/chrome #open uses a custom profilePath if supplied 1'] = ` Can't run because you've entered an invalid browser name. -Browser: 'browserNotGonnaBeFound' was not found on your system or is not supported by Cypress. +Browser: browserNotGonnaBeFound was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - chrome diff --git a/packages/server/__snapshots__/cri-client_spec.ts.js b/packages/server/__snapshots__/cri-client_spec.ts.js index 5ca24ba31347..05b47b1e33d1 100644 --- a/packages/server/__snapshots__/cri-client_spec.ts.js +++ b/packages/server/__snapshots__/cri-client_spec.ts.js @@ -1,7 +1,7 @@ exports['lib/browsers/cri-client .create #send calls cri.send with command and data 1'] = ` Can't run because you've entered an invalid browser name. -Browser: 'browserNotGonnaBeFound' was not found on your system or is not supported by Cypress. +Browser: browserNotGonnaBeFound was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - chrome diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js index 25203ec8c173..fbd6f7322582 100644 --- a/system-tests/__snapshots__/plugins_spec.js +++ b/system-tests/__snapshots__/plugins_spec.js @@ -135,7 +135,7 @@ Found an error while validating the \`browsers\` list. Expected \`displayName\` exports['e2e plugins can filter browsers from config 1'] = ` Can't run because you've entered an invalid browser name. -Browser: 'chrome' was not found on your system or is not supported by Cypress. +Browser: chrome was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - chrome diff --git a/system-tests/__snapshots__/record_spec.js b/system-tests/__snapshots__/record_spec.js index 1ecb387ff6e8..6ddc188e1201 100644 --- a/system-tests/__snapshots__/record_spec.js +++ b/system-tests/__snapshots__/record_spec.js @@ -2643,7 +2643,7 @@ exports['e2e record empty specs succeeds when empty spec file 1'] = ` exports['e2e record misconfiguration errors and exits when no browser found 1'] = ` Can't run because you've entered an invalid browser name. -Browser: 'browserDoesNotExist' was not found on your system or is not supported by Cypress. +Browser: browserDoesNotExist was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - chrome diff --git a/system-tests/__snapshots__/retries_spec.ts.js b/system-tests/__snapshots__/retries_spec.ts.js index 39bea32e843c..4df487a56ca7 100644 --- a/system-tests/__snapshots__/retries_spec.ts.js +++ b/system-tests/__snapshots__/retries_spec.ts.js @@ -67,7 +67,7 @@ exports['retries / supports retries'] = ` ` exports['retries / warns about retries plugin'] = ` -We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at \`node_modules/cypress-plugin-retries\`. +We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at node_modules/cypress-plugin-retries. Test retries is now supported in Cypress version \`5.0.0\`. From c80f226ebf14fcabf3c8cc3a6f0cd8ee05a2d578 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Mon, 31 Jan 2022 09:37:25 -0500 Subject: [PATCH 006/165] Fix types --- packages/server/lib/errors.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/server/lib/errors.ts b/packages/server/lib/errors.ts index af28f3aeee97..6f1d465f5bc8 100644 --- a/packages/server/lib/errors.ts +++ b/packages/server/lib/errors.ts @@ -1189,7 +1189,13 @@ interface ClonedError { message?: string } -const clone = function (err: CypressErr, options: {html?: boolean} = {}) { +// For when the error is passed via the socket-base +interface GenericError extends Error { + forBrowser?: never + stackWithoutMessage?: never +} + +const clone = function (err: CypressErr | GenericError, options: {html?: boolean} = {}) { _.defaults(options, { html: false, }) From 45e2221d72ad3cba22b911f5367e527cd0298ce6 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 31 Jan 2022 09:39:53 -0500 Subject: [PATCH 007/165] normalize snapshot - remove chalk ansi colors --- packages/server/test/unit/browsers/browsers_spec.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/server/test/unit/browsers/browsers_spec.js b/packages/server/test/unit/browsers/browsers_spec.js index 03d6b8cccef0..491a6fa20dff 100644 --- a/packages/server/test/unit/browsers/browsers_spec.js +++ b/packages/server/test/unit/browsers/browsers_spec.js @@ -1,8 +1,13 @@ require('../../spec_helper') +const snapshot = require('snap-shot-it') +const stripAnsi = require('strip-ansi') const browsers = require(`${root}../lib/browsers`) const utils = require(`${root}../lib/browsers/utils`) -const snapshot = require('snap-shot-it') + +const normalizeSnapshot = (str) => { + return snapshot(stripAnsi(str)) +} const normalizeBrowsers = (message) => { return message.replace(/(found on your system are:)(?:\n- .*)*/, '$1\n- chrome\n- firefox\n- electron') @@ -64,7 +69,7 @@ describe('lib/browsers/index', () => { return expect(browsers.ensureAndGetByNameOrPath('browserNotGonnaBeFound')) .to.be.rejectedWith({ type: 'BROWSER_NOT_FOUND_BY_NAME' }) .then((err) => { - return snapshot(normalizeBrowsers(err.message)) + return normalizeSnapshot(normalizeBrowsers(err.message)) }) }) @@ -78,7 +83,7 @@ describe('lib/browsers/index', () => { return expect(browsers.ensureAndGetByNameOrPath('canary')) .to.be.rejectedWith({ type: 'BROWSER_NOT_FOUND_BY_NAME' }) .then((err) => { - return snapshot(err.message) + return normalizeSnapshot(err.message) }) }) }) From 3ebe6f74c7c94990c7ad59009fbe57e3f8e30fa1 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 31 Jan 2022 10:00:27 -0500 Subject: [PATCH 008/165] more unit test fixes --- packages/server/test/unit/browsers/browsers_spec.js | 5 +++-- packages/server/test/unit/errors_spec.js | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/server/test/unit/browsers/browsers_spec.js b/packages/server/test/unit/browsers/browsers_spec.js index 491a6fa20dff..cc5114512b41 100644 --- a/packages/server/test/unit/browsers/browsers_spec.js +++ b/packages/server/test/unit/browsers/browsers_spec.js @@ -1,5 +1,6 @@ require('../../spec_helper') +const chalk = require('chalk') const snapshot = require('snap-shot-it') const stripAnsi = require('strip-ansi') const browsers = require(`${root}../lib/browsers`) @@ -89,7 +90,7 @@ describe('lib/browsers/index', () => { }) context('.open', () => { - it('throws an error if browser family doesn\'t exist', () => { + it(`throws an error if browser family doesn't exist`, () => { return browsers.open({ name: 'foo-bad-bang', family: 'foo-bad', @@ -103,7 +104,7 @@ describe('lib/browsers/index', () => { // we will get good error message that includes the "err" object expect(err).to.have.property('type').to.eq('BROWSER_NOT_FOUND_BY_NAME') - expect(err).to.have.property('message').to.contain('foo-bad-bang was not found on your system') + expect(err).to.have.property('message').to.contain(`${chalk.blue('foo-bad-bang')} was not found on your system`) }) }) }) diff --git a/packages/server/test/unit/errors_spec.js b/packages/server/test/unit/errors_spec.js index 84be6f5fd62f..9e086a3120be 100644 --- a/packages/server/test/unit/errors_spec.js +++ b/packages/server/test/unit/errors_spec.js @@ -1,14 +1,14 @@ require('../spec_helper') -const style = require('ansi-styles') const chalk = require('chalk') +const style = require('ansi-styles') +const snapshot = require('snap-shot-it') const errors = require(`${root}lib/errors`) const logger = require(`${root}lib/logger`) -const snapshot = require('snap-shot-it') describe('lib/errors', () => { beforeEach(() => { - return sinon.spy(console, 'log') + sinon.stub(console, 'log') }) context('.log', () => { @@ -63,7 +63,7 @@ describe('lib/errors', () => { expect(console.log).to.be.calledWithMatch('foo/bar/baz') - expect(console.log).to.be.calledWithMatch('\n', 'details huh') + expect(console.log).to.be.calledWithMatch(`\n${ chalk.yellow('details huh')}`) }) it('logs err.stack in development', () => { From 74619e31f9b09216c0f3bcd871fe93a51ef832c0 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 31 Jan 2022 10:08:47 -0500 Subject: [PATCH 009/165] more system test fixes --- packages/server/lib/errors.ts | 17 +++++++++-------- system-tests/__snapshots__/plugins_spec.js | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/server/lib/errors.ts b/packages/server/lib/errors.ts index 6f1d465f5bc8..95aa20fc59f6 100644 --- a/packages/server/lib/errors.ts +++ b/packages/server/lib/errors.ts @@ -1,8 +1,9 @@ +import Bluebird from 'bluebird' /* eslint-disable no-console */ import chalk from 'chalk' import _ from 'lodash' -import Bluebird from 'bluebird' -import { errTemplate, details, guard, ErrTemplateResult, backtick } from './util/err_template' +import { backtick, details, errTemplate, guard, ErrTemplateResult } from './util/err_template' + const strip = require('strip-ansi') const AU = require('ansi_up') const { stripIndent } = require('./util/strip_indent') @@ -148,7 +149,7 @@ const AllCypressErrors = { We could not identify a known browser at the path you provided: ${arg1} The output from the command we ran was: - + ${details(arg2)}` }, NOT_LOGGED_IN: () => { @@ -164,7 +165,7 @@ const AllCypressErrors = { return errTemplate`The browser never connected. Something is wrong. The tests cannot run. Aborting...` }, DASHBOARD_CANCEL_SKIPPED_SPEC: () => { - return errTemplate`\n This spec and its tests were skipped because the run has been canceled.` + return errTemplate`${guard(`\n `)}This spec and its tests were skipped because the run has been canceled.` }, DASHBOARD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delay: number, response: string}) => { return errTemplate`\ @@ -626,7 +627,7 @@ const AllCypressErrors = { We loaded the \`pluginsFile\` from: ${arg1} It exported: - + ${details(arg2)} ` }, @@ -635,7 +636,7 @@ const AllCypressErrors = { The function exported by the plugins file threw an error. We invoked the function exported by ${arg1}, but it threw an error. - + ${details(arg2)} ` }, @@ -649,7 +650,7 @@ const AllCypressErrors = { PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string) => { return errTemplate` The following validation error was thrown by your plugins file (${arg1}). - + ${details(arg2)} ` }, @@ -1097,7 +1098,7 @@ const AllCypressErrors = { }, NODE_VERSION_DEPRECATION_BUNDLED: (arg1: {name: string, value: any, configFile: string}) => { return errTemplate`\ - Deprecation Warning: ${chalk.yellow(`\`${arg1.name}\``)} is currently set to ${chalk.yellow(`\`${arg1.value}\``)} in the ${chalk.yellow(`\`${arg1.configFile}\``)} configuration file. As of Cypress version \`9.0.0\` the default behavior of ${chalk.yellow(`\`${arg1.name}\``)} has changed to always use the version of Node used to start cypress via the cli. When ${chalk.yellow(`\`${arg1.name}\``)} is set to ${chalk.yellow(`\`${arg1.value}\``)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. + Deprecation Warning: ${chalk.yellow(`\`${arg1.name}\``)} is currently set to ${chalk.yellow(`\`${arg1.value}\``)} in the ${chalk.yellow(`\`${arg1.configFile}\``)} configuration file. As of Cypress version \`9.0.0\` the default behavior of ${chalk.yellow(`\`${arg1.name}\``)} has changed to always use the version of Node used to start cypress via the cli. When ${chalk.yellow(`\`${arg1.name}\``)} is set to ${chalk.yellow(`\`${arg1.value}\``)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. As the ${chalk.yellow(`\`${arg1.name}\``)} configuration option will be removed in a future release, it is recommended to remove the ${chalk.yellow(`\`${arg1.name}\``)} configuration option from ${chalk.yellow(`\`${arg1.configFile}\``)}. ` }, diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js index fbd6f7322582..6c5e5ad09973 100644 --- a/system-tests/__snapshots__/plugins_spec.js +++ b/system-tests/__snapshots__/plugins_spec.js @@ -415,7 +415,7 @@ module.exports = function (on, config) { Learn more: https://on.cypress.io/plugins-api -We loaded the \`pluginsFile\` from: \`/foo/bar/.projects/plugin-empty/cypress/plugins/index.js\` +We loaded the \`pluginsFile\` from: /foo/bar/.projects/plugin-empty/cypress/plugins/index.js It exported: From 2faecf51461a519890e977eb9ddbea71d9ff412b Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 31 Jan 2022 10:12:29 -0500 Subject: [PATCH 010/165] circleci build From 8322e07023cd0d967604a244eec916609859b874 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 31 Jan 2022 11:02:02 -0500 Subject: [PATCH 011/165] backtick always in stdout, fix error formatting and failing snapshots --- packages/server/lib/errors.ts | 21 +++++++++++++------ packages/server/lib/util/err_template.ts | 5 ++--- system-tests/__snapshots__/config_spec.js | 5 +++-- .../__snapshots__/system_node_spec.js | 12 +++++++++-- 4 files changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/server/lib/errors.ts b/packages/server/lib/errors.ts index 95aa20fc59f6..d523f8403e05 100644 --- a/packages/server/lib/errors.ts +++ b/packages/server/lib/errors.ts @@ -757,8 +757,9 @@ const AllCypressErrors = { // TODO: update with vetted cypress language }, CONFIG_FILES_LANGUAGE_CONFLICT: (arg1: string, arg2: string, arg3: string) => { - return stripIndent` - There is both a \`${arg2}\` and a \`${arg3}\` at the location below: + return errTemplate` + There is both a ${backtick(arg2)} and a ${backtick(arg3)} at the location below: + ${arg1} Cypress does not know which one to read for config. Please remove one of the two and try again. @@ -1092,14 +1093,22 @@ const AllCypressErrors = { }, NODE_VERSION_DEPRECATION_SYSTEM: (arg1: {name: string, value: any, configFile: string}) => { return errTemplate`\ - Deprecation Warning: ${chalk.yellow(`\`${arg1.name}\``)} is currently set to ${chalk.yellow(`\`${arg1.value}\``)} in the ${chalk.yellow(`\`${arg1.configFile}\``)} configuration file. As of Cypress version \`9.0.0\` the default behavior of ${chalk.yellow(`\`${arg1.name}\``)} has changed to always use the version of Node used to start cypress via the cli. - Please remove the ${chalk.yellow(`\`${arg1.name}\``)} configuration option from ${chalk.yellow(`\`${arg1.configFile}\``)}. + Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. + + As of Cypress version \`9.0.0\` the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. + + Please remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. ` }, NODE_VERSION_DEPRECATION_BUNDLED: (arg1: {name: string, value: any, configFile: string}) => { return errTemplate`\ - Deprecation Warning: ${chalk.yellow(`\`${arg1.name}\``)} is currently set to ${chalk.yellow(`\`${arg1.value}\``)} in the ${chalk.yellow(`\`${arg1.configFile}\``)} configuration file. As of Cypress version \`9.0.0\` the default behavior of ${chalk.yellow(`\`${arg1.name}\``)} has changed to always use the version of Node used to start cypress via the cli. When ${chalk.yellow(`\`${arg1.name}\``)} is set to ${chalk.yellow(`\`${arg1.value}\``)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. - As the ${chalk.yellow(`\`${arg1.name}\``)} configuration option will be removed in a future release, it is recommended to remove the ${chalk.yellow(`\`${arg1.name}\``)} configuration option from ${chalk.yellow(`\`${arg1.configFile}\``)}. + Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. + + As of Cypress version \`9.0.0\` the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. + + When ${backtick(arg1.name)} is set to ${backtick(arg1.value)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. + + As the ${backtick(arg1.name)} configuration option will be removed in a future release, it is recommended to remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. ` }, } as const diff --git a/packages/server/lib/util/err_template.ts b/packages/server/lib/util/err_template.ts index 5f7e9f20674b..372a7658d24b 100644 --- a/packages/server/lib/util/err_template.ts +++ b/packages/server/lib/util/err_template.ts @@ -1,10 +1,9 @@ +import assert from 'assert' /** * Guarding the value, involves */ import chalk from 'chalk' -import assert from 'assert' import stripAnsi from 'strip-ansi' - import { trimMultipleNewLines } from '../errors-child' const { stripIndent } = require('./strip_indent') @@ -105,7 +104,7 @@ export const errTemplate = (strings: TemplateStringsArray, ...args: Array Date: Tue, 1 Feb 2022 19:01:48 -0500 Subject: [PATCH 012/165] refactor: create @packages/errors --- .vscode/settings.json | 4 +- package.json | 2 +- packages/driver/src/cypress/stack_utils.ts | 2 +- packages/errors/README.md | 3 + .../__snapshots__/errors_spec.ts.js} | 0 packages/errors/index.js | 5 + packages/errors/package.json | 30 + .../lib/util => errors/src}/err_template.ts | 25 +- packages/errors/src/errorTypes.ts | 50 + packages/errors/src/error_utils.ts | 89 ++ packages/errors/src/errors.ts | 1208 ++++++++++++++++ packages/errors/src/index.ts | 12 + .../lib/util => errors/src}/stack_utils.ts | 13 +- .../src/strip_indent.ts} | 6 +- packages/errors/test/.mocharc.js | 1 + .../test/unit/__snapshots__/errors_spec.js | 4 + .../test/unit}/err_template_spec.ts | 10 +- packages/errors/test/unit/errors_spec.ts | 123 ++ .../test/unit}/strip_indent_spec.ts | 4 +- packages/errors/tsconfig.json | 24 + packages/server/lib/browsers/utils.ts | 5 +- packages/server/lib/errors-child.js | 82 -- packages/server/lib/errors.ts | 1263 +---------------- .../lib/plugins/child/browser_launch.js | 4 +- packages/server/lib/plugins/child/task.js | 2 +- packages/server/lib/util/require_async.ts | 10 +- packages/server/test/unit/errors_spec.js | 183 +-- packages/server/tsconfig.json | 2 +- yarn.lock | 29 +- 29 files changed, 1646 insertions(+), 1549 deletions(-) create mode 100644 packages/errors/README.md rename packages/{server/__snapshots__/errors_spec.js => errors/__snapshots__/errors_spec.ts.js} (100%) create mode 100644 packages/errors/index.js create mode 100644 packages/errors/package.json rename packages/{server/lib/util => errors/src}/err_template.ts (89%) create mode 100644 packages/errors/src/errorTypes.ts create mode 100644 packages/errors/src/error_utils.ts create mode 100644 packages/errors/src/errors.ts create mode 100644 packages/errors/src/index.ts rename packages/{server/lib/util => errors/src}/stack_utils.ts (68%) rename packages/{server/lib/util/strip_indent.js => errors/src/strip_indent.ts} (76%) create mode 100644 packages/errors/test/.mocharc.js create mode 100644 packages/errors/test/unit/__snapshots__/errors_spec.js rename packages/{server/test/unit/util => errors/test/unit}/err_template_spec.ts (88%) create mode 100644 packages/errors/test/unit/errors_spec.ts rename packages/{server/test/unit/util => errors/test/unit}/strip_indent_spec.ts (88%) create mode 100644 packages/errors/tsconfig.json delete mode 100644 packages/server/lib/errors-child.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 2824e4f981b2..934ee91ed0ce 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -36,6 +36,6 @@ "i18n-ally.keystyle": "nested", // Volar is the main extension that powers Vue's language features. - "volar.autoCompleteRefs": false, - "volar.takeOverMode.enabled": true + // "volar.autoCompleteRefs": false, + // "volar.takeOverMode.enabled": true } diff --git a/package.json b/package.json index 948f809ed601..f57be74168df 100644 --- a/package.json +++ b/package.json @@ -181,7 +181,7 @@ "snap-shot-it": "7.9.3", "start-server-and-test": "1.10.8", "stop-only": "3.0.1", - "strip-ansi": "4.0.0", + "strip-ansi": "6.0.0", "term-to-html": "1.2.0", "terminal-banner": "1.1.0", "through": "2.3.8", diff --git a/packages/driver/src/cypress/stack_utils.ts b/packages/driver/src/cypress/stack_utils.ts index 0a3d2a188d3d..ac524885109f 100644 --- a/packages/driver/src/cypress/stack_utils.ts +++ b/packages/driver/src/cypress/stack_utils.ts @@ -7,7 +7,7 @@ import { codeFrameColumns } from '@babel/code-frame' import $utils from './utils' import $sourceMapUtils from './source_map_utils' -import { getStackLines, replacedStack, stackWithoutMessage, splitStack, unsplitStack } from '@packages/server/lib/util/stack_utils' +import { getStackLines, replacedStack, stackWithoutMessage, splitStack, unsplitStack } from '@packages/errors/src/stack_utils' const whitespaceRegex = /^(\s*)*/ const stackLineRegex = /^\s*(at )?.*@?\(?.*\:\d+\:\d+\)?$/ diff --git a/packages/errors/README.md b/packages/errors/README.md new file mode 100644 index 000000000000..ee4ea656181d --- /dev/null +++ b/packages/errors/README.md @@ -0,0 +1,3 @@ +## @packages/errors + +Error definitions and utilities for Cypress \ No newline at end of file diff --git a/packages/server/__snapshots__/errors_spec.js b/packages/errors/__snapshots__/errors_spec.ts.js similarity index 100% rename from packages/server/__snapshots__/errors_spec.js rename to packages/errors/__snapshots__/errors_spec.ts.js diff --git a/packages/errors/index.js b/packages/errors/index.js new file mode 100644 index 000000000000..643cb42256ee --- /dev/null +++ b/packages/errors/index.js @@ -0,0 +1,5 @@ +if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { + require('@packages/ts/register') +} + +module.exports = require('./src') diff --git a/packages/errors/package.json b/packages/errors/package.json new file mode 100644 index 000000000000..82efbde9d28c --- /dev/null +++ b/packages/errors/package.json @@ -0,0 +1,30 @@ +{ + "name": "@packages/errors", + "version": "0.0.0-development", + "description": "Error definitions and utilities for Cypress", + "main": "index.js", + "browser": "src/index.ts", + "scripts": { + "build-prod": "tsc || echo 'built, with errors'", + "check-ts": "tsc --noEmit", + "clean-deps": "rm -rf node_modules", + "clean": "rm -f ./src/*.js ./src/**/*.js ./src/**/**/*.js ./test/**/*.js || echo 'cleaned'", + "test-unit": "mocha -r @packages/ts/register test/unit/**/*spec.ts --config ./test/.mocharc.js --exit" + }, + "dependencies": { + "ansi_up": "5.0.0", + "strip-ansi": "6.0.0" + }, + "devDependencies": { + "@packages/ts": "0.0.0-development", + "@types/node": "14.14.31", + "@types/strip-ansi": "^5.2.1", + "chai": "4.2.0", + "mocha": "7.0.1", + "sinon": "7.5.0" + }, + "files": [ + "src" + ], + "types": "src/index.ts" +} diff --git a/packages/server/lib/util/err_template.ts b/packages/errors/src/err_template.ts similarity index 89% rename from packages/server/lib/util/err_template.ts rename to packages/errors/src/err_template.ts index 372a7658d24b..50f79787b418 100644 --- a/packages/server/lib/util/err_template.ts +++ b/packages/errors/src/err_template.ts @@ -1,12 +1,13 @@ import assert from 'assert' +import { stripIndent } from './strip_indent' + /** * Guarding the value, involves */ import chalk from 'chalk' import stripAnsi from 'strip-ansi' -import { trimMultipleNewLines } from '../errors-child' - -const { stripIndent } = require('./strip_indent') +import { trimMultipleNewLines } from './error_utils' +import type { ErrTemplateResult } from './errorTypes' export class Guard { constructor (readonly val: string | number) {} @@ -24,7 +25,7 @@ export class Backtick { constructor (readonly val: string | number) {} } -export function backtick (val) { +export function backtick (val: string) { return new Backtick(val) } @@ -43,16 +44,6 @@ export function details (val: string | Error | object) { return new Details(val) } -export interface ErrTemplateResult { - message: string - details?: string - originalError?: Error - forBrowser(): { - message: string - details?: string - } -} - /** * Creates a consistently formatted object to return from the error call. * @@ -79,7 +70,8 @@ export const errTemplate = (strings: TemplateStringsArray, ...args: Array { + return Boolean(err.isCypressErr) +} + +export const listItems = (paths: string[]) => { + return guard(_ + .chain(paths) + .map((p) => { + return stripIndent`- ${chalk.blue(p)}` + }).join('\n') + .value()) +} + +export const displayFlags = (obj: Record, mapper: Record) => { + return guard(_ + .chain(mapper) + .map((flag, key) => { + let v + + v = obj[key] + + if (v) { + return `The ${flag} flag you passed was: ${chalk.blue(v)}` + } + + return undefined + }).compact() + .join('\n') + .value()) +} + +const twoOrMoreNewLinesRe = /\n{2,}/ + +export const trimMultipleNewLines = (str: string) => { + return _ + .chain(str) + .split(twoOrMoreNewLinesRe) + .compact() + .join('\n\n') + .value() +} + +type AllowedChalkColors = 'red' | 'blue' | 'green' | 'magenta' | 'yellow' + +/** + * + * @param err + * @param color + * @returns + */ +export const logError = function (err: CypressError | ErrorLike, color: AllowedChalkColors = 'red') { + console.log(chalk[color](err.message)) + + if (err.details) { + console.log(`\n${chalk['yellow'](err.details)}`) + } + + // bail if this error came from known + // list of Cypress errors + if (isCypressErr(err)) { + return + } + + console.log(chalk[color](err.stack ?? '')) + + return err +} + +export const warnIfExplicitCiBuildId = function (ciBuildId?: string | null) { + if (!ciBuildId) { + return '' + } + + return `\ +It also looks like you also passed in an explicit --ci-build-id flag. + +This is only necessary if you are NOT running in one of our supported CI providers. + +This flag must be unique for each new run, but must also be identical for each machine you are trying to --group or run in --parallel.\ +` +} diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts new file mode 100644 index 000000000000..06613544736b --- /dev/null +++ b/packages/errors/src/errors.ts @@ -0,0 +1,1208 @@ +/* eslint-disable no-console */ +import chalk from 'chalk' +import _ from 'lodash' +import stripAnsi from 'strip-ansi' +import AU from 'ansi_up' + +import { backtick, details, errTemplate, guard } from './err_template' +import { stripIndent } from './strip_indent' +import { displayFlags, listItems, logError, warnIfExplicitCiBuildId } from './error_utils' +import type { ClonedError, CypressError, ErrTemplateResult } from './errorTypes' +import { stackWithoutMessage } from './stack_utils' + +export { + stripAnsi, +} + +const ansi_up = new AU() + +ansi_up.use_classes = true + +const displayRetriesRemaining = function (tries: number) { + const times = tries === 1 ? 'time' : 'times' + + const lastTryNewLine = tries === 1 ? '\n' : '' + + return chalk.gray( + `We will try connecting to it ${tries} more ${times}...${lastTryNewLine}`, + ) +} + +/** + * All Cypress Errors should be defined here: + * + * The errors must return an "errTemplate", this is processed by the + */ +export const AllCypressErrors = { + CANNOT_TRASH_ASSETS: (arg1: string) => { + return errTemplate`\ + Warning: We failed to trash the existing run results. + + This error will not alter the exit code. + + ${details(arg1)}` + }, + CANNOT_REMOVE_OLD_BROWSER_PROFILES: (arg1: string) => { + return errTemplate`\ + Warning: We failed to remove old browser profiles from previous runs. + + This error will not alter the exit code. + + ${details(arg1)}` + }, + VIDEO_RECORDING_FAILED: (arg1: string) => { + return errTemplate`\ + Warning: We failed to record the video. + + This error will not alter the exit code. + + ${details(arg1)}` + }, + VIDEO_POST_PROCESSING_FAILED: (arg1: string) => { + return errTemplate`\ + Warning: We failed processing this video. + + This error will not alter the exit code. + + ${details(arg1)}` + }, + CHROME_WEB_SECURITY_NOT_SUPPORTED: (browser: string) => { + return errTemplate`\ + Your project has set the configuration option: \`chromeWebSecurity: false\` + + This option will not have an effect in ${guard(_.capitalize(browser))}. Tests that rely on web security being disabled will not run as expected.` + }, + BROWSER_NOT_FOUND_BY_NAME: (arg1: string, arg2: string) => { + let canarySuffix = '' + + if (arg1 === 'canary') { + canarySuffix += '\n\n' + canarySuffix += stripIndent`\ + Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. + + See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.` + } + + return errTemplate`\ + Can't run because you've entered an invalid browser name. + + Browser: ${arg1} was not found on your system or is not supported by Cypress. + + Cypress supports the following browsers: + - chrome + - chromium + - edge + - electron + - firefox + + You can also use a custom browser: https://on.cypress.io/customize-browsers + + Available browsers found on your system are: + ${guard(arg2)}${guard(canarySuffix)}` + }, + BROWSER_NOT_FOUND_BY_PATH: (arg1: string, arg2: string) => { + return errTemplate`\ + We could not identify a known browser at the path you provided: ${arg1} + + The output from the command we ran was: + + ${details(arg2)}` + }, + NOT_LOGGED_IN: () => { + return errTemplate`\ + You're not logged in. + + Run \`cypress open\` to open the Desktop App and log in.` + }, + TESTS_DID_NOT_START_RETRYING: (arg1: string) => { + return errTemplate`Timed out waiting for the browser to connect. ${guard(arg1)}` + }, + TESTS_DID_NOT_START_FAILED: () => { + return errTemplate`The browser never connected. Something is wrong. The tests cannot run. Aborting...` + }, + DASHBOARD_CANCEL_SKIPPED_SPEC: () => { + return errTemplate`${guard(`\n `)}This spec and its tests were skipped because the run has been canceled.` + }, + DASHBOARD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delay: number, response: string}) => { + return errTemplate`\ + We encountered an unexpected error talking to our servers. + + We will retry ${arg1.tries} more ${guard(arg1.tries === 1 ? 'time' : 'times')} in ${guard(arg1.delay)}... + + The server's response was: + + ${arg1.response}` + /* Because of displayFlags() and listItems() */ + /* eslint-disable indent */ + }, + DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: (arg1: {flags: any, response: string}) => { + return errTemplate`\ + We encountered an unexpected error talking to our servers. + + Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers. + + ${displayFlags(arg1.flags, { + group: '--group', + ciBuildId: '--ciBuildId', + })} + + The server's response was: + + ${arg1.response}` + }, + DASHBOARD_CANNOT_PROCEED_IN_SERIAL: (arg1: {flags: any, response: string}) => { + return errTemplate`\ + We encountered an unexpected error talking to our servers. + + ${displayFlags(arg1.flags, { + group: '--group', + ciBuildId: '--ciBuildId', + })} + + The server's response was: + + ${arg1.response}` + }, + DASHBOARD_UNKNOWN_INVALID_REQUEST: (arg1: {flags: any, response: string}) => { + return errTemplate`\ + We encountered an unexpected error talking to our servers. + + There is likely something wrong with the request. + + ${displayFlags(arg1.flags, { + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} + + The server's response was: + + ${arg1.response}` + }, + DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: (arg1: {props: any, message: string}) => { + return errTemplate`\ + Warning from Cypress Dashboard: ${arg1.message} + + Details: + ${JSON.stringify(arg1.props, null, 2)}` + }, + DASHBOARD_STALE_RUN: (arg1: {runUrl: string}) => { + return errTemplate`\ + You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago. + + The existing run is: ${arg1.runUrl} + + You cannot parallelize a run that has been complete for that long. + + ${displayFlags(arg1, { + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} + + https://on.cypress.io/stale-run` + }, + DASHBOARD_ALREADY_COMPLETE: (arg1: {runUrl: string}) => { + return errTemplate`\ + The run you are attempting to access is already complete and will not accept new groups. + + The existing run is: ${arg1.runUrl} + + When a run finishes all of its groups, it waits for a configurable set of time before finally completing. You must add more groups during that time period. + + ${displayFlags(arg1, { + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} + + https://on.cypress.io/already-complete` + }, + DASHBOARD_PARALLEL_REQUIRED: (arg1: {runUrl: string}) => { + return errTemplate`\ + You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag. + + The existing run is: ${arg1.runUrl} + + ${displayFlags(arg1, { + tags: '--tag', + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} + + You must use the --parallel flag with this group. + + https://on.cypress.io/parallel-required` + }, + DASHBOARD_PARALLEL_DISALLOWED: (arg1: {runUrl: string}) => { + return errTemplate`\ + You passed the --parallel flag, but this run group was originally created without the --parallel flag. + + The existing run is: ${arg1.runUrl} + + ${displayFlags(arg1, { + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} + + You can not use the --parallel flag with this group. + + https://on.cypress.io/parallel-disallowed` + }, + DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: (arg1: {runUrl: string, parameters: any}) => { + return errTemplate`\ + You passed the --parallel flag, but we do not parallelize tests across different environments. + + This machine is sending different environment parameters than the first machine that started this parallel run. + + The existing run is: ${arg1.runUrl} + + In order to run in parallel mode each machine must send identical environment parameters such as: + + ${listItems([ + 'specs', + 'osName', + 'osVersion', + 'browserName', + 'browserVersion (major)', + ])} + + This machine sent the following parameters: + + ${JSON.stringify(arg1.parameters, null, 2)} + + https://on.cypress.io/parallel-group-params-mismatch` + }, + DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: (arg1: {runUrl: string, ciBuildId?: string | null}) => { + return errTemplate`\ + You passed the --group flag, but this group name has already been used for this run. + + The existing run is: ${arg1.runUrl} + + ${displayFlags(arg1, { + group: '--group', + parallel: '--parallel', + ciBuildId: '--ciBuildId', + })} + + If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name. + + ${warnIfExplicitCiBuildId(arg1.ciBuildId)} + + https://on.cypress.io/run-group-name-not-unique` + }, + DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { + return errTemplate`\ + Deprecation Warning: The \`before:browser:launch\` plugin event changed its signature in version \`4.0.0\` + + The \`before:browser:launch\` plugin event switched from yielding the second argument as an \`array\` of browser arguments to an options \`object\` with an \`args\` property. + + We've detected that your code is still using the previous, deprecated interface signature. + + This code will not work in a future version of Cypress. Please see the upgrade guide: ${chalk.yellow('https://on.cypress.io/deprecated-before-browser-launch-args')}` + }, + DUPLICATE_TASK_KEY: (arg1: string) => { + return errTemplate`\ + Warning: Multiple attempts to register the following task(s): ${arg1}. Only the last attempt will be registered. + ` + }, + INDETERMINATE_CI_BUILD_ID: (arg1: Record, arg2: string[]) => { + return errTemplate`\ + You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId. + + ${displayFlags(arg1, { + group: '--group', + parallel: '--parallel', + })} + + In order to use either of these features a ciBuildId must be determined. + + The ciBuildId is automatically detected if you are running Cypress in any of the these CI providers: + + ${listItems(arg2)} + + Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually. + + https://on.cypress.io/indeterminate-ci-build-id` + }, + RECORD_PARAMS_WITHOUT_RECORDING: (arg1: Record) => { + return errTemplate`\ + You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag. + + ${displayFlags(arg1, { + ciBuildId: '--ci-build-id', + tags: '--tag', + group: '--group', + parallel: '--parallel', + })} + + These flags can only be used when recording to the Cypress Dashboard service. + + https://on.cypress.io/record-params-without-recording` + }, + INCORRECT_CI_BUILD_ID_USAGE: (arg1: Record) => { + return errTemplate`\ + You passed the --ci-build-id flag but did not provide either a --group or --parallel flag. + + ${displayFlags(arg1, { + ciBuildId: '--ci-build-id', + })} + + The --ci-build-id flag is used to either group or parallelize multiple runs together. + + https://on.cypress.io/incorrect-ci-build-id-usage` + /* eslint-enable indent */ + }, + RECORD_KEY_MISSING: () => { + return errTemplate`\ + You passed the --record flag but did not provide us your Record Key. + + You can pass us your Record Key like this: + + ${chalk.blue('cypress run --record --key ')} + + You can also set the key as an environment variable with the name CYPRESS_RECORD_KEY. + + https://on.cypress.io/how-do-i-record-runs` + }, + CANNOT_RECORD_NO_PROJECT_ID: (arg1: string) => { + return errTemplate`\ + You passed the --record flag but this project has not been setup to record. + + This project is missing the 'projectId' inside of '${arg1}'. + + We cannot uniquely identify this project without this id. + + You need to setup this project to record. This will generate a unique 'projectId'. + + Alternatively if you omit the --record flag this project will run without recording. + + https://on.cypress.io/recording-project-runs` + }, + PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION: (arg1: string) => { + return errTemplate`\ + This project has been configured to record runs on our Dashboard. + + It currently has the projectId: ${chalk.green(arg1)} + + You also provided your Record Key, but you did not pass the --record flag. + + This run will not be recorded. + + If you meant to have this run recorded please additionally pass this flag. + + ${'cypress run --record'} + + If you don't want to record these runs, you can silence this warning: + + ${chalk.yellow('cypress run --record false')} + + https://on.cypress.io/recording-project-runs` + }, + DASHBOARD_INVALID_RUN_REQUEST: (arg1: {message: string, errors: any, object: any}) => { + return errTemplate`\ + Recording this run failed because the request was invalid. + + ${arg1.message} + + Errors: + + ${JSON.stringify(arg1.errors, null, 2)} + + Request Sent: + + ${JSON.stringify(arg1.object, null, 2)}` + }, + RECORDING_FROM_FORK_PR: () => { + return errTemplate`\ + Warning: It looks like you are trying to record this run from a forked PR. + + The 'Record Key' is missing. Your CI provider is likely not passing private environment variables to builds from forks. + + These results will not be recorded. + + This error will not alter the exit code.` + }, + DASHBOARD_CANNOT_UPLOAD_RESULTS: (arg1: string) => { + return errTemplate`\ + Warning: We encountered an error while uploading results from your run. + + These results will not be recorded. + + This error will not alter the exit code. + + ${arg1}` + }, + DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: (arg1: string) => { + return errTemplate`\ + Warning: We encountered an error talking to our servers. + + This run will not be recorded. + + This error will not alter the exit code. + + ${arg1}` + }, + DASHBOARD_RECORD_KEY_NOT_VALID: (arg1: string, arg2: string) => { + return errTemplate`\ + Your Record Key ${chalk.yellow(arg1)} is not valid with this project: ${chalk.blue(arg2)} + + It may have been recently revoked by you or another user. + + Please log into the Dashboard to see the valid record keys. + + https://on.cypress.io/dashboard/projects/${arg2}` + }, + DASHBOARD_PROJECT_NOT_FOUND: (arg1: string, arg2: string) => { + return errTemplate`\ + We could not find a project with the ID: ${chalk.yellow(arg1)} + + This projectId came from your '${arg2}' file or an environment variable. + + Please log into the Dashboard and find your project. + + We will list the correct projectId in the 'Settings' tab. + + Alternatively, you can create a new project using the Desktop Application. + + https://on.cypress.io/dashboard` + }, + NO_PROJECT_ID: (arg1: string, arg2: string) => { + return errTemplate`Can't find 'projectId' in the '${arg1}' file for this project: ${chalk.blue(arg2)}` + }, + NO_PROJECT_FOUND_AT_PROJECT_ROOT: (arg1: string) => { + return errTemplate`Can't find project at the path: ${chalk.blue(arg1)}` + }, + CANNOT_FETCH_PROJECT_TOKEN: () => { + return errTemplate`Can't find project's secret key.` + }, + CANNOT_CREATE_PROJECT_TOKEN: () => { + return errTemplate`Can't create project's secret key.` + }, + PORT_IN_USE_SHORT: (arg1: string) => { + return errTemplate`Port ${arg1} is already in use.` + }, + PORT_IN_USE_LONG: (arg1: string) => { + return errTemplate`\ + Can't run project because port is currently in use: ${chalk.blue(arg1)} + + ${chalk.yellow('Assign a different port with the \'--port \' argument or shut down the other running process.')}` + }, + ERROR_READING_FILE: (arg1: string, arg2: Record) => { + let filePath = `${arg1}` + + let err = `\`${arg2.type || arg2.code || arg2.name}: ${arg2.message}\`` + + return errTemplate`\ + Error reading from: ${chalk.blue(filePath)} + + ${chalk.yellow(err)}` + }, + ERROR_WRITING_FILE: (arg1: string, arg2: string) => { + let filePath = `${arg1}` + + let err = `\`${arg2}\`` + + return errTemplate`\ + Error writing to: ${chalk.blue(filePath)} + + ${chalk.yellow(err)}` + }, + NO_SPECS_FOUND: (arg1: string, arg2?: string | null) => { + // no glob provided, searched all specs + if (!arg2) { + return errTemplate`\ + Can't run because no spec files were found. + + We searched for any files inside of this folder: + + ${chalk.blue(arg1)}` + } + + return errTemplate`\ + Can't run because no spec files were found. + + We searched for any files matching this glob pattern: + + ${chalk.blue(arg2)} + + Relative to the project root folder: + + ${chalk.blue(arg1)}` + }, + RENDERER_CRASHED: () => { + return errTemplate`\ + We detected that the Chromium Renderer process just crashed. + + This is the equivalent to seeing the 'sad face' when Chrome dies. + + This can happen for a number of different reasons: + + - You wrote an endless loop and you must fix your own code + - There is a memory leak in Cypress (unlikely but possible) + - You are running Docker (there is an easy fix for this: see link below) + - You are running lots of tests on a memory intense application + - You are running in a memory starved VM environment + - There are problems with your GPU / GPU drivers + - There are browser bugs in Chromium + + You can learn more including how to fix Docker here: + + https://on.cypress.io/renderer-process-crashed` + }, + AUTOMATION_SERVER_DISCONNECTED: () => { + return errTemplate`The automation client disconnected. Cannot continue running tests.` + }, + SUPPORT_FILE_NOT_FOUND: (arg1: string, arg2: string) => { + return errTemplate`\ + The support file is missing or invalid. + + Your ${'supportFile'} is set to ${arg1}, but either the file is missing or it's invalid. The \`supportFile\` must be a \`.js\`, \`.ts\`, \`.coffee\` file or be supported by your preprocessor plugin (if configured). + + Correct your ${backtick(arg2)}, create the appropriate file, or set \`supportFile\` to \`false\` if a support file is not necessary for your project. + + Or you might have renamed the extension of your \`supportFile\` to \`.ts\`. If that's the case, restart the test runner. + + Learn more at https://on.cypress.io/support-file-missing-or-invalid` + }, + PLUGINS_FILE_ERROR: (arg1: string, arg2: string) => { + return errTemplate`\ + The plugins file is missing or invalid. + + Your \`pluginsFile\` is set to ${arg1}, but either the file is missing, it contains a syntax error, or threw an error when required. The \`pluginsFile\` must be a \`.js\`, \`.ts\`, or \`.coffee\` file. + + Or you might have renamed the extension of your \`pluginsFile\`. If that's the case, restart the test runner. + + Please fix this, or set \`pluginsFile\` to \`false\` if a plugins file is not necessary for your project. + + ${details(arg2)} + ` + }, + PLUGINS_DIDNT_EXPORT_FUNCTION: (arg1: string, arg2: any) => { + return errTemplate`\ + The \`pluginsFile\` must export a function with the following signature: + + \`\`\` + module.exports = function (on, config) { + // configure plugins here + } + \`\`\` + + Learn more: https://on.cypress.io/plugins-api + + We loaded the \`pluginsFile\` from: ${arg1} + + It exported: + + ${details(arg2)} + ` + }, + PLUGINS_FUNCTION_ERROR: (arg1: string, arg2: string) => { + return errTemplate`\ + The function exported by the plugins file threw an error. + + We invoked the function exported by ${arg1}, but it threw an error. + + ${details(arg2)} + ` + }, + PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string) => { + return errTemplate` + The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (${arg1}) + + ${details(arg2)} + ` + }, + PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string) => { + return errTemplate` + The following validation error was thrown by your plugins file (${arg1}). + + ${details(arg2)} + ` + }, + BUNDLE_ERROR: (arg1: string, arg2: string) => { + // IF YOU MODIFY THIS MAKE SURE TO UPDATE + // THE ERROR MESSAGE IN THE RUNNER TOO + return errTemplate`\ + Oops...we found an error preparing this test file: + + ${chalk.blue(arg1)} + + The error was: + + ${chalk.yellow(arg2)} + + This occurred while Cypress was compiling and bundling your test code. This is usually caused by: + + - A missing file or dependency + - A syntax error in the file or one of its dependencies + + Fix the error in your code and re-run your tests.` + // happens when there is an error in configuration file like "cypress.json" + }, + SETTINGS_VALIDATION_ERROR: (arg1: string, arg2: string) => { + return errTemplate`\ + We found an invalid value in the file: ${arg1} + + ${chalk.yellow(arg2)}` + // happens when there is an invalid config value is returned from the + // project's plugins file like "cypress/plugins.index.js" + }, + PLUGINS_CONFIG_VALIDATION_ERROR: (arg1: string, arg2: string) => { + let filePath = `${arg1}` + + return errTemplate`\ + An invalid configuration value returned from the plugins file: ${chalk.blue(filePath)} + + ${chalk.yellow(arg2)}` + // general configuration error not-specific to configuration or plugins files + }, + CONFIG_VALIDATION_ERROR: (arg1: string) => { + return errTemplate`\ + We found an invalid configuration value: + + ${chalk.yellow(arg1)}` + }, + RENAMED_CONFIG_OPTION: (arg1: {name: string, newName: string}) => { + return errTemplate`\ + The ${chalk.yellow(arg1.name)} configuration option you have supplied has been renamed. + + Please rename ${chalk.yellow(arg1.name)} to ${chalk.blue(arg1.newName)}` + }, + CANNOT_CONNECT_BASE_URL: () => { + return errTemplate`\ + Cypress failed to verify that your server is running. + + Please start this server and then run Cypress again.` + }, + CANNOT_CONNECT_BASE_URL_WARNING: (arg1: string) => { + return errTemplate`\ + Cypress could not verify that this server is running: + + > ${chalk.blue(arg1)} + + This server has been configured as your \`baseUrl\`, and tests will likely fail if it is not running.` + }, + CANNOT_CONNECT_BASE_URL_RETRYING: (arg1: {attempt: number, baseUrl: string, remaining: number, delay: number}) => { + switch (arg1.attempt) { + case 1: + return errTemplate`\ + Cypress could not verify that this server is running: + + > ${chalk.blue(arg1.baseUrl)} + + We are verifying this server because it has been configured as your \`baseUrl\`. + + Cypress automatically waits until your server is accessible before running tests. + + ${displayRetriesRemaining(arg1.remaining)}` + default: + return errTemplate`${guard(displayRetriesRemaining(arg1.remaining))}` + } + }, + INVALID_REPORTER_NAME: (arg1: {name: string, paths: string[], error: string}) => { + return errTemplate`\ + Could not load reporter by name: ${chalk.yellow(arg1.name)} + + We searched for the reporter in these paths: + + ${listItems(arg1.paths)} + + The error we received was: + + ${chalk.yellow(arg1.error)} + + Learn more at https://on.cypress.io/reporters` + // TODO: update with vetted cypress language + }, + NO_DEFAULT_CONFIG_FILE_FOUND: (arg1: string) => { + return errTemplate`\ + Could not find a Cypress configuration file, exiting. + + We looked but did not find a default config file in this folder: ${chalk.blue(arg1)}` + // TODO: update with vetted cypress language + }, + CONFIG_FILES_LANGUAGE_CONFLICT: (arg1: string, arg2: string, arg3: string) => { + return errTemplate` + There is both a ${backtick(arg2)} and a ${backtick(arg3)} at the location below: + + ${arg1} + + Cypress does not know which one to read for config. Please remove one of the two and try again. + ` + }, + CONFIG_FILE_NOT_FOUND: (arg1: string, arg2: string) => { + return errTemplate`\ + Could not find a Cypress configuration file, exiting. + + We looked but did not find a ${chalk.blue(arg1)} file in this folder: ${chalk.blue(arg2)}` + }, + INVOKED_BINARY_OUTSIDE_NPM_MODULE: () => { + return errTemplate`\ + It looks like you are running the Cypress binary directly. + + This is not the recommended approach, and Cypress may not work correctly. + + Please install the 'cypress' NPM package and follow the instructions here: + + https://on.cypress.io/installing-cypress` + }, + FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + return errTemplate`\ + You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} + + To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. + + ${guard(arg1.link)}` + }, + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { + return errTemplate`\ + You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} + + Your plan is now in a grace period, which means your tests will still be recorded until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue recording tests on the Cypress Dashboard in the future. + + ${guard(arg1.link)}` + }, + PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + return errTemplate`\ + You've exceeded the limit of private test results under your current billing plan this month. ${arg1.usedTestsMessage} + + To upgrade your account, please visit your billing to upgrade to another billing plan. + + ${guard(arg1.link)}` + }, + FREE_PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + return errTemplate`\ + You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} + + To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. + + ${guard(arg1.link)}` + }, + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { + return errTemplate`\ + You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} + + Your plan is now in a grace period, which means you will have the full benefits of your current plan until ${arg1.gracePeriodMessage}. + + Please visit your billing to upgrade your plan. + + ${guard(arg1.link)}` + }, + PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + return errTemplate`\ + You've exceeded the limit of test results under your ${arg1.planType} billing plan this month. ${arg1.usedTestsMessage} + + To continue getting the full benefits of your current plan, please visit your billing to upgrade. + + ${guard(arg1.link)}` + }, + FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: (arg1: {link: string, gracePeriodMessage: string}) => { + return errTemplate`\ + Parallelization is not included under your free plan. + + Your plan is now in a grace period, which means your tests will still run in parallel until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests in parallel in the future. + + ${guard(arg1.link)}` + }, + PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { + return errTemplate`\ + Parallelization is not included under your current billing plan. + + To run your tests in parallel, please visit your billing and upgrade to another plan with parallelization. + + ${guard(arg1.link)}` + }, + PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: (arg1: {link: string, gracePeriodMessage: string}) => { + return errTemplate`\ + Grouping is not included under your free plan. + + Your plan is now in a grace period, which means your tests will still run with groups until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests with groups in the future. + + ${guard(arg1.link)}` + }, + RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { + return errTemplate`\ + Grouping is not included under your current billing plan. + + To run your tests with groups, please visit your billing and upgrade to another plan with grouping. + + ${guard(arg1.link)}` + }, + FIXTURE_NOT_FOUND: (arg1: string, arg2: string[]) => { + return errTemplate`\ + A fixture file could not be found at any of the following paths: + + > ${arg1} + > ${arg1}{{extension}} + + Cypress looked for these file extensions at the provided path: + + > ${arg2.join(', ')} + + Provide a path to an existing fixture file.` + }, + AUTH_COULD_NOT_LAUNCH_BROWSER: (arg1: string) => { + return errTemplate`\ + Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser: + + \`\`\` + ${arg1} + \`\`\`` + }, + AUTH_BROWSER_LAUNCHED: () => { + return errTemplate`Check your browser to continue logging in.` + }, + BAD_POLICY_WARNING: (arg1: string[]) => { + return errTemplate`\ + Cypress detected policy settings on your computer that may cause issues. + + The following policies were detected that may prevent Cypress from automating Chrome: + + ${guard(arg1.map((line) => ` > ${line}`).join('\n'))} + + For more information, see https://on.cypress.io/bad-browser-policy` + }, + BAD_POLICY_WARNING_TOOLTIP: () => { + return errTemplate`Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy` + }, + EXTENSION_NOT_LOADED: (arg1: string, arg2: string) => { + return errTemplate`\ + ${guard(arg1)} could not install the extension at path: + + > ${arg2} + + Please verify that this is the path to a valid, unpacked WebExtension.` + }, + COULD_NOT_FIND_SYSTEM_NODE: (arg1: string) => { + return errTemplate`\ + \`nodeVersion\` is set to \`system\`, but Cypress could not find a usable Node executable on your PATH. + + Make sure that your Node executable exists and can be run by the current user. + + Cypress will use the built-in Node version (v${arg1}) instead.` + }, + INVALID_CYPRESS_INTERNAL_ENV: (arg1: string) => { + return errTemplate`\ + We have detected an unknown or unsupported "CYPRESS_INTERNAL_ENV" value + + ${chalk.yellow(arg1)} + + "CYPRESS_INTERNAL_ENV" is reserved and should only be used internally. + + Do not modify the "CYPRESS_INTERNAL_ENV" value.` + }, + CDP_VERSION_TOO_OLD: (arg1: string, arg2: {major: number, minor: string | number}) => { + return errTemplate`A minimum CDP version of v${guard(arg1)} is required, but the current browser has ${guard(arg2.major !== 0 ? `v${arg2.major}.${arg2.minor}` : 'an older version')}.` + }, + CDP_COULD_NOT_CONNECT: (arg1: string, arg2: Error, arg3: string) => { + return errTemplate`\ + Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds. + + This usually indicates there was a problem opening the ${guard(arg3)} browser. + + The CDP port requested was ${guard(chalk.yellow(arg1))}. + + Error details: + + ${details(arg2)}` + }, + FIREFOX_COULD_NOT_CONNECT: (arg1: Error) => { + return errTemplate`\ + Cypress failed to make a connection to Firefox. + + This usually indicates there was a problem opening the Firefox browser. + + Error details: + + ${details(arg1)}` + }, + CDP_COULD_NOT_RECONNECT: (arg1: Error) => { + return errTemplate`\ + There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser. + + ${details(arg1)}` + }, + CDP_RETRYING_CONNECTION: (attempt: string | number, browserType: string) => { + return errTemplate`Still waiting to connect to ${guard(browserType)}, retrying in 1 second (attempt ${chalk.yellow(`${attempt}`)}/62)` + }, + UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: (arg1: string[], arg2: string[]) => { + return errTemplate`\ + The \`launchOptions\` object returned by your plugin's \`before:browser:launch\` handler contained unexpected properties: + + ${listItems(arg1)} + + \`launchOptions\` may only contain the properties: + + ${listItems(arg2)} + + https://on.cypress.io/browser-launch-api` + }, + COULD_NOT_PARSE_ARGUMENTS: (arg1: string, arg2: string, arg3: string) => { + return errTemplate`\ + Cypress encountered an error while parsing the argument ${chalk.gray(arg1)} + + You passed: ${arg2} + + The error was: ${arg3}` + }, + FIREFOX_MARIONETTE_FAILURE: (arg1: string, arg2: string) => { + return errTemplate`\ + Cypress could not connect to Firefox. + + An unexpected error was received from Marionette ${guard(arg1)}: + + ${guard(arg2)} + + To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running.` + }, + FOLDER_NOT_WRITABLE: (arg1: string) => { + return errTemplate`\ + Folder ${arg1} is not writable. + + Writing to this directory is required by Cypress in order to store screenshots and videos. + + Enable write permissions to this directory to ensure screenshots and videos are stored. + + If you don't require screenshots or videos to be stored you can safely ignore this warning.` + }, + EXPERIMENTAL_SAMESITE_REMOVED: () => { + return errTemplate`\ + The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress version \`5.0.0\`. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. + + You can safely remove this option from your config.` + }, + EXPERIMENTAL_COMPONENT_TESTING_REMOVED: (arg1: {configFile: string}) => { + return errTemplate`\ + The ${'experimentalComponentTesting'} configuration option was removed in Cypress version \`7.0.0\`. Please remove this flag from ${arg1.configFile}. + + Cypress Component Testing is now a standalone command. You can now run your component tests with: + + ${chalk.yellow(`\`cypress open-ct\``)} + + https://on.cypress.io/migration-guide` + }, + EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { + return errTemplate`\ + The \`experimentalShadowDomSupport\` configuration option was removed in Cypress version \`5.2.0\`. It is no longer necessary when utilizing the \`includeShadowDom\` option. + + You can safely remove this option from your config.` + }, + EXPERIMENTAL_NETWORK_STUBBING_REMOVED: () => { + return errTemplate`\ + The \`experimentalNetworkStubbing\` configuration option was removed in Cypress version \`6.0.0\`. + It is no longer necessary for using \`cy.intercept()\` (formerly \`cy.route2()\`). + + You can safely remove this option from your config.` + }, + EXPERIMENTAL_RUN_EVENTS_REMOVED: () => { + return errTemplate`\ + The \`experimentalRunEvents\` configuration option was removed in Cypress version \`6.7.0\`. It is no longer necessary when listening to run events in the plugins file. + + You can safely remove this option from your config.` + }, + FIREFOX_GC_INTERVAL_REMOVED: () => { + return errTemplate`\ + The \`firefoxGcInterval\` configuration option was removed in Cypress version \`8.0.0\`. It was introduced to work around a bug in Firefox 79 and below. + + Since Cypress no longer supports Firefox 85 and below in Cypress 8, this option was removed. + + You can safely remove this option from your config.` + }, + INCOMPATIBLE_PLUGIN_RETRIES: (arg1: string) => { + return errTemplate`\ + We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at ${arg1}. + + Test retries is now supported in Cypress version \`5.0.0\`. + + Remove the plugin from your dependencies to silence this warning. + + https://on.cypress.io/test-retries + ` + }, + INVALID_CONFIG_OPTION: (arg1: string[]) => { + return errTemplate`\ + ${arg1.map((arg) => `\`${arg}\` is not a valid configuration option`).join('\n')} + + https://on.cypress.io/configuration + ` + }, + PLUGINS_RUN_EVENT_ERROR: (arg1: string, arg2: string) => { + return errTemplate`\ + An error was thrown in your plugins file while executing the handler for the '${chalk.blue(arg1)}' event. + + The error we received was: + + ${chalk.yellow(arg2)} + ` + }, + CT_NO_DEV_START_EVENT: (arg1: string) => { + const pluginsFilePath = arg1 ? + stripIndent`\ + You can find the \'pluginsFile\' at the following path: + + ${arg1} + ` : '' + + return errTemplate`\ + To run component-testing, cypress needs the \`dev-server:start\` event. + + Implement it by adding a \`on('dev-server:start', () => startDevServer())\` call in your pluginsFile. + ${pluginsFilePath} + Learn how to set up component testing: + + https://on.cypress.io/component-testing + ` + }, + UNSUPPORTED_BROWSER_VERSION: (errorMsg: string) => { + return errTemplate`${guard(errorMsg)}` + }, + NODE_VERSION_DEPRECATION_SYSTEM: (arg1: {name: string, value: any, configFile: string}) => { + return errTemplate`\ + Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. + + As of Cypress version \`9.0.0\` the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. + + Please remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. + ` + }, + NODE_VERSION_DEPRECATION_BUNDLED: (arg1: {name: string, value: any, configFile: string}) => { + return errTemplate`\ + Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. + + As of Cypress version \`9.0.0\` the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. + + When ${backtick(arg1.name)} is set to ${backtick(arg1.value)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. + + As the ${backtick(arg1.name)} configuration option will be removed in a future release, it is recommended to remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. + ` + }, +} as const + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const _typeCheck: Record ErrTemplateResult> = AllCypressErrors + +type AllCypressErrorObj = typeof AllCypressErrors + +export function getMsgByType (type: Type, ...args: Parameters): string { + const err = getError(type, ...args) + + return err.message +} + +/** + * Given an error name & params for the error, returns a "CypressError", + * with a forBrowser property, used when we want to format the value for sending to + * the browser rather than the terminal. + * + * @param type + * @param args + * @returns + */ +export const getError = function (type: Type, ...args: Parameters) { + // @ts-expect-error + const result = AllCypressErrors[type](...args) as ErrTemplateResult + + const { message, details, originalError, forBrowser } = result + + const err = new Error(message) as CypressError + + err.isCypressErr = true + err.type = type + err.details = details + err.forBrowser = forBrowser + err.originalError = originalError + + if (originalError) { + err.stack = originalError.stack + err.stackWithoutMessage = stackWithoutMessage(originalError.stack ?? '') + } else { + const newErr = new Error() + + Error.captureStackTrace(newErr, getError) + err.stack = newErr.stack + err.stackWithoutMessage = stackWithoutMessage(err.stack ?? '') + } + + return err +} + +export const logWarning = function (type: Type, ...args: Parameters) { + const err = getError(type, ...args) + + logError(err, 'magenta') + + return null +} + +export const throwErr = function (type: Type, ...args: Parameters) { + const err = getError(type, ...args) + + if (!err.originalError) { + Error.captureStackTrace(err, throwErr) + err.stackWithoutMessage = stackWithoutMessage(err.stack ?? '') + } + + throw err +} + +// For when the error is passed via the socket-base +interface GenericError extends Error { + forBrowser?: never + stackWithoutMessage?: never + [key: string]: any +} + +export const cloneError = function (err: CypressError | GenericError, options: {html?: boolean} = {}) { + _.defaults(options, { + html: false, + }) + + const message = _.isFunction(err.forBrowser) ? err.forBrowser().message : err.message + + // pull off these properties + const obj = _.pick(err, 'type', 'name', 'stack', 'fileName', 'lineNumber', 'columnNumber') as ClonedError + + if (options.html) { + obj.message = ansi_up.ansi_to_html(message) + // revert back the distorted characters + // in case there is an error in a child_process + // that contains quotes + .replace(/\&\#x27;/g, '\'') + .replace(/\"\;/g, '"') + } else { + obj.message = message + } + + // and any own (custom) properties + // of the err object + for (let prop of Object.keys(err || {})) { + const val = err[prop] + + obj[prop] = val + } + + if (err.stackWithoutMessage) { + obj.stack = err.stackWithoutMessage + } + + return obj +} + +export { + cloneError as clone, + throwErr as throw, + getError as get, + logWarning as warning, +} + +// Re-exporting old namespaces for legacy server access +export { + logError as log, + isCypressErr, +} from './error_utils' diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts new file mode 100644 index 000000000000..12a35e12464c --- /dev/null +++ b/packages/errors/src/index.ts @@ -0,0 +1,12 @@ +import * as errorsApi from './errors' + +export * from './errors' + +import * as stackUtils from './stack_utils' +import * as errorUtils from './error_utils' + +export { stackUtils, errorUtils } + +export * from './errorTypes' + +export default errorsApi diff --git a/packages/server/lib/util/stack_utils.ts b/packages/errors/src/stack_utils.ts similarity index 68% rename from packages/server/lib/util/stack_utils.ts rename to packages/errors/src/stack_utils.ts index 1018a367a2a5..3f7f5ca1b9a4 100644 --- a/packages/server/lib/util/stack_utils.ts +++ b/packages/errors/src/stack_utils.ts @@ -1,7 +1,10 @@ import _ from 'lodash' +import type { ErrorLike } from './errorTypes' const stackLineRegex = /^\s*(at )?.*@?\(?.*\:\d+\:\d+\)?$/ +type MessageLines = [string[], string[]] & {messageEnded?: boolean} + // returns tuple of [message, stack] export const splitStack = (stack: string) => { const lines = stack.split('\n') @@ -15,24 +18,24 @@ export const splitStack = (stack: string) => { } return memo - }, [[], []] as any[] & {messageEnded: boolean}) + }, [[], []] as MessageLines) } -export const unsplitStack = (messageLines, stackLines) => { +export const unsplitStack = (messageLines: string, stackLines: string[]) => { return _.castArray(messageLines).concat(stackLines).join('\n') } -export const getStackLines = (stack) => { +export const getStackLines = (stack: string) => { const [, stackLines] = splitStack(stack) return stackLines } -export const stackWithoutMessage = (stack) => { +export const stackWithoutMessage = (stack: string) => { return getStackLines(stack).join('\n') } -export const replacedStack = (err, newStack) => { +export const replacedStack = (err: ErrorLike, newStack: string) => { // if err already lacks a stack or we've removed the stack // for some reason, keep it stackless if (!err.stack) return err.stack diff --git a/packages/server/lib/util/strip_indent.js b/packages/errors/src/strip_indent.ts similarity index 76% rename from packages/server/lib/util/strip_indent.js rename to packages/errors/src/strip_indent.ts index eac57e876b9a..dfee48aca2fb 100644 --- a/packages/server/lib/util/strip_indent.js +++ b/packages/errors/src/strip_indent.ts @@ -1,5 +1,5 @@ -const stripIndent = (strings, ...args) => { - const parts = [] +export const stripIndent = (strings: TemplateStringsArray, ...args: any[]): string => { + const parts: any[] = [] for (let i = 0; i < strings.length; i++) { parts.push(strings[i]) @@ -10,7 +10,7 @@ const stripIndent = (strings, ...args) => { } const lines = parts.join('').split('\n') - const firstLine = lines[0].length === 0 ? lines[1] : lines[0] + const firstLine = (lines[0]?.length === 0 ? lines[1] : lines[0]) ?? '' let indentSize = 0 for (let i = 0; i < firstLine.length; i++) { diff --git a/packages/errors/test/.mocharc.js b/packages/errors/test/.mocharc.js new file mode 100644 index 000000000000..4ba52ba2c8df --- /dev/null +++ b/packages/errors/test/.mocharc.js @@ -0,0 +1 @@ +module.exports = {} diff --git a/packages/errors/test/unit/__snapshots__/errors_spec.js b/packages/errors/test/unit/__snapshots__/errors_spec.js new file mode 100644 index 000000000000..c809a4d34bea --- /dev/null +++ b/packages/errors/test/unit/__snapshots__/errors_spec.js @@ -0,0 +1,4 @@ +exports['tags and name only'] = ` +The --tag flag you passed was: nightly,staging +The --name flag you passed was: my tests +` diff --git a/packages/server/test/unit/util/err_template_spec.ts b/packages/errors/test/unit/err_template_spec.ts similarity index 88% rename from packages/server/test/unit/util/err_template_spec.ts rename to packages/errors/test/unit/err_template_spec.ts index 9d1a9335dbc1..712bb0144931 100644 --- a/packages/server/test/unit/util/err_template_spec.ts +++ b/packages/errors/test/unit/err_template_spec.ts @@ -1,9 +1,8 @@ -/// import { expect } from 'chai' import chalk from 'chalk' -import { details, errTemplate, guard } from '../../../lib/util/err_template' -import { stripIndent } from '../../../lib/util/strip_indent' +import { details, errTemplate, guard } from '../../src/err_template' +import { stripIndent } from '../../src/strip_indent' describe('err_template', () => { it('returns an object w/ basic props & forBrowser', () => { @@ -35,7 +34,6 @@ describe('err_template', () => { ${details(errStack)} ` - expect(obj.forBrowser()).to.include({ message: `This was an error`, details: errStack }) expect(obj).to.include({ message: `This was an error`, details: errStack }) }) @@ -73,7 +71,7 @@ describe('err_template', () => { ${details(someObj)} ` - expect(obj.forBrowser()).to.include({ message: `This was returned from the app:`, details: JSON.stringify(someObj, null, 2) }) + expect(obj.forBrowser()).to.include({ message: `This was returned from the app:` }) expect(obj).to.include({ message: `This was returned from the app:`, details: JSON.stringify(someObj, null, 2) }) }) @@ -86,7 +84,7 @@ describe('err_template', () => { ${details(err)} ` - expect(obj.forBrowser()).to.include({ message: `This was an error in \`specFile.js\``, details: err.stack }) + expect(obj.forBrowser()).to.include({ message: `This was an error in \`specFile.js\`` }) expect(obj).to.include({ message: `This was an error in ${chalk.blue(specFile)}`, details: err.stack }) }) diff --git a/packages/errors/test/unit/errors_spec.ts b/packages/errors/test/unit/errors_spec.ts new file mode 100644 index 000000000000..ff058fe198b4 --- /dev/null +++ b/packages/errors/test/unit/errors_spec.ts @@ -0,0 +1,123 @@ +/* eslint-disable no-console */ +import chalk from 'chalk' +import style from 'ansi-styles' +import snapshot from 'snap-shot-it' +import sinon from 'sinon' + +import * as errors from '../../src' +import chai, { expect } from 'chai' + +chai.use(require('@cypress/sinon-chai')) + +afterEach(() => { + sinon.restore() +}) + +describe('lib/errors', () => { + beforeEach(() => { + sinon.stub(console, 'log') + }) + + context('.log', () => { + it('uses red by default', () => { + const err = errors.get('NOT_LOGGED_IN') + + const ret = errors.log(err) + + expect(ret).to.be.undefined + + const { + red, + } = style + + expect(console.log).to.be.calledWithMatch(red.open) + + expect(console.log).to.be.calledWithMatch(red.close) + }) + + it('can change the color', () => { + const err = errors.get('NOT_LOGGED_IN') + + const ret = errors.log(err, 'yellow') + + expect(ret).to.be.undefined + + const { + yellow, + } = style + + expect(console.log).to.be.calledWithMatch(yellow.open) + + expect(console.log).to.be.calledWithMatch(yellow.close) + }) + + it('logs err.message', () => { + const err = errors.getError('NO_PROJECT_ID', 'cypress.json', 'foo/bar/baz') + + const ret = errors.log(err) + + expect(ret).to.be.undefined + + expect(console.log).to.be.calledWithMatch('foo/bar/baz') + }) + + it('logs err.details', () => { + const err = errors.get('PLUGINS_FUNCTION_ERROR', 'foo/bar/baz', 'details huh') + + const ret = errors.log(err) + + expect(ret).to.be.undefined + + expect(console.log).to.be.calledWithMatch('foo/bar/baz') + + expect(console.log).to.be.calledWithMatch(`\n${ chalk.yellow('details huh')}`) + }) + + it('logs err.stack in development', () => { + process.env.CYPRESS_INTERNAL_ENV = 'development' + + const err = new Error('foo') + + const ret = errors.log(err) + + expect(ret).to.eq(err) + + expect(console.log).to.be.calledWith(chalk.red(err.stack)) + }) + }) + + context('.clone', () => { + it('converts err.message from ansi to html with span classes when html true', () => { + const err = new Error(`foo${chalk.blue('bar')}${chalk.yellow('baz')}`) + const obj = errors.clone(err, { html: true }) + + expect(obj.message).to.eq('foobarbaz') + }) + + it('does not convert err.message from ansi to html when no html option', () => { + const err = new Error(`foo${chalk.blue('bar')}${chalk.yellow('baz')}`) + const obj = errors.clone(err) + + expect(obj.message).to.eq('foo\u001b[34mbar\u001b[39m\u001b[33mbaz\u001b[39m') + }) + }) + + context('.displayFlags', () => { + it('returns string formatted from selected keys', () => { + const options = { + tags: 'nightly,staging', + name: 'my tests', + unused: 'some other value', + } + // we are only interested in showig tags and name values + // and prepending them with custom prefixes + const mapping = { + tags: '--tag', + name: '--name', + } + const text = errors.errorUtils.displayFlags(options, mapping) + + return snapshot('tags and name only', text.val) + }) + }) +}) diff --git a/packages/server/test/unit/util/strip_indent_spec.ts b/packages/errors/test/unit/strip_indent_spec.ts similarity index 88% rename from packages/server/test/unit/util/strip_indent_spec.ts rename to packages/errors/test/unit/strip_indent_spec.ts index c1d1fac674b6..a4677abe7c67 100644 --- a/packages/server/test/unit/util/strip_indent_spec.ts +++ b/packages/errors/test/unit/strip_indent_spec.ts @@ -1,7 +1,5 @@ -import '../../spec_helper' - import { expect } from 'chai' -import { stripIndent } from '../../../lib/util/strip_indent' +import { stripIndent } from '../../src/strip_indent' describe('lib/util/strip_indent', () => { it('does not trip right end', () => { diff --git a/packages/errors/tsconfig.json b/packages/errors/tsconfig.json new file mode 100644 index 000000000000..dc41270918bc --- /dev/null +++ b/packages/errors/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../ts/tsconfig.json", + "include": [ + "src" + ], + "exclude": [ + "test", + "script" + ], + "compilerOptions": { + "strict": true, + "allowJs": false, + "rootDir": "src", + "outDir": "dist", + "noImplicitAny": true, + "resolveJsonModule": true, + "experimentalDecorators": true, + "noUncheckedIndexedAccess": true, + "importsNotUsedAsValues": "error", + "types": [ + "node" + ] + } +} \ No newline at end of file diff --git a/packages/server/lib/browsers/utils.ts b/packages/server/lib/browsers/utils.ts index d75e6447bbac..1b1df9e767fc 100644 --- a/packages/server/lib/browsers/utils.ts +++ b/packages/server/lib/browsers/utils.ts @@ -4,8 +4,7 @@ import type { FoundBrowser } from '@packages/launcher' import errors from '../errors' // @ts-ignore import plugins from '../plugins' - -const errorsChild = require('../errors-child') +import { getError } from '@packages/errors' const path = require('path') const debug = require('debug')('cypress:server:browsers:utils') @@ -140,7 +139,7 @@ function extendLaunchOptionsFromPlugins (launchOptions, pluginConfigResult, opti // interface and we need to warn them // TODO: remove this logic in >= v5.0.0 if (pluginConfigResult[0]) { - options.onWarning(errorsChild.get( + options.onWarning(getError( 'DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS', )) diff --git a/packages/server/lib/errors-child.js b/packages/server/lib/errors-child.js deleted file mode 100644 index d3abf2065210..000000000000 --- a/packages/server/lib/errors-child.js +++ /dev/null @@ -1,82 +0,0 @@ -/* eslint-disable no-console */ -// Used in both the child process - -const chalk = require('chalk') -const { stripIndent } = require('common-tags') -const _ = require('lodash') - -const isCypressErr = (err) => { - return Boolean(err.isCypressErr) -} - -const log = function (err, color = 'red') { - console.log(chalk[color](err.message)) - - if (err.details) { - console.log(`\n${chalk['yellow'](err.details)}`) - } - - // bail if this error came from known - // list of Cypress errors - if (isCypressErr(err)) { - return - } - - console.log(chalk[color](err.stack)) - - return err -} - -const get = (type, ...args) => { - let msg = trimMultipleNewLines(ErrorsUsedInChildProcess[type](...args)) - - const err = new Error(msg) - - err.isCypressErr = true - err.type = type - - return err -} - -const warning = function (type, ...args) { - const err = get(type, ...args) - - log(err, 'magenta') - - return null -} - -const twoOrMoreNewLinesRe = /\n{2,}/ - -const trimMultipleNewLines = (str) => { - return _ - .chain(str) - .split(twoOrMoreNewLinesRe) - .compact() - .join('\n\n') - .value() -} - -const ErrorsUsedInChildProcess = { - DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { - return stripIndent`\ - Deprecation Warning: The \`before:browser:launch\` plugin event changed its signature in version \`4.0.0\` - - The \`before:browser:launch\` plugin event switched from yielding the second argument as an \`array\` of browser arguments to an options \`object\` with an \`args\` property. - - We've detected that your code is still using the previous, deprecated interface signature. - - This code will not work in a future version of Cypress. Please see the upgrade guide: ${chalk.yellow('https://on.cypress.io/deprecated-before-browser-launch-args')}` - }, - - DUPLICATE_TASK_KEY: (arg1) => { - return `Warning: Multiple attempts to register the following task(s): ${chalk.blue(arg1)}. Only the last attempt will be registered.` - }, -} - -module.exports = { - log, - warning, - trimMultipleNewLines, - get, -} diff --git a/packages/server/lib/errors.ts b/packages/server/lib/errors.ts index d523f8403e05..bb27595811d6 100644 --- a/packages/server/lib/errors.ts +++ b/packages/server/lib/errors.ts @@ -1,1246 +1,10 @@ +import errors from '@packages/errors' import Bluebird from 'bluebird' -/* eslint-disable no-console */ -import chalk from 'chalk' -import _ from 'lodash' -import { backtick, details, errTemplate, guard, ErrTemplateResult } from './util/err_template' - -const strip = require('strip-ansi') -const AU = require('ansi_up') -const { stripIndent } = require('./util/strip_indent') -const { log } = require('./errors-child') - -const ansi_up = new AU.default - -ansi_up.use_classes = true const isProduction = () => { return process.env['CYPRESS_INTERNAL_ENV'] === 'production' } -const listItems = (paths) => { - return guard(_ - .chain(paths) - .map((p) => { - return stripIndent`- ${chalk.blue(p)}` - }).join('\n') - .value()) -} - -const displayFlags = (obj, mapper: Record) => { - return guard(_ - .chain(mapper) - .map((flag, key) => { - let v - - v = obj[key] - - if (v) { - return `The ${flag} flag you passed was: ${chalk.blue(v)}` - } - - return undefined - }).compact() - .join('\n') - .value()) -} - -const displayRetriesRemaining = function (tries) { - const times = tries === 1 ? 'time' : 'times' - - const lastTryNewLine = tries === 1 ? '\n' : '' - - return chalk.gray( - `We will try connecting to it ${tries} more ${times}...${lastTryNewLine}`, - ) -} - -const warnIfExplicitCiBuildId = function (ciBuildId) { - if (!ciBuildId) { - return '' - } - - return `\ -It also looks like you also passed in an explicit --ci-build-id flag. - -This is only necessary if you are NOT running in one of our supported CI providers. - -This flag must be unique for each new run, but must also be identical for each machine you are trying to --group or run in --parallel.\ -` -} - -const isCypressErr = (err) => { - return Boolean(err.isCypressErr) -} - -/** - * All Cypress Errors should be defined here: - * - * The errors must return an "errTemplate", this is processed by the - */ -const AllCypressErrors = { - CANNOT_TRASH_ASSETS: (arg1: string) => { - return errTemplate`\ - Warning: We failed to trash the existing run results. - - This error will not alter the exit code. - - ${details(arg1)}` - }, - CANNOT_REMOVE_OLD_BROWSER_PROFILES: (arg1: string) => { - return errTemplate`\ - Warning: We failed to remove old browser profiles from previous runs. - - This error will not alter the exit code. - - ${details(arg1)}` - }, - VIDEO_RECORDING_FAILED: (arg1: string) => { - return errTemplate`\ - Warning: We failed to record the video. - - This error will not alter the exit code. - - ${details(arg1)}` - }, - VIDEO_POST_PROCESSING_FAILED: (arg1: string) => { - return errTemplate`\ - Warning: We failed processing this video. - - This error will not alter the exit code. - - ${details(arg1)}` - }, - CHROME_WEB_SECURITY_NOT_SUPPORTED: (arg1: string) => { - return errTemplate`\ - Your project has set the configuration option: \`chromeWebSecurity: false\` - - This option will not have an effect in ${guard(_.capitalize(arg1))}. Tests that rely on web security being disabled will not run as expected.` - }, - BROWSER_NOT_FOUND_BY_NAME: (arg1: string, arg2: string) => { - let canarySuffix = '' - - if (arg1 === 'canary') { - canarySuffix += '\n\n' - canarySuffix += stripIndent`\ - Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. - - See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.` - } - - return errTemplate`\ - Can't run because you've entered an invalid browser name. - - Browser: ${arg1} was not found on your system or is not supported by Cypress. - - Cypress supports the following browsers: - - chrome - - chromium - - edge - - electron - - firefox - - You can also use a custom browser: https://on.cypress.io/customize-browsers - - Available browsers found on your system are: - ${guard(arg2)}${guard(canarySuffix)}` - }, - BROWSER_NOT_FOUND_BY_PATH: (arg1: string, arg2: string) => { - return errTemplate`\ - We could not identify a known browser at the path you provided: ${arg1} - - The output from the command we ran was: - - ${details(arg2)}` - }, - NOT_LOGGED_IN: () => { - return errTemplate`\ - You're not logged in. - - Run \`cypress open\` to open the Desktop App and log in.` - }, - TESTS_DID_NOT_START_RETRYING: (arg1: string) => { - return errTemplate`Timed out waiting for the browser to connect. ${guard(arg1)}` - }, - TESTS_DID_NOT_START_FAILED: () => { - return errTemplate`The browser never connected. Something is wrong. The tests cannot run. Aborting...` - }, - DASHBOARD_CANCEL_SKIPPED_SPEC: () => { - return errTemplate`${guard(`\n `)}This spec and its tests were skipped because the run has been canceled.` - }, - DASHBOARD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delay: number, response: string}) => { - return errTemplate`\ - We encountered an unexpected error talking to our servers. - - We will retry ${arg1.tries} more ${guard(arg1.tries === 1 ? 'time' : 'times')} in ${guard(arg1.delay)}... - - The server's response was: - - ${arg1.response}` - /* Because of displayFlags() and listItems() */ - /* eslint-disable indent */ - }, - DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: (arg1: {flags: any, response: string}) => { - return errTemplate`\ - We encountered an unexpected error talking to our servers. - - Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers. - - ${displayFlags(arg1.flags, { - group: '--group', - ciBuildId: '--ciBuildId', - })} - - The server's response was: - - ${arg1.response}` - }, - DASHBOARD_CANNOT_PROCEED_IN_SERIAL: (arg1: {flags: any, response: string}) => { - return errTemplate`\ - We encountered an unexpected error talking to our servers. - - ${displayFlags(arg1.flags, { - group: '--group', - ciBuildId: '--ciBuildId', - })} - - The server's response was: - - ${arg1.response}` - }, - DASHBOARD_UNKNOWN_INVALID_REQUEST: (arg1: {flags: any, response: string}) => { - return errTemplate`\ - We encountered an unexpected error talking to our servers. - - There is likely something wrong with the request. - - ${displayFlags(arg1.flags, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} - - The server's response was: - - ${arg1.response}` - }, - DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: (arg1: {props: any, message: string}) => { - return errTemplate`\ - Warning from Cypress Dashboard: ${arg1.message} - - Details: - ${JSON.stringify(arg1.props, null, 2)}` - }, - DASHBOARD_STALE_RUN: (arg1: {runUrl: string}) => { - return errTemplate`\ - You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago. - - The existing run is: ${arg1.runUrl} - - You cannot parallelize a run that has been complete for that long. - - ${displayFlags(arg1, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} - - https://on.cypress.io/stale-run` - }, - DASHBOARD_ALREADY_COMPLETE: (arg1: {runUrl: string}) => { - return errTemplate`\ - The run you are attempting to access is already complete and will not accept new groups. - - The existing run is: ${arg1.runUrl} - - When a run finishes all of its groups, it waits for a configurable set of time before finally completing. You must add more groups during that time period. - - ${displayFlags(arg1, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} - - https://on.cypress.io/already-complete` - }, - DASHBOARD_PARALLEL_REQUIRED: (arg1: {runUrl: string}) => { - return errTemplate`\ - You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag. - - The existing run is: ${arg1.runUrl} - - ${displayFlags(arg1, { - tags: '--tag', - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} - - You must use the --parallel flag with this group. - - https://on.cypress.io/parallel-required` - }, - DASHBOARD_PARALLEL_DISALLOWED: (arg1: {runUrl: string}) => { - return errTemplate`\ - You passed the --parallel flag, but this run group was originally created without the --parallel flag. - - The existing run is: ${arg1.runUrl} - - ${displayFlags(arg1, { - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} - - You can not use the --parallel flag with this group. - - https://on.cypress.io/parallel-disallowed` - }, - DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: (arg1: {runUrl: string, parameters: any}) => { - return errTemplate`\ - You passed the --parallel flag, but we do not parallelize tests across different environments. - - This machine is sending different environment parameters than the first machine that started this parallel run. - - The existing run is: ${arg1.runUrl} - - In order to run in parallel mode each machine must send identical environment parameters such as: - - ${listItems([ - 'specs', - 'osName', - 'osVersion', - 'browserName', - 'browserVersion (major)', - ])} - - This machine sent the following parameters: - - ${JSON.stringify(arg1.parameters, null, 2)} - - https://on.cypress.io/parallel-group-params-mismatch` - }, - DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: (arg1: {runUrl: string, ciBuildId?: string | null}) => { - return errTemplate`\ - You passed the --group flag, but this group name has already been used for this run. - - The existing run is: ${arg1.runUrl} - - ${displayFlags(arg1, { - group: '--group', - parallel: '--parallel', - ciBuildId: '--ciBuildId', - })} - - If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name. - - ${warnIfExplicitCiBuildId(arg1.ciBuildId)} - - https://on.cypress.io/run-group-name-not-unique` - }, - INDETERMINATE_CI_BUILD_ID: (arg1: object, arg2: string[]) => { - return errTemplate`\ - You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId. - - ${displayFlags(arg1, { - group: '--group', - parallel: '--parallel', - })} - - In order to use either of these features a ciBuildId must be determined. - - The ciBuildId is automatically detected if you are running Cypress in any of the these CI providers: - - ${listItems(arg2)} - - Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually. - - https://on.cypress.io/indeterminate-ci-build-id` - }, - RECORD_PARAMS_WITHOUT_RECORDING: (arg1: object) => { - return errTemplate`\ - You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag. - - ${displayFlags(arg1, { - ciBuildId: '--ci-build-id', - tags: '--tag', - group: '--group', - parallel: '--parallel', - })} - - These flags can only be used when recording to the Cypress Dashboard service. - - https://on.cypress.io/record-params-without-recording` - }, - INCORRECT_CI_BUILD_ID_USAGE: (arg1: object) => { - return errTemplate`\ - You passed the --ci-build-id flag but did not provide either a --group or --parallel flag. - - ${displayFlags(arg1, { - ciBuildId: '--ci-build-id', - })} - - The --ci-build-id flag is used to either group or parallelize multiple runs together. - - https://on.cypress.io/incorrect-ci-build-id-usage` - /* eslint-enable indent */ - }, - RECORD_KEY_MISSING: () => { - return errTemplate`\ - You passed the --record flag but did not provide us your Record Key. - - You can pass us your Record Key like this: - - ${chalk.blue('cypress run --record --key ')} - - You can also set the key as an environment variable with the name CYPRESS_RECORD_KEY. - - https://on.cypress.io/how-do-i-record-runs` - }, - CANNOT_RECORD_NO_PROJECT_ID: (arg1: string) => { - return errTemplate`\ - You passed the --record flag but this project has not been setup to record. - - This project is missing the 'projectId' inside of '${arg1}'. - - We cannot uniquely identify this project without this id. - - You need to setup this project to record. This will generate a unique 'projectId'. - - Alternatively if you omit the --record flag this project will run without recording. - - https://on.cypress.io/recording-project-runs` - }, - PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION: (arg1: string) => { - return errTemplate`\ - This project has been configured to record runs on our Dashboard. - - It currently has the projectId: ${chalk.green(arg1)} - - You also provided your Record Key, but you did not pass the --record flag. - - This run will not be recorded. - - If you meant to have this run recorded please additionally pass this flag. - - ${'cypress run --record'} - - If you don't want to record these runs, you can silence this warning: - - ${chalk.yellow('cypress run --record false')} - - https://on.cypress.io/recording-project-runs` - }, - DASHBOARD_INVALID_RUN_REQUEST: (arg1: {message: string, errors: any, object: any}) => { - return errTemplate`\ - Recording this run failed because the request was invalid. - - ${arg1.message} - - Errors: - - ${JSON.stringify(arg1.errors, null, 2)} - - Request Sent: - - ${JSON.stringify(arg1.object, null, 2)}` - }, - RECORDING_FROM_FORK_PR: () => { - return errTemplate`\ - Warning: It looks like you are trying to record this run from a forked PR. - - The 'Record Key' is missing. Your CI provider is likely not passing private environment variables to builds from forks. - - These results will not be recorded. - - This error will not alter the exit code.` - }, - DASHBOARD_CANNOT_UPLOAD_RESULTS: (arg1: string) => { - return errTemplate`\ - Warning: We encountered an error while uploading results from your run. - - These results will not be recorded. - - This error will not alter the exit code. - - ${arg1}` - }, - DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: (arg1: string) => { - return errTemplate`\ - Warning: We encountered an error talking to our servers. - - This run will not be recorded. - - This error will not alter the exit code. - - ${arg1}` - }, - DASHBOARD_RECORD_KEY_NOT_VALID: (arg1: string, arg2: string) => { - return errTemplate`\ - Your Record Key ${chalk.yellow(arg1)} is not valid with this project: ${chalk.blue(arg2)} - - It may have been recently revoked by you or another user. - - Please log into the Dashboard to see the valid record keys. - - https://on.cypress.io/dashboard/projects/${arg2}` - }, - DASHBOARD_PROJECT_NOT_FOUND: (arg1: string, arg2: string) => { - return errTemplate`\ - We could not find a project with the ID: ${chalk.yellow(arg1)} - - This projectId came from your '${arg2}' file or an environment variable. - - Please log into the Dashboard and find your project. - - We will list the correct projectId in the 'Settings' tab. - - Alternatively, you can create a new project using the Desktop Application. - - https://on.cypress.io/dashboard` - }, - NO_PROJECT_ID: (arg1: string, arg2: string) => { - return errTemplate`Can't find 'projectId' in the '${arg1}' file for this project: ${chalk.blue(arg2)}` - }, - NO_PROJECT_FOUND_AT_PROJECT_ROOT: (arg1: string) => { - return errTemplate`Can't find project at the path: ${chalk.blue(arg1)}` - }, - CANNOT_FETCH_PROJECT_TOKEN: () => { - return errTemplate`Can't find project's secret key.` - }, - CANNOT_CREATE_PROJECT_TOKEN: () => { - return errTemplate`Can't create project's secret key.` - }, - PORT_IN_USE_SHORT: (arg1: string) => { - return errTemplate`Port ${arg1} is already in use.` - }, - PORT_IN_USE_LONG: (arg1: string) => { - return errTemplate`\ - Can't run project because port is currently in use: ${chalk.blue(arg1)} - - ${chalk.yellow('Assign a different port with the \'--port \' argument or shut down the other running process.')}` - }, - ERROR_READING_FILE: (arg1: string, arg2: Record) => { - let filePath = `${arg1}` - - let err = `\`${arg2.type || arg2.code || arg2.name}: ${arg2.message}\`` - - return errTemplate`\ - Error reading from: ${chalk.blue(filePath)} - - ${chalk.yellow(err)}` - }, - ERROR_WRITING_FILE: (arg1: string, arg2: string) => { - let filePath = `${arg1}` - - let err = `\`${arg2}\`` - - return errTemplate`\ - Error writing to: ${chalk.blue(filePath)} - - ${chalk.yellow(err)}` - }, - NO_SPECS_FOUND: (arg1: string, arg2?: string | null) => { - // no glob provided, searched all specs - if (!arg2) { - return errTemplate`\ - Can't run because no spec files were found. - - We searched for any files inside of this folder: - - ${chalk.blue(arg1)}` - } - - return errTemplate`\ - Can't run because no spec files were found. - - We searched for any files matching this glob pattern: - - ${chalk.blue(arg2)} - - Relative to the project root folder: - - ${chalk.blue(arg1)}` - }, - RENDERER_CRASHED: () => { - return errTemplate`\ - We detected that the Chromium Renderer process just crashed. - - This is the equivalent to seeing the 'sad face' when Chrome dies. - - This can happen for a number of different reasons: - - - You wrote an endless loop and you must fix your own code - - There is a memory leak in Cypress (unlikely but possible) - - You are running Docker (there is an easy fix for this: see link below) - - You are running lots of tests on a memory intense application - - You are running in a memory starved VM environment - - There are problems with your GPU / GPU drivers - - There are browser bugs in Chromium - - You can learn more including how to fix Docker here: - - https://on.cypress.io/renderer-process-crashed` - }, - AUTOMATION_SERVER_DISCONNECTED: () => { - return errTemplate`The automation client disconnected. Cannot continue running tests.` - }, - SUPPORT_FILE_NOT_FOUND: (arg1: string, arg2: string) => { - return errTemplate`\ - The support file is missing or invalid. - - Your ${'supportFile'} is set to ${arg1}, but either the file is missing or it's invalid. The \`supportFile\` must be a \`.js\`, \`.ts\`, \`.coffee\` file or be supported by your preprocessor plugin (if configured). - - Correct your ${backtick(arg2)}, create the appropriate file, or set \`supportFile\` to \`false\` if a support file is not necessary for your project. - - Or you might have renamed the extension of your \`supportFile\` to \`.ts\`. If that's the case, restart the test runner. - - Learn more at https://on.cypress.io/support-file-missing-or-invalid` - }, - PLUGINS_FILE_ERROR: (arg1: string, arg2: string) => { - return errTemplate`\ - The plugins file is missing or invalid. - - Your \`pluginsFile\` is set to ${arg1}, but either the file is missing, it contains a syntax error, or threw an error when required. The \`pluginsFile\` must be a \`.js\`, \`.ts\`, or \`.coffee\` file. - - Or you might have renamed the extension of your \`pluginsFile\`. If that's the case, restart the test runner. - - Please fix this, or set \`pluginsFile\` to \`false\` if a plugins file is not necessary for your project. - - ${details(arg2)} - ` - }, - PLUGINS_DIDNT_EXPORT_FUNCTION: (arg1: string, arg2: any) => { - return errTemplate`\ - The \`pluginsFile\` must export a function with the following signature: - - \`\`\` - module.exports = function (on, config) { - // configure plugins here - } - \`\`\` - - Learn more: https://on.cypress.io/plugins-api - - We loaded the \`pluginsFile\` from: ${arg1} - - It exported: - - ${details(arg2)} - ` - }, - PLUGINS_FUNCTION_ERROR: (arg1: string, arg2: string) => { - return errTemplate`\ - The function exported by the plugins file threw an error. - - We invoked the function exported by ${arg1}, but it threw an error. - - ${details(arg2)} - ` - }, - PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string) => { - return errTemplate` - The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (${arg1}) - - ${details(arg2)} - ` - }, - PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string) => { - return errTemplate` - The following validation error was thrown by your plugins file (${arg1}). - - ${details(arg2)} - ` - }, - BUNDLE_ERROR: (arg1: string, arg2: string) => { - // IF YOU MODIFY THIS MAKE SURE TO UPDATE - // THE ERROR MESSAGE IN THE RUNNER TOO - return errTemplate`\ - Oops...we found an error preparing this test file: - - ${chalk.blue(arg1)} - - The error was: - - ${chalk.yellow(arg2)} - - This occurred while Cypress was compiling and bundling your test code. This is usually caused by: - - - A missing file or dependency - - A syntax error in the file or one of its dependencies - - Fix the error in your code and re-run your tests.` - // happens when there is an error in configuration file like "cypress.json" - }, - SETTINGS_VALIDATION_ERROR: (arg1: string, arg2: string) => { - return errTemplate`\ - We found an invalid value in the file: ${arg1} - - ${chalk.yellow(arg2)}` - // happens when there is an invalid config value is returned from the - // project's plugins file like "cypress/plugins.index.js" - }, - PLUGINS_CONFIG_VALIDATION_ERROR: (arg1: string, arg2: string) => { - let filePath = `${arg1}` - - return errTemplate`\ - An invalid configuration value returned from the plugins file: ${chalk.blue(filePath)} - - ${chalk.yellow(arg2)}` - // general configuration error not-specific to configuration or plugins files - }, - CONFIG_VALIDATION_ERROR: (arg1: string) => { - return errTemplate`\ - We found an invalid configuration value: - - ${chalk.yellow(arg1)}` - }, - RENAMED_CONFIG_OPTION: (arg1: {name: string, newName: string}) => { - return errTemplate`\ - The ${chalk.yellow(arg1.name)} configuration option you have supplied has been renamed. - - Please rename ${chalk.yellow(arg1.name)} to ${chalk.blue(arg1.newName)}` - }, - CANNOT_CONNECT_BASE_URL: () => { - return errTemplate`\ - Cypress failed to verify that your server is running. - - Please start this server and then run Cypress again.` - }, - CANNOT_CONNECT_BASE_URL_WARNING: (arg1: string) => { - return errTemplate`\ - Cypress could not verify that this server is running: - - > ${chalk.blue(arg1)} - - This server has been configured as your \`baseUrl\`, and tests will likely fail if it is not running.` - }, - CANNOT_CONNECT_BASE_URL_RETRYING: (arg1: {attempt: number, baseUrl: string, remaining: number, delay: number}) => { - switch (arg1.attempt) { - case 1: - return errTemplate`\ - Cypress could not verify that this server is running: - - > ${chalk.blue(arg1.baseUrl)} - - We are verifying this server because it has been configured as your \`baseUrl\`. - - Cypress automatically waits until your server is accessible before running tests. - - ${displayRetriesRemaining(arg1.remaining)}` - default: - return errTemplate`${guard(displayRetriesRemaining(arg1.remaining))}` - } - }, - INVALID_REPORTER_NAME: (arg1: {name: string, paths: string[], error: string}) => { - return errTemplate`\ - Could not load reporter by name: ${chalk.yellow(arg1.name)} - - We searched for the reporter in these paths: - - ${listItems(arg1.paths)} - - The error we received was: - - ${chalk.yellow(arg1.error)} - - Learn more at https://on.cypress.io/reporters` - // TODO: update with vetted cypress language - }, - NO_DEFAULT_CONFIG_FILE_FOUND: (arg1: string) => { - return errTemplate`\ - Could not find a Cypress configuration file, exiting. - - We looked but did not find a default config file in this folder: ${chalk.blue(arg1)}` - // TODO: update with vetted cypress language - }, - CONFIG_FILES_LANGUAGE_CONFLICT: (arg1: string, arg2: string, arg3: string) => { - return errTemplate` - There is both a ${backtick(arg2)} and a ${backtick(arg3)} at the location below: - - ${arg1} - - Cypress does not know which one to read for config. Please remove one of the two and try again. - ` - }, - CONFIG_FILE_NOT_FOUND: (arg1: string, arg2: string) => { - return errTemplate`\ - Could not find a Cypress configuration file, exiting. - - We looked but did not find a ${chalk.blue(arg1)} file in this folder: ${chalk.blue(arg2)}` - }, - INVOKED_BINARY_OUTSIDE_NPM_MODULE: () => { - return errTemplate`\ - It looks like you are running the Cypress binary directly. - - This is not the recommended approach, and Cypress may not work correctly. - - Please install the 'cypress' NPM package and follow the instructions here: - - https://on.cypress.io/installing-cypress` - }, - FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { - return errTemplate`\ - You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} - - To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. - - ${guard(arg1.link)}` - }, - FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { - return errTemplate`\ - You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} - - Your plan is now in a grace period, which means your tests will still be recorded until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue recording tests on the Cypress Dashboard in the future. - - ${guard(arg1.link)}` - }, - PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { - return errTemplate`\ - You've exceeded the limit of private test results under your current billing plan this month. ${arg1.usedTestsMessage} - - To upgrade your account, please visit your billing to upgrade to another billing plan. - - ${guard(arg1.link)}` - }, - FREE_PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { - return errTemplate`\ - You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} - - To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. - - ${guard(arg1.link)}` - }, - FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { - return errTemplate`\ - You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} - - Your plan is now in a grace period, which means you will have the full benefits of your current plan until ${arg1.gracePeriodMessage}. - - Please visit your billing to upgrade your plan. - - ${guard(arg1.link)}` - }, - PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { - return errTemplate`\ - You've exceeded the limit of test results under your ${arg1.planType} billing plan this month. ${arg1.usedTestsMessage} - - To continue getting the full benefits of your current plan, please visit your billing to upgrade. - - ${guard(arg1.link)}` - }, - FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: (arg1: {link: string, gracePeriodMessage: string}) => { - return errTemplate`\ - Parallelization is not included under your free plan. - - Your plan is now in a grace period, which means your tests will still run in parallel until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests in parallel in the future. - - ${guard(arg1.link)}` - }, - PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { - return errTemplate`\ - Parallelization is not included under your current billing plan. - - To run your tests in parallel, please visit your billing and upgrade to another plan with parallelization. - - ${guard(arg1.link)}` - }, - PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: (arg1: {link: string, gracePeriodMessage: string}) => { - return errTemplate`\ - Grouping is not included under your free plan. - - Your plan is now in a grace period, which means your tests will still run with groups until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests with groups in the future. - - ${guard(arg1.link)}` - }, - RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { - return errTemplate`\ - Grouping is not included under your current billing plan. - - To run your tests with groups, please visit your billing and upgrade to another plan with grouping. - - ${guard(arg1.link)}` - }, - FIXTURE_NOT_FOUND: (arg1: string, arg2: string[]) => { - return errTemplate`\ - A fixture file could not be found at any of the following paths: - - > ${arg1} - > ${arg1}{{extension}} - - Cypress looked for these file extensions at the provided path: - - > ${arg2.join(', ')} - - Provide a path to an existing fixture file.` - }, - AUTH_COULD_NOT_LAUNCH_BROWSER: (arg1: string) => { - return errTemplate`\ - Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser: - - \`\`\` - ${arg1} - \`\`\`` - }, - AUTH_BROWSER_LAUNCHED: () => { - return errTemplate`Check your browser to continue logging in.` - }, - BAD_POLICY_WARNING: (arg1: string[]) => { - return errTemplate`\ - Cypress detected policy settings on your computer that may cause issues. - - The following policies were detected that may prevent Cypress from automating Chrome: - - ${guard(arg1.map((line) => ` > ${line}`).join('\n'))} - - For more information, see https://on.cypress.io/bad-browser-policy` - }, - BAD_POLICY_WARNING_TOOLTIP: () => { - return errTemplate`Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy` - }, - EXTENSION_NOT_LOADED: (arg1: string, arg2: string) => { - return errTemplate`\ - ${guard(arg1)} could not install the extension at path: - - > ${arg2} - - Please verify that this is the path to a valid, unpacked WebExtension.` - }, - COULD_NOT_FIND_SYSTEM_NODE: (arg1: string) => { - return errTemplate`\ - \`nodeVersion\` is set to \`system\`, but Cypress could not find a usable Node executable on your PATH. - - Make sure that your Node executable exists and can be run by the current user. - - Cypress will use the built-in Node version (v${arg1}) instead.` - }, - INVALID_CYPRESS_INTERNAL_ENV: (arg1: string) => { - return errTemplate`\ - We have detected an unknown or unsupported "CYPRESS_INTERNAL_ENV" value - - ${chalk.yellow(arg1)} - - "CYPRESS_INTERNAL_ENV" is reserved and should only be used internally. - - Do not modify the "CYPRESS_INTERNAL_ENV" value.` - }, - CDP_VERSION_TOO_OLD: (arg1: string, arg2: {major: number, minor: string | number}) => { - return errTemplate`A minimum CDP version of v${guard(arg1)} is required, but the current browser has ${guard(arg2.major !== 0 ? `v${arg2.major}.${arg2.minor}` : 'an older version')}.` - }, - CDP_COULD_NOT_CONNECT: (arg1: string, arg2: Error, arg3: string) => { - return errTemplate`\ - Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds. - - This usually indicates there was a problem opening the ${guard(arg3)} browser. - - The CDP port requested was ${guard(chalk.yellow(arg1))}. - - Error details: - - ${details(arg2)}` - }, - FIREFOX_COULD_NOT_CONNECT: (arg1: Error) => { - return errTemplate`\ - Cypress failed to make a connection to Firefox. - - This usually indicates there was a problem opening the Firefox browser. - - Error details: - - ${details(arg1)}` - }, - CDP_COULD_NOT_RECONNECT: (arg1: Error) => { - return errTemplate`\ - There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser. - - ${details(arg1)}` - }, - CDP_RETRYING_CONNECTION: (attempt: string | number, browserType: string) => { - return errTemplate`Still waiting to connect to ${guard(browserType)}, retrying in 1 second (attempt ${chalk.yellow(`${attempt}`)}/62)` - }, - UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: (arg1: string[], arg2: string[]) => { - return errTemplate`\ - The \`launchOptions\` object returned by your plugin's \`before:browser:launch\` handler contained unexpected properties: - - ${listItems(arg1)} - - \`launchOptions\` may only contain the properties: - - ${listItems(arg2)} - - https://on.cypress.io/browser-launch-api` - }, - COULD_NOT_PARSE_ARGUMENTS: (arg1: string, arg2: string, arg3: string) => { - return errTemplate`\ - Cypress encountered an error while parsing the argument ${chalk.gray(arg1)} - - You passed: ${arg2} - - The error was: ${arg3}` - }, - FIREFOX_MARIONETTE_FAILURE: (arg1: string, arg2: string) => { - return errTemplate`\ - Cypress could not connect to Firefox. - - An unexpected error was received from Marionette ${guard(arg1)}: - - ${guard(arg2)} - - To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running.` - }, - FOLDER_NOT_WRITABLE: (arg1: string) => { - return errTemplate`\ - Folder ${arg1} is not writable. - - Writing to this directory is required by Cypress in order to store screenshots and videos. - - Enable write permissions to this directory to ensure screenshots and videos are stored. - - If you don't require screenshots or videos to be stored you can safely ignore this warning.` - }, - EXPERIMENTAL_SAMESITE_REMOVED: () => { - return errTemplate`\ - The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress version \`5.0.0\`. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. - - You can safely remove this option from your config.` - }, - EXPERIMENTAL_COMPONENT_TESTING_REMOVED: (arg1: {configFile: string}) => { - return errTemplate`\ - The ${'experimentalComponentTesting'} configuration option was removed in Cypress version \`7.0.0\`. Please remove this flag from ${arg1.configFile}. - - Cypress Component Testing is now a standalone command. You can now run your component tests with: - - ${chalk.yellow(`\`cypress open-ct\``)} - - https://on.cypress.io/migration-guide` - }, - EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { - return errTemplate`\ - The \`experimentalShadowDomSupport\` configuration option was removed in Cypress version \`5.2.0\`. It is no longer necessary when utilizing the \`includeShadowDom\` option. - - You can safely remove this option from your config.` - }, - EXPERIMENTAL_NETWORK_STUBBING_REMOVED: () => { - return errTemplate`\ - The \`experimentalNetworkStubbing\` configuration option was removed in Cypress version \`6.0.0\`. - It is no longer necessary for using \`cy.intercept()\` (formerly \`cy.route2()\`). - - You can safely remove this option from your config.` - }, - EXPERIMENTAL_RUN_EVENTS_REMOVED: () => { - return errTemplate`\ - The \`experimentalRunEvents\` configuration option was removed in Cypress version \`6.7.0\`. It is no longer necessary when listening to run events in the plugins file. - - You can safely remove this option from your config.` - }, - FIREFOX_GC_INTERVAL_REMOVED: () => { - return errTemplate`\ - The \`firefoxGcInterval\` configuration option was removed in Cypress version \`8.0.0\`. It was introduced to work around a bug in Firefox 79 and below. - - Since Cypress no longer supports Firefox 85 and below in Cypress 8, this option was removed. - - You can safely remove this option from your config.` - }, - INCOMPATIBLE_PLUGIN_RETRIES: (arg1: string) => { - return errTemplate`\ - We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at ${arg1}. - - Test retries is now supported in Cypress version \`5.0.0\`. - - Remove the plugin from your dependencies to silence this warning. - - https://on.cypress.io/test-retries - ` - }, - INVALID_CONFIG_OPTION: (arg1: string[]) => { - return errTemplate`\ - ${arg1.map((arg) => `\`${arg}\` is not a valid configuration option`).join('\n')} - - https://on.cypress.io/configuration - ` - }, - PLUGINS_RUN_EVENT_ERROR: (arg1: string, arg2: string) => { - return errTemplate`\ - An error was thrown in your plugins file while executing the handler for the '${chalk.blue(arg1)}' event. - - The error we received was: - - ${chalk.yellow(arg2)} - ` - }, - CT_NO_DEV_START_EVENT: (arg1: string) => { - const pluginsFilePath = arg1 ? - stripIndent`\ - You can find the \'pluginsFile\' at the following path: - - ${arg1} - ` : '' - - return errTemplate`\ - To run component-testing, cypress needs the \`dev-server:start\` event. - - Implement it by adding a \`on('dev-server:start', () => startDevServer())\` call in your pluginsFile. - ${pluginsFilePath} - Learn how to set up component testing: - - https://on.cypress.io/component-testing - ` - }, - UNSUPPORTED_BROWSER_VERSION: (errorMsg: string) => { - return errTemplate`${guard(errorMsg)}` - }, - NODE_VERSION_DEPRECATION_SYSTEM: (arg1: {name: string, value: any, configFile: string}) => { - return errTemplate`\ - Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. - - As of Cypress version \`9.0.0\` the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. - - Please remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. - ` - }, - NODE_VERSION_DEPRECATION_BUNDLED: (arg1: {name: string, value: any, configFile: string}) => { - return errTemplate`\ - Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. - - As of Cypress version \`9.0.0\` the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. - - When ${backtick(arg1.name)} is set to ${backtick(arg1.value)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. - - As the ${backtick(arg1.name)} configuration option will be removed in a future release, it is recommended to remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. - ` - }, -} as const - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const _typeCheck: Record ErrTemplateResult> = AllCypressErrors - -type AllCypressErrorObj = typeof AllCypressErrors - -function getMsgByType (type: Type, ...args: Parameters): string { - // @ts-expect-error - const result = AllCypressErrors[type](...args) as ErrTemplateResult - - return result.message -} - -/** - * Given an error name & params for the error, returns a "CypressError", - * with a forBrowser property, used when we want to format the value for sending to - * the browser rather than the terminal. - * - * @param type - * @param args - * @returns - */ -const get = function (type: Type, ...args: Parameters) { - // @ts-expect-error - const result = AllCypressErrors[type](...args) as ErrTemplateResult - - const { message, details, originalError, forBrowser } = result - - const err = new Error(message) as CypressErr - - err.isCypressErr = true - err.type = type - err.details = details - err.forBrowser = forBrowser - - if (originalError) { - err.stack = originalError.stack - } else { - const newErr = new Error() - - Error.captureStackTrace(newErr, get) - err.errWithoutMessage = newErr - } - - return err -} - -interface CypressErr extends Error { - isCypressErr: boolean - type: keyof AllCypressErrorObj - details?: string - code?: string | number - errno?: string | number - errWithoutMessage?: Error - stackWithoutMessage?: string - forBrowser: ErrTemplateResult['forBrowser'] -} - -const warning = function (type: Type, ...args: Parameters) { - const err = get(type, ...args) - - log(err, 'magenta') - - return null -} - -const throwErr = function (type: Type, ...args: Parameters) { - const err = get(type, ...args) - - if (err.errWithoutMessage) { - Error.captureStackTrace(err.errWithoutMessage, throwErr) - err.stackWithoutMessage = err.errWithoutMessage.stack?.split('\n').slice(1).join('\n') - } - - throw err -} - -interface ClonedError { - type: string - name: string - columnNumber?: string - lineNumber?: string - fileName?: String - stack?: string - message?: string -} - -// For when the error is passed via the socket-base -interface GenericError extends Error { - forBrowser?: never - stackWithoutMessage?: never -} - -const clone = function (err: CypressErr | GenericError, options: {html?: boolean} = {}) { - _.defaults(options, { - html: false, - }) - - const message = _.isFunction(err.forBrowser) ? err.forBrowser().message : err.message - - // pull off these properties - const obj = _.pick(err, 'type', 'name', 'stack', 'fileName', 'lineNumber', 'columnNumber') as ClonedError - - if (options.html) { - obj.message = ansi_up.ansi_to_html(message) - // revert back the distorted characters - // in case there is an error in a child_process - // that contains quotes - .replace(/\&\#x27;/g, '\'') - .replace(/\"\;/g, '"') - } else { - obj.message = message - } - - // and any own (custom) properties - // of the err object - for (let prop of Object.keys(err || {})) { - const val = err[prop] - - obj[prop] = val - } - - if (err.stackWithoutMessage) { - obj.stack = err.stackWithoutMessage - } - - return obj -} - const logException = Bluebird.method(function (this: any, err) { // TODO: remove context here if (this.log(err) && isProduction()) { @@ -1252,26 +16,9 @@ const logException = Bluebird.method(function (this: any, err) { } }) -export = { - get, - - log, - +const errorApi = { + ...errors, logException, - - clone, - - warning, - - // forms well-formatted user-friendly error for most common - // errors Cypress can encounter - getMsgByType, - - isCypressErr, - - throw: throwErr, - - stripAnsi: strip, - - displayFlags, } + +export = errorApi diff --git a/packages/server/lib/plugins/child/browser_launch.js b/packages/server/lib/plugins/child/browser_launch.js index a6904f04721f..c4f7152259df 100644 --- a/packages/server/lib/plugins/child/browser_launch.js +++ b/packages/server/lib/plugins/child/browser_launch.js @@ -1,5 +1,5 @@ const util = require('../util') -const errorsChild = require('../../errors-child') +const { getError } = require('@packages/errors') const ARRAY_METHODS = ['concat', 'push', 'unshift', 'slice', 'pop', 'shift', 'slice', 'splice', 'filter', 'map', 'forEach', 'reduce', 'reverse', 'splice', 'includes'] @@ -21,7 +21,7 @@ module.exports = { hasEmittedWarning = true - const warning = errorsChild.get( + const warning = getError( 'DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS', ) diff --git a/packages/server/lib/plugins/child/task.js b/packages/server/lib/plugins/child/task.js index 7cb96426a34f..fd4109803383 100644 --- a/packages/server/lib/plugins/child/task.js +++ b/packages/server/lib/plugins/child/task.js @@ -1,6 +1,6 @@ const _ = require('lodash') const util = require('../util') -const errorsChild = require('../../errors-child') +const errorsChild = require('../../../../errors/src/errors-child') const getBody = (ipc, events, ids, [event]) => { const taskEvent = _.find(events, { event: 'task' }).handler diff --git a/packages/server/lib/util/require_async.ts b/packages/server/lib/util/require_async.ts index 518a4a8204ae..d6868ded7b2d 100644 --- a/packages/server/lib/util/require_async.ts +++ b/packages/server/lib/util/require_async.ts @@ -65,12 +65,12 @@ export async function requireAsync (filePath: string, options: RequireAsyncOptio debug('load:error %s, rejecting', type) killChildProcess() - const err = errors.get(type, ...args) + let err - // if it's a non-cypress error, restore the initial error - if (!(err.message?.length)) { - err.isCypressErr = false - err.message = args[1] + if (errors.AllCypressErrors[type]) { + err = errors.get(type, ...args) + } else { + err = new Error(args[1]) err.code = type err.name = type } diff --git a/packages/server/test/unit/errors_spec.js b/packages/server/test/unit/errors_spec.js index 9e086a3120be..fe122f137f0b 100644 --- a/packages/server/test/unit/errors_spec.js +++ b/packages/server/test/unit/errors_spec.js @@ -1,172 +1,59 @@ -require('../spec_helper') - +const logger = require(`../../lib/logger`) const chalk = require('chalk') -const style = require('ansi-styles') -const snapshot = require('snap-shot-it') -const errors = require(`${root}lib/errors`) -const logger = require(`${root}lib/logger`) - -describe('lib/errors', () => { - beforeEach(() => { - sinon.stub(console, 'log') - }) - - context('.log', () => { - it('uses red by default', () => { - const err = errors.get('NOT_LOGGED_IN') - - const ret = errors.log(err) - - expect(ret).to.be.undefined - - const { - red, - } = style - - expect(console.log).to.be.calledWithMatch(red.open) - - expect(console.log).to.be.calledWithMatch(red.close) - }) - - it('can change the color', () => { - const err = errors.get('NOT_LOGGED_IN') - - const ret = errors.log(err, 'yellow') - - expect(ret).to.be.undefined - - const { - yellow, - } = style - - expect(console.log).to.be.calledWithMatch(yellow.open) - - expect(console.log).to.be.calledWithMatch(yellow.close) - }) - - it('logs err.message', () => { - const err = errors.get('NO_PROJECT_ID', 'foo/bar/baz') - - const ret = errors.log(err) - - expect(ret).to.be.undefined - - expect(console.log).to.be.calledWithMatch('foo/bar/baz') - }) - - it('logs err.details', () => { - const err = errors.get('PLUGINS_FUNCTION_ERROR', 'foo/bar/baz', 'details huh') +const errors = require('../../lib/errors') - const ret = errors.log(err) +context('.logException', () => { + it('calls logger.createException with unknown error', () => { + sinon.stub(logger, 'createException').resolves() + sinon.stub(process.env, 'CYPRESS_INTERNAL_ENV').value('production') - expect(ret).to.be.undefined - - expect(console.log).to.be.calledWithMatch('foo/bar/baz') - - expect(console.log).to.be.calledWithMatch(`\n${ chalk.yellow('details huh')}`) - }) - - it('logs err.stack in development', () => { - process.env.CYPRESS_INTERNAL_ENV = 'development' - - const err = new Error('foo') - - const ret = errors.log(err) - - expect(ret).to.eq(err) + const err = new Error('foo') + return errors.logException(err) + .then(() => { expect(console.log).to.be.calledWith(chalk.red(err.stack)) - }) - }) - - context('.logException', () => { - it('calls logger.createException with unknown error', () => { - sinon.stub(logger, 'createException').resolves() - sinon.stub(process.env, 'CYPRESS_INTERNAL_ENV').value('production') - - const err = new Error('foo') - - return errors.logException(err) - .then(() => { - expect(console.log).to.be.calledWith(chalk.red(err.stack)) - expect(logger.createException).to.be.calledWith(err) - }) + expect(logger.createException).to.be.calledWith(err) }) + }) - it('does not call logger.createException when known error', () => { - sinon.stub(logger, 'createException').resolves() - sinon.stub(process.env, 'CYPRESS_INTERNAL_ENV').value('production') - - const err = errors.get('NOT_LOGGED_IN') - - return errors.logException(err) - .then(() => { - expect(console.log).not.to.be.calledWith(err.stack) - - expect(logger.createException).not.to.be.called - }) - }) - - it('does not call logger.createException when not in production env', () => { - sinon.stub(logger, 'createException').resolves() - sinon.stub(process.env, 'CYPRESS_INTERNAL_ENV').value('development') - - const err = new Error('foo') - - return errors.logException(err) - .then(() => { - expect(console.log).not.to.be.calledWith(err.stack) - - expect(logger.createException).not.to.be.called - }) - }) + it('does not call logger.createException when known error', () => { + sinon.stub(logger, 'createException').resolves() + sinon.stub(process.env, 'CYPRESS_INTERNAL_ENV').value('production') - it('swallows creating exception errors', () => { - sinon.stub(logger, 'createException').rejects(new Error('foo')) - sinon.stub(process.env, 'CYPRESS_INTERNAL_ENV').value('production') + const err = errors.getError('NOT_LOGGED_IN') - const err = errors.get('NOT_LOGGED_IN') + return errors.logException(err) + .then(() => { + expect(console.log).not.to.be.calledWith(err.stack) - return errors.logException(err) - .then((ret) => { - expect(ret).to.be.undefined - }) + expect(logger.createException).not.to.be.called }) }) - context('.clone', () => { - it('converts err.message from ansi to html with span classes when html true', () => { - const err = new Error(`foo${chalk.blue('bar')}${chalk.yellow('baz')}`) - const obj = errors.clone(err, { html: true }) + it('does not call logger.createException when not in production env', () => { + sinon.stub(logger, 'createException').resolves() + sinon.stub(process.env, 'CYPRESS_INTERNAL_ENV').value('development') - expect(obj.message).to.eq('foobarbaz') - }) + const err = new Error('foo') - it('does not convert err.message from ansi to html when no html option', () => { - const err = new Error(`foo${chalk.blue('bar')}${chalk.yellow('baz')}`) - const obj = errors.clone(err) + return errors.logException(err) + .then(() => { + expect(console.log).not.to.be.calledWith(err.stack) - expect(obj.message).to.eq('foo\u001b[34mbar\u001b[39m\u001b[33mbaz\u001b[39m') + expect(logger.createException).not.to.be.called }) }) - context('.displayFlags', () => { - it('returns string formatted from selected keys', () => { - const options = { - tags: 'nightly,staging', - name: 'my tests', - unused: 'some other value', - } - // we are only interested in showig tags and name values - // and prepending them with custom prefixes - const mapping = { - tags: '--tag', - name: '--name', - } - const text = errors.displayFlags(options, mapping) + it('swallows creating exception errors', () => { + sinon.stub(logger, 'createException').rejects(new Error('foo')) + sinon.stub(process.env, 'CYPRESS_INTERNAL_ENV').value('production') + + const err = errors.getError('NOT_LOGGED_IN') - return snapshot('tags and name only', text.val) + return errors.logException(err) + .then((ret) => { + expect(ret).to.be.undefined }) }) }) diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 4f8f749850ff..74d14cce7eac 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -2,7 +2,7 @@ "extends": "./../ts/tsconfig.json", "include": [ "lib/*.ts", - "lib/**/*.ts" + "lib/**/*.ts", ], "files": [ "./../ts/index.d.ts" diff --git a/yarn.lock b/yarn.lock index 43bcb56d1859..7bcbf1c3f479 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8454,6 +8454,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw== +"@types/strip-ansi@^5.2.1": + version "5.2.1" + resolved "https://registry.yarnpkg.com/@types/strip-ansi/-/strip-ansi-5.2.1.tgz#acd97f1f091e332bb7ce697c4609eb2370fa2a92" + integrity sha512-1l5iM0LBkVU8JXxnIoBqNvg+yyOXxPeN6DNoD+7A9AN1B8FhYPSeIXgyNqwIqg1uzipTgVC2hmuDzB0u9qw/PA== + dependencies: + strip-ansi "*" + "@types/strip-bom@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" @@ -10282,10 +10289,10 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.0.tgz#ecc7f5933cbe5ac7b33e209a5ff409ab1669c6b2" - integrity sha512-tAaOSrWCHF+1Ear1Z4wnJCXA9GGox4K6Ic85a5qalES2aeEwQGr7UC93mwef49536PkCYjzkp0zIxfFvexJ6zQ== +ansi-regex@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" + integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== ansi-styles@3.2.1, ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" @@ -36956,6 +36963,13 @@ stringset@~0.2.1: resolved "https://registry.yarnpkg.com/stringset/-/stringset-0.2.1.tgz#ef259c4e349344377fcd1c913dd2e848c9c042b5" integrity sha1-7yWcTjSTRDd/zRyRPdLoSMnAQrU= +strip-ansi@*, strip-ansi@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== + dependencies: + ansi-regex "^6.0.1" + strip-ansi@4.0.0, strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" @@ -36991,13 +37005,6 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.0.tgz#1dc49b980c3a4100366617adac59327eefdefcb0" - integrity sha512-UhDTSnGF1dc0DRbUqr1aXwNoY3RgVkSWG8BrpnuFIxhP57IqbS7IRta2Gfiavds4yCxc5+fEAVVOgBZWnYkvzg== - dependencies: - ansi-regex "^6.0.0" - strip-ansi@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.1.1.tgz#39e8a98d044d150660abe4a6808acf70bb7bc991" From 931482dda1a7fb4e0fc6f79d535412f8a911eadc Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 1 Feb 2022 19:14:34 -0500 Subject: [PATCH 013/165] fix import --- packages/server/lib/reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/lib/reporter.js b/packages/server/lib/reporter.js index 5c5b6135c87e..e27c1cac6180 100644 --- a/packages/server/lib/reporter.js +++ b/packages/server/lib/reporter.js @@ -1,6 +1,6 @@ const _ = require('lodash') const path = require('path') -const stackUtils = require('./util/stack_utils') +const stackUtils = require('@packages/errors/src/stack_utils') // mocha-* is used to allow us to have later versions of mocha specified in devDependencies // and prevents accidently upgrading this one // TODO: look into upgrading this to version in driver From 3c7483e2babc22598dc27c78566c6874fdec7b0f Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 1 Feb 2022 19:26:35 -0500 Subject: [PATCH 014/165] fix import --- packages/errors/index.js | 5 +++-- packages/server/lib/plugins/child/task.js | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/errors/index.js b/packages/errors/index.js index 643cb42256ee..5ac28679944b 100644 --- a/packages/errors/index.js +++ b/packages/errors/index.js @@ -1,5 +1,6 @@ if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { require('@packages/ts/register') + module.exports = require('./src') +} else { + module.exports = require('./dist') } - -module.exports = require('./src') diff --git a/packages/server/lib/plugins/child/task.js b/packages/server/lib/plugins/child/task.js index fd4109803383..9aec891852b1 100644 --- a/packages/server/lib/plugins/child/task.js +++ b/packages/server/lib/plugins/child/task.js @@ -1,6 +1,6 @@ const _ = require('lodash') const util = require('../util') -const errorsChild = require('../../../../errors/src/errors-child') +const errors = require('@packages/errors') const getBody = (ipc, events, ids, [event]) => { const taskEvent = _.find(events, { event: 'task' }).handler @@ -24,7 +24,7 @@ const merge = (prevEvents, events) => { const duplicates = _.intersection(_.keys(prevEvents), _.keys(events)) if (duplicates.length) { - errorsChild.warning('DUPLICATE_TASK_KEY', duplicates.join(', ')) + errors.warning('DUPLICATE_TASK_KEY', duplicates.join(', ')) } return _.extend(prevEvents, events) From 55499d187d2e3bb8e173ad4cc9b56e2b7ab37a86 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 1 Feb 2022 21:45:18 -0500 Subject: [PATCH 015/165] fixing build / tests --- packages/errors/index.js | 7 ++++--- packages/errors/package.json | 1 + packages/errors/src/errors.ts | 8 ++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/packages/errors/index.js b/packages/errors/index.js index 5ac28679944b..c9fa09d90d4a 100644 --- a/packages/errors/index.js +++ b/packages/errors/index.js @@ -1,6 +1,7 @@ -if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { +try { + require.resolve('./dist') + module.exports = require('./dist') +} catch (e) { require('@packages/ts/register') module.exports = require('./src') -} else { - module.exports = require('./dist') } diff --git a/packages/errors/package.json b/packages/errors/package.json index 82efbde9d28c..76e3e81a256e 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -5,6 +5,7 @@ "main": "index.js", "browser": "src/index.ts", "scripts": { + "build": "tsc", "build-prod": "tsc || echo 'built, with errors'", "check-ts": "tsc --noEmit", "clean-deps": "rm -rf node_modules", diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 06613544736b..a48ab4ff30ab 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -10,10 +10,6 @@ import { displayFlags, listItems, logError, warnIfExplicitCiBuildId } from './er import type { ClonedError, CypressError, ErrTemplateResult } from './errorTypes' import { stackWithoutMessage } from './stack_utils' -export { - stripAnsi, -} - const ansi_up = new AU() ansi_up.use_classes = true @@ -308,8 +304,7 @@ export const AllCypressErrors = { }, DUPLICATE_TASK_KEY: (arg1: string) => { return errTemplate`\ - Warning: Multiple attempts to register the following task(s): ${arg1}. Only the last attempt will be registered. - ` + Warning: Multiple attempts to register the following task(s): ${arg1}. Only the last attempt will be registered.` }, INDETERMINATE_CI_BUILD_ID: (arg1: Record, arg2: string[]) => { return errTemplate`\ @@ -1195,6 +1190,7 @@ export const cloneError = function (err: CypressError | GenericError, options: { } export { + stripAnsi, cloneError as clone, throwErr as throw, getError as get, From b63865c87323b51723922f3521aa9c17144cb32e Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 1 Feb 2022 21:46:58 -0500 Subject: [PATCH 016/165] remove extraneous file --- packages/errors/test/unit/__snapshots__/errors_spec.js | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 packages/errors/test/unit/__snapshots__/errors_spec.js diff --git a/packages/errors/test/unit/__snapshots__/errors_spec.js b/packages/errors/test/unit/__snapshots__/errors_spec.js deleted file mode 100644 index c809a4d34bea..000000000000 --- a/packages/errors/test/unit/__snapshots__/errors_spec.js +++ /dev/null @@ -1,4 +0,0 @@ -exports['tags and name only'] = ` -The --tag flag you passed was: nightly,staging -The --name flag you passed was: my tests -` From 8d2c96391931b41e8620098abc3435eded8680a3 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 1 Feb 2022 21:54:11 -0500 Subject: [PATCH 017/165] move warnIfExplicitCiBuildId --- packages/errors/src/error_utils.ts | 14 -------------- packages/errors/src/errors.ts | 16 +++++++++++++++- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/errors/src/error_utils.ts b/packages/errors/src/error_utils.ts index 040b6a5f398c..42d57f25db69 100644 --- a/packages/errors/src/error_utils.ts +++ b/packages/errors/src/error_utils.ts @@ -73,17 +73,3 @@ export const logError = function (err: CypressError | ErrorLike, color: AllowedC return err } - -export const warnIfExplicitCiBuildId = function (ciBuildId?: string | null) { - if (!ciBuildId) { - return '' - } - - return `\ -It also looks like you also passed in an explicit --ci-build-id flag. - -This is only necessary if you are NOT running in one of our supported CI providers. - -This flag must be unique for each new run, but must also be identical for each machine you are trying to --group or run in --parallel.\ -` -} diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index a48ab4ff30ab..a5842519a5a9 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -6,7 +6,7 @@ import AU from 'ansi_up' import { backtick, details, errTemplate, guard } from './err_template' import { stripIndent } from './strip_indent' -import { displayFlags, listItems, logError, warnIfExplicitCiBuildId } from './error_utils' +import { displayFlags, listItems, logError } from './error_utils' import type { ClonedError, CypressError, ErrTemplateResult } from './errorTypes' import { stackWithoutMessage } from './stack_utils' @@ -24,6 +24,20 @@ const displayRetriesRemaining = function (tries: number) { ) } +export const warnIfExplicitCiBuildId = function (ciBuildId?: string | null) { + if (!ciBuildId) { + return '' + } + + return `\ +It also looks like you also passed in an explicit --ci-build-id flag. + +This is only necessary if you are NOT running in one of our supported CI providers. + +This flag must be unique for each new run, but must also be identical for each machine you are trying to --group or run in --parallel.\ +` +} + /** * All Cypress Errors should be defined here: * From 5041e053eb46b11b103123ba3d7ff5282003805f Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 1 Feb 2022 22:40:59 -0500 Subject: [PATCH 018/165] fix build / tests --- packages/errors/package.json | 3 ++- packages/server/test/unit/errors_spec.js | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/errors/package.json b/packages/errors/package.json index 76e3e81a256e..e14e741d9123 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -25,7 +25,8 @@ "sinon": "7.5.0" }, "files": [ - "src" + "src", + "dist" ], "types": "src/index.ts" } diff --git a/packages/server/test/unit/errors_spec.js b/packages/server/test/unit/errors_spec.js index fe122f137f0b..cd7d90ef0f7e 100644 --- a/packages/server/test/unit/errors_spec.js +++ b/packages/server/test/unit/errors_spec.js @@ -3,6 +3,10 @@ const chalk = require('chalk') const errors = require('../../lib/errors') context('.logException', () => { + beforeEach(() => { + sinon.stub(console, 'log') + }) + it('calls logger.createException with unknown error', () => { sinon.stub(logger, 'createException').resolves() sinon.stub(process.env, 'CYPRESS_INTERNAL_ENV').value('production') From 06b73b8f8aeaadc81392735e4bdd350694e73027 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 1 Feb 2022 22:53:34 -0500 Subject: [PATCH 019/165] Fix --- packages/server/lib/reporter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/lib/reporter.js b/packages/server/lib/reporter.js index e27c1cac6180..ceac97f2c4ff 100644 --- a/packages/server/lib/reporter.js +++ b/packages/server/lib/reporter.js @@ -1,6 +1,6 @@ const _ = require('lodash') const path = require('path') -const stackUtils = require('@packages/errors/src/stack_utils') +const { stackUtils } = require('@packages/errors') // mocha-* is used to allow us to have later versions of mocha specified in devDependencies // and prevents accidently upgrading this one // TODO: look into upgrading this to version in driver From 3fd204363815fd4d1210da676f53c460e11dde27 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 2 Feb 2022 13:14:42 -0500 Subject: [PATCH 020/165] error, type fixes, documentation, standardize child process error serialization --- guides/error-handling.md | 85 +++++++++++++++++++ package.json | 2 +- packages/driver/src/cypress/stack_utils.ts | 2 +- packages/errors/package.json | 4 +- .../src/{err_template.ts => errTemplate.ts} | 29 +++++-- packages/errors/src/errorTypes.ts | 13 ++- .../src/{error_utils.ts => errorUtils.ts} | 4 +- packages/errors/src/errors.ts | 29 ++++--- packages/errors/src/index.ts | 4 +- .../src/{stack_utils.ts => stackUtils.ts} | 4 + .../src/{strip_indent.ts => stripIndent.ts} | 0 ...r_template_spec.ts => errTemplate_spec.ts} | 4 +- ...rip_indent_spec.ts => stripIndent_spec.ts} | 2 +- packages/server/lib/config.ts | 4 +- .../server/lib/plugins/child/run_plugins.js | 6 +- packages/server/lib/plugins/index.js | 2 +- .../unit/plugins/child/run_plugins_spec.js | 14 +-- .../server/test/unit/plugins/index_spec.js | 18 ++-- 18 files changed, 175 insertions(+), 51 deletions(-) create mode 100644 guides/error-handling.md rename packages/errors/src/{err_template.ts => errTemplate.ts} (80%) rename packages/errors/src/{error_utils.ts => errorUtils.ts} (94%) rename packages/errors/src/{stack_utils.ts => stackUtils.ts} (90%) rename packages/errors/src/{strip_indent.ts => stripIndent.ts} (100%) rename packages/errors/test/unit/{err_template_spec.ts => errTemplate_spec.ts} (96%) rename packages/errors/test/unit/{strip_indent_spec.ts => stripIndent_spec.ts} (92%) diff --git a/guides/error-handling.md b/guides/error-handling.md new file mode 100644 index 000000000000..d5b2a60dbda1 --- /dev/null +++ b/guides/error-handling.md @@ -0,0 +1,85 @@ +## Error Handling in Cypress + +Clear, consistent, errors are one of the important parts of the Cypress experience. When something goes wrong, there should be clear, actionable feedback for the user about exactly *what* went wrong, *where* it went wrong, and next steps on how to fix. + +### @packages/errors + +All error related logic for the server should be added to `@packages/errors`. This logic has been separated out from the `@packages/server` to enable strict type checking & use in other packages we have added in the `10.0-release` branch. + +Summary of the Errors package: + +- `errors.ts`: A key/value mapping of known errors to functions returning "ErrorTemplates", described below, also includes/re-exports several helper utilities: + - `get` / `getError`: builds & retrieves the error as a `CypressError`, should be the main way we retrieve errors throughout Cypress. Aliased as `errors.get` for existing use in the server package + - `throw` / `throwErr`: Get & throw the error, so we can spy/stub it in a test. Aliased as `errors.throw` for existing use in the server package + - `logWarning`: Logs the error as a warning to the console, aliased as `errors.log` for existing use in the server package +- `errTemplate.ts`: Tagged template literal formatting the error as described below +- `stackUtils.ts`: Utilities for working with a stack trace, extended by the driver package + +### errTemplate + +The `errTemplate` is a tagged template literal. It allows us to maintain consistent behavior & formatting in our error messages, when we see a variable, we format it depending on the target environment. + +The error message returns a message that defaults to being formatted for the terminal, and has a `forBrowser` method which returns the error message where the variables are wrapped in backticks '`' for Markdown display in the browser. + +Return Value of `errTemplate` (`ErrTemplateResult`): + +```ts +{ + message: string, // Will always exist, this is the terminal-formatted error message + details?: string, // Exists if there is `details()` call in the errTemplate + originalError?: ErrorLike // Exists if an error was passed into the `details()` + forBrowser(): { + message: string // Ansi stripped message for rendering in the browser, with the variables wrapped in backticks + } +} +``` + +#### Example: + +```ts +CANNOT_TRASH_ASSETS: (arg1: string) => { + return errTemplate`\ + Warning: We failed to trash the existing run results. + + This error will not alter the exit code. + + ${details(arg1)}` +}, +``` + +In this case, `arg1` will be highlighted in yellow when printed to the terminal. + + +```ts +PLUGINS_FILE_ERROR: (arg1: string, arg2: Error) => { + return errTemplate`\ + The plugins file is missing or invalid. + + Your \`pluginsFile\` is set to ${arg1}, but either the file is missing, it contains a syntax error, or threw an error when required. The \`pluginsFile\` must be a \`.js\`, \`.ts\`, or \`.coffee\` file. + + Or you might have renamed the extension of your \`pluginsFile\`. If that's the case, restart the test runner. + + Please fix this, or set \`pluginsFile\` to \`false\` if a plugins file is not necessary for your project. + + ${details(arg2)} + ` +}, +``` + +`arg1` will be highlighted in `blue` for the terminal, and wrapped in backticks when called with `forBrowser`. Details will be printed in `yellow` as a stack trace when printed to the terminal, or shown as a stack-trace in the browser. + +### Error Wrapping + +Any time we know about an edge case that is an error, we should define an error in `errors.ts`. This error should be retrieved by `getError`, which converts it to a `CypressError`. + +The `CypressError` is an `Error` containing the message returned from the `errTemplate`. The `stack` is set to that of the `originalError` if it exists (error object passed into `details`), otherwise it's the `stack` from where the `getError` / `throwError` is called. + + +The `CypressError` has a `isCypressErr` prop which we use as a duck-type guard against exiting the process when logging exceptions. It also maintains a reference to the `originalError` if it exists. + +### Child-Process Errors + +All errors that occur in a child process spawned by Cypress should be sent over the `ipc` bridge using `util.serializeError`. + +This ensures the `name`, `message`, `stack`, and any other relevant details are preserved and can be handled by the standard process of Cypress' error standardization / wrapping. + diff --git a/package.json b/package.json index f57be74168df..4fadd2896804 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "stop-only": "npx stop-only --skip .cy,.publish,.projects,node_modules,dist,dist-test,fixtures,lib,bower_components,src,__snapshots__ --exclude e2e.ts,cypress-tests.ts,*only_spec.js", "stop-only-all": "yarn stop-only --folder packages", "pretest": "yarn ensure-deps", - "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,electron,extension,https-proxy,launcher,net-stubbing,network,proxy,rewriter,runner,runner-shared,socket}'\"", + "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,errors,electron,extension,https-proxy,launcher,net-stubbing,network,proxy,rewriter,runner,runner-shared,socket}'\"", "test-debug": "lerna exec yarn test-debug --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"", "pretest-e2e": "yarn ensure-deps", "test-integration": "lerna exec yarn test-integration --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"", diff --git a/packages/driver/src/cypress/stack_utils.ts b/packages/driver/src/cypress/stack_utils.ts index ac524885109f..ff315b528eaa 100644 --- a/packages/driver/src/cypress/stack_utils.ts +++ b/packages/driver/src/cypress/stack_utils.ts @@ -7,7 +7,7 @@ import { codeFrameColumns } from '@babel/code-frame' import $utils from './utils' import $sourceMapUtils from './source_map_utils' -import { getStackLines, replacedStack, stackWithoutMessage, splitStack, unsplitStack } from '@packages/errors/src/stack_utils' +import { getStackLines, replacedStack, stackWithoutMessage, splitStack, unsplitStack } from '@packages/errors/src/stackUtils' const whitespaceRegex = /^(\s*)*/ const stackLineRegex = /^\s*(at )?.*@?\(?.*\:\d+\:\d+\)?$/ diff --git a/packages/errors/package.json b/packages/errors/package.json index e14e741d9123..901e33f72b92 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -5,8 +5,8 @@ "main": "index.js", "browser": "src/index.ts", "scripts": { - "build": "tsc", - "build-prod": "tsc || echo 'built, with errors'", + "test": "yarn test-unit", + "build-prod": "tsc", "check-ts": "tsc --noEmit", "clean-deps": "rm -rf node_modules", "clean": "rm -f ./src/*.js ./src/**/*.js ./src/**/**/*.js ./test/**/*.js || echo 'cleaned'", diff --git a/packages/errors/src/err_template.ts b/packages/errors/src/errTemplate.ts similarity index 80% rename from packages/errors/src/err_template.ts rename to packages/errors/src/errTemplate.ts index 50f79787b418..f440a9d0c4e7 100644 --- a/packages/errors/src/err_template.ts +++ b/packages/errors/src/errTemplate.ts @@ -1,13 +1,13 @@ import assert from 'assert' -import { stripIndent } from './strip_indent' +import { stripIndent } from './stripIndent' /** * Guarding the value, involves */ import chalk from 'chalk' import stripAnsi from 'strip-ansi' -import { trimMultipleNewLines } from './error_utils' -import type { ErrTemplateResult } from './errorTypes' +import { trimMultipleNewLines } from './errorUtils' +import type { ErrTemplateResult, SerializedError } from './errorTypes' export class Guard { constructor (readonly val: string | number) {} @@ -44,6 +44,10 @@ export function details (val: string | Error | object) { return new Details(val) } +export function isErrorLike (err: any): err is SerializedError | Error { + return err && typeof err === 'object' && Boolean('name' in err && 'message' in err) +} + /** * Creates a consistently formatted object to return from the error call. * @@ -57,7 +61,7 @@ export function details (val: string | Error | object) { */ export const errTemplate = (strings: TemplateStringsArray, ...args: Array): ErrTemplateResult => { let originalError: Error | undefined = undefined - let messageDetails + let messageDetails: string | undefined function prepMessage (forTerminal = true) { function isScalar (val: any): val is string | number | null | boolean { @@ -65,7 +69,7 @@ export const errTemplate = (strings: TemplateStringsArray, ...args: Array = [] @@ -105,12 +116,12 @@ export const errTemplate = (strings: TemplateStringsArray, ...args: Array { return Boolean(err.isCypressErr) diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index a5842519a5a9..0d8c02bd6b1c 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -4,11 +4,11 @@ import _ from 'lodash' import stripAnsi from 'strip-ansi' import AU from 'ansi_up' -import { backtick, details, errTemplate, guard } from './err_template' -import { stripIndent } from './strip_indent' -import { displayFlags, listItems, logError } from './error_utils' -import type { ClonedError, CypressError, ErrTemplateResult } from './errorTypes' -import { stackWithoutMessage } from './stack_utils' +import { backtick, details, errTemplate, guard } from './errTemplate' +import { stripIndent } from './stripIndent' +import { displayFlags, listItems, logError } from './errorUtils' +import type { ClonedError, CypressError, ErrorLike, ErrTemplateResult } from './errorTypes' +import { stackWithoutMessage } from './stackUtils' const ansi_up = new AU() @@ -579,7 +579,7 @@ export const AllCypressErrors = { Learn more at https://on.cypress.io/support-file-missing-or-invalid` }, - PLUGINS_FILE_ERROR: (arg1: string, arg2: string) => { + PLUGINS_FILE_ERROR: (arg1: string, arg2: Error) => { return errTemplate`\ The plugins file is missing or invalid. @@ -611,7 +611,7 @@ export const AllCypressErrors = { ${details(arg2)} ` }, - PLUGINS_FUNCTION_ERROR: (arg1: string, arg2: string) => { + PLUGINS_FUNCTION_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate`\ The function exported by the plugins file threw an error. @@ -620,14 +620,14 @@ export const AllCypressErrors = { ${details(arg2)} ` }, - PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string) => { + PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (${arg1}) ${details(arg2)} ` }, - PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string) => { + PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` The following validation error was thrown by your plugins file (${arg1}). @@ -1114,6 +1114,15 @@ export function getMsgByType (type: Type, * @returns */ export const getError = function (type: Type, ...args: Parameters) { + // If we don't know this "type" of error, return as a non-cypress error + if (!AllCypressErrors[type]) { + const err = new Error(args[1] || type) as ErrorLike + + err.type = type + + return err + } + // @ts-expect-error const result = AllCypressErrors[type](...args) as ErrTemplateResult @@ -1215,4 +1224,4 @@ export { export { logError as log, isCypressErr, -} from './error_utils' +} from './errorUtils' diff --git a/packages/errors/src/index.ts b/packages/errors/src/index.ts index 12a35e12464c..d0430e92f7f7 100644 --- a/packages/errors/src/index.ts +++ b/packages/errors/src/index.ts @@ -2,8 +2,8 @@ import * as errorsApi from './errors' export * from './errors' -import * as stackUtils from './stack_utils' -import * as errorUtils from './error_utils' +import * as stackUtils from './stackUtils' +import * as errorUtils from './errorUtils' export { stackUtils, errorUtils } diff --git a/packages/errors/src/stack_utils.ts b/packages/errors/src/stackUtils.ts similarity index 90% rename from packages/errors/src/stack_utils.ts rename to packages/errors/src/stackUtils.ts index 3f7f5ca1b9a4..78017b185736 100644 --- a/packages/errors/src/stack_utils.ts +++ b/packages/errors/src/stackUtils.ts @@ -31,6 +31,10 @@ export const getStackLines = (stack: string) => { return stackLines } +/** + * Takes the stack and returns only the lines that contain stack-frame like entries, + * matching the `stackLineRegex` above + */ export const stackWithoutMessage = (stack: string) => { return getStackLines(stack).join('\n') } diff --git a/packages/errors/src/strip_indent.ts b/packages/errors/src/stripIndent.ts similarity index 100% rename from packages/errors/src/strip_indent.ts rename to packages/errors/src/stripIndent.ts diff --git a/packages/errors/test/unit/err_template_spec.ts b/packages/errors/test/unit/errTemplate_spec.ts similarity index 96% rename from packages/errors/test/unit/err_template_spec.ts rename to packages/errors/test/unit/errTemplate_spec.ts index 712bb0144931..abbb08b284d2 100644 --- a/packages/errors/test/unit/err_template_spec.ts +++ b/packages/errors/test/unit/errTemplate_spec.ts @@ -1,10 +1,10 @@ import { expect } from 'chai' import chalk from 'chalk' -import { details, errTemplate, guard } from '../../src/err_template' +import { details, errTemplate, guard } from '../../src/errTemplate' import { stripIndent } from '../../src/strip_indent' -describe('err_template', () => { +describe('errTemplate', () => { it('returns an object w/ basic props & forBrowser', () => { const obj = errTemplate`Hello world` diff --git a/packages/errors/test/unit/strip_indent_spec.ts b/packages/errors/test/unit/stripIndent_spec.ts similarity index 92% rename from packages/errors/test/unit/strip_indent_spec.ts rename to packages/errors/test/unit/stripIndent_spec.ts index a4677abe7c67..ae300f0c7fef 100644 --- a/packages/errors/test/unit/strip_indent_spec.ts +++ b/packages/errors/test/unit/stripIndent_spec.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { stripIndent } from '../../src/strip_indent' +import { stripIndent } from '../../src/stripIndent' describe('lib/util/strip_indent', () => { it('does not trip right end', () => { diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index ddbef9f25e09..ce7a1cfcd238 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -523,7 +523,7 @@ export const setPluginsFile = Promise.method((obj, defaults) => { obj.pluginsFile = utils.resolveModule(pluginsFile) return debug(`set pluginsFile to ${obj.pluginsFile}`) - }).catch({ code: 'MODULE_NOT_FOUND' }, () => { + }).catch({ code: 'MODULE_NOT_FOUND' }, (e) => { debug('plugins module does not exist %o', { pluginsFile }) const isLoadingDefaultPluginsFile = pluginsFile === path.resolve(obj.projectRoot, defaults.pluginsFile) @@ -535,7 +535,7 @@ export const setPluginsFile = Promise.method((obj, defaults) => { }) .then((result) => { if (result === null) { - return errors.throw('PLUGINS_FILE_ERROR', path.resolve(obj.projectRoot, pluginsFile), new Error().stack ?? '') + return errors.throw('PLUGINS_FILE_ERROR', path.resolve(obj.projectRoot, pluginsFile), e as unknown as Error) } debug('setting plugins file to %o', { result }) diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index d96f2d8ec709..2462fa5f817c 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -49,7 +49,7 @@ const load = (ipc, config, pluginsFile) => { const { isValid, error } = validateEvent(event, handler, config) if (!isValid) { - ipc.send('load:error', 'PLUGINS_VALIDATION_ERROR', pluginsFile, error.stack) + ipc.send('load:error', 'PLUGINS_VALIDATION_ERROR', pluginsFile, util.serializeError(error)) return } @@ -101,7 +101,7 @@ const load = (ipc, config, pluginsFile) => { }) .catch((err) => { debug('plugins file errored:', err && err.stack) - ipc.send('load:error', 'PLUGINS_FUNCTION_ERROR', pluginsFile, err.stack) + ipc.send('load:error', 'PLUGINS_FUNCTION_ERROR', pluginsFile, util.serializeError(err)) }) } @@ -187,7 +187,7 @@ const runPlugins = (ipc, pluginsFile, projectRoot) => { } } catch (err) { debug('failed to require pluginsFile:\n%s', err.stack) - ipc.send('load:error', 'PLUGINS_FILE_ERROR', pluginsFile, err.stack) + ipc.send('load:error', 'PLUGINS_FILE_ERROR', pluginsFile, util.serializeError(err)) return } diff --git a/packages/server/lib/plugins/index.js b/packages/server/lib/plugins/index.js index c34ddad44c6f..e356a684763e 100644 --- a/packages/server/lib/plugins/index.js +++ b/packages/server/lib/plugins/index.js @@ -191,7 +191,7 @@ const init = (config, options) => { killPluginsProcess() - err = errors.get('PLUGINS_UNEXPECTED_ERROR', config.pluginsFile, err.annotated || err.stack || err.message) + err = errors.get('PLUGINS_UNEXPECTED_ERROR', config.pluginsFile, err) err.title = 'Error running plugin' // this can sometimes trigger before the promise is fulfilled and diff --git a/packages/server/test/unit/plugins/child/run_plugins_spec.js b/packages/server/test/unit/plugins/child/run_plugins_spec.js index c26cc4c553c2..1f0b8e1d1de1 100644 --- a/packages/server/test/unit/plugins/child/run_plugins_spec.js +++ b/packages/server/test/unit/plugins/child/run_plugins_spec.js @@ -57,7 +57,7 @@ describe('lib/plugins/child/run_plugins', () => { runPlugins(this.ipc, 'plugins-file', 'proj-root') expect(this.ipc.send).to.be.calledWith('load:error', 'PLUGINS_FILE_ERROR', 'plugins-file') - return snapshot(this.ipc.send.lastCall.args[3].split('\n')[0]) + return snapshot(this.ipc.send.lastCall.args[3].stack.split('\n')[0]) }) it('sends error message if requiring pluginsFile errors', function () { @@ -70,7 +70,7 @@ describe('lib/plugins/child/run_plugins', () => { runPlugins(this.ipc, 'plugins-file', 'proj-root') expect(this.ipc.send).to.be.calledWith('load:error', 'PLUGINS_FILE_ERROR', 'plugins-file') - return snapshot(this.ipc.send.lastCall.args[3].split('\n')[0]) + return snapshot(this.ipc.send.lastCall.args[3].stack.split('\n')[0]) }) it('sends error message if pluginsFile has syntax error', function () { @@ -83,7 +83,7 @@ describe('lib/plugins/child/run_plugins', () => { runPlugins(this.ipc, 'plugins-file', 'proj-root') expect(this.ipc.send).to.be.calledWith('load:error', 'PLUGINS_FILE_ERROR', 'plugins-file') - return snapshot(withoutColorCodes(withoutPath(this.ipc.send.lastCall.args[3].replace(/( +at[^$]+$)+/g, '[stack trace]')))) + return snapshot(withoutColorCodes(withoutPath(this.ipc.send.lastCall.args[3].stack.replace(/( +at[^$]+$)+/g, '[stack trace]')))) }) it('sends error message if pluginsFile does not export a function', function () { @@ -242,11 +242,11 @@ describe('lib/plugins/child/run_plugins', () => { this.ipc.on.withArgs('load').yields({}) runPlugins(this.ipc, 'plugins-file', 'proj-root') - this.ipc.send = _.once((event, errorType, pluginsFile, stack) => { + this.ipc.send = _.once((event, errorType, pluginsFile, result) => { expect(event).to.eq('load:error') expect(errorType).to.eq('PLUGINS_FUNCTION_ERROR') expect(pluginsFile).to.eq('plugins-file') - expect(stack).to.eq(err.stack) + expect(result.stack).to.eq(err.stack) return done() }) @@ -276,11 +276,11 @@ describe('lib/plugins/child/run_plugins', () => { runPlugins(this.ipc, 'plugins-file', 'proj-root') this.ipc.on.withArgs('load').yield({}) - this.ipc.send = _.once((event, errorType, pluginsFile, stack) => { + this.ipc.send = _.once((event, errorType, pluginsFile, serializedErr) => { expect(event).to.eq('load:error') expect(errorType).to.eq('PLUGINS_FUNCTION_ERROR') expect(pluginsFile).to.eq('plugins-file') - expect(stack).to.eq(err.stack) + expect(serializedErr.stack).to.eq(err.stack) return done() }) diff --git a/packages/server/test/unit/plugins/index_spec.js b/packages/server/test/unit/plugins/index_spec.js index 8af2124a8a0e..12c6e96ac7ea 100644 --- a/packages/server/test/unit/plugins/index_spec.js +++ b/packages/server/test/unit/plugins/index_spec.js @@ -177,7 +177,10 @@ describe('lib/plugins/index', () => { describe('load:error message', () => { context('PLUGINS_FILE_ERROR', () => { beforeEach(() => { - ipc.on.withArgs('load:error').yields('PLUGINS_FILE_ERROR', 'path/to/pluginsFile.js', 'error message stack') + const e = new Error('some error') + + e.stack = 'error message stack' + ipc.on.withArgs('load:error').yields('PLUGINS_FILE_ERROR', 'path/to/pluginsFile.js', e) }) it('rejects plugins.init', () => { @@ -240,7 +243,7 @@ describe('lib/plugins/index', () => { pluginsProcess.on.withArgs('error').yield(err) expect(onError).to.be.called expect(onError.lastCall.args[0].title).to.equal('Error running plugin') - expect(onError.lastCall.args[0].stack).to.include('The following error was thrown by a plugin') + expect(onError.lastCall.args[0].message).to.include('The following error was thrown by a plugin') expect(onError.lastCall.args[0].details).to.include(err.message) }) @@ -249,7 +252,7 @@ describe('lib/plugins/index', () => { ipc.on.withArgs('error').yield(err) expect(onError).to.be.called expect(onError.lastCall.args[0].title).to.equal('Error running plugin') - expect(onError.lastCall.args[0].stack).to.include('The following error was thrown by a plugin') + expect(onError.lastCall.args[0].message).to.include('The following error was thrown by a plugin') expect(onError.lastCall.args[0].details).to.include(err.message) }) @@ -262,6 +265,7 @@ describe('lib/plugins/index', () => { err = { name: 'error name', message: 'error message', + stack: 'error stack', } pluginsProcess.on.withArgs('error').yields(err) @@ -274,8 +278,8 @@ describe('lib/plugins/index', () => { }) .catch((_err) => { expect(_err.title).to.equal('Error running plugin') - expect(_err.stack).to.include('The following error was thrown by a plugin') - expect(_err.details).to.include(err.message) + expect(_err.message).to.include('The following error was thrown by a plugin') + expect(_err.details).to.include(err.stack) }) }) @@ -286,8 +290,8 @@ describe('lib/plugins/index', () => { }) .catch((_err) => { expect(_err.title).to.equal('Error running plugin') - expect(_err.stack).to.include('The following error was thrown by a plugin') - expect(_err.details).to.include(err.message) + expect(_err.message).to.include('The following error was thrown by a plugin') + expect(_err.details).to.include(err.stack) }) }) }) From 00e479a6c983a5631ac6ea6fa95e784338594c58 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 2 Feb 2022 13:38:11 -0500 Subject: [PATCH 021/165] fix import --- packages/errors/test/unit/errTemplate_spec.ts | 2 +- packages/errors/test/unit/stripIndent_spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/errors/test/unit/errTemplate_spec.ts b/packages/errors/test/unit/errTemplate_spec.ts index abbb08b284d2..c54c14561f4c 100644 --- a/packages/errors/test/unit/errTemplate_spec.ts +++ b/packages/errors/test/unit/errTemplate_spec.ts @@ -2,7 +2,7 @@ import { expect } from 'chai' import chalk from 'chalk' import { details, errTemplate, guard } from '../../src/errTemplate' -import { stripIndent } from '../../src/strip_indent' +import { stripIndent } from '../../src/stripIndent' describe('errTemplate', () => { it('returns an object w/ basic props & forBrowser', () => { diff --git a/packages/errors/test/unit/stripIndent_spec.ts b/packages/errors/test/unit/stripIndent_spec.ts index ae300f0c7fef..bcc720de0cca 100644 --- a/packages/errors/test/unit/stripIndent_spec.ts +++ b/packages/errors/test/unit/stripIndent_spec.ts @@ -1,7 +1,7 @@ import { expect } from 'chai' import { stripIndent } from '../../src/stripIndent' -describe('lib/util/strip_indent', () => { +describe('src/stripIndent', () => { it('does not trip right end', () => { const str = stripIndent`\ There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser. From 1dc7621e9795a1bcc97d6e58c1bdf42d4d180ccd Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 2 Feb 2022 13:49:40 -0500 Subject: [PATCH 022/165] build errors on install --- packages/errors/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/errors/package.json b/packages/errors/package.json index 901e33f72b92..69f2038d3dfa 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -6,6 +6,7 @@ "browser": "src/index.ts", "scripts": { "test": "yarn test-unit", + "build": "tsc", "build-prod": "tsc", "check-ts": "tsc --noEmit", "clean-deps": "rm -rf node_modules", From 6714aad69351b5857470a4617bb0e0f7018e637a Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 3 Feb 2022 05:32:16 -0500 Subject: [PATCH 023/165] wrote specs generating visual images of all errors --- packages/errors/package.json | 9 +- packages/errors/test/.mocharc.js | 1 - packages/errors/test/mocha.opts | 4 + .../test/unit/visualSnapshotErrors_spec.ts | 691 ++++++++++++++++++ yarn.lock | 130 +++- 5 files changed, 800 insertions(+), 35 deletions(-) delete mode 100644 packages/errors/test/.mocharc.js create mode 100644 packages/errors/test/mocha.opts create mode 100644 packages/errors/test/unit/visualSnapshotErrors_spec.ts diff --git a/packages/errors/package.json b/packages/errors/package.json index 69f2038d3dfa..1eb504b3dd0b 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -11,7 +11,7 @@ "check-ts": "tsc --noEmit", "clean-deps": "rm -rf node_modules", "clean": "rm -f ./src/*.js ./src/**/*.js ./src/**/**/*.js ./test/**/*.js || echo 'cleaned'", - "test-unit": "mocha -r @packages/ts/register test/unit/**/*spec.ts --config ./test/.mocharc.js --exit" + "test-unit": "electron ./node_modules/.bin/_mocha --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json --exit" }, "dependencies": { "ansi_up": "5.0.0", @@ -22,8 +22,13 @@ "@types/node": "14.14.31", "@types/strip-ansi": "^5.2.1", "chai": "4.2.0", + "electron-mocha": "^11.0.2", + "globby": "^13.1.1", + "is-ci": "^3.0.1", "mocha": "7.0.1", - "sinon": "7.5.0" + "sinon": "7.5.0", + "terminal-banner": "^1.1.0", + "xvfb-maybe": "^0.2.1" }, "files": [ "src", diff --git a/packages/errors/test/.mocharc.js b/packages/errors/test/.mocharc.js deleted file mode 100644 index 4ba52ba2c8df..000000000000 --- a/packages/errors/test/.mocharc.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = {} diff --git a/packages/errors/test/mocha.opts b/packages/errors/test/mocha.opts new file mode 100644 index 000000000000..13cc465f8dbc --- /dev/null +++ b/packages/errors/test/mocha.opts @@ -0,0 +1,4 @@ +test/unit +-r @packages/ts/register +--recursive +--extension=js,ts diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts new file mode 100644 index 000000000000..f9e9b9e9f5ca --- /dev/null +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -0,0 +1,691 @@ +import Bluebird from 'bluebird' +import chai, { expect } from 'chai' +/* eslint-disable no-console */ +import fse from 'fs-extra' +import globby from 'globby' +import isCi from 'is-ci' +import _ from 'lodash' +import path from 'path' +import sinon, { SinonSpy } from 'sinon' +import termToHtml from 'term-to-html' +import { terminalBanner } from 'terminal-banner' +import * as errors from '../../src' + +interface ErrorGenerator { + default: Parameters + [key: string]: Parameters +} + +type CypressErrorType = keyof typeof errors.AllCypressErrors + +chai.config.truncateThreshold = 0 +chai.use(require('@cypress/sinon-chai')) + +termToHtml.themes.dark.bg = '#111' + +let win + +const convertHtmlToImage = (htmlfile) => { + const { app, BrowserWindow } = require('electron') + + const HEIGHT = 550 + const WIDTH = 1200 + + return app.whenReady() + .then(() => { + if (!win) { + win = new BrowserWindow({ + show: false, + width: WIDTH, + height: HEIGHT, + }) + + win.webContents.debugger.attach() + } + + // win.once('ready-to-show', () => { + // win.webContents.openDevTools({ mode: 'bottom' }) + // }) + + return new Bluebird((resolve, reject) => { + win.webContents.once('did-finish-load', (evt) => { + // win.webContents.enableDeviceEmulation({ + // screenPosition: 'desktop', + // deviceScaleFactor: 1, + // viewSize: { width: 1200, height: 600 }, + // }) + + return win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { + width: WIDTH, + height: HEIGHT, + deviceScaleFactor: 1, + mobile: false, + }) + .then(() => { + return win.webContents.debugger.sendCommand('Page.captureScreenshot', { + format: 'png', + quality: 100, + }) + }) + // win.capturePage() + // .then((nativeImage) => { + .then(({ data }) => { + const imagefile = htmlfile.replace('.html', '.png') + + return Promise.all([ + fse.outputFile(imagefile, Buffer.from(data, 'base64')), + fse.remove(htmlfile), + ]) + .then(resolve) + }) + .catch(reject) + }) + + win.loadFile(htmlfile) + }) + .timeout(2000) + }) +} + +const outputHtmlFolder = path.join(__dirname, '..', '..', '__snapshot-images__') + +const saveHtml = async (filename, html) => { + await fse.outputFile(filename, html, 'utf8') +} + +const snapshotErrorConsoleLogs = function (errorFileName: string) { + const logs = _ + .chain(consoleLog.args) + .map((args) => { + return args.join(' ') + }) + .join('\n') + .value() + + // if the sanitized snapshot matches, let's save the ANSI colors converted into HTML + const html = termToHtml + .strings(logs, termToHtml.themes.dark.name) + .split('color:#00A').join('color:#2472c7') // replace blue colors + .split('color:#00A').join('color:#e05561') // replace red colors + .split('color:#A50').join('color:#e5e510') // replace yellow colors + .split('Courier New').join('MesloLGS NF') // replace font + .split('').join(` + body { + margin: 5px; + padding: 0; + } + pre { + white-space: pre-wrap; + word-break: break-word; + } + + `) // remove margin/padding and force text overflow like a terminal + + return saveHtml(errorFileName, html) +} + +let consoleLog: SinonSpy + +beforeEach(() => { + consoleLog = sinon.spy(console, 'log') +}) + +afterEach(() => { + sinon.restore() +}) + +const testVisualError = (errorGeneratorFn: () => ErrorGenerator, errorType: K) => { + it(errorType, () => { + const variants = errorGeneratorFn() + + expect(variants).to.be.an('object') + + if (isCi) { + return + } + + const snapshots = _.mapValues(variants, (arr, key: string) => { + const filename = key === 'default' ? errorType : `${errorType} - ${key}` + + terminalBanner(filename) + + consoleLog.resetHistory() + + const err = errors.get(errorType, ...arr) + + errors.log(err) + + const htmlFilename = path.join(outputHtmlFolder, `${filename }.html`) + + return snapshotErrorConsoleLogs(htmlFilename) + .then(() => { + return convertHtmlToImage(htmlFilename) + }) + }) + + return Bluebird.props(snapshots) + }) +} + +const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K in CypressErrorType]: () => ErrorGenerator}) => { + // if we aren't testing all the errors + if (whichError !== '*') { + // then just test this individual error + return testVisualError(errorsToTest[whichError], whichError) + } + + // otherwise test all the errors + before(() => { + // prune out all existing snapshot images in case + // errors were removed and we have stale snapshots + return fse.remove(outputHtmlFolder) + }) + + after(() => { + // if we're in CI, make sure there's all the files + // we expect there to be in __snapshot-images__ + if (isCi) { + return Bluebird.resolve() + .then(() => { + return globby(`${outputHtmlFolder}/*`) + }) + .map((file) => { + return path.basename(file, '.png').split(' ')[0] + }) + .then((errorImageNames) => { + const uniqErrors = _.uniq(errorImageNames) + + expect(uniqErrors).to.deep.eq(_.keys(errors.AllCypressErrors)) + }) + } + }) + + // test each error visually + _.forEach(errorsToTest, testVisualError) + + // if we are testing all the errors then make sure we + // have a test to validate that we've written a test + // for each error type + it('ensures there are matching tests for each cypress error', () => { + const { missingErrorTypes, excessErrorTypes } = _ + .chain(errors.AllCypressErrors) + .keys() + .thru((errorTypes) => { + const errorsToTestTypes = _.keys(errorsToTest) + + return { + missingErrorTypes: _.difference(errorTypes, errorsToTestTypes), + excessErrorTypes: _.difference(errorsToTestTypes, errorTypes), + } + }) + .value() + + expect(missingErrorTypes, 'you are missing tests around the following error types').to.be.empty + expect(excessErrorTypes, 'you have added excessive tests for errors which do not exist').to.be.empty + }) +} + +const makeErr = () => { + const err = new Error('fail whale') + + err.stack = err.stack.split('\n').slice(0, 5).join('\n') + + return err +} + +testVisualErrors('*', { +// testVisualErrors('CANNOT_TRASH_ASSETS', { + CANNOT_TRASH_ASSETS: () => { + const err = makeErr() + + return { + default: [err.stack], + } + }, + CANNOT_REMOVE_OLD_BROWSER_PROFILES: () => { + const err = makeErr() + + return { + default: [err.stack], + } + }, + VIDEO_RECORDING_FAILED: () => { + const err = makeErr() + + return { + default: [err.stack], + } + }, + VIDEO_POST_PROCESSING_FAILED: () => { + const err = makeErr() + + return { + default: [err.stack], + } + }, + CHROME_WEB_SECURITY_NOT_SUPPORTED: () => { + // const err = makeErr() + + // return { + // default: [err.stack], + // } + }, + BROWSER_NOT_FOUND_BY_NAME: () => { + + }, + BROWSER_NOT_FOUND_BY_PATH: () => { + const err = makeErr() + + return { + default: ['/path/does/not/exist', err.message], + } + }, + NOT_LOGGED_IN: () => { + return { + default: [], + } + }, + TESTS_DID_NOT_START_RETRYING: () => { + return { + default: ['Retrying...'], + retryingAgain: ['Retrying again...'], + } + }, + TESTS_DID_NOT_START_FAILED: () => { + return { + default: [], + } + }, + DASHBOARD_CANCEL_SKIPPED_SPEC: () => { + return { + default: [], + } + }, + DASHBOARD_API_RESPONSE_FAILED_RETRYING: () => { + + }, + DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: () => { + + }, + DASHBOARD_CANNOT_PROCEED_IN_SERIAL: () => { + + }, + DASHBOARD_UNKNOWN_INVALID_REQUEST: () => { + + }, + DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: () => { + + }, + DASHBOARD_STALE_RUN: () => { + + }, + DASHBOARD_ALREADY_COMPLETE: () => { + + }, + DASHBOARD_PARALLEL_REQUIRED: () => { + + }, + DASHBOARD_PARALLEL_DISALLOWED: () => { + + }, + DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: () => { + + }, + DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: () => { + + }, + DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { + return { + default: [], + } + }, + DUPLICATE_TASK_KEY: () => { + return { + default: ['foo, bar, baz'], + } + }, + INDETERMINATE_CI_BUILD_ID: () => { + + }, + RECORD_PARAMS_WITHOUT_RECORDING: () => { + + }, + INCORRECT_CI_BUILD_ID_USAGE: () => { + + }, + RECORD_KEY_MISSING: () => { + return { + default: [], + } + }, + CANNOT_RECORD_NO_PROJECT_ID: () => { + return { + default: ['/path/to/cypress.json'], + } + }, + PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION: () => { + return { + default: ['abc123'], + } + }, + DASHBOARD_INVALID_RUN_REQUEST: () => { + + }, + RECORDING_FROM_FORK_PR: () => { + return { + default: [], + } + }, + DASHBOARD_CANNOT_UPLOAD_RESULTS: () => { + const err = makeErr() + + return { + default: [err], + } + }, + DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: () => { + const err = makeErr() + + return { + default: [err], + } + }, + DASHBOARD_RECORD_KEY_NOT_VALID: () => { + + }, + DASHBOARD_PROJECT_NOT_FOUND: () => { + return { + default: ['abc123', '/path/to/cypress.json'], + } + }, + NO_PROJECT_ID: () => { + return { + default: ['cypress.json', '/path/to/project'], + } + }, + NO_PROJECT_FOUND_AT_PROJECT_ROOT: () => { + return { + default: ['/path/to/project'], + } + }, + CANNOT_FETCH_PROJECT_TOKEN: () => { + return { + default: [], + } + }, + CANNOT_CREATE_PROJECT_TOKEN: () => { + return { + default: [], + } + }, + PORT_IN_USE_SHORT: () => { + return { + default: [2020], + } + }, + PORT_IN_USE_LONG: () => { + return { + default: [2020], + } + }, + ERROR_READING_FILE: () => { + + }, + ERROR_WRITING_FILE: () => { + + }, + NO_SPECS_FOUND: () => { + + }, + RENDERER_CRASHED: () => { + return { + default: [], + } + }, + AUTOMATION_SERVER_DISCONNECTED: () => { + return { + default: [], + } + }, + SUPPORT_FILE_NOT_FOUND: () => { + return { + default: ['/path/to/supportFile', '/path/to/cypress.json'], + } + }, + PLUGINS_FILE_ERROR: () => { + const err = makeErr() + + return { + default: ['/path/to/pluginsFile', err], + } + }, + PLUGINS_DIDNT_EXPORT_FUNCTION: () => { + return { + default: ['/path/to/pluginsFile', () => 'some function'], + } + }, + PLUGINS_FUNCTION_ERROR: () => { + const err = makeErr() + + return { + default: ['/path/to/pluginsFile', err], + } + }, + PLUGINS_UNEXPECTED_ERROR: () => { + const err = makeErr() + + return { + default: ['/path/to/pluginsFile', err], + } + }, + PLUGINS_VALIDATION_ERROR: () => { + const err = makeErr() + + return { + default: ['/path/to/pluginsFile', err], + } + }, + BUNDLE_ERROR: () => { + const err = makeErr() + + return { + default: ['/path/to/file', err.message], + } + }, + SETTINGS_VALIDATION_ERROR: () => { + const err = makeErr() + + return { + default: ['/path/to/file', err.message], + } + }, + PLUGINS_CONFIG_VALIDATION_ERROR: () => { + const err = makeErr() + + return { + default: ['/path/to/pluginsFile', err.message], + } + }, + CONFIG_VALIDATION_ERROR: () => { + const err = makeErr() + + return { + default: [err.message], + } + }, + RENAMED_CONFIG_OPTION: () => { + + }, + CANNOT_CONNECT_BASE_URL: () => { + return { + default: [], + } + }, + CANNOT_CONNECT_BASE_URL_WARNING: () => { + return { + default: ['http://localhost:3000'], + } + }, + CANNOT_CONNECT_BASE_URL_RETRYING: () => { + + }, + INVALID_REPORTER_NAME: () => { + + }, + NO_DEFAULT_CONFIG_FILE_FOUND: () => { + return { + default: ['/path/to/project/root'], + } + }, + CONFIG_FILES_LANGUAGE_CONFLICT: () => { + + }, + CONFIG_FILE_NOT_FOUND: () => { + return { + default: ['cypress.json', '/path/to/project/root'], + } + }, + INVOKED_BINARY_OUTSIDE_NPM_MODULE: () => { + return { + default: [], + } + }, + FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { + + }, + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { + + }, + PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { + + }, + FREE_PLAN_EXCEEDS_MONTHLY_TESTS: () => { + + }, + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: () => { + + }, + PLAN_EXCEEDS_MONTHLY_TESTS: () => { + + }, + FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: () => { + + }, + PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { + + }, + PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: () => { + + }, + RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { + + }, + FIXTURE_NOT_FOUND: () => { + return { + default: ['file', ['js', 'ts', 'json']], + } + }, + AUTH_COULD_NOT_LAUNCH_BROWSER: () => { + return { + default: ['http://dashboard.cypress.io/login'], + } + }, + AUTH_BROWSER_LAUNCHED: () => { + return { + default: [], + } + }, + BAD_POLICY_WARNING: () => { + + }, + BAD_POLICY_WARNING_TOOLTIP: () => { + return { + default: [], + } + }, + EXTENSION_NOT_LOADED: () => { + + }, + COULD_NOT_FIND_SYSTEM_NODE: () => { + + }, + INVALID_CYPRESS_INTERNAL_ENV: () => { + return { + default: ['foo'], + } + }, + CDP_VERSION_TOO_OLD: () => { + + }, + CDP_COULD_NOT_CONNECT: () => { + + }, + FIREFOX_COULD_NOT_CONNECT: () => { + const err = makeErr() + + return { + default: [err], + } + }, + CDP_COULD_NOT_RECONNECT: () => { + const err = makeErr() + + return { + default: [err], + } + }, + CDP_RETRYING_CONNECTION: () => { + + }, + UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: () => { + + }, + COULD_NOT_PARSE_ARGUMENTS: () => { + + }, + FIREFOX_MARIONETTE_FAILURE: () => { + const err = makeErr() + + return { + default: ['connection', err], + } + }, + FOLDER_NOT_WRITABLE: () => { + return { + default: ['/path/to/folder'], + } + }, + EXPERIMENTAL_SAMESITE_REMOVED: () => { + return { + default: [], + } + }, + EXPERIMENTAL_COMPONENT_TESTING_REMOVED: () => { + + }, + EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { + return { + default: [], + } + }, + EXPERIMENTAL_NETWORK_STUBBING_REMOVED: () => { + return { + default: [], + } + }, + EXPERIMENTAL_RUN_EVENTS_REMOVED: () => { + return { + default: [], + } + }, + FIREFOX_GC_INTERVAL_REMOVED: () => { + return { + default: [], + } + }, + INCOMPATIBLE_PLUGIN_RETRIES: () => { + return { + default: ['./path/to/cypress-plugin-retries'], + } + }, +}) diff --git a/yarn.lock b/yarn.lock index 7bcbf1c3f479..ee716221ea18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13844,10 +13844,10 @@ chokidar@3.5.1: optionalDependencies: fsevents "~2.3.1" -"chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.2.3, chokidar@^3.3.0, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.0, chokidar@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== +chokidar@3.5.3, "chokidar@>=2.0.0 <4.0.0", chokidar@^3.0.0, chokidar@^3.2.3, chokidar@^3.3.0, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.0, chokidar@^3.5.1: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== dependencies: anymatch "~3.1.2" braces "~3.0.2" @@ -13935,10 +13935,10 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -ci-info@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.1.1.tgz#9a32fcefdf7bcdb6f0a7e1c0f8098ec57897b80a" - integrity sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ== +ci-info@^3.1.1, ci-info@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" + integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== cidr-regex@^2.0.10: version "2.0.10" @@ -16169,10 +16169,10 @@ de-indent@^1.0.2: resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= -debug@*, debug@4, debug@4.3.2, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@~4.3.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== +debug@*, debug@4, debug@4.3.3, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@~4.3.1: + version "4.3.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: ms "2.1.2" @@ -16232,6 +16232,13 @@ debug@4.3.1: dependencies: ms "2.1.2" +debug@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -17385,6 +17392,18 @@ electron-is-dev@^2.0.0: resolved "https://registry.npmjs.org/electron-is-dev/-/electron-is-dev-2.0.0.tgz#833487a069b8dad21425c67a19847d9064ab19bd" integrity sha512-3X99K852Yoqu9AcW50qz3ibYBWY79/pBhlMCab8ToEWS48R0T9tyxRiQhwylE7zQdXrMnx2JKqUJyMPmt5FBqA== +electron-mocha@^11.0.2: + version "11.0.2" + resolved "https://registry.yarnpkg.com/electron-mocha/-/electron-mocha-11.0.2.tgz#f8fd6c3af539f3c7a9aed4aba29cf12c3f408810" + integrity sha512-fOk+zUgSIsmL2cuIrd7IlK4eRhGVi1PYIB3QvqiBO+6f6AP8XLkYkT9eORlL2xwaS3yAAk02Y+4OTuhtqHPkEQ== + dependencies: + ansi-colors "^4.1.1" + electron-window "^0.8.0" + fs-extra "^10.0.0" + mocha "^9.1.1" + which "^2.0.2" + yargs "^16.2.0" + electron-notarize@^1.0.0, electron-notarize@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-1.1.1.tgz#3ed274b36158c1beb1dbef14e7faf5927e028629" @@ -17458,6 +17477,13 @@ electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.378, electron-to-chromi resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz#857e310ca00f0b75da4e1db6ff0e073cc4a91ddf" integrity sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg== +electron-window@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/electron-window/-/electron-window-0.8.1.tgz#16ca187eb4870b0679274fc8299c5960e6ab2c5e" + integrity sha1-FsoYfrSHCwZ5J0/IKZxZYOarLF4= + dependencies: + is-electron-renderer "^2.0.0" + electron@15.3.4: version "15.3.4" resolved "https://registry.yarnpkg.com/electron/-/electron-15.3.4.tgz#811e8872f4500b88ad49e005cbe8f93e10676f6d" @@ -22889,12 +22915,12 @@ is-ci@^1.0.10: dependencies: ci-info "^1.5.0" -is-ci@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" - integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ== +is-ci@^3.0.0, is-ci@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867" + integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ== dependencies: - ci-info "^3.1.1" + ci-info "^3.2.0" is-cidr@^3.0.0: version "3.1.1" @@ -22987,6 +23013,11 @@ is-dotfile@^1.0.0: resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= +is-electron-renderer@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-electron-renderer/-/is-electron-renderer-2.0.1.tgz#a469d056f975697c58c98c6023eb0aa79af895a2" + integrity sha1-pGnQVvl1aXxYyYxgI+sKp5r4laI= + is-equal-shallow@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" @@ -24553,7 +24584,7 @@ js-yaml@4.0.0: dependencies: argparse "^2.0.1" -js-yaml@^4.1.0: +js-yaml@4.1.0, js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -26296,7 +26327,7 @@ log-symbols@4.0.0: dependencies: chalk "^4.0.0" -log-symbols@^4.0.0, log-symbols@^4.1.0: +log-symbols@4.1.0, log-symbols@^4.0.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -27666,7 +27697,37 @@ mocha@7.1.2, mocha@^7.1.0: yargs-parser "13.1.2" yargs-unparser "1.6.0" -mocha@>=1.13.0, mocha@^8.1.1, mocha@^8.1.3: +mocha@>=1.13.0, mocha@^9.1.1: + version "9.2.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.0.tgz#2bfba73d46e392901f877ab9a47b7c9c5d0275cc" + integrity sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.3" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.2.0" + growl "1.10.5" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "3.0.4" + ms "2.1.3" + nanoid "3.2.0" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + which "2.0.2" + workerpool "6.2.0" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +mocha@^8.1.1, mocha@^8.1.3: version "8.3.1" resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.3.1.tgz#b9eda6da1eb8cb8d29860a9c2205de5b8a076560" integrity sha512-5SBMxANWqOv5bw3Hx+HVgaWlcWcFEQDUdaUAr1AUU+qwtx6cowhn7gEDT/DwQP7uYxnvShdUOVLbTYAHOEGfDQ== @@ -28025,10 +28086,10 @@ nanoid@3.1.20: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== -nanoid@^3.1.16, nanoid@^3.1.22, nanoid@^3.1.23: - version "3.1.23" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81" - integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw== +nanoid@3.2.0, nanoid@^3.1.16, nanoid@^3.1.22, nanoid@^3.1.23: + version "3.2.0" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.2.0.tgz#62667522da6673971cca916a6d3eff3f415ff80c" + integrity sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA== nanomatch@^1.2.9: version "1.2.13" @@ -35335,6 +35396,13 @@ serialize-javascript@5.0.1, serialize-javascript@^5.0.1: dependencies: randombytes "^2.1.0" +serialize-javascript@6.0.0, serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + serialize-javascript@^1.7.0: version "1.9.1" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb" @@ -35352,13 +35420,6 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" -serialize-javascript@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - serve-favicon@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.5.0.tgz#935d240cdfe0f5805307fdfe967d88942a2cbcf0" @@ -37724,7 +37785,7 @@ term-to-html@1.2.0: arg "4.1.3" escape-html "1.0.3" -terminal-banner@1.1.0: +terminal-banner@1.1.0, terminal-banner@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/terminal-banner/-/terminal-banner-1.1.0.tgz#ef81ce7d9d7e541a81d09eb2c0257c3d5463c3ea" integrity sha512-A70B8Io5gGOTKQuoqU6LUPLouNd9DvFLgw3cPh6bfrQjdy7HWW1t04VJfQwjTnygTVDX0xremaj1cg3SQaCGyg== @@ -41390,6 +41451,11 @@ workerpool@6.1.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.0.tgz#a8e038b4c94569596852de7a8ea4228eefdeb37b" integrity sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg== +workerpool@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b" + integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A== + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" From d2b2f8d2345d1ab9fb19644ca960c4bb6a5262af Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 3 Feb 2022 05:32:28 -0500 Subject: [PATCH 024/165] remove unused dep --- packages/runner-ct/package.json | 1 - packages/runner/package.json | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/runner-ct/package.json b/packages/runner-ct/package.json index 02536f7c6ff6..e9a7001d3539 100644 --- a/packages/runner-ct/package.json +++ b/packages/runner-ct/package.json @@ -26,7 +26,6 @@ "@types/http-proxy": "1.17.4", "@types/node": "14.14.31", "@types/sockjs-client": "1.1.0", - "ansi-to-html": "0.6.14", "babel-loader": "8.1.0", "bluebird": "3.5.3", "cash-dom": "^8.1.0", diff --git a/packages/runner/package.json b/packages/runner/package.json index cb33caafbb50..a1d7e4ca429c 100644 --- a/packages/runner/package.json +++ b/packages/runner/package.json @@ -33,7 +33,6 @@ "@packages/web-config": "0.0.0-development", "@reach/dialog": "0.10.5", "@reach/visually-hidden": "0.10.4", - "ansi-to-html": "0.6.14", "babel-plugin-prismjs": "1.0.2", "bluebird": "3.5.3", "chai": "4.2.0", From 7badd37db9506a1534b1c44fcd395fcd2b05f15c Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 3 Feb 2022 05:57:20 -0500 Subject: [PATCH 025/165] sanitize stack traces --- .../errors/test/unit/visualSnapshotErrors_spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index f9e9b9e9f5ca..bdcca172878b 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -25,6 +25,8 @@ termToHtml.themes.dark.bg = '#111' let win +const lineAndColNumsRe = /:\d+:\d+/ + const convertHtmlToImage = (htmlfile) => { const { app, BrowserWindow } = require('electron') @@ -93,11 +95,19 @@ const saveHtml = async (filename, html) => { await fse.outputFile(filename, html, 'utf8') } +const cypressRootPath = path.join(__dirname, '..', '..', '..', '..') + +const sanitize = (str: string) => { + return str + .split(lineAndColNumsRe).join('') + .split(cypressRootPath).join('cypress') +} + const snapshotErrorConsoleLogs = function (errorFileName: string) { const logs = _ .chain(consoleLog.args) .map((args) => { - return args.join(' ') + return args.map(sanitize).join(' ') }) .join('\n') .value() @@ -228,7 +238,7 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K const makeErr = () => { const err = new Error('fail whale') - err.stack = err.stack.split('\n').slice(0, 5).join('\n') + err.stack = err.stack.split('\n').slice(0, 4).join('\n') return err } From 9ff1ca47512f4d89dfc2453d2119a154aa348454 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 3 Feb 2022 13:27:05 -0500 Subject: [PATCH 026/165] 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 --- .gitignore | 3 + packages/errors/package.json | 6 +- .../test/unit/visualSnapshotErrors_spec.ts | 95 ++++++++++++++----- yarn.lock | 30 ++++-- 4 files changed, 102 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index d660a5be4b81..e12ffde75d41 100644 --- a/.gitignore +++ b/.gitignore @@ -83,6 +83,9 @@ system-tests/fixtures/large-img # from runner-ct /packages/runner-ct/cypress/screenshots +# from errors +/packages/errors/__snapshot-images__ + # graphql, auto-generated /packages/launchpad/src/generated /packages/app/src/generated diff --git a/packages/errors/package.json b/packages/errors/package.json index 1eb504b3dd0b..a58a6e1e7d8d 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -11,7 +11,7 @@ "check-ts": "tsc --noEmit", "clean-deps": "rm -rf node_modules", "clean": "rm -f ./src/*.js ./src/**/*.js ./src/**/**/*.js ./test/**/*.js || echo 'cleaned'", - "test-unit": "electron ./node_modules/.bin/_mocha --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json --exit" + "test-unit": "xvfb-maybe electron ./node_modules/.bin/_mocha --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json --exit" }, "dependencies": { "ansi_up": "5.0.0", @@ -23,9 +23,11 @@ "@types/strip-ansi": "^5.2.1", "chai": "4.2.0", "electron-mocha": "^11.0.2", - "globby": "^13.1.1", + "globby": "^11.0.1", "is-ci": "^3.0.1", "mocha": "7.0.1", + "pixelmatch": "^5.2.1", + "pngjs": "^6.0.0", "sinon": "7.5.0", "terminal-banner": "^1.1.0", "xvfb-maybe": "^0.2.1" diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index bdcca172878b..0a419a499f7a 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -1,11 +1,14 @@ import Bluebird from 'bluebird' import chai, { expect } from 'chai' +import { app, BrowserWindow } from 'electron' /* eslint-disable no-console */ import fse from 'fs-extra' import globby from 'globby' import isCi from 'is-ci' import _ from 'lodash' import path from 'path' +import pixelmatch from 'pixelmatch' +import { PNG } from 'pngjs' import sinon, { SinonSpy } from 'sinon' import termToHtml from 'term-to-html' import { terminalBanner } from 'terminal-banner' @@ -23,13 +26,17 @@ chai.use(require('@cypress/sinon-chai')) termToHtml.themes.dark.bg = '#111' -let win +let win: BrowserWindow const lineAndColNumsRe = /:\d+:\d+/ -const convertHtmlToImage = (htmlfile) => { - const { app, BrowserWindow } = require('electron') +const EXT = '.png' + +const copyImageToBase = (from, to) => { + return fse.copy(from, to, { overwrite: true }) +} +const convertHtmlToImage = (htmlfile) => { const HEIGHT = 550 const WIDTH = 1200 @@ -45,18 +52,8 @@ const convertHtmlToImage = (htmlfile) => { win.webContents.debugger.attach() } - // win.once('ready-to-show', () => { - // win.webContents.openDevTools({ mode: 'bottom' }) - // }) - return new Bluebird((resolve, reject) => { win.webContents.once('did-finish-load', (evt) => { - // win.webContents.enableDeviceEmulation({ - // screenPosition: 'desktop', - // deviceScaleFactor: 1, - // viewSize: { width: 1200, height: 600 }, - // }) - return win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { width: WIDTH, height: HEIGHT, @@ -69,15 +66,45 @@ const convertHtmlToImage = (htmlfile) => { quality: 100, }) }) - // win.capturePage() - // .then((nativeImage) => { .then(({ data }) => { - const imagefile = htmlfile.replace('.html', '.png') + const imagePath = htmlfile.replace('.html', EXT) + const baseImagePath = path.join(baseImageFolder, path.basename(imagePath)) + + const receivedImageBuffer = Buffer.from(data, 'base64') + const receivedPng = PNG.sync.read(receivedImageBuffer) + const receivedPngBuffer = PNG.sync.write(receivedPng) return Promise.all([ - fse.outputFile(imagefile, Buffer.from(data, 'base64')), + fse.outputFile(imagePath, receivedPngBuffer), fse.remove(htmlfile), ]) + .then(() => { + // - if image does not exist in __snapshot-bases__ + // then copy into __snapshot-bases__ + // - if image does exist then diff if, and if its + // greater than >.01 diff, then copy it in + // - unless we're in CI, then fail if there's a diff + return fse.readFile(baseImagePath) + .then((buf) => { + const existingPng = PNG.sync.read(buf) + const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) + + const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.1 }) + + if (changed) { + if (isCi) { + throw new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}`) + } + + return copyImageToBase(imagePath, baseImagePath) + } + }) + .catch((err) => { + if (err.code === 'ENOENT') { + return copyImageToBase(imagePath, baseImagePath) + } + }) + }) .then(resolve) }) .catch(reject) @@ -90,6 +117,7 @@ const convertHtmlToImage = (htmlfile) => { } const outputHtmlFolder = path.join(__dirname, '..', '..', '__snapshot-images__') +const baseImageFolder = path.join(__dirname, '..', '..', '__snapshot-bases__') const saveHtml = async (filename, html) => { await fse.outputFile(filename, html, 'utf8') @@ -150,10 +178,6 @@ const testVisualError = (errorGeneratorFn: () => Er expect(variants).to.be.an('object') - if (isCi) { - return - } - const snapshots = _.mapValues(variants, (arr, key: string) => { const filename = key === 'default' ? errorType : `${errorType} - ${key}` @@ -169,7 +193,9 @@ const testVisualError = (errorGeneratorFn: () => Er return snapshotErrorConsoleLogs(htmlFilename) .then(() => { - return convertHtmlToImage(htmlFilename) + if (process.env.SKIP_IMAGE_CONVERSION !== '1') { + return convertHtmlToImage(htmlFilename) + } }) }) @@ -200,7 +226,7 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K return globby(`${outputHtmlFolder}/*`) }) .map((file) => { - return path.basename(file, '.png').split(' ')[0] + return path.basename(file, EXT).split(' ')[0] }) .then((errorImageNames) => { const uniqErrors = _.uniq(errorImageNames) @@ -208,6 +234,29 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K expect(uniqErrors).to.deep.eq(_.keys(errors.AllCypressErrors)) }) } + + // otherwise if we're local, then prune out anything from + // __snapshot-bases__ that's stale + return Bluebird.resolve() + .then(() => { + return globby(`${baseImageFolder}/*`) + }) + .map((file) => { + return path.basename(file, EXT).split(' ')[0] + }) + .then((errorImageNames) => { + const excessImages = _ + .chain(errorImageNames) + .uniq() + .difference(_.keys(errors.AllCypressErrors)) + .value() + + return Bluebird.map(excessImages, (img) => { + const pathToImage = path.join(baseImageFolder, img + EXT) + + return fse.remove(pathToImage) + }) + }) }) // test each error visually diff --git a/yarn.lock b/yarn.lock index ee716221ea18..1e4ad55703ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13935,7 +13935,7 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -ci-info@^3.1.1, ci-info@^3.2.0: +ci-info@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2" integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw== @@ -19107,7 +19107,7 @@ fast-glob@3.1.1: merge2 "^1.3.0" micromatch "^4.0.2" -fast-glob@3.2.7, fast-glob@^3.0.3, fast-glob@^3.1.1, fast-glob@^3.2.4: +fast-glob@3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.7.tgz#fd6cb7a2d7e9aa7a7846111e85a196d6b2f766a1" integrity sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q== @@ -19130,6 +19130,17 @@ fast-glob@^2.0.2, fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" +fast-glob@^3.0.3, fast-glob@^3.1.1, fast-glob@^3.2.4: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-parse@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/fast-json-parse/-/fast-json-parse-1.0.3.tgz#43e5c61ee4efa9265633046b770fb682a7577c4d" @@ -22309,9 +22320,9 @@ ignore@^4.0.3, ignore@^4.0.6: integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== ignore@^5.1.1, ignore@^5.1.4: - version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" - integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== image-q@^1.1.1: version "1.1.1" @@ -30566,7 +30577,7 @@ pixelmatch@^4.0.2: dependencies: pngjs "^3.0.0" -pixelmatch@^5.1.0: +pixelmatch@^5.1.0, pixelmatch@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.2.1.tgz#9e4e4f4aa59648208a31310306a5bed5522b0d65" integrity sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ== @@ -30699,6 +30710,11 @@ pngjs@^4.0.1: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg== +pngjs@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821" + integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg== + pnp-webpack-plugin@1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.5.0.tgz#62a1cd3068f46d564bb33c56eb250e4d586676eb" @@ -41760,7 +41776,7 @@ xtend@~3.0.0: resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" integrity sha1-XM50B7r2Qsunvs2laBEcST9ZZlo= -xvfb-maybe@0.2.1: +xvfb-maybe@0.2.1, xvfb-maybe@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/xvfb-maybe/-/xvfb-maybe-0.2.1.tgz#ed8cb132957b7848b439984c66f010ea7f24361b" integrity sha1-7YyxMpV7eEi0OZhMZvAQ6n8kNhs= From 148220639a361dae7ca01a35d68e054b50b1d3b3 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 3 Feb 2022 18:49:00 -0500 Subject: [PATCH 027/165] remove Courier New + MesloLGS NF font --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 0a419a499f7a..cfa0567c22b8 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -146,7 +146,7 @@ const snapshotErrorConsoleLogs = function (errorFileName: string) { .split('color:#00A').join('color:#2472c7') // replace blue colors .split('color:#00A').join('color:#e05561') // replace red colors .split('color:#A50').join('color:#e5e510') // replace yellow colors - .split('Courier New').join('MesloLGS NF') // replace font + .split('"Courier New", ').join('') // replace font .split('').join(` body { margin: 5px; From a75689bb8348f5d7ffc965b5b65ac8cc171a2eb2 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 3 Feb 2022 14:23:52 -0500 Subject: [PATCH 028/165] type fixes, remove Bluebird, general cleanup --- .gitignore | 2 + package.json | 1 - packages/errors/package.json | 8 +- packages/errors/src/errors.ts | 74 ++- packages/errors/test/unit/errTemplate_spec.ts | 6 +- packages/errors/test/unit/errors_spec.ts | 8 +- .../test/unit/visualSnapshotErrors_spec.ts | 471 ++++++++++++------ packages/errors/tsconfig.json | 10 +- packages/server/lib/browsers/index.js | 2 + packages/server/lib/browsers/protocol.ts | 2 +- packages/server/lib/plugins/run_events.js | 2 +- packages/server/test/unit/errors_spec.js | 2 +- .../test/unit/plugins/run_events_spec.js | 2 +- yarn.lock | 54 +- 14 files changed, 413 insertions(+), 231 deletions(-) diff --git a/.gitignore b/.gitignore index e12ffde75d41..f55d29e87acf 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ packages/config/lib/*.js # from data-context, compiled .js files packages/data-context/src/**/*.js +packages/errors/src/**/*.js +packages/errors/test/**/*.js # from desktop-gui packages/desktop-gui/cypress/videos diff --git a/package.json b/package.json index 4fadd2896804..de31579cb37c 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,6 @@ "@types/sinon-chai": "3.2.3", "@typescript-eslint/eslint-plugin": "4.18.0", "@typescript-eslint/parser": "4.18.0", - "ansi-styles": "3.2.1", "arg": "4.1.2", "ascii-table": "0.0.9", "aws-sdk": "2.814.0", diff --git a/packages/errors/package.json b/packages/errors/package.json index a58a6e1e7d8d..11a3f04d359b 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -8,6 +8,7 @@ "test": "yarn test-unit", "build": "tsc", "build-prod": "tsc", + "postinstall": "rm -rf test/**/*.js", "check-ts": "tsc --noEmit", "clean-deps": "rm -rf node_modules", "clean": "rm -f ./src/*.js ./src/**/*.js ./src/**/**/*.js ./test/**/*.js || echo 'cleaned'", @@ -19,11 +20,16 @@ }, "devDependencies": { "@packages/ts": "0.0.0-development", + "@types/chai": "^4.3.0", + "@types/mocha": "8.0.3", "@types/node": "14.14.31", + "@types/pixelmatch": "^5.2.4", + "@types/pngjs": "^6.0.1", "@types/strip-ansi": "^5.2.1", + "ansi-styles": "^5", "chai": "4.2.0", "electron-mocha": "^11.0.2", - "globby": "^11.0.1", + "globby": "^11.1.0", "is-ci": "^3.0.1", "mocha": "7.0.1", "pixelmatch": "^5.2.1", diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 0d8c02bd6b1c..4e145d3f92bd 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -82,10 +82,10 @@ export const AllCypressErrors = { This option will not have an effect in ${guard(_.capitalize(browser))}. Tests that rely on web security being disabled will not run as expected.` }, - BROWSER_NOT_FOUND_BY_NAME: (arg1: string, arg2: string) => { + BROWSER_NOT_FOUND_BY_NAME: (browser: string, foundBrowsersStr: string) => { let canarySuffix = '' - if (arg1 === 'canary') { + if (browser === 'canary') { canarySuffix += '\n\n' canarySuffix += stripIndent`\ Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. @@ -96,7 +96,7 @@ export const AllCypressErrors = { return errTemplate`\ Can't run because you've entered an invalid browser name. - Browser: ${arg1} was not found on your system or is not supported by Cypress. + Browser: ${browser} was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - chrome @@ -108,7 +108,7 @@ export const AllCypressErrors = { You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: - ${guard(arg2)}${guard(canarySuffix)}` + ${guard(foundBrowsersStr)}${guard(canarySuffix)}` }, BROWSER_NOT_FOUND_BY_PATH: (arg1: string, arg2: string) => { return errTemplate`\ @@ -197,7 +197,7 @@ export const AllCypressErrors = { Details: ${JSON.stringify(arg1.props, null, 2)}` }, - DASHBOARD_STALE_RUN: (arg1: {runUrl: string}) => { + DASHBOARD_STALE_RUN: (arg1: {runUrl: string, [key: string]: any}) => { return errTemplate`\ You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago. @@ -437,7 +437,7 @@ export const AllCypressErrors = { This error will not alter the exit code.` }, - DASHBOARD_CANNOT_UPLOAD_RESULTS: (arg1: string) => { + DASHBOARD_CANNOT_UPLOAD_RESULTS: (arg1: string | Error) => { return errTemplate`\ Warning: We encountered an error while uploading results from your run. @@ -447,7 +447,7 @@ export const AllCypressErrors = { ${arg1}` }, - DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: (arg1: string) => { + DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: (arg1: string | Error) => { return errTemplate`\ Warning: We encountered an error talking to our servers. @@ -457,15 +457,15 @@ export const AllCypressErrors = { ${arg1}` }, - DASHBOARD_RECORD_KEY_NOT_VALID: (arg1: string, arg2: string) => { + DASHBOARD_RECORD_KEY_NOT_VALID: (recordKey: string, projectId: string) => { return errTemplate`\ - Your Record Key ${chalk.yellow(arg1)} is not valid with this project: ${chalk.blue(arg2)} + Your Record Key ${chalk.yellow(recordKey)} is not valid with this project: ${chalk.blue(projectId)} It may have been recently revoked by you or another user. Please log into the Dashboard to see the valid record keys. - https://on.cypress.io/dashboard/projects/${arg2}` + https://on.cypress.io/dashboard/projects/${guard(projectId)}` }, DASHBOARD_PROJECT_NOT_FOUND: (arg1: string, arg2: string) => { return errTemplate`\ @@ -493,34 +493,26 @@ export const AllCypressErrors = { CANNOT_CREATE_PROJECT_TOKEN: () => { return errTemplate`Can't create project's secret key.` }, - PORT_IN_USE_SHORT: (arg1: string) => { + PORT_IN_USE_SHORT: (arg1: string | number) => { return errTemplate`Port ${arg1} is already in use.` }, - PORT_IN_USE_LONG: (arg1: string) => { + PORT_IN_USE_LONG: (arg1: string | number) => { return errTemplate`\ - Can't run project because port is currently in use: ${chalk.blue(arg1)} + Can't run project because port is currently in use: ${arg1} - ${chalk.yellow('Assign a different port with the \'--port \' argument or shut down the other running process.')}` + ${chalk.yellow('Assign a different port with the \'--port \' argument or shut down the other running process.')}` }, - ERROR_READING_FILE: (arg1: string, arg2: Record) => { - let filePath = `${arg1}` - - let err = `\`${arg2.type || arg2.code || arg2.name}: ${arg2.message}\`` - + ERROR_READING_FILE: (filePath: string, err: Error) => { return errTemplate`\ - Error reading from: ${chalk.blue(filePath)} + Error reading from: ${filePath} - ${chalk.yellow(err)}` + ${details(err)}` }, - ERROR_WRITING_FILE: (arg1: string, arg2: string) => { - let filePath = `${arg1}` - - let err = `\`${arg2}\`` - + ERROR_WRITING_FILE: (filePath: string, err: Error) => { return errTemplate`\ - Error writing to: ${chalk.blue(filePath)} + Error writing to: ${filePath} - ${chalk.yellow(err)}` + ${details(err)}` }, NO_SPECS_FOUND: (arg1: string, arg2?: string | null) => { // no glob provided, searched all specs @@ -858,11 +850,9 @@ export const AllCypressErrors = { }, AUTH_COULD_NOT_LAUNCH_BROWSER: (arg1: string) => { return errTemplate`\ - Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser: + Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser: - \`\`\` - ${arg1} - \`\`\`` + ${arg1}` }, AUTH_BROWSER_LAUNCHED: () => { return errTemplate`Check your browser to continue logging in.` @@ -909,17 +899,17 @@ export const AllCypressErrors = { CDP_VERSION_TOO_OLD: (arg1: string, arg2: {major: number, minor: string | number}) => { return errTemplate`A minimum CDP version of v${guard(arg1)} is required, but the current browser has ${guard(arg2.major !== 0 ? `v${arg2.major}.${arg2.minor}` : 'an older version')}.` }, - CDP_COULD_NOT_CONNECT: (arg1: string, arg2: Error, arg3: string) => { + CDP_COULD_NOT_CONNECT: (arg1: string, arg2: string, arg3: Error) => { return errTemplate`\ Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds. - This usually indicates there was a problem opening the ${guard(arg3)} browser. + This usually indicates there was a problem opening the ${guard(arg1)} browser. - The CDP port requested was ${guard(chalk.yellow(arg1))}. + The CDP port requested was ${guard(chalk.yellow(arg2))}. Error details: - ${details(arg2)}` + ${details(arg3)}` }, FIREFOX_COULD_NOT_CONNECT: (arg1: Error) => { return errTemplate`\ @@ -960,15 +950,15 @@ export const AllCypressErrors = { The error was: ${arg3}` }, - FIREFOX_MARIONETTE_FAILURE: (arg1: string, arg2: string) => { + FIREFOX_MARIONETTE_FAILURE: (arg1: string, arg2: Error) => { return errTemplate`\ Cypress could not connect to Firefox. - An unexpected error was received from Marionette ${guard(arg1)}: + An unexpected error was received from Marionette ${guard(arg1)} - ${guard(arg2)} + To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running. - To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running.` + ${details(arg2)}` }, FOLDER_NOT_WRITABLE: (arg1: string) => { return errTemplate`\ @@ -1041,13 +1031,13 @@ export const AllCypressErrors = { https://on.cypress.io/configuration ` }, - PLUGINS_RUN_EVENT_ERROR: (arg1: string, arg2: string) => { + PLUGINS_RUN_EVENT_ERROR: (arg1: string, arg2: Error) => { return errTemplate`\ An error was thrown in your plugins file while executing the handler for the '${chalk.blue(arg1)}' event. The error we received was: - ${chalk.yellow(arg2)} + ${details(arg2)} ` }, CT_NO_DEV_START_EVENT: (arg1: string) => { diff --git a/packages/errors/test/unit/errTemplate_spec.ts b/packages/errors/test/unit/errTemplate_spec.ts index c54c14561f4c..08aeb25000f4 100644 --- a/packages/errors/test/unit/errTemplate_spec.ts +++ b/packages/errors/test/unit/errTemplate_spec.ts @@ -27,7 +27,7 @@ describe('errTemplate', () => { }) it('provides as details for toErrorProps', () => { - const errStack = new Error().stack + const errStack = new Error().stack ?? '' const obj = errTemplate` This was an error @@ -93,9 +93,9 @@ describe('errTemplate', () => { errTemplate` Hello world - ${details(new Error().stack)} + ${details(new Error())} - ${details(new Error().stack)} + ${details(new Error())} ` }).to.throw(/Cannot use details\(\) multiple times in the same errTemplate/) }) diff --git a/packages/errors/test/unit/errors_spec.ts b/packages/errors/test/unit/errors_spec.ts index ff058fe198b4..df22e5b4a508 100644 --- a/packages/errors/test/unit/errors_spec.ts +++ b/packages/errors/test/unit/errors_spec.ts @@ -3,18 +3,16 @@ import chalk from 'chalk' import style from 'ansi-styles' import snapshot from 'snap-shot-it' import sinon from 'sinon' +import 'sinon-chai' import * as errors from '../../src' import chai, { expect } from 'chai' chai.use(require('@cypress/sinon-chai')) -afterEach(() => { - sinon.restore() -}) - describe('lib/errors', () => { beforeEach(() => { + sinon.restore() sinon.stub(console, 'log') }) @@ -82,7 +80,7 @@ describe('lib/errors', () => { expect(ret).to.eq(err) - expect(console.log).to.be.calledWith(chalk.red(err.stack)) + expect(console.log).to.be.calledWith(chalk.red(err.stack ?? '')) }) }) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index cfa0567c22b8..878707add9a5 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -1,19 +1,27 @@ -import Bluebird from 'bluebird' import chai, { expect } from 'chai' import { app, BrowserWindow } from 'electron' /* eslint-disable no-console */ import fse from 'fs-extra' import globby from 'globby' -import isCi from 'is-ci' import _ from 'lodash' import path from 'path' import pixelmatch from 'pixelmatch' -import { PNG } from 'pngjs' import sinon, { SinonSpy } from 'sinon' -import termToHtml from 'term-to-html' -import { terminalBanner } from 'terminal-banner' +import { PNG } from 'pngjs' + import * as errors from '../../src' +// For importing the files below +process.env.CYPRESS_INTERNAL_ENV = 'test' + +// require'd so the unsafe types from the server / missing types don't mix in here +const termToHtml = require('term-to-html') +const isCi = require('is-ci') +const { terminalBanner } = require('terminal-banner') +const ciProvider = require('@packages/server/lib/util/ci_provider') +const browsers = require('@packages/server/lib/browsers') +const launcherBrowsers = require('@packages/launcher/lib/browsers') + interface ErrorGenerator { default: Parameters [key: string]: Parameters @@ -32,94 +40,96 @@ const lineAndColNumsRe = /:\d+:\d+/ const EXT = '.png' -const copyImageToBase = (from, to) => { +const copyImageToBase = (from: string, to: string) => { return fse.copy(from, to, { overwrite: true }) } -const convertHtmlToImage = (htmlfile) => { +const convertHtmlToImage = async (htmlfile: string) => { const HEIGHT = 550 const WIDTH = 1200 - return app.whenReady() - .then(() => { - if (!win) { - win = new BrowserWindow({ - show: false, - width: WIDTH, - height: HEIGHT, - }) + await app.whenReady() - win.webContents.debugger.attach() - } + if (!win) { + win = new BrowserWindow({ + show: false, + width: WIDTH, + height: HEIGHT, + }) + + win.webContents.debugger.attach() + } + + return new Promise((resolve, reject) => { + setTimeout(() => { + reject(new Error('Timed out creating Snapshot')) + }, 2000) - return new Bluebird((resolve, reject) => { - win.webContents.once('did-finish-load', (evt) => { - return win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { + win.webContents.once('did-finish-load', async () => { + try { + await win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { width: WIDTH, height: HEIGHT, deviceScaleFactor: 1, mobile: false, }) - .then(() => { - return win.webContents.debugger.sendCommand('Page.captureScreenshot', { - format: 'png', - quality: 100, - }) - }) - .then(({ data }) => { - const imagePath = htmlfile.replace('.html', EXT) - const baseImagePath = path.join(baseImageFolder, path.basename(imagePath)) - - const receivedImageBuffer = Buffer.from(data, 'base64') - const receivedPng = PNG.sync.read(receivedImageBuffer) - const receivedPngBuffer = PNG.sync.write(receivedPng) - - return Promise.all([ - fse.outputFile(imagePath, receivedPngBuffer), - fse.remove(htmlfile), - ]) - .then(() => { - // - if image does not exist in __snapshot-bases__ - // then copy into __snapshot-bases__ - // - if image does exist then diff if, and if its - // greater than >.01 diff, then copy it in - // - unless we're in CI, then fail if there's a diff - return fse.readFile(baseImagePath) - .then((buf) => { - const existingPng = PNG.sync.read(buf) - const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) - - const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.1 }) - - if (changed) { - if (isCi) { - throw new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}`) - } - - return copyImageToBase(imagePath, baseImagePath) - } - }) - .catch((err) => { - if (err.code === 'ENOENT') { - return copyImageToBase(imagePath, baseImagePath) - } - }) - }) - .then(resolve) + + const { data } = await win.webContents.debugger.sendCommand('Page.captureScreenshot', { + format: 'png', + quality: 100, }) - .catch(reject) - }) - win.loadFile(htmlfile) + const imagePath = htmlfile.replace('.html', EXT) + const baseImagePath = path.join(baseImageFolder, path.basename(imagePath)) + + const receivedImageBuffer = Buffer.from(data, 'base64') + const receivedPng = PNG.sync.read(receivedImageBuffer) + const receivedPngBuffer = PNG.sync.write(receivedPng) + + await Promise.all([ + fse.outputFile(imagePath, receivedPngBuffer), + fse.remove(htmlfile), + ]) + + // - if image does not exist in __snapshot-bases__ + // then copy into __snapshot-bases__ + // - if image does exist then diff if, and if its + // greater than >.01 diff, then copy it in + // - unless we're in CI, then fail if there's a diff + try { + const buf = await fse.readFile(baseImagePath) + const existingPng = PNG.sync.read(buf) + const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) + const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.1 }) + + if (changed) { + console.log({ changed }) + if (isCi) { + return reject(new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}`)) + } + + await copyImageToBase(imagePath, baseImagePath) + } + } catch (err: any) { + if (err.code === 'ENOENT') { + await copyImageToBase(imagePath, baseImagePath) + } + } finally { + resolve({}) + } + } catch (e) { + reject(e) + } }) - .timeout(2000) + + win.loadFile(htmlfile).catch(reject) }) } const outputHtmlFolder = path.join(__dirname, '..', '..', '__snapshot-images__') const baseImageFolder = path.join(__dirname, '..', '..', '__snapshot-bases__') -const saveHtml = async (filename, html) => { +const saveHtml = async (filename: string, html: string) => { await fse.outputFile(filename, html, 'utf8') } @@ -178,7 +188,11 @@ const testVisualError = (errorGeneratorFn: () => Er expect(variants).to.be.an('object') - const snapshots = _.mapValues(variants, (arr, key: string) => { + if (isCi) { + return + } + + const snapshots = _.mapValues(variants, async (arr, key: string) => { const filename = key === 'default' ? errorType : `${errorType} - ${key}` terminalBanner(filename) @@ -191,15 +205,14 @@ const testVisualError = (errorGeneratorFn: () => Er const htmlFilename = path.join(outputHtmlFolder, `${filename }.html`) - return snapshotErrorConsoleLogs(htmlFilename) - .then(() => { - if (process.env.SKIP_IMAGE_CONVERSION !== '1') { - return convertHtmlToImage(htmlFilename) - } - }) + await snapshotErrorConsoleLogs(htmlFilename) + + if (process.env.SKIP_IMAGE_CONVERSION !== '1') { + return convertHtmlToImage(htmlFilename) + } }) - return Bluebird.props(snapshots) + return Promise.all(Object.values(snapshots)) }) } @@ -217,49 +230,29 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K return fse.remove(outputHtmlFolder) }) - after(() => { + after(async () => { // if we're in CI, make sure there's all the files // we expect there to be in __snapshot-images__ if (isCi) { - return Bluebird.resolve() - .then(() => { - return globby(`${outputHtmlFolder}/*`) - }) - .map((file) => { - return path.basename(file, EXT).split(' ')[0] - }) - .then((errorImageNames) => { - const uniqErrors = _.uniq(errorImageNames) - - expect(uniqErrors).to.deep.eq(_.keys(errors.AllCypressErrors)) + const files = await globby(`${outputHtmlFolder}/*`) + const errorImageNames = files.map((file) => { + return path.basename(file, '.png').split(' ')[0] }) - } + const uniqErrors = _.uniq(errorImageNames) + const excessImages = _.difference(uniqErrors, _.keys(errors.AllCypressErrors)) - // otherwise if we're local, then prune out anything from - // __snapshot-bases__ that's stale - return Bluebird.resolve() - .then(() => { - return globby(`${baseImageFolder}/*`) - }) - .map((file) => { - return path.basename(file, EXT).split(' ')[0] - }) - .then((errorImageNames) => { - const excessImages = _ - .chain(errorImageNames) - .uniq() - .difference(_.keys(errors.AllCypressErrors)) - .value() - - return Bluebird.map(excessImages, (img) => { + await Promise.all(excessImages.map((img) => { const pathToImage = path.join(baseImageFolder, img + EXT) return fse.remove(pathToImage) - }) - }) + })) + + expect(uniqErrors).to.deep.eq(_.keys(errors.AllCypressErrors)) + } }) // test each error visually + // @ts-expect-error _.forEach(errorsToTest, testVisualError) // if we are testing all the errors then make sure we @@ -287,9 +280,9 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K const makeErr = () => { const err = new Error('fail whale') - err.stack = err.stack.split('\n').slice(0, 4).join('\n') + err.stack = err.stack?.split('\n').slice(0, 4).join('\n') ?? '' - return err + return err as Error & {stack: string} } testVisualErrors('*', { @@ -323,14 +316,15 @@ testVisualErrors('*', { } }, CHROME_WEB_SECURITY_NOT_SUPPORTED: () => { - // const err = makeErr() - - // return { - // default: [err.stack], - // } + return { + default: ['electron'], + } }, BROWSER_NOT_FOUND_BY_NAME: () => { - + return { + default: ['invalid-browser', browsers.formatBrowsersToOptions(launcherBrowsers.browsers)], + canary: ['canary', browsers.formatBrowsersToOptions(launcherBrowsers.browsers)], + } }, BROWSER_NOT_FOUND_BY_PATH: () => { const err = makeErr() @@ -361,37 +355,74 @@ testVisualErrors('*', { } }, DASHBOARD_API_RESPONSE_FAILED_RETRYING: () => { - + return { + default: [{ tries: 3, delay: 5000, response: '500 server down' }], + } }, DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: () => { - + return { + default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Invalid CI Build ID' }], + } }, DASHBOARD_CANNOT_PROCEED_IN_SERIAL: () => { - + return { + default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Invalid CI Build ID' }], + } }, DASHBOARD_UNKNOWN_INVALID_REQUEST: () => { - + return { + default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Unexpected 500 Error' }], + } }, DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: () => { - + return { + default: [{ props: { ciBuildId: 'invalid', group: 'foo' }, message: 'You have been warned' }], + } }, DASHBOARD_STALE_RUN: () => { - + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } }, DASHBOARD_ALREADY_COMPLETE: () => { - + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } }, DASHBOARD_PARALLEL_REQUIRED: () => { - + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } }, DASHBOARD_PARALLEL_DISALLOWED: () => { - + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } }, DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: () => { - + return { + default: [ + { + group: 'foo', + runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', + ciBuildId: 'test-ciBuildId-123', + parameters: { + osName: 'darwin', + osVersion: 'v1', + browserName: 'Electron', + browserVersion: '59.1.2.3', + specs: [ + 'cypress/integration/app_spec.js', + ], + }, + }, + ], + } }, DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: () => { - + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } }, DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { return { @@ -404,13 +435,22 @@ testVisualErrors('*', { } }, INDETERMINATE_CI_BUILD_ID: () => { - + return { + default: [ + { group: 'foo', parallel: 'false' }, + ciProvider.detectableCiBuildIdProviders(), + ], + } }, RECORD_PARAMS_WITHOUT_RECORDING: () => { - + return { + default: [{ parallel: 'true' }], + } }, INCORRECT_CI_BUILD_ID_USAGE: () => { - + return { + default: [{ ciBuildId: 'ciBuildId123' }], + } }, RECORD_KEY_MISSING: () => { return { @@ -428,7 +468,9 @@ testVisualErrors('*', { } }, DASHBOARD_INVALID_RUN_REQUEST: () => { - + return { + default: [{ message: 'Error on Run Request', errors: [], object: {} }], + } }, RECORDING_FROM_FORK_PR: () => { return { @@ -450,7 +492,9 @@ testVisualErrors('*', { } }, DASHBOARD_RECORD_KEY_NOT_VALID: () => { - + return { + default: ['record-key-1234', 'projectId'], + } }, DASHBOARD_PROJECT_NOT_FOUND: () => { return { @@ -488,13 +532,20 @@ testVisualErrors('*', { } }, ERROR_READING_FILE: () => { - + return { + default: ['/path/to/read/file.ts', makeErr()], + } }, ERROR_WRITING_FILE: () => { - + return { + default: ['path/to/write/file.ts', makeErr()], + } }, NO_SPECS_FOUND: () => { - + return { + default: ['/path/to/project/root', '**_spec.js'], + noPattern: ['/path/to/project/root'], + } }, RENDERER_CRASHED: () => { return { @@ -573,7 +624,9 @@ testVisualErrors('*', { } }, RENAMED_CONFIG_OPTION: () => { - + return { + default: [{ name: 'oldName', newName: 'newName' }], + } }, CANNOT_CONNECT_BASE_URL: () => { return { @@ -586,10 +639,18 @@ testVisualErrors('*', { } }, CANNOT_CONNECT_BASE_URL_RETRYING: () => { - + return { + default: [{ attempt: 0, baseUrl: 'http://localhost:3000', remaining: 60, delay: 500 }], + } }, INVALID_REPORTER_NAME: () => { - + return { + default: [{ + name: 'missing-reporter-name', + paths: ['/path/to/reporter', '/path/reporter'], + error: `stack-trace`, + }], + } }, NO_DEFAULT_CONFIG_FILE_FOUND: () => { return { @@ -597,7 +658,13 @@ testVisualErrors('*', { } }, CONFIG_FILES_LANGUAGE_CONFLICT: () => { - + return { + default: [ + 'cypress.config.js', + 'cypress.config.ts', + '/path/to/project/root', + ], + } }, CONFIG_FILE_NOT_FOUND: () => { return { @@ -610,34 +677,63 @@ testVisualErrors('*', { } }, FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { - + return { + default: [{ + link: 'https://dashboard.cypress.io/project/abcd', + planType: 'Test Plan', + usedTestsMessage: 'The limit is 500 free results', + }], + } }, FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { - + return { + default: [{ + link: 'https://dashboard.cypress.io/project/abcd', + planType: 'Grace Period Plan', + usedTestsMessage: 'The limit is 500 free results', + gracePeriodMessage: 'You are on a grace period for 20 days', + }], + } }, PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { - + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], + } }, FREE_PLAN_EXCEEDS_MONTHLY_TESTS: () => { - + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], + } }, FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: () => { - + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results', gracePeriodMessage: 'Feb 1, 2022' }], + } }, PLAN_EXCEEDS_MONTHLY_TESTS: () => { - + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], + } }, FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: () => { - + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', gracePeriodMessage: 'Feb 1, 2022' }], + } }, PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { - + return { + default: [{ link: 'https://on.cypress.io/set-up-billing' }], + } }, PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: () => { - + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', gracePeriodMessage: 'Feb 1, 2022' }], + } }, RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { - + return { + default: [{ link: 'https://on.cypress.io/set-up-billing' }], + } }, FIXTURE_NOT_FOUND: () => { return { @@ -655,7 +751,12 @@ testVisualErrors('*', { } }, BAD_POLICY_WARNING: () => { - + return { + default: [[ + 'HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome\\ProxyServer', + 'HKEY_CURRENT_USER\\Software\\Policies\\Google\\Chromium\\ExtensionSettings', + ]], + } }, BAD_POLICY_WARNING_TOOLTIP: () => { return { @@ -663,10 +764,14 @@ testVisualErrors('*', { } }, EXTENSION_NOT_LOADED: () => { - + return { + default: ['Electron', '/path/to/extension'], + } }, COULD_NOT_FIND_SYSTEM_NODE: () => { - + return { + default: ['16.2.1'], + } }, INVALID_CYPRESS_INTERNAL_ENV: () => { return { @@ -674,10 +779,14 @@ testVisualErrors('*', { } }, CDP_VERSION_TOO_OLD: () => { - + return { + default: ['89', { major: 90, minor: 2 }], + } }, CDP_COULD_NOT_CONNECT: () => { - + return { + default: ['chrome', '2345', makeErr()], + } }, FIREFOX_COULD_NOT_CONNECT: () => { const err = makeErr() @@ -694,13 +803,21 @@ testVisualErrors('*', { } }, CDP_RETRYING_CONNECTION: () => { - + return { + default: [1, 'chrome'], + } }, UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: () => { - + return { + default: [ + ['baz'], ['preferences', 'extensions', 'args'], + ], + } }, COULD_NOT_PARSE_ARGUMENTS: () => { - + return { + default: ['spec', '1', 'spec must be a string or comma-separated list'], + } }, FIREFOX_MARIONETTE_FAILURE: () => { const err = makeErr() @@ -720,7 +837,9 @@ testVisualErrors('*', { } }, EXPERIMENTAL_COMPONENT_TESTING_REMOVED: () => { - + return { + default: [{ configFile: '/path/to/configFile.json' }], + } }, EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { return { @@ -747,4 +866,34 @@ testVisualErrors('*', { default: ['./path/to/cypress-plugin-retries'], } }, + NODE_VERSION_DEPRECATION_BUNDLED: () => { + return { + default: [{ name: 'nodeVersion', value: 'bundled', 'configFile': 'cypress.json' }], + } + }, + NODE_VERSION_DEPRECATION_SYSTEM: () => { + return { + default: [{ name: 'nodeVersion', value: 'system', 'configFile': 'cypress.json' }], + } + }, + CT_NO_DEV_START_EVENT: () => { + return { + default: ['/path/to/plugins/file.js'], + } + }, + PLUGINS_RUN_EVENT_ERROR: () => { + return { + default: ['before:spec', makeErr()], + } + }, + INVALID_CONFIG_OPTION: () => { + return { + default: [['test', 'foo']], + } + }, + UNSUPPORTED_BROWSER_VERSION: () => { + return { + default: [`Cypress does not support running chrome version 64. To use chrome with Cypress, install a version of chrome newer than or equal to 64.`], + } + }, }) diff --git a/packages/errors/tsconfig.json b/packages/errors/tsconfig.json index dc41270918bc..2f71b11b5a46 100644 --- a/packages/errors/tsconfig.json +++ b/packages/errors/tsconfig.json @@ -1,24 +1,24 @@ { "extends": "../ts/tsconfig.json", "include": [ - "src" + "src", + "test", ], "exclude": [ - "test", "script" ], "compilerOptions": { "strict": true, "allowJs": false, - "rootDir": "src", - "outDir": "dist", "noImplicitAny": true, "resolveJsonModule": true, "experimentalDecorators": true, + "noImplicitReturns": false, "noUncheckedIndexedAccess": true, "importsNotUsedAsValues": "error", "types": [ - "node" + "node", + "mocha" ] } } \ No newline at end of file diff --git a/packages/server/lib/browsers/index.js b/packages/server/lib/browsers/index.js index b0b1223d2543..3ae32bc3ebeb 100644 --- a/packages/server/lib/browsers/index.js +++ b/packages/server/lib/browsers/index.js @@ -155,6 +155,8 @@ module.exports = { close: kill, + formatBrowsersToOptions, + _setInstance (_instance) { // for testing instance = _instance diff --git a/packages/server/lib/browsers/protocol.ts b/packages/server/lib/browsers/protocol.ts index bf6054ecea4c..fadacc9ada70 100644 --- a/packages/server/lib/browsers/protocol.ts +++ b/packages/server/lib/browsers/protocol.ts @@ -128,6 +128,6 @@ export const getWsTargetFor = (port: number, browserName: string) => { }) .catch((err) => { debug('failed to connect to CDP %o', { connectOpts, err }) - errors.throw('CDP_COULD_NOT_CONNECT', port, err, browserName) + errors.throw('CDP_COULD_NOT_CONNECT', browserName, port, err) }) } diff --git a/packages/server/lib/plugins/run_events.js b/packages/server/lib/plugins/run_events.js index 6f73c8bfab7c..6c3e3192a69f 100644 --- a/packages/server/lib/plugins/run_events.js +++ b/packages/server/lib/plugins/run_events.js @@ -11,7 +11,7 @@ module.exports = { .catch((err) => { err = err || {} - errors.throw('PLUGINS_RUN_EVENT_ERROR', eventName, err.stack || err.message || err) + errors.throw('PLUGINS_RUN_EVENT_ERROR', eventName, err) }) }), } diff --git a/packages/server/test/unit/errors_spec.js b/packages/server/test/unit/errors_spec.js index cd7d90ef0f7e..a5ca69ddba49 100644 --- a/packages/server/test/unit/errors_spec.js +++ b/packages/server/test/unit/errors_spec.js @@ -15,7 +15,7 @@ context('.logException', () => { return errors.logException(err) .then(() => { - expect(console.log).to.be.calledWith(chalk.red(err.stack)) + expect(console.log).to.be.calledWith(chalk.red(err.stack ?? '')) expect(logger.createException).to.be.calledWith(err) }) diff --git a/packages/server/test/unit/plugins/run_events_spec.js b/packages/server/test/unit/plugins/run_events_spec.js index a5503a565e8b..678450573983 100644 --- a/packages/server/test/unit/plugins/run_events_spec.js +++ b/packages/server/test/unit/plugins/run_events_spec.js @@ -45,7 +45,7 @@ describe('lib/plugins/run_events', () => { return runEvents.execute('before:spec', {}, 'arg1', 'arg2') .then(() => { - expect(errors.throw).to.be.calledWith('PLUGINS_RUN_EVENT_ERROR', 'before:spec', 'The event threw an error') + expect(errors.throw).to.be.calledWith('PLUGINS_RUN_EVENT_ERROR', 'before:spec', { name: 'Error', message: 'The event threw an error' }) }) }) }) diff --git a/yarn.lock b/yarn.lock index 1e4ad55703ff..2e15185fe22b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7736,6 +7736,11 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.15.tgz#b7a6d263c2cecf44b6de9a051cf496249b154553" integrity sha512-rYff6FI+ZTKAPkJUoyz7Udq3GaoDZnxYDEvdEdFZASiA7PoErltHezDishqQiSDWrGxvxmplH304jyzQmjp0AQ== +"@types/chai@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc" + integrity sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw== + "@types/chalk@2.2.0", "@types/chalk@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.0.tgz#b7f6e446f4511029ee8e3f43075fb5b73fbaa0ba" @@ -8256,6 +8261,13 @@ resolved "https://registry.yarnpkg.com/@types/parsimmon/-/parsimmon-1.10.6.tgz#8fcf95990514d2a7624aa5f630c13bf2427f9cdd" integrity sha512-FwAQwMRbkhx0J6YELkwIpciVzCcgEqXEbIrIn3a2P5d3kGEHQ3wVhlN3YdVepYP+bZzCYO6OjmD4o9TGOZ40rA== +"@types/pixelmatch@^5.2.4": + version "5.2.4" + resolved "https://registry.yarnpkg.com/@types/pixelmatch/-/pixelmatch-5.2.4.tgz#ca145cc5ede1388c71c68edf2d1f5190e5ddd0f6" + integrity sha512-HDaSHIAv9kwpMN7zlmwfTv6gax0PiporJOipcrGsVNF3Ba+kryOZc0Pio5pn6NhisgWr7TaajlPEKTbTAypIBQ== + dependencies: + "@types/node" "*" + "@types/plist@^3.0.1": version "3.0.2" resolved "https://registry.yarnpkg.com/@types/plist/-/plist-3.0.2.tgz#61b3727bba0f5c462fe333542534a0c3e19ccb01" @@ -8264,6 +8276,13 @@ "@types/node" "*" xmlbuilder ">=11.0.1" +"@types/pngjs@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-6.0.1.tgz#c711ec3fbbf077fed274ecccaf85dd4673130072" + integrity sha512-J39njbdW1U/6YyVXvC9+1iflZghP8jgRf2ndYghdJb5xL49LYDB+1EuAxfbuJ2IBbWIL3AjHPQhgaTxT3YaYeg== + dependencies: + "@types/node" "*" + "@types/pretty-hrtime@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/pretty-hrtime/-/pretty-hrtime-1.0.0.tgz#c5a2d644a135e988b2932f99737e67b3c62528d0" @@ -10294,18 +10313,18 @@ ansi-regex@^6.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== -ansi-styles@3.2.1, ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -10313,6 +10332,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +ansi-styles@^5: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + ansi-styles@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.0.0.tgz#cb102df1c56f5123eab8b67cd7b98027a0279178" @@ -19130,7 +19154,7 @@ fast-glob@^2.0.2, fast-glob@^2.2.6: merge2 "^1.2.3" micromatch "^3.1.10" -fast-glob@^3.0.3, fast-glob@^3.1.1, fast-glob@^3.2.4: +fast-glob@^3.0.3, fast-glob@^3.1.1, fast-glob@^3.2.4, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== @@ -21026,6 +21050,18 @@ globby@8.0.2: pify "^3.0.0" slash "^1.0.0" +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + globby@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" @@ -22319,7 +22355,7 @@ ignore@^4.0.3, ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.1.1, ignore@^5.1.4: +ignore@^5.1.1, ignore@^5.1.4, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== @@ -26995,7 +27031,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.2.3, merge2@^1.3.0: +merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== From 09d5c86a3cd21a2bd6260f7c479245d15049e178 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 3 Feb 2022 15:51:26 -0500 Subject: [PATCH 029/165] TS Cleanup --- packages/errors/package.json | 4 ++-- .../test/unit/visualSnapshotErrors_spec.ts | 20 +++++++++---------- packages/errors/tsconfig.json | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/errors/package.json b/packages/errors/package.json index 11a3f04d359b..23bb954b85ac 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -6,13 +6,13 @@ "browser": "src/index.ts", "scripts": { "test": "yarn test-unit", - "build": "tsc", + "build": "tsc || echo 'type errors'", "build-prod": "tsc", "postinstall": "rm -rf test/**/*.js", "check-ts": "tsc --noEmit", "clean-deps": "rm -rf node_modules", "clean": "rm -f ./src/*.js ./src/**/*.js ./src/**/**/*.js ./test/**/*.js || echo 'cleaned'", - "test-unit": "xvfb-maybe electron ./node_modules/.bin/_mocha --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json --exit" + "test-unit": "yarn clean && xvfb-maybe electron --no-sandbox ./node_modules/.bin/_mocha --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json --exit" }, "dependencies": { "ansi_up": "5.0.0", diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 878707add9a5..6df7f379b893 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -105,7 +105,7 @@ const convertHtmlToImage = async (htmlfile: string) => { if (changed) { console.log({ changed }) if (isCi) { - return reject(new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}`)) + return reject(new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}, off by ${changed} pixels`)) } await copyImageToBase(imagePath, baseImagePath) @@ -183,7 +183,7 @@ afterEach(() => { }) const testVisualError = (errorGeneratorFn: () => ErrorGenerator, errorType: K) => { - it(errorType, () => { + it(errorType, async () => { const variants = errorGeneratorFn() expect(variants).to.be.an('object') @@ -192,7 +192,7 @@ const testVisualError = (errorGeneratorFn: () => Er return } - const snapshots = _.mapValues(variants, async (arr, key: string) => { + for (const [key, arr] of Object.entries(variants)) { const filename = key === 'default' ? errorType : `${errorType} - ${key}` terminalBanner(filename) @@ -208,19 +208,19 @@ const testVisualError = (errorGeneratorFn: () => Er await snapshotErrorConsoleLogs(htmlFilename) if (process.env.SKIP_IMAGE_CONVERSION !== '1') { - return convertHtmlToImage(htmlFilename) + await convertHtmlToImage(htmlFilename) } - }) - - return Promise.all(Object.values(snapshots)) + } }) } -const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K in CypressErrorType]: () => ErrorGenerator}) => { +const testVisualErrors = (whichError: CypressErrorType[] | '*', errorsToTest: {[K in CypressErrorType]: () => ErrorGenerator}) => { // if we aren't testing all the errors if (whichError !== '*') { - // then just test this individual error - return testVisualError(errorsToTest[whichError], whichError) + for (const errorToTest of whichError) { + // then just test this individual error + return testVisualError(errorsToTest[errorToTest], errorToTest) + } } // otherwise test all the errors diff --git a/packages/errors/tsconfig.json b/packages/errors/tsconfig.json index 2f71b11b5a46..38db667bebbc 100644 --- a/packages/errors/tsconfig.json +++ b/packages/errors/tsconfig.json @@ -18,7 +18,7 @@ "importsNotUsedAsValues": "error", "types": [ "node", - "mocha" + "@types/mocha" ] } } \ No newline at end of file From 7f7a1aefbc71364d2e41f9c11b2369c2f951561c Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 3 Feb 2022 16:00:12 -0500 Subject: [PATCH 030/165] skip typecheck on tests for now --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 4 +++- packages/errors/tsconfig.json | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 6df7f379b893..b22cdb5c7cef 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -111,8 +111,10 @@ const convertHtmlToImage = async (htmlfile: string) => { await copyImageToBase(imagePath, baseImagePath) } } catch (err: any) { - if (err.code === 'ENOENT') { + if (err.code === 'ENOENT' && !isCi) { await copyImageToBase(imagePath, baseImagePath) + } else { + throw err } } finally { resolve({}) diff --git a/packages/errors/tsconfig.json b/packages/errors/tsconfig.json index 38db667bebbc..45fd51b844ee 100644 --- a/packages/errors/tsconfig.json +++ b/packages/errors/tsconfig.json @@ -2,10 +2,10 @@ "extends": "../ts/tsconfig.json", "include": [ "src", - "test", ], "exclude": [ - "script" + "script", + "test", ], "compilerOptions": { "strict": true, @@ -18,7 +18,7 @@ "importsNotUsedAsValues": "error", "types": [ "node", - "@types/mocha" + "mocha" ] } } \ No newline at end of file From ed39d17102f9ddf4eabe403684d6c67c9b75602d Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 3 Feb 2022 16:00:36 -0500 Subject: [PATCH 031/165] yarn.lock --- yarn.lock | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2e15185fe22b..5e32e39fcfa8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7731,16 +7731,16 @@ "@types/chai" "*" "@types/jquery" "*" -"@types/chai@*", "@types/chai@4.2.15": - version "4.2.15" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.15.tgz#b7a6d263c2cecf44b6de9a051cf496249b154553" - integrity sha512-rYff6FI+ZTKAPkJUoyz7Udq3GaoDZnxYDEvdEdFZASiA7PoErltHezDishqQiSDWrGxvxmplH304jyzQmjp0AQ== - -"@types/chai@^4.3.0": +"@types/chai@*", "@types/chai@^4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc" integrity sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw== +"@types/chai@4.2.15": + version "4.2.15" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.15.tgz#b7a6d263c2cecf44b6de9a051cf496249b154553" + integrity sha512-rYff6FI+ZTKAPkJUoyz7Udq3GaoDZnxYDEvdEdFZASiA7PoErltHezDishqQiSDWrGxvxmplH304jyzQmjp0AQ== + "@types/chalk@2.2.0", "@types/chalk@^2.2.0": version "2.2.0" resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-2.2.0.tgz#b7f6e446f4511029ee8e3f43075fb5b73fbaa0ba" @@ -21025,7 +21025,7 @@ globby@11.0.1: merge2 "^1.3.0" slash "^3.0.0" -globby@11.0.3, globby@^11.0.0, globby@^11.0.1, globby@^11.0.2: +globby@11.0.3: version "11.0.3" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb" integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg== @@ -21050,7 +21050,7 @@ globby@8.0.2: pify "^3.0.0" slash "^1.0.0" -globby@^11.1.0: +globby@^11.0.0, globby@^11.0.1, globby@^11.0.2, globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== From a09795591da55283a204ef3f77889bc5f2952b54 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 3 Feb 2022 16:20:37 -0500 Subject: [PATCH 032/165] fix @types/chai version --- packages/errors/package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/errors/package.json b/packages/errors/package.json index 23bb954b85ac..6679f6d87adf 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -20,7 +20,7 @@ }, "devDependencies": { "@packages/ts": "0.0.0-development", - "@types/chai": "^4.3.0", + "@types/chai": "4.2.15", "@types/mocha": "8.0.3", "@types/node": "14.14.31", "@types/pixelmatch": "^5.2.4", diff --git a/yarn.lock b/yarn.lock index 5e32e39fcfa8..e2e4c2260002 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7731,7 +7731,7 @@ "@types/chai" "*" "@types/jquery" "*" -"@types/chai@*", "@types/chai@^4.3.0": +"@types/chai@*": version "4.3.0" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc" integrity sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw== From 73e88bc27d03a578841d0df2695b9be9bd44fc35 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 3 Feb 2022 16:29:18 -0500 Subject: [PATCH 033/165] fix yarn.lock --- yarn.lock | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index e2e4c2260002..2b5d57ee2104 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7731,12 +7731,7 @@ "@types/chai" "*" "@types/jquery" "*" -"@types/chai@*": - version "4.3.0" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.0.tgz#23509ebc1fa32f1b4d50d6a66c4032d5b8eaabdc" - integrity sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw== - -"@types/chai@4.2.15": +"@types/chai@*", "@types/chai@4.2.15": version "4.2.15" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.15.tgz#b7a6d263c2cecf44b6de9a051cf496249b154553" integrity sha512-rYff6FI+ZTKAPkJUoyz7Udq3GaoDZnxYDEvdEdFZASiA7PoErltHezDishqQiSDWrGxvxmplH304jyzQmjp0AQ== From 7aa1f87b6a58a3e1b47da5fb83dffc04421e1f46 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 3 Feb 2022 18:27:08 -0500 Subject: [PATCH 034/165] Different version of mocha types so it isnt patched --- packages/errors/package.json | 2 +- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 4 ++-- packages/errors/tsconfig.json | 2 +- yarn.lock | 7 ++++++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/errors/package.json b/packages/errors/package.json index 6679f6d87adf..8fbb95e48ab6 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -21,7 +21,7 @@ "devDependencies": { "@packages/ts": "0.0.0-development", "@types/chai": "4.2.15", - "@types/mocha": "8.0.3", + "@types/mocha": "8.2.2", "@types/node": "14.14.31", "@types/pixelmatch": "^5.2.4", "@types/pngjs": "^6.0.1", diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index b22cdb5c7cef..057686a8e430 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -111,10 +111,10 @@ const convertHtmlToImage = async (htmlfile: string) => { await copyImageToBase(imagePath, baseImagePath) } } catch (err: any) { - if (err.code === 'ENOENT' && !isCi) { + if (err.code === 'ENOENT') { await copyImageToBase(imagePath, baseImagePath) } else { - throw err + reject(err) } } finally { resolve({}) diff --git a/packages/errors/tsconfig.json b/packages/errors/tsconfig.json index 45fd51b844ee..842a64e22c9f 100644 --- a/packages/errors/tsconfig.json +++ b/packages/errors/tsconfig.json @@ -2,10 +2,10 @@ "extends": "../ts/tsconfig.json", "include": [ "src", + "test", ], "exclude": [ "script", - "test", ], "compilerOptions": { "strict": true, diff --git a/yarn.lock b/yarn.lock index 2b5d57ee2104..60f603c97bdb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8147,11 +8147,16 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.1.tgz#283f669ff76d7b8260df8ab7a4262cc83d988256" integrity sha512-fZQQafSREFyuZcdWFAExYjBiCL7AUCdgsk80iO0q4yihYYdcIiH28CcuPTGFgLOCC8RlW49GSQxdHwZP+I7CNg== -"@types/mocha@8.0.3", "@types/mocha@^8.0.2", "@types/mocha@^8.0.3": +"@types/mocha@8.0.3": version "8.0.3" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.0.3.tgz#51b21b6acb6d1b923bbdc7725c38f9f455166402" integrity sha512-vyxR57nv8NfcU0GZu8EUXZLTbCMupIUwy95LJ6lllN+JRPG25CwMHoB1q5xKh8YKhQnHYRAn4yW2yuHbf/5xgg== +"@types/mocha@8.2.2", "@types/mocha@^8.0.2", "@types/mocha@^8.0.3": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.2.tgz#91daa226eb8c2ff261e6a8cbf8c7304641e095e0" + integrity sha512-Lwh0lzzqT5Pqh6z61P3c3P5nm6fzQK/MMHl9UKeneAeInVflBSz1O2EkX6gM6xfJd7FBXBY5purtLx7fUiZ7Hw== + "@types/mocha@9.0.0": version "9.0.0" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-9.0.0.tgz#3205bcd15ada9bc681ac20bef64e9e6df88fd297" From f35a3e67761cb013d8b8f8cf8571ef6ad6194226 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 3 Feb 2022 18:45:20 -0500 Subject: [PATCH 035/165] errors spec snapshot --- packages/server/__snapshots__/errors_spec.ts.js | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/server/__snapshots__/errors_spec.ts.js diff --git a/packages/server/__snapshots__/errors_spec.ts.js b/packages/server/__snapshots__/errors_spec.ts.js new file mode 100644 index 000000000000..c809a4d34bea --- /dev/null +++ b/packages/server/__snapshots__/errors_spec.ts.js @@ -0,0 +1,4 @@ +exports['tags and name only'] = ` +The --tag flag you passed was: nightly,staging +The --name flag you passed was: my tests +` From bc1823a6b3978ec3174f3deb192c363c2698c1d9 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 3 Feb 2022 19:24:00 -0500 Subject: [PATCH 036/165] CI fix --- .../test/unit/visualSnapshotErrors_spec.ts | 4 ---- .../server/test/unit/browsers/firefox_spec.ts | 16 +++++++++++++--- .../server/test/unit/plugins/run_events_spec.js | 2 +- packages/server/test/unit/util/settings_spec.js | 3 ++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 057686a8e430..0a6f8dcb24d8 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -190,10 +190,6 @@ const testVisualError = (errorGeneratorFn: () => Er expect(variants).to.be.an('object') - if (isCi) { - return - } - for (const [key, arr] of Object.entries(variants)) { const filename = key === 'default' ? errorType : `${errorType} - ${key}` diff --git a/packages/server/test/unit/browsers/firefox_spec.ts b/packages/server/test/unit/browsers/firefox_spec.ts index 36fb071519cd..57375a6fdc25 100644 --- a/packages/server/test/unit/browsers/firefox_spec.ts +++ b/packages/server/test/unit/browsers/firefox_spec.ts @@ -412,7 +412,11 @@ describe('lib/browsers/firefox', () => { } await expect(firefoxUtil.setupMarionette([], '', port)) - .to.be.rejectedWith('An unexpected error was received from Marionette Socket:\n\nError: foo error') + .to.be.rejected.then((err) => { + expect(err.message).to.include('An unexpected error was received from Marionette Socket') + expect(err.details).to.include('Error: foo error') + expect(err.originalError.message).to.eq('foo error') + }) }) it('rejects on errors from marionette commands', async () => { @@ -421,14 +425,20 @@ describe('lib/browsers/firefox', () => { } await expect(firefoxUtil.setupMarionette([], '', port)) - .to.be.rejectedWith('An unexpected error was received from Marionette commands:\n\nError: foo error') + .to.be.rejected.then((err) => { + expect(err.message).to.include('An unexpected error was received from Marionette commands') + expect(err.details).to.include('Error: foo error') + }) }) it('rejects on errors during initial Marionette connection', async () => { marionetteDriver.connect.rejects(new Error('not connectable')) await expect(firefoxUtil.setupMarionette([], '', port)) - .to.be.rejectedWith('An unexpected error was received from Marionette connection:\n\nError: not connectable') + .to.be.rejected.then((err) => { + expect(err.message).to.include('An unexpected error was received from Marionette connection') + expect(err.details).to.include('Error: not connectable') + }) }) }) diff --git a/packages/server/test/unit/plugins/run_events_spec.js b/packages/server/test/unit/plugins/run_events_spec.js index 678450573983..864d6e39720b 100644 --- a/packages/server/test/unit/plugins/run_events_spec.js +++ b/packages/server/test/unit/plugins/run_events_spec.js @@ -41,7 +41,7 @@ describe('lib/plugins/run_events', () => { it('throws custom error if plugins.execute errors', () => { plugins.has.returns(true) - plugins.execute.rejects({ stack: 'The event threw an error' }) + plugins.execute.rejects({ name: 'Error', message: 'The event threw an error', stack: 'The event threw an error' }) return runEvents.execute('before:spec', {}, 'arg1', 'arg2') .then(() => { diff --git a/packages/server/test/unit/util/settings_spec.js b/packages/server/test/unit/util/settings_spec.js index a7c4f2c2aaeb..a2e528b14b2e 100644 --- a/packages/server/test/unit/util/settings_spec.js +++ b/packages/server/test/unit/util/settings_spec.js @@ -58,7 +58,8 @@ describe('lib/util/settings', () => { return settings.readEnv(projectRoot) .catch((err) => { expect(err.type).to.eq('ERROR_READING_FILE') - expect(err.message).to.include('SyntaxError') + expect(err.stack).to.include('SyntaxError') + expect(err.originalError.name).to.equal('SyntaxError') expect(err.message).to.include(projectRoot) }) From 5fb95a234a77a788b4bc70e8928d79c58e403da4 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 3 Feb 2022 20:12:10 -0500 Subject: [PATCH 037/165] fixes --- packages/errors/index.js | 8 +------- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 2 +- packages/server/test/unit/plugins/run_events_spec.js | 4 ++-- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/errors/index.js b/packages/errors/index.js index c9fa09d90d4a..a2958b789f92 100644 --- a/packages/errors/index.js +++ b/packages/errors/index.js @@ -1,7 +1 @@ -try { - require.resolve('./dist') - module.exports = require('./dist') -} catch (e) { - require('@packages/ts/register') - module.exports = require('./src') -} +module.exports = require('./src') diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 0a6f8dcb24d8..bd182914364d 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -60,7 +60,7 @@ const convertHtmlToImage = async (htmlfile: string) => { win.webContents.debugger.attach() } - return new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { setTimeout(() => { reject(new Error('Timed out creating Snapshot')) }, 2000) diff --git a/packages/server/test/unit/plugins/run_events_spec.js b/packages/server/test/unit/plugins/run_events_spec.js index 864d6e39720b..429678a924f3 100644 --- a/packages/server/test/unit/plugins/run_events_spec.js +++ b/packages/server/test/unit/plugins/run_events_spec.js @@ -41,11 +41,11 @@ describe('lib/plugins/run_events', () => { it('throws custom error if plugins.execute errors', () => { plugins.has.returns(true) - plugins.execute.rejects({ name: 'Error', message: 'The event threw an error', stack: 'The event threw an error' }) + plugins.execute.rejects({ name: 'Error', message: 'The event threw an error', stack: 'stack trace' }) return runEvents.execute('before:spec', {}, 'arg1', 'arg2') .then(() => { - expect(errors.throw).to.be.calledWith('PLUGINS_RUN_EVENT_ERROR', 'before:spec', { name: 'Error', message: 'The event threw an error' }) + expect(errors.throw).to.be.calledWith('PLUGINS_RUN_EVENT_ERROR', 'before:spec', { name: 'Error', message: 'The event threw an error', stack: 'stack trace' }) }) }) }) From 2a849ffaa86a94598d2e5fe23bad1141bfd17a21 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 3 Feb 2022 23:37:54 -0500 Subject: [PATCH 038/165] store snapshot images in circle artifacts --- circle.yml | 11 +++++++---- .../errors/test/unit/visualSnapshotErrors_spec.ts | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/circle.yml b/circle.yml index 2cbffc852f30..67cb3905367f 100644 --- a/circle.yml +++ b/circle.yml @@ -89,7 +89,7 @@ executors: # https://github.com/CircleCI-Public/windows-orb/blob/master/src/executors/default.yml # https://circleci.com/docs/2.0/hello-world-windows/#software-pre-installed-in-the-windows-image windows: &windows-executor - machine: + machine: image: windows-server-2019-vs2019:stable shell: bash.exe -eo pipefail resource_class: windows.medium @@ -151,7 +151,7 @@ commands: - cypress - .ssh - node_modules # contains the npm i -g modules - + install_cache_helpers_dependencies: steps: - run: @@ -1092,6 +1092,9 @@ jobs: # CLI tests generate HTML files with sample CLI command output - store_artifacts: path: cli/test/html + - store_artifacts: + path: packages/errors/__snapshot-images__ + destination: error-snapshot - store-npm-logs unit-tests-release: @@ -2413,14 +2416,14 @@ windows-workflow: &windows-workflow executor: windows requires: - windows-build - + - unit-tests: name: windows-unit-tests executor: windows resource_class: windows.medium requires: - windows-build - + - create-build-artifacts: name: windows-create-build-artifacts executor: windows diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index bd182914364d..2b85d39d68f5 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -6,9 +6,8 @@ import globby from 'globby' import _ from 'lodash' import path from 'path' import pixelmatch from 'pixelmatch' -import sinon, { SinonSpy } from 'sinon' import { PNG } from 'pngjs' - +import sinon, { SinonSpy } from 'sinon' import * as errors from '../../src' // For importing the files below @@ -104,6 +103,7 @@ const convertHtmlToImage = async (htmlfile: string) => { if (changed) { console.log({ changed }) + if (isCi) { return reject(new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}, off by ${changed} pixels`)) } From 98e47bbec18c7d4f91e706aa73a69798875c3e90 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 3 Feb 2022 23:39:15 -0500 Subject: [PATCH 039/165] dont change artifact destination prefix --- circle.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/circle.yml b/circle.yml index 67cb3905367f..0995990fc8d9 100644 --- a/circle.yml +++ b/circle.yml @@ -1094,7 +1094,6 @@ jobs: path: cli/test/html - store_artifacts: path: packages/errors/__snapshot-images__ - destination: error-snapshot - store-npm-logs unit-tests-release: From f713ef52e9ca5d512b5d6772a4abedaa3b6bbdee Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 01:43:06 -0500 Subject: [PATCH 040/165] use Courier Prime --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 2b85d39d68f5..0b06b782df8b 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -158,7 +158,13 @@ const snapshotErrorConsoleLogs = function (errorFileName: string) { .split('color:#00A').join('color:#2472c7') // replace blue colors .split('color:#00A').join('color:#e05561') // replace red colors .split('color:#A50').join('color:#e5e510') // replace yellow colors - .split('"Courier New", ').join('') // replace font + .split('"Courier New", ').join('"Courier Prime", ') // replace font + .split('').join(` body { margin: 5px; From 557ab5c5a025da87578b87f15576fc86856984c4 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 02:02:46 -0500 Subject: [PATCH 041/165] antialias the text --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 0b06b782df8b..595e62ad338f 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -173,6 +173,7 @@ const snapshotErrorConsoleLogs = function (errorFileName: string) { pre { white-space: pre-wrap; word-break: break-word; + -webkit-font-smoothing: antialiased; } `) // remove margin/padding and force text overflow like a terminal From baf643072de8b60216264743c53de25048584cc4 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 02:22:04 -0500 Subject: [PATCH 042/165] decrease pixelmatch threshold, fail in CI only when changed pixels > 100 --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 595e62ad338f..178e94e37eb0 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -99,11 +99,11 @@ const convertHtmlToImage = async (htmlfile: string) => { const buf = await fse.readFile(baseImagePath) const existingPng = PNG.sync.read(buf) const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) - const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.1 }) + const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.3 }) - if (changed) { - console.log({ changed }) + console.log('num pixels different:', changed) + if (changed > 100) { if (isCi) { return reject(new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}, off by ${changed} pixels`)) } From f11bc28f5c67bcc1a99071ab29f58245ecfaeebe Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 02:34:14 -0500 Subject: [PATCH 043/165] increase timeout --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 178e94e37eb0..0afd10d72d09 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -60,10 +60,6 @@ const convertHtmlToImage = async (htmlfile: string) => { } return await new Promise((resolve, reject) => { - setTimeout(() => { - reject(new Error('Timed out creating Snapshot')) - }, 2000) - win.webContents.once('did-finish-load', async () => { try { await win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { @@ -216,7 +212,7 @@ const testVisualError = (errorGeneratorFn: () => Er await convertHtmlToImage(htmlFilename) } } - }) + }).timeout(10000) } const testVisualErrors = (whichError: CypressErrorType[] | '*', errorsToTest: {[K in CypressErrorType]: () => ErrorGenerator}) => { From 8d77745299f7a050819d14a2e141de40076c5037 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sat, 5 Feb 2022 01:03:20 -0500 Subject: [PATCH 044/165] overflow: hidden, remove new Promise, add debug logging Co-Authored-By: Tim Griesser --- .../test/unit/visualSnapshotErrors_spec.ts | 128 +++++++++--------- 1 file changed, 66 insertions(+), 62 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 0afd10d72d09..b8fc35880302 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -8,6 +8,8 @@ import path from 'path' import pixelmatch from 'pixelmatch' import { PNG } from 'pngjs' import sinon, { SinonSpy } from 'sinon' +import debugLib from 'debug' + import * as errors from '../../src' // For importing the files below @@ -21,6 +23,8 @@ const ciProvider = require('@packages/server/lib/util/ci_provider') const browsers = require('@packages/server/lib/browsers') const launcherBrowsers = require('@packages/launcher/lib/browsers') +const debug = debugLib(isCi ? '*' : 'visualSnapshotErrors') + interface ErrorGenerator { default: Parameters [key: string]: Parameters @@ -59,69 +63,62 @@ const convertHtmlToImage = async (htmlfile: string) => { win.webContents.debugger.attach() } - return await new Promise((resolve, reject) => { - win.webContents.once('did-finish-load', async () => { - try { - await win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { - width: WIDTH, - height: HEIGHT, - deviceScaleFactor: 1, - mobile: false, - }) - - const { data } = await win.webContents.debugger.sendCommand('Page.captureScreenshot', { - format: 'png', - quality: 100, - }) - - const imagePath = htmlfile.replace('.html', EXT) - const baseImagePath = path.join(baseImageFolder, path.basename(imagePath)) - - const receivedImageBuffer = Buffer.from(data, 'base64') - const receivedPng = PNG.sync.read(receivedImageBuffer) - const receivedPngBuffer = PNG.sync.write(receivedPng) - - await Promise.all([ - fse.outputFile(imagePath, receivedPngBuffer), - fse.remove(htmlfile), - ]) - - // - if image does not exist in __snapshot-bases__ - // then copy into __snapshot-bases__ - // - if image does exist then diff if, and if its - // greater than >.01 diff, then copy it in - // - unless we're in CI, then fail if there's a diff - try { - const buf = await fse.readFile(baseImagePath) - const existingPng = PNG.sync.read(buf) - const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) - const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.3 }) - - console.log('num pixels different:', changed) - - if (changed > 100) { - if (isCi) { - return reject(new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}, off by ${changed} pixels`)) - } - - await copyImageToBase(imagePath, baseImagePath) - } - } catch (err: any) { - if (err.code === 'ENOENT') { - await copyImageToBase(imagePath, baseImagePath) - } else { - reject(err) - } - } finally { - resolve({}) - } - } catch (e) { - reject(e) - } - }) + debug(`Loading ${htmlfile}`) - win.loadFile(htmlfile).catch(reject) + await win.loadFile(htmlfile) + + await win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { + width: WIDTH, + height: HEIGHT, + deviceScaleFactor: 1, + mobile: false, + }) + + const { data } = await win.webContents.debugger.sendCommand('Page.captureScreenshot', { + format: 'png', + quality: 100, }) + + const imagePath = htmlfile.replace('.html', EXT) + const baseImagePath = path.join(baseImageFolder, path.basename(imagePath)) + + const receivedImageBuffer = Buffer.from(data, 'base64') + const receivedPng = PNG.sync.read(receivedImageBuffer) + const receivedPngBuffer = PNG.sync.write(receivedPng) + + await Promise.all([ + fse.outputFile(imagePath, receivedPngBuffer), + fse.remove(htmlfile), + ]) + + // - if image does not exist in __snapshot-bases__ + // then copy into __snapshot-bases__ + // - if image does exist then diff if, and if its + // greater than >.01 diff, then copy it in + // - unless we're in CI, then fail if there's a diff + try { + const buf = await fse.readFile(baseImagePath) + const existingPng = PNG.sync.read(buf) + const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) + const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.3 }) + + console.log('num pixels different:', changed) + + if (changed > 100) { + if (isCi) { + throw new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}, off by ${changed} pixels`) + } + + await copyImageToBase(imagePath, baseImagePath) + } + } catch (e: any) { + if (e.code === 'ENOENT') { + debug(`Adding new image: ${imagePath}`) + await copyImageToBase(imagePath, baseImagePath) + } else { + throw e + } + } } const outputHtmlFolder = path.join(__dirname, '..', '..', '__snapshot-images__') @@ -165,6 +162,7 @@ const snapshotErrorConsoleLogs = function (errorFileName: string) { body { margin: 5px; padding: 0; + overflow: hidden; } pre { white-space: pre-wrap; @@ -196,6 +194,8 @@ const testVisualError = (errorGeneratorFn: () => Er for (const [key, arr] of Object.entries(variants)) { const filename = key === 'default' ? errorType : `${errorType} - ${key}` + debug(`Converting ${filename}`) + terminalBanner(filename) consoleLog.resetHistory() @@ -204,12 +204,16 @@ const testVisualError = (errorGeneratorFn: () => Er errors.log(err) - const htmlFilename = path.join(outputHtmlFolder, `${filename }.html`) + const htmlFilename = path.join(outputHtmlFolder, `${filename}.html`) await snapshotErrorConsoleLogs(htmlFilename) + debug(`Snapshotted ${htmlFilename}`) + if (process.env.SKIP_IMAGE_CONVERSION !== '1') { + debug(`Converting ${errorType} to image`) await convertHtmlToImage(htmlFilename) + debug(`Conversion complete for ${errorType}`) } } }).timeout(10000) From d3efd64b5e8db481cb1b35779b41d6552ac7d944 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 4 Feb 2022 09:53:24 -0500 Subject: [PATCH 045/165] run unit tests w/ concurrency=1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index de31579cb37c..6c777c6d5b83 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "stop-only": "npx stop-only --skip .cy,.publish,.projects,node_modules,dist,dist-test,fixtures,lib,bower_components,src,__snapshots__ --exclude e2e.ts,cypress-tests.ts,*only_spec.js", "stop-only-all": "yarn stop-only --folder packages", "pretest": "yarn ensure-deps", - "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,errors,electron,extension,https-proxy,launcher,net-stubbing,network,proxy,rewriter,runner,runner-shared,socket}'\"", + "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,errors,electron,extension,https-proxy,launcher,net-stubbing,network,proxy,rewriter,runner,runner-shared,socket}'\" --concurrency=1", "test-debug": "lerna exec yarn test-debug --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"", "pretest-e2e": "yarn ensure-deps", "test-integration": "lerna exec yarn test-integration --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"", From 672bf822c40f778d03dc9abd354d8f8a69465fbb Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 4 Feb 2022 10:23:13 -0500 Subject: [PATCH 046/165] unique window per file --- .../test/unit/visualSnapshotErrors_spec.ts | 112 +++++++++--------- 1 file changed, 59 insertions(+), 53 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index b8fc35880302..4dfcc89d1297 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -37,8 +37,6 @@ chai.use(require('@cypress/sinon-chai')) termToHtml.themes.dark.bg = '#111' -let win: BrowserWindow - const lineAndColNumsRe = /:\d+:\d+/ const EXT = '.png' @@ -53,71 +51,79 @@ const convertHtmlToImage = async (htmlfile: string) => { await app.whenReady() - if (!win) { - win = new BrowserWindow({ - show: false, - width: WIDTH, - height: HEIGHT, - }) + app.on('window-all-closed', () => { + // + }) + const win = new BrowserWindow({ + show: false, + width: WIDTH, + height: HEIGHT, + }) + + try { win.webContents.debugger.attach() - } - debug(`Loading ${htmlfile}`) + debug(`Loading %s`, htmlfile) - await win.loadFile(htmlfile) + await win.loadFile(htmlfile) - await win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { - width: WIDTH, - height: HEIGHT, - deviceScaleFactor: 1, - mobile: false, - }) + await win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { + width: WIDTH, + height: HEIGHT, + deviceScaleFactor: 1, + mobile: false, + }) - const { data } = await win.webContents.debugger.sendCommand('Page.captureScreenshot', { - format: 'png', - quality: 100, - }) + const { data } = await win.webContents.debugger.sendCommand('Page.captureScreenshot', { + format: 'png', + quality: 100, + }) - const imagePath = htmlfile.replace('.html', EXT) - const baseImagePath = path.join(baseImageFolder, path.basename(imagePath)) + const imagePath = htmlfile.replace('.html', EXT) + const baseImagePath = path.join(baseImageFolder, path.basename(imagePath)) - const receivedImageBuffer = Buffer.from(data, 'base64') - const receivedPng = PNG.sync.read(receivedImageBuffer) - const receivedPngBuffer = PNG.sync.write(receivedPng) + debug('baseImagePath %s', baseImagePath) - await Promise.all([ - fse.outputFile(imagePath, receivedPngBuffer), - fse.remove(htmlfile), - ]) + const receivedImageBuffer = Buffer.from(data, 'base64') + const receivedPng = PNG.sync.read(receivedImageBuffer) + const receivedPngBuffer = PNG.sync.write(receivedPng) - // - if image does not exist in __snapshot-bases__ - // then copy into __snapshot-bases__ - // - if image does exist then diff if, and if its - // greater than >.01 diff, then copy it in - // - unless we're in CI, then fail if there's a diff - try { - const buf = await fse.readFile(baseImagePath) - const existingPng = PNG.sync.read(buf) - const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) - const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.3 }) + await Promise.all([ + fse.outputFile(imagePath, receivedPngBuffer), + fse.remove(htmlfile), + ]) - console.log('num pixels different:', changed) + // - if image does not exist in __snapshot-bases__ + // then copy into __snapshot-bases__ + // - if image does exist then diff if, and if its + // greater than >.01 diff, then copy it in + // - unless we're in CI, then fail if there's a diff + try { + const buf = await fse.readFile(baseImagePath) + const existingPng = PNG.sync.read(buf) + const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) + const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.3 }) - if (changed > 100) { - if (isCi) { - throw new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}, off by ${changed} pixels`) - } + console.log('num pixels different:', changed) - await copyImageToBase(imagePath, baseImagePath) - } - } catch (e: any) { - if (e.code === 'ENOENT') { - debug(`Adding new image: ${imagePath}`) - await copyImageToBase(imagePath, baseImagePath) - } else { - throw e + if (changed > 100) { + if (isCi) { + throw new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}, off by ${changed} pixels`) + } + + await copyImageToBase(imagePath, baseImagePath) + } + } catch (e: any) { + if (e.code === 'ENOENT') { + debug(`Adding new image: ${imagePath}`) + await copyImageToBase(imagePath, baseImagePath) + } else { + throw e + } } + } finally { + win.destroy() } } From 61f16990057be591fa4f49ca625c2755d2f57a86 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 10:55:49 -0500 Subject: [PATCH 047/165] disable app hardware acceleration + use in process gpu + single process --- .../errors/test/unit/visualSnapshotErrors_spec.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 4dfcc89d1297..17f6844c4ee0 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -1,15 +1,15 @@ import chai, { expect } from 'chai' +import debugLib from 'debug' import { app, BrowserWindow } from 'electron' /* eslint-disable no-console */ import fse from 'fs-extra' import globby from 'globby' import _ from 'lodash' +import os from 'os' import path from 'path' import pixelmatch from 'pixelmatch' import { PNG } from 'pngjs' import sinon, { SinonSpy } from 'sinon' -import debugLib from 'debug' - import * as errors from '../../src' // For importing the files below @@ -25,6 +25,14 @@ const launcherBrowsers = require('@packages/launcher/lib/browsers') const debug = debugLib(isCi ? '*' : 'visualSnapshotErrors') +if (os.platform() === 'linux') { + app.disableHardwareAcceleration() + + // app.commandLine.appendSwitch('no-sandbox') + app.commandLine.appendSwitch('in-process-gpu') + app.commandLine.appendSwitch('single-process') +} + interface ErrorGenerator { default: Parameters [key: string]: Parameters From 86a6192a65c27a7d5de829bb5776ce6d63e8e9ec Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 11:57:34 -0500 Subject: [PATCH 048/165] do not do image diffing - conditionally convert html to images - store html snapshots - do not store images in git --- packages/errors/test/support/utils.ts | 94 +++++++++++ .../test/unit/visualSnapshotErrors_spec.ts | 148 ++++-------------- 2 files changed, 121 insertions(+), 121 deletions(-) create mode 100644 packages/errors/test/support/utils.ts diff --git a/packages/errors/test/support/utils.ts b/packages/errors/test/support/utils.ts new file mode 100644 index 000000000000..ee08a5e307dd --- /dev/null +++ b/packages/errors/test/support/utils.ts @@ -0,0 +1,94 @@ +import Debug from 'debug' +import { app, BrowserWindow } from 'electron' +import fse from 'fs-extra' +import path from 'path' +import { PNG } from 'pngjs' + +const isCi = require('is-ci') + +// const outputHtmlFolder = path.join(__dirname, '..', '..', '__snapshot-images__') +// const baseImageFolder = path.join(__dirname, '..', '..', '__snapshot-bases__') +app.on('window-all-closed', () => { + +}) + +const HEIGHT = 550 +const WIDTH = 1200 +const EXT = '.png' +const debug = Debug(isCi ? '*' : 'visualSnapshotErrors') + +export const copyImageToBase = (from: string, to: string) => { + return fse.copy(from, to, { overwrite: true }) +} + +export const convertHtmlToImage = async (htmlFile: string, snapshotImagesFolder: string) => { + const win = new BrowserWindow({ + show: false, + width: WIDTH, + height: HEIGHT, + }) + + try { + await app.isReady() + + win.webContents.debugger.attach() + + debug(`Loading %s`, htmlFile) + + await win.loadFile(htmlFile) + + await win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { + width: WIDTH, + height: HEIGHT, + deviceScaleFactor: 1, + mobile: false, + }) + + const { data } = await win.webContents.debugger.sendCommand('Page.captureScreenshot', { + format: 'png', + quality: 100, + }) + + const imagePath = htmlFile.replace('.html', EXT) + const snapshotImagePath = path.join(snapshotImagesFolder, path.basename(imagePath)) + + debug('snapshotImagePath %s', snapshotImagePath) + + const receivedImageBuffer = Buffer.from(data, 'base64') + const receivedPng = PNG.sync.read(receivedImageBuffer) + const receivedPngBuffer = PNG.sync.write(receivedPng) + + await fse.outputFile(snapshotImagePath, receivedPngBuffer) + + // - if image does not exist in __snapshot-bases__ + // then copy into __snapshot-bases__ + // - if image does exist then diff if, and if its + // greater than >.01 diff, then copy it in + // - unless we're in CI, then fail if there's a diff + // try { + // const buf = await fse.readFile(snapshotImagePath) + // const existingPng = PNG.sync.read(buf) + // const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) + // const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.3 }) + + // debug('num pixels different: %s', changed) + + // if (changed > 100) { + // if (isCi) { + // throw new Error(`Image difference detected. Base error image no longer matches for file: ${snapshotImagePath}, off by ${changed} pixels`) + // } + + // await copyImageToBase(imagePath, snapshotImagePath) + // } + // } catch (e: any) { + // if (e.code === 'ENOENT') { + // debug(`Adding new image: ${imagePath}`) + // await copyImageToBase(imagePath, snapshotImagePath) + // } else { + // throw e + // } + // } + } finally { + win.destroy() + } +} diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 17f6844c4ee0..5d5369604e88 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -1,16 +1,12 @@ import chai, { expect } from 'chai' -import debugLib from 'debug' -import { app, BrowserWindow } from 'electron' -/* eslint-disable no-console */ +import Debug from 'debug' import fse from 'fs-extra' import globby from 'globby' import _ from 'lodash' -import os from 'os' import path from 'path' -import pixelmatch from 'pixelmatch' -import { PNG } from 'pngjs' import sinon, { SinonSpy } from 'sinon' import * as errors from '../../src' +import { convertHtmlToImage } from '../support/utils' // For importing the files below process.env.CYPRESS_INTERNAL_ENV = 'test' @@ -23,15 +19,7 @@ const ciProvider = require('@packages/server/lib/util/ci_provider') const browsers = require('@packages/server/lib/browsers') const launcherBrowsers = require('@packages/launcher/lib/browsers') -const debug = debugLib(isCi ? '*' : 'visualSnapshotErrors') - -if (os.platform() === 'linux') { - app.disableHardwareAcceleration() - - // app.commandLine.appendSwitch('no-sandbox') - app.commandLine.appendSwitch('in-process-gpu') - app.commandLine.appendSwitch('single-process') -} +const debug = Debug(isCi ? '*' : 'visualSnapshotErrors') interface ErrorGenerator { default: Parameters @@ -47,96 +35,8 @@ termToHtml.themes.dark.bg = '#111' const lineAndColNumsRe = /:\d+:\d+/ -const EXT = '.png' - -const copyImageToBase = (from: string, to: string) => { - return fse.copy(from, to, { overwrite: true }) -} - -const convertHtmlToImage = async (htmlfile: string) => { - const HEIGHT = 550 - const WIDTH = 1200 - - await app.whenReady() - - app.on('window-all-closed', () => { - // - }) - - const win = new BrowserWindow({ - show: false, - width: WIDTH, - height: HEIGHT, - }) - - try { - win.webContents.debugger.attach() - - debug(`Loading %s`, htmlfile) - - await win.loadFile(htmlfile) - - await win.webContents.debugger.sendCommand('Emulation.setDeviceMetricsOverride', { - width: WIDTH, - height: HEIGHT, - deviceScaleFactor: 1, - mobile: false, - }) - - const { data } = await win.webContents.debugger.sendCommand('Page.captureScreenshot', { - format: 'png', - quality: 100, - }) - - const imagePath = htmlfile.replace('.html', EXT) - const baseImagePath = path.join(baseImageFolder, path.basename(imagePath)) - - debug('baseImagePath %s', baseImagePath) - - const receivedImageBuffer = Buffer.from(data, 'base64') - const receivedPng = PNG.sync.read(receivedImageBuffer) - const receivedPngBuffer = PNG.sync.write(receivedPng) - - await Promise.all([ - fse.outputFile(imagePath, receivedPngBuffer), - fse.remove(htmlfile), - ]) - - // - if image does not exist in __snapshot-bases__ - // then copy into __snapshot-bases__ - // - if image does exist then diff if, and if its - // greater than >.01 diff, then copy it in - // - unless we're in CI, then fail if there's a diff - try { - const buf = await fse.readFile(baseImagePath) - const existingPng = PNG.sync.read(buf) - const diffPng = new PNG({ width: WIDTH, height: HEIGHT }) - const changed = pixelmatch(existingPng.data, receivedPng.data, diffPng.data, WIDTH, HEIGHT, { threshold: 0.3 }) - - console.log('num pixels different:', changed) - - if (changed > 100) { - if (isCi) { - throw new Error(`Image difference detected. Base error image no longer matches for file: ${baseImagePath}, off by ${changed} pixels`) - } - - await copyImageToBase(imagePath, baseImagePath) - } - } catch (e: any) { - if (e.code === 'ENOENT') { - debug(`Adding new image: ${imagePath}`) - await copyImageToBase(imagePath, baseImagePath) - } else { - throw e - } - } - } finally { - win.destroy() - } -} - -const outputHtmlFolder = path.join(__dirname, '..', '..', '__snapshot-images__') -const baseImageFolder = path.join(__dirname, '..', '..', '__snapshot-bases__') +const snapshotHtmlFolder = path.join(__dirname, '..', '..', '__snapshot-html__') +const snapshotImagesFolder = path.join(__dirname, '..', '..', '__snapshot-images__') const saveHtml = async (filename: string, html: string) => { await fse.outputFile(filename, html, 'utf8') @@ -218,19 +118,22 @@ const testVisualError = (errorGeneratorFn: () => Er errors.log(err) - const htmlFilename = path.join(outputHtmlFolder, `${filename}.html`) + const htmlFilename = path.join(snapshotHtmlFolder, `${filename}.html`) await snapshotErrorConsoleLogs(htmlFilename) debug(`Snapshotted ${htmlFilename}`) - if (process.env.SKIP_IMAGE_CONVERSION !== '1') { + // dont run html -> image conversion if we're in CI + if (!isCi && process.env.SKIP_HTML_IMAGE_CONVERSION !== '1') { debug(`Converting ${errorType} to image`) - await convertHtmlToImage(htmlFilename) + + await convertHtmlToImage(htmlFilename, snapshotImagesFolder) + debug(`Conversion complete for ${errorType}`) } } - }).timeout(10000) + }).timeout(5000) } const testVisualErrors = (whichError: CypressErrorType[] | '*', errorsToTest: {[K in CypressErrorType]: () => ErrorGenerator}) => { @@ -244,27 +147,30 @@ const testVisualErrors = (whichError: CypressErrorType[] | '*', errorsToTest: {[ // otherwise test all the errors before(() => { - // prune out all existing snapshot images in case + // prune out all existing snapshot html in case // errors were removed and we have stale snapshots - return fse.remove(outputHtmlFolder) + return Promise.all([ + fse.remove(snapshotHtmlFolder), + fse.remove(snapshotImagesFolder), + ]) }) after(async () => { // if we're in CI, make sure there's all the files - // we expect there to be in __snapshot-images__ + // we expect there to be in __snapshot-html__ if (isCi) { - const files = await globby(`${outputHtmlFolder}/*`) - const errorImageNames = files.map((file) => { - return path.basename(file, '.png').split(' ')[0] + const files = await globby(`${snapshotHtmlFolder}/*`) + const errorNames = files.map((file) => { + return path.basename(file, '.html').split(' ')[0] }) - const uniqErrors = _.uniq(errorImageNames) - const excessImages = _.difference(uniqErrors, _.keys(errors.AllCypressErrors)) + const uniqErrors = _.uniq(errorNames) + // const excessErrors = _.difference(uniqErrors, _.keys(errors.AllCypressErrors)) - await Promise.all(excessImages.map((img) => { - const pathToImage = path.join(baseImageFolder, img + EXT) + // await Promise.all(excessErrors.map((file) => { + // const pathToHtml = path.join(baseImageFolder, file + EXT) - return fse.remove(pathToImage) - })) + // return fse.remove(pathToHtml) + // })) expect(uniqErrors).to.deep.eq(_.keys(errors.AllCypressErrors)) } From 011e2aab822ec5bde08a29fc3a517c61be577855 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 11:58:21 -0500 Subject: [PATCH 049/165] store snapshot html --- .../AUTH_BROWSER_LAUNCHED.html | 38 ++++++++++ .../AUTH_COULD_NOT_LAUNCH_BROWSER.html | 40 ++++++++++ .../AUTOMATION_SERVER_DISCONNECTED.html | 38 ++++++++++ .../__snapshot-html__/BAD_POLICY_WARNING.html | 45 ++++++++++++ .../BAD_POLICY_WARNING_TOOLTIP.html | 38 ++++++++++ .../BROWSER_NOT_FOUND_BY_NAME - canary.html | 56 ++++++++++++++ .../BROWSER_NOT_FOUND_BY_NAME.html | 52 +++++++++++++ .../BROWSER_NOT_FOUND_BY_PATH.html | 42 +++++++++++ .../__snapshot-html__/BUNDLE_ERROR.html | 51 +++++++++++++ .../CANNOT_CONNECT_BASE_URL.html | 40 ++++++++++ .../CANNOT_CONNECT_BASE_URL_RETRYING.html | 38 ++++++++++ .../CANNOT_CONNECT_BASE_URL_WARNING.html | 42 +++++++++++ .../CANNOT_CREATE_PROJECT_TOKEN.html | 38 ++++++++++ .../CANNOT_FETCH_PROJECT_TOKEN.html | 38 ++++++++++ .../CANNOT_RECORD_NO_PROJECT_ID.html | 48 ++++++++++++ .../CANNOT_REMOVE_OLD_BROWSER_PROFILES.html | 45 ++++++++++++ .../CANNOT_TRASH_ASSETS.html | 45 ++++++++++++ .../CDP_COULD_NOT_CONNECT.html | 49 +++++++++++++ .../CDP_COULD_NOT_RECONNECT.html | 43 +++++++++++ .../CDP_RETRYING_CONNECTION.html | 38 ++++++++++ .../CDP_VERSION_TOO_OLD.html | 38 ++++++++++ .../CHROME_WEB_SECURITY_NOT_SUPPORTED.html | 40 ++++++++++ .../CONFIG_FILES_LANGUAGE_CONFLICT.html | 43 +++++++++++ .../CONFIG_FILE_NOT_FOUND.html | 40 ++++++++++ .../CONFIG_VALIDATION_ERROR.html | 40 ++++++++++ .../COULD_NOT_FIND_SYSTEM_NODE.html | 42 +++++++++++ .../COULD_NOT_PARSE_ARGUMENTS.html | 42 +++++++++++ .../CT_NO_DEV_START_EVENT.html | 48 ++++++++++++ .../DASHBOARD_ALREADY_COMPLETE.html | 47 ++++++++++++ ...ASHBOARD_API_RESPONSE_FAILED_RETRYING.html | 44 +++++++++++ .../DASHBOARD_CANCEL_SKIPPED_SPEC.html | 39 ++++++++++ ...SHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html | 44 +++++++++++ .../DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html | 47 ++++++++++++ .../DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html | 45 ++++++++++++ .../DASHBOARD_CANNOT_UPLOAD_RESULTS.html | 44 +++++++++++ .../DASHBOARD_INVALID_RUN_REQUEST.html | 48 ++++++++++++ .../DASHBOARD_PARALLEL_DISALLOWED.html | 47 ++++++++++++ ...HBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html | 64 ++++++++++++++++ .../DASHBOARD_PARALLEL_REQUIRED.html | 47 ++++++++++++ .../DASHBOARD_PROJECT_NOT_FOUND.html | 48 ++++++++++++ .../DASHBOARD_RECORD_KEY_NOT_VALID.html | 44 +++++++++++ .../DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html | 47 ++++++++++++ .../DASHBOARD_STALE_RUN.html | 47 ++++++++++++ .../DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html | 44 +++++++++++ .../DASHBOARD_UNKNOWN_INVALID_REQUEST.html | 47 ++++++++++++ ...DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html | 44 +++++++++++ .../__snapshot-html__/DUPLICATE_TASK_KEY.html | 38 ++++++++++ .../__snapshot-html__/ERROR_READING_FILE.html | 43 +++++++++++ .../__snapshot-html__/ERROR_WRITING_FILE.html | 43 +++++++++++ ...XPERIMENTAL_COMPONENT_TESTING_REMOVED.html | 44 +++++++++++ ...EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html | 41 +++++++++++ .../EXPERIMENTAL_RUN_EVENTS_REMOVED.html | 40 ++++++++++ .../EXPERIMENTAL_SAMESITE_REMOVED.html | 40 ++++++++++ .../EXPERIMENTAL_SHADOW_DOM_REMOVED.html | 40 ++++++++++ .../EXTENSION_NOT_LOADED.html | 42 +++++++++++ .../FIREFOX_COULD_NOT_CONNECT.html | 47 ++++++++++++ .../FIREFOX_GC_INTERVAL_REMOVED.html | 42 +++++++++++ .../FIREFOX_MARIONETTE_FAILURE.html | 47 ++++++++++++ .../__snapshot-html__/FIXTURE_NOT_FOUND.html | 47 ++++++++++++ .../FOLDER_NOT_WRITABLE.html | 44 +++++++++++ ...EE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html | 42 +++++++++++ .../FREE_PLAN_EXCEEDS_MONTHLY_TESTS.html | 42 +++++++++++ ..._PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS.html | 42 +++++++++++ ...IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS.html | 44 +++++++++++ ...PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html | 42 +++++++++++ .../INCOMPATIBLE_PLUGIN_RETRIES.html | 45 ++++++++++++ .../INCORRECT_CI_BUILD_ID_USAGE.html | 44 +++++++++++ .../INDETERMINATE_CI_BUILD_ID.html | 73 +++++++++++++++++++ .../INVALID_CONFIG_OPTION.html | 42 +++++++++++ .../INVALID_CYPRESS_INTERNAL_ENV.html | 44 +++++++++++ .../INVALID_REPORTER_NAME.html | 49 +++++++++++++ .../INVOKED_BINARY_OUTSIDE_NPM_MODULE.html | 44 +++++++++++ .../NODE_VERSION_DEPRECATION_BUNDLED.html | 45 ++++++++++++ .../NODE_VERSION_DEPRECATION_SYSTEM.html | 43 +++++++++++ .../__snapshot-html__/NOT_LOGGED_IN.html | 40 ++++++++++ .../NO_DEFAULT_CONFIG_FILE_FOUND.html | 40 ++++++++++ .../NO_PROJECT_FOUND_AT_PROJECT_ROOT.html | 38 ++++++++++ .../__snapshot-html__/NO_PROJECT_ID.html | 38 ++++++++++ .../NO_SPECS_FOUND - noPattern.html | 42 +++++++++++ .../__snapshot-html__/NO_SPECS_FOUND.html | 46 ++++++++++++ ...ID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html | 42 +++++++++++ ...ARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html | 42 +++++++++++ .../PLAN_EXCEEDS_MONTHLY_TESTS.html | 42 +++++++++++ ...RACE_PERIOD_RUN_GROUPING_FEATURE_USED.html | 42 +++++++++++ .../PLUGINS_CONFIG_VALIDATION_ERROR.html | 40 ++++++++++ .../PLUGINS_DIDNT_EXPORT_FUNCTION.html | 50 +++++++++++++ .../__snapshot-html__/PLUGINS_FILE_ERROR.html | 49 +++++++++++++ .../PLUGINS_FUNCTION_ERROR.html | 45 ++++++++++++ .../PLUGINS_RUN_EVENT_ERROR.html | 45 ++++++++++++ .../PLUGINS_UNEXPECTED_ERROR.html | 43 +++++++++++ .../PLUGINS_VALIDATION_ERROR.html | 43 +++++++++++ .../__snapshot-html__/PORT_IN_USE_LONG.html | 40 ++++++++++ .../__snapshot-html__/PORT_IN_USE_SHORT.html | 38 ++++++++++ ..._ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html | 54 ++++++++++++++ .../RECORDING_FROM_FORK_PR.html | 44 +++++++++++ .../__snapshot-html__/RECORD_KEY_MISSING.html | 46 ++++++++++++ .../RECORD_PARAMS_WITHOUT_RECORDING.html | 44 +++++++++++ .../RENAMED_CONFIG_OPTION.html | 40 ++++++++++ .../__snapshot-html__/RENDERER_CRASHED.html | 54 ++++++++++++++ ...ROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html | 42 +++++++++++ .../SETTINGS_VALIDATION_ERROR.html | 40 ++++++++++ .../SUPPORT_FILE_NOT_FOUND.html | 46 ++++++++++++ .../TESTS_DID_NOT_START_FAILED.html | 38 ++++++++++ ...ID_NOT_START_RETRYING - retryingAgain.html | 38 ++++++++++ .../TESTS_DID_NOT_START_RETRYING.html | 38 ++++++++++ ...CTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html | 48 ++++++++++++ .../UNSUPPORTED_BROWSER_VERSION.html | 38 ++++++++++ .../VIDEO_POST_PROCESSING_FAILED.html | 45 ++++++++++++ .../VIDEO_RECORDING_FAILED.html | 45 ++++++++++++ 109 files changed, 4778 insertions(+) create mode 100644 packages/errors/__snapshot-html__/AUTH_BROWSER_LAUNCHED.html create mode 100644 packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html create mode 100644 packages/errors/__snapshot-html__/AUTOMATION_SERVER_DISCONNECTED.html create mode 100644 packages/errors/__snapshot-html__/BAD_POLICY_WARNING.html create mode 100644 packages/errors/__snapshot-html__/BAD_POLICY_WARNING_TOOLTIP.html create mode 100644 packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html create mode 100644 packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html create mode 100644 packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html create mode 100644 packages/errors/__snapshot-html__/BUNDLE_ERROR.html create mode 100644 packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL.html create mode 100644 packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html create mode 100644 packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_WARNING.html create mode 100644 packages/errors/__snapshot-html__/CANNOT_CREATE_PROJECT_TOKEN.html create mode 100644 packages/errors/__snapshot-html__/CANNOT_FETCH_PROJECT_TOKEN.html create mode 100644 packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html create mode 100644 packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html create mode 100644 packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html create mode 100644 packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html create mode 100644 packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html create mode 100644 packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html create mode 100644 packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD.html create mode 100644 packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html create mode 100644 packages/errors/__snapshot-html__/CONFIG_FILES_LANGUAGE_CONFLICT.html create mode 100644 packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html create mode 100644 packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html create mode 100644 packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html create mode 100644 packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html create mode 100644 packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_CANCEL_SKIPPED_SPEC.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html create mode 100644 packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html create mode 100644 packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html create mode 100644 packages/errors/__snapshot-html__/DUPLICATE_TASK_KEY.html create mode 100644 packages/errors/__snapshot-html__/ERROR_READING_FILE.html create mode 100644 packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html create mode 100644 packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html create mode 100644 packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html create mode 100644 packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html create mode 100644 packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html create mode 100644 packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html create mode 100644 packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html create mode 100644 packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html create mode 100644 packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html create mode 100644 packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html create mode 100644 packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html create mode 100644 packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html create mode 100644 packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html create mode 100644 packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_TESTS.html create mode 100644 packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS.html create mode 100644 packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS.html create mode 100644 packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html create mode 100644 packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html create mode 100644 packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html create mode 100644 packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html create mode 100644 packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION.html create mode 100644 packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html create mode 100644 packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html create mode 100644 packages/errors/__snapshot-html__/INVOKED_BINARY_OUTSIDE_NPM_MODULE.html create mode 100644 packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html create mode 100644 packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html create mode 100644 packages/errors/__snapshot-html__/NOT_LOGGED_IN.html create mode 100644 packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html create mode 100644 packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html create mode 100644 packages/errors/__snapshot-html__/NO_PROJECT_ID.html create mode 100644 packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html create mode 100644 packages/errors/__snapshot-html__/NO_SPECS_FOUND.html create mode 100644 packages/errors/__snapshot-html__/PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html create mode 100644 packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html create mode 100644 packages/errors/__snapshot-html__/PLAN_EXCEEDS_MONTHLY_TESTS.html create mode 100644 packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html create mode 100644 packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html create mode 100644 packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html create mode 100644 packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html create mode 100644 packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html create mode 100644 packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html create mode 100644 packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html create mode 100644 packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html create mode 100644 packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html create mode 100644 packages/errors/__snapshot-html__/PORT_IN_USE_SHORT.html create mode 100644 packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html create mode 100644 packages/errors/__snapshot-html__/RECORDING_FROM_FORK_PR.html create mode 100644 packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html create mode 100644 packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html create mode 100644 packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html create mode 100644 packages/errors/__snapshot-html__/RENDERER_CRASHED.html create mode 100644 packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html create mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html create mode 100644 packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html create mode 100644 packages/errors/__snapshot-html__/TESTS_DID_NOT_START_FAILED.html create mode 100644 packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING - retryingAgain.html create mode 100644 packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING.html create mode 100644 packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html create mode 100644 packages/errors/__snapshot-html__/UNSUPPORTED_BROWSER_VERSION.html create mode 100644 packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html create mode 100644 packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html diff --git a/packages/errors/__snapshot-html__/AUTH_BROWSER_LAUNCHED.html b/packages/errors/__snapshot-html__/AUTH_BROWSER_LAUNCHED.html new file mode 100644 index 000000000000..949c84c49d55 --- /dev/null +++ b/packages/errors/__snapshot-html__/AUTH_BROWSER_LAUNCHED.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Check your browser to continue logging in.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html b/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html new file mode 100644 index 000000000000..01814a1cbb30 --- /dev/null +++ b/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser:
+
+http://dashboard.cypress.io/login
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/AUTOMATION_SERVER_DISCONNECTED.html b/packages/errors/__snapshot-html__/AUTOMATION_SERVER_DISCONNECTED.html new file mode 100644 index 000000000000..f2ac614f8c08 --- /dev/null +++ b/packages/errors/__snapshot-html__/AUTOMATION_SERVER_DISCONNECTED.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
The automation client disconnected. Cannot continue running tests.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BAD_POLICY_WARNING.html b/packages/errors/__snapshot-html__/BAD_POLICY_WARNING.html new file mode 100644 index 000000000000..0b3b4d7aab3f --- /dev/null +++ b/packages/errors/__snapshot-html__/BAD_POLICY_WARNING.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
Cypress detected policy settings on your computer that may cause issues.
+
+The following policies were detected that may prevent Cypress from automating Chrome:
+
+ > HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\ProxyServer
+ > HKEY_CURRENT_USER\Software\Policies\Google\Chromium\ExtensionSettings
+
+For more information, see https://on.cypress.io/bad-browser-policy
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BAD_POLICY_WARNING_TOOLTIP.html b/packages/errors/__snapshot-html__/BAD_POLICY_WARNING_TOOLTIP.html new file mode 100644 index 000000000000..e7be5ad7fae0 --- /dev/null +++ b/packages/errors/__snapshot-html__/BAD_POLICY_WARNING_TOOLTIP.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html new file mode 100644 index 000000000000..76e1e5e13570 --- /dev/null +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html @@ -0,0 +1,56 @@ + + + + + + + + + + + +
Can't run because you've entered an invalid browser name.
+
+Browser: canary was not found on your system or is not supported by Cypress.
+
+Cypress supports the following browsers:
+- chrome
+- chromium
+- edge
+- electron
+- firefox
+
+You can also use a custom browser: https://on.cypress.io/customize-browsers
+
+Available browsers found on your system are:
+chrome,chromium,chrome:beta,chrome:canary,firefox,firefox:dev,firefox:nightly,edge,edge:canary,edge:beta,edge:dev
+
+Note: In Cypress version 4.0.0, Canary must be launched as `chrome:canary`, not `canary`.
+
+See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html new file mode 100644 index 000000000000..c5575316eb39 --- /dev/null +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html @@ -0,0 +1,52 @@ + + + + + + + + + + + +
Can't run because you've entered an invalid browser name.
+
+Browser: invalid-browser was not found on your system or is not supported by Cypress.
+
+Cypress supports the following browsers:
+- chrome
+- chromium
+- edge
+- electron
+- firefox
+
+You can also use a custom browser: https://on.cypress.io/customize-browsers
+
+Available browsers found on your system are:
+chrome,chromium,chrome:beta,chrome:canary,firefox,firefox:dev,firefox:nightly,edge,edge:canary,edge:beta,edge:dev
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html new file mode 100644 index 000000000000..576599e0f35f --- /dev/null +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
We could not identify a known browser at the path you provided: /path/does/not/exist
+
+The output from the command we ran was:
+
+fail whale
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BUNDLE_ERROR.html b/packages/errors/__snapshot-html__/BUNDLE_ERROR.html new file mode 100644 index 000000000000..334f9c72095f --- /dev/null +++ b/packages/errors/__snapshot-html__/BUNDLE_ERROR.html @@ -0,0 +1,51 @@ + + + + + + + + + + + +
Oops...we found an error preparing this test file:
+
+  /path/to/file
+
+The error was:
+
+fail whale
+
+This occurred while Cypress was compiling and bundling your test code. This is usually caused by:
+
+- A missing file or dependency
+- A syntax error in the file or one of its dependencies
+
+Fix the error in your code and re-run your tests.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL.html b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL.html new file mode 100644 index 000000000000..6fb05776bd7b --- /dev/null +++ b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
Cypress failed to verify that your server is running.
+
+Please start this server and then run Cypress again.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html new file mode 100644 index 000000000000..e2ec0ae2c1b8 --- /dev/null +++ b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
We will try connecting to it 60 more times...
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_WARNING.html b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_WARNING.html new file mode 100644 index 000000000000..77da743609e4 --- /dev/null +++ b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_WARNING.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Cypress could not verify that this server is running:
+
+  > http://localhost:3000
+
+This server has been configured as your `baseUrl`, and tests will likely fail if it is not running.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CREATE_PROJECT_TOKEN.html b/packages/errors/__snapshot-html__/CANNOT_CREATE_PROJECT_TOKEN.html new file mode 100644 index 000000000000..6c9b5af2909b --- /dev/null +++ b/packages/errors/__snapshot-html__/CANNOT_CREATE_PROJECT_TOKEN.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Can't create project's secret key.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_FETCH_PROJECT_TOKEN.html b/packages/errors/__snapshot-html__/CANNOT_FETCH_PROJECT_TOKEN.html new file mode 100644 index 000000000000..4a7521ffc1c2 --- /dev/null +++ b/packages/errors/__snapshot-html__/CANNOT_FETCH_PROJECT_TOKEN.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Can't find project's secret key.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html b/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html new file mode 100644 index 000000000000..ffecb223ca6b --- /dev/null +++ b/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html @@ -0,0 +1,48 @@ + + + + + + + + + + + +
You passed the --record flag but this project has not been setup to record.
+
+This project is missing the 'projectId' inside of '/path/to/cypress.json'.
+
+We cannot uniquely identify this project without this id.
+
+You need to setup this project to record. This will generate a unique 'projectId'.
+
+Alternatively if you omit the --record flag this project will run without recording.
+
+https://on.cypress.io/recording-project-runs
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html new file mode 100644 index 000000000000..cbc96e23c351 --- /dev/null +++ b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
Warning: We failed to remove old browser profiles from previous runs.
+
+This error will not alter the exit code.
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CANNOT_REMOVE_OLD_BROWSER_PROFILES (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html new file mode 100644 index 000000000000..ed33e7c29eb2 --- /dev/null +++ b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
Warning: We failed to trash the existing run results.
+
+This error will not alter the exit code.
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CANNOT_TRASH_ASSETS (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html new file mode 100644 index 000000000000..c7211a48ff0d --- /dev/null +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html @@ -0,0 +1,49 @@ + + + + + + + + + + + +
Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds.
+
+This usually indicates there was a problem opening the chrome browser.
+
+The CDP port requested was 2345.
+
+Error details:
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CDP_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html new file mode 100644 index 000000000000..04ef0a7eb5a1 --- /dev/null +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser.
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CDP_COULD_NOT_RECONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html b/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html new file mode 100644 index 000000000000..6ca121df29eb --- /dev/null +++ b/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Still waiting to connect to chrome, retrying in 1 second (attempt 1/62)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD.html b/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD.html new file mode 100644 index 000000000000..f4c345cf408f --- /dev/null +++ b/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
A minimum CDP version of v89 is required, but the current browser has v90.2.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html b/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html new file mode 100644 index 000000000000..6e888b47b152 --- /dev/null +++ b/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
Your project has set the configuration option: `chromeWebSecurity: false`
+
+This option will not have an effect in Electron. Tests that rely on web security being disabled will not run as expected.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_FILES_LANGUAGE_CONFLICT.html b/packages/errors/__snapshot-html__/CONFIG_FILES_LANGUAGE_CONFLICT.html new file mode 100644 index 000000000000..750fd109b9e0 --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_FILES_LANGUAGE_CONFLICT.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
There is both a `cypress.config.ts` and a `/path/to/project/root` at the location below:
+
+cypress.config.js
+
+Cypress does not know which one to read for config. Please remove one of the two and try again.
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html new file mode 100644 index 000000000000..2415364ba2f0 --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
Could not find a Cypress configuration file, exiting.
+
+We looked but did not find a cypress.json file in this folder: /path/to/project/root
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html new file mode 100644 index 000000000000..df27d789ad0c --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
We found an invalid configuration value:
+
+fail whale
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html b/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html new file mode 100644 index 000000000000..cf5fa6e35787 --- /dev/null +++ b/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
`nodeVersion` is set to `system`, but Cypress could not find a usable Node executable on your PATH.
+
+Make sure that your Node executable exists and can be run by the current user.
+
+Cypress will use the built-in Node version (v16.2.1) instead.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html b/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html new file mode 100644 index 000000000000..a794086f9ba0 --- /dev/null +++ b/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Cypress encountered an error while parsing the argument spec
+
+You passed: 1
+
+The error was: spec must be a string or comma-separated list
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html b/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html new file mode 100644 index 000000000000..68a5fb364441 --- /dev/null +++ b/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html @@ -0,0 +1,48 @@ + + + + + + + + + + + +
To run component-testing, cypress needs the `dev-server:start` event.
+
+Implement it by adding a `on('dev-server:start', () => startDevServer())` call in your pluginsFile.
+You can find the 'pluginsFile' at the following path:
+
+/path/to/plugins/file.js
+
+Learn how to set up component testing:
+
+https://on.cypress.io/component-testing
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html b/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html new file mode 100644 index 000000000000..ec658ad9b729 --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
The run you are attempting to access is already complete and will not accept new groups.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+When a run finishes all of its groups, it waits for a configurable set of time before finally completing. You must add more groups during that time period.
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+https://on.cypress.io/already-complete
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html new file mode 100644 index 000000000000..d9db94ba2b68 --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
We encountered an unexpected error talking to our servers.
+
+We will retry 3 more times in 5000...
+
+The server's response was:
+
+500 server down
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANCEL_SKIPPED_SPEC.html b/packages/errors/__snapshot-html__/DASHBOARD_CANCEL_SKIPPED_SPEC.html new file mode 100644 index 000000000000..29121e39a9b3 --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANCEL_SKIPPED_SPEC.html @@ -0,0 +1,39 @@ + + + + + + + + + + + +

+  This spec and its tests were skipped because the run has been canceled.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html new file mode 100644 index 000000000000..0a675bf5b7eb --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
Warning: We encountered an error talking to our servers.
+
+This run will not be recorded.
+
+This error will not alter the exit code.
+
+Error: fail whale
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html new file mode 100644 index 000000000000..556c942117cc --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
We encountered an unexpected error talking to our servers.
+
+Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
+
+The --group flag you passed was: foo
+The --ciBuildId flag you passed was: invalid
+
+The server's response was:
+
+Invalid CI Build ID
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html new file mode 100644 index 000000000000..28fc227487c6 --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
We encountered an unexpected error talking to our servers.
+
+The --group flag you passed was: foo
+The --ciBuildId flag you passed was: invalid
+
+The server's response was:
+
+Invalid CI Build ID
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html new file mode 100644 index 000000000000..e9d5e67b88ed --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
Warning: We encountered an error while uploading results from your run.
+
+These results will not be recorded.
+
+This error will not alter the exit code.
+
+Error: fail whale
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html b/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html new file mode 100644 index 000000000000..707fc131d29d --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html @@ -0,0 +1,48 @@ + + + + + + + + + + + +
Recording this run failed because the request was invalid.
+
+Error on Run Request
+
+Errors:
+
+[]
+
+Request Sent:
+
+{}
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html new file mode 100644 index 000000000000..4535d42bfc2f --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
You passed the --parallel flag, but this run group was originally created without the --parallel flag.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+You can not use the --parallel flag with this group.
+
+https://on.cypress.io/parallel-disallowed
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html new file mode 100644 index 000000000000..f7b5aff4196d --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html @@ -0,0 +1,64 @@ + + + + + + + + + + + +
You passed the --parallel flag, but we do not parallelize tests across different environments.
+
+This machine is sending different environment parameters than the first machine that started this parallel run.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+In order to run in parallel mode each machine must send identical environment parameters such as:
+
+- specs
+- osName
+- osVersion
+- browserName
+- browserVersion (major)
+
+This machine sent the following parameters:
+
+{
+  "osName": "darwin",
+  "osVersion": "v1",
+  "browserName": "Electron",
+  "browserVersion": "59.1.2.3",
+  "specs": [
+    "cypress/integration/app_spec.js"
+  ]
+}
+
+https://on.cypress.io/parallel-group-params-mismatch
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html new file mode 100644 index 000000000000..78bb8bb4402b --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+You must use the --parallel flag with this group.
+
+https://on.cypress.io/parallel-required
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html b/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html new file mode 100644 index 000000000000..a63f6d5f91d2 --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html @@ -0,0 +1,48 @@ + + + + + + + + + + + +
We could not find a project with the ID: abc123
+
+This projectId came from your '/path/to/cypress.json' file or an environment variable.
+
+Please log into the Dashboard and find your project.
+
+We will list the correct projectId in the 'Settings' tab.
+
+Alternatively, you can create a new project using the Desktop Application.
+
+https://on.cypress.io/dashboard
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html b/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html new file mode 100644 index 000000000000..923712640a7e --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
Your Record Key record-key-1234 is not valid with this project: projectId
+
+It may have been recently revoked by you or another user.
+
+Please log into the Dashboard to see the valid record keys.
+
+https://on.cypress.io/dashboard/projects/projectId
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html b/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html new file mode 100644 index 000000000000..4affb21085b8 --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
You passed the --group flag, but this group name has already been used for this run.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name.
+
+https://on.cypress.io/run-group-name-not-unique
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html b/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html new file mode 100644 index 000000000000..a656b852c31a --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+You cannot parallelize a run that has been complete for that long.
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+https://on.cypress.io/stale-run
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html new file mode 100644 index 000000000000..577198165ed1 --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
Warning from Cypress Dashboard: You have been warned
+
+Details:
+{
+  "ciBuildId": "invalid",
+  "group": "foo"
+}
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html new file mode 100644 index 000000000000..d206c65e9adb --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
We encountered an unexpected error talking to our servers.
+
+There is likely something wrong with the request.
+
+The --group flag you passed was: foo
+The --ciBuildId flag you passed was: invalid
+
+The server's response was:
+
+Unexpected 500 Error
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html b/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html new file mode 100644 index 000000000000..f7bc7e68beb5 --- /dev/null +++ b/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
Deprecation Warning: The `before:browser:launch` plugin event changed its signature in version `4.0.0`
+
+The `before:browser:launch` plugin event switched from yielding the second argument as an `array` of browser arguments to an options `object` with an `args` property.
+
+We've detected that your code is still using the previous, deprecated interface signature.
+
+This code will not work in a future version of Cypress. Please see the upgrade guide: https://on.cypress.io/deprecated-before-browser-launch-args
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DUPLICATE_TASK_KEY.html b/packages/errors/__snapshot-html__/DUPLICATE_TASK_KEY.html new file mode 100644 index 000000000000..2b5f01a41def --- /dev/null +++ b/packages/errors/__snapshot-html__/DUPLICATE_TASK_KEY.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Warning: Multiple attempts to register the following task(s): foo, bar, baz. Only the last attempt will be registered.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_READING_FILE.html b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html new file mode 100644 index 000000000000..c85380fb1e5f --- /dev/null +++ b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
Error reading from: /path/to/read/file.ts
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at ERROR_READING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html new file mode 100644 index 000000000000..f01d8f5fa52a --- /dev/null +++ b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
Error writing to: path/to/write/file.ts
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at ERROR_WRITING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html new file mode 100644 index 000000000000..61ba4c65e439 --- /dev/null +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
The experimentalComponentTesting configuration option was removed in Cypress version `7.0.0`. Please remove this flag from /path/to/configFile.json.
+
+Cypress Component Testing is now a standalone command. You can now run your component tests with:
+
+`cypress open-ct`
+
+https://on.cypress.io/migration-guide
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html new file mode 100644 index 000000000000..d1273dfe4b71 --- /dev/null +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html @@ -0,0 +1,41 @@ + + + + + + + + + + + +
The `experimentalNetworkStubbing` configuration option was removed in Cypress version `6.0.0`.
+It is no longer necessary for using `cy.intercept()` (formerly `cy.route2()`).
+
+You can safely remove this option from your config.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html new file mode 100644 index 000000000000..355b138d91c9 --- /dev/null +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
The `experimentalRunEvents` configuration option was removed in Cypress version `6.7.0`. It is no longer necessary when listening to run events in the plugins file.
+
+You can safely remove this option from your config.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html new file mode 100644 index 000000000000..f16bbd21b097 --- /dev/null +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
The `experimentalGetCookiesSameSite` configuration option was removed in Cypress version `5.0.0`. Yielding the `sameSite` property is now the default behavior of the `cy.cookie` commands.
+
+You can safely remove this option from your config.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html new file mode 100644 index 000000000000..44708092c63c --- /dev/null +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
The `experimentalShadowDomSupport` configuration option was removed in Cypress version `5.2.0`. It is no longer necessary when utilizing the `includeShadowDom` option.
+
+You can safely remove this option from your config.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html b/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html new file mode 100644 index 000000000000..18e918f34b96 --- /dev/null +++ b/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Electron could not install the extension at path:
+
+> /path/to/extension
+
+Please verify that this is the path to a valid, unpacked WebExtension.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html new file mode 100644 index 000000000000..cdec24ad31e0 --- /dev/null +++ b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
Cypress failed to make a connection to Firefox.
+
+This usually indicates there was a problem opening the Firefox browser.
+
+Error details:
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at FIREFOX_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html b/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html new file mode 100644 index 000000000000..fba58f5cfe58 --- /dev/null +++ b/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
The `firefoxGcInterval` configuration option was removed in Cypress version `8.0.0`. It was introduced to work around a bug in Firefox 79 and below.
+
+Since Cypress no longer supports Firefox 85 and below in Cypress 8, this option was removed.
+
+You can safely remove this option from your config.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html new file mode 100644 index 000000000000..e0656bed01f5 --- /dev/null +++ b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
Cypress could not connect to Firefox.
+
+An unexpected error was received from Marionette connection
+
+To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running.
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at FIREFOX_MARIONETTE_FAILURE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html b/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html new file mode 100644 index 000000000000..d95064c2c419 --- /dev/null +++ b/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html @@ -0,0 +1,47 @@ + + + + + + + + + + + +
A fixture file could not be found at any of the following paths:
+
+> file
+> file{{extension}}
+
+Cypress looked for these file extensions at the provided path:
+
+> js, ts, json
+
+Provide a path to an existing fixture file.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html b/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html new file mode 100644 index 000000000000..831fd4cc73ca --- /dev/null +++ b/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
Folder /path/to/folder is not writable.
+
+Writing to this directory is required by Cypress in order to store screenshots and videos.
+
+Enable write permissions to this directory to ensure screenshots and videos are stored.
+
+If you don't require screenshots or videos to be stored you can safely ignore this warning.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html b/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html new file mode 100644 index 000000000000..01124be756e7 --- /dev/null +++ b/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
You've exceeded the limit of private test results under your free plan this month. The limit is 500 free results
+
+To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan.
+
+https://dashboard.cypress.io/project/abcd
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_TESTS.html b/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_TESTS.html new file mode 100644 index 000000000000..0831de83e81f --- /dev/null +++ b/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_TESTS.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
You've exceeded the limit of test results under your free plan this month. The limit is 500 free results
+
+To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan.
+
+https://on.cypress.io/set-up-billing
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS.html b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS.html new file mode 100644 index 000000000000..5b691f602af4 --- /dev/null +++ b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
You've exceeded the limit of private test results under your free plan this month. The limit is 500 free results
+
+Your plan is now in a grace period, which means your tests will still be recorded until You are on a grace period for 20 days. Please upgrade your plan to continue recording tests on the Cypress Dashboard in the future.
+
+https://dashboard.cypress.io/project/abcd
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS.html b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS.html new file mode 100644 index 000000000000..264a947c3830 --- /dev/null +++ b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
You've exceeded the limit of test results under your free plan this month. The limit is 500 free results
+
+Your plan is now in a grace period, which means you will have the full benefits of your current plan until Feb 1, 2022.
+
+Please visit your billing to upgrade your plan.
+
+https://on.cypress.io/set-up-billing
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html new file mode 100644 index 000000000000..ebb2527f5153 --- /dev/null +++ b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Parallelization is not included under your free plan.
+
+Your plan is now in a grace period, which means your tests will still run in parallel until Feb 1, 2022. Please upgrade your plan to continue running your tests in parallel in the future.
+
+https://on.cypress.io/set-up-billing
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html b/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html new file mode 100644 index 000000000000..c3c70d514912 --- /dev/null +++ b/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
We've detected that the incompatible plugin `cypress-plugin-retries` is installed at ./path/to/cypress-plugin-retries.
+
+Test retries is now supported in Cypress version `5.0.0`.
+
+Remove the plugin from your dependencies to silence this warning.
+
+https://on.cypress.io/test-retries
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html b/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html new file mode 100644 index 000000000000..201b802906ce --- /dev/null +++ b/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
You passed the --ci-build-id flag but did not provide either a --group or --parallel flag.
+
+The --ci-build-id flag you passed was: ciBuildId123
+
+The --ci-build-id flag is used to either group or parallelize multiple runs together.
+
+https://on.cypress.io/incorrect-ci-build-id-usage
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html b/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html new file mode 100644 index 000000000000..0b609011279c --- /dev/null +++ b/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html @@ -0,0 +1,73 @@ + + + + + + + + + + + +
You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId.
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: false
+
+In order to use either of these features a ciBuildId must be determined.
+
+The ciBuildId is automatically detected if you are running Cypress in any of the these CI providers:
+
+- appveyor
+- azure
+- awsCodeBuild
+- bamboo
+- bitbucket
+- buildkite
+- circle
+- codeshipBasic
+- codeshipPro
+- concourse
+- codeFresh
+- drone
+- githubActions
+- gitlab
+- goCD
+- googleCloud
+- jenkins
+- semaphore
+- shippable
+- teamfoundation
+- travis
+- netlify
+- layerci
+
+Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually.
+
+https://on.cypress.io/indeterminate-ci-build-id
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION.html b/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION.html new file mode 100644 index 000000000000..dfd4c9467607 --- /dev/null +++ b/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
`test` is not a valid configuration option
+`foo` is not a valid configuration option
+
+https://on.cypress.io/configuration
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html b/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html new file mode 100644 index 000000000000..3d0f56ca0001 --- /dev/null +++ b/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
We have detected an unknown or unsupported "CYPRESS_INTERNAL_ENV" value
+
+  foo
+
+"CYPRESS_INTERNAL_ENV" is reserved and should only be used internally.
+
+Do not modify the "CYPRESS_INTERNAL_ENV" value.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html b/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html new file mode 100644 index 000000000000..0c7d16628118 --- /dev/null +++ b/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html @@ -0,0 +1,49 @@ + + + + + + + + + + + +
Could not load reporter by name: missing-reporter-name
+
+We searched for the reporter in these paths:
+
+- /path/to/reporter
+- /path/reporter
+
+The error we received was:
+
+stack-trace
+
+Learn more at https://on.cypress.io/reporters
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVOKED_BINARY_OUTSIDE_NPM_MODULE.html b/packages/errors/__snapshot-html__/INVOKED_BINARY_OUTSIDE_NPM_MODULE.html new file mode 100644 index 000000000000..837ba33218cc --- /dev/null +++ b/packages/errors/__snapshot-html__/INVOKED_BINARY_OUTSIDE_NPM_MODULE.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
It looks like you are running the Cypress binary directly.
+
+This is not the recommended approach, and Cypress may not work correctly.
+
+Please install the 'cypress' NPM package and follow the instructions here:
+
+https://on.cypress.io/installing-cypress
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html new file mode 100644 index 000000000000..e140265d7b0c --- /dev/null +++ b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
Deprecation Warning: `nodeVersion` is currently set to `bundled` in the `cypress.json` configuration file.
+
+As of Cypress version `9.0.0` the default behavior of `nodeVersion` has changed to always use the version of Node used to start cypress via the cli.
+
+When `nodeVersion` is set to `bundled`, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations.
+
+As the `nodeVersion` configuration option will be removed in a future release, it is recommended to remove the `nodeVersion` configuration option from `cypress.json`.
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html new file mode 100644 index 000000000000..dd454e40330e --- /dev/null +++ b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
Deprecation Warning: `nodeVersion` is currently set to `system` in the `cypress.json` configuration file.
+
+As of Cypress version `9.0.0` the default behavior of `nodeVersion` has changed to always use the version of Node used to start cypress via the cli.
+
+Please remove the `nodeVersion` configuration option from `cypress.json`.
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NOT_LOGGED_IN.html b/packages/errors/__snapshot-html__/NOT_LOGGED_IN.html new file mode 100644 index 000000000000..d3c51d8435f0 --- /dev/null +++ b/packages/errors/__snapshot-html__/NOT_LOGGED_IN.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
You're not logged in.
+
+Run `cypress open` to open the Desktop App and log in.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html b/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html new file mode 100644 index 000000000000..5d1abfb10317 --- /dev/null +++ b/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
Could not find a Cypress configuration file, exiting.
+
+We looked but did not find a default config file in this folder: /path/to/project/root
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html b/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html new file mode 100644 index 000000000000..37ec6b829594 --- /dev/null +++ b/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Can't find project at the path: /path/to/project
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_PROJECT_ID.html b/packages/errors/__snapshot-html__/NO_PROJECT_ID.html new file mode 100644 index 000000000000..0f2d2c1bd3b1 --- /dev/null +++ b/packages/errors/__snapshot-html__/NO_PROJECT_ID.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Can't find 'projectId' in the 'cypress.json' file for this project: /path/to/project
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html new file mode 100644 index 000000000000..69143a3055da --- /dev/null +++ b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Can't run because no spec files were found.
+
+We searched for any files inside of this folder:
+
+/path/to/project/root
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html b/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html new file mode 100644 index 000000000000..4e93dacee17c --- /dev/null +++ b/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html @@ -0,0 +1,46 @@ + + + + + + + + + + + +
Can't run because no spec files were found.
+
+We searched for any files matching this glob pattern:
+
+**_spec.js
+
+Relative to the project root folder:
+
+/path/to/project/root
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html b/packages/errors/__snapshot-html__/PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html new file mode 100644 index 000000000000..0bf8f34d7deb --- /dev/null +++ b/packages/errors/__snapshot-html__/PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
You've exceeded the limit of private test results under your current billing plan this month. The limit is 500 free results
+
+To upgrade your account, please visit your billing to upgrade to another billing plan.
+
+https://on.cypress.io/set-up-billing
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html b/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html new file mode 100644 index 000000000000..43fe54bbcbc2 --- /dev/null +++ b/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Parallelization is not included under your current billing plan.
+
+To run your tests in parallel, please visit your billing and upgrade to another plan with parallelization.
+
+https://on.cypress.io/set-up-billing
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLAN_EXCEEDS_MONTHLY_TESTS.html b/packages/errors/__snapshot-html__/PLAN_EXCEEDS_MONTHLY_TESTS.html new file mode 100644 index 000000000000..6b6a853d67f5 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLAN_EXCEEDS_MONTHLY_TESTS.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
You've exceeded the limit of test results under your Test Plan billing plan this month. The limit is 500 free results
+
+To continue getting the full benefits of your current plan, please visit your billing to upgrade.
+
+https://on.cypress.io/set-up-billing
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html b/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html new file mode 100644 index 000000000000..b9ea4790eacc --- /dev/null +++ b/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Grouping is not included under your free plan.
+
+Your plan is now in a grace period, which means your tests will still run with groups until Feb 1, 2022. Please upgrade your plan to continue running your tests with groups in the future.
+
+https://on.cypress.io/set-up-billing
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html new file mode 100644 index 000000000000..e352e75c6b08 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
An invalid configuration value returned from the plugins file: /path/to/pluginsFile
+
+fail whale
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html new file mode 100644 index 000000000000..223514b5d6b6 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html @@ -0,0 +1,50 @@ + + + + + + + + + + + +
The `pluginsFile` must export a function with the following signature:
+
+```
+module.exports = function (on, config) {
+  // configure plugins here
+}
+```
+
+Learn more: https://on.cypress.io/plugins-api
+
+We loaded the `pluginsFile` from: /path/to/pluginsFile
+
+It exported:
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html new file mode 100644 index 000000000000..f6279e4ba2f1 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html @@ -0,0 +1,49 @@ + + + + + + + + + + + +
The plugins file is missing or invalid.
+
+Your `pluginsFile` is set to /path/to/pluginsFile, but either the file is missing, it contains a syntax error, or threw an error when required. The `pluginsFile` must be a `.js`, `.ts`, or `.coffee` file.
+
+Or you might have renamed the extension of your `pluginsFile`. If that's the case, restart the test runner.
+
+Please fix this, or set `pluginsFile` to `false` if a plugins file is not necessary for your project.
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_FILE_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html new file mode 100644 index 000000000000..d6f9dcc1276c --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
The function exported by the plugins file threw an error.
+
+We invoked the function exported by /path/to/pluginsFile, but it threw an error.
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_FUNCTION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html new file mode 100644 index 000000000000..3b8e56684a1d --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
An error was thrown in your plugins file while executing the handler for the 'before:spec' event.
+
+The error we received was:
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_RUN_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html new file mode 100644 index 000000000000..0a11737af5d8 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (/path/to/pluginsFile)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html new file mode 100644 index 000000000000..fcb0ccb9b976 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
The following validation error was thrown by your plugins file (/path/to/pluginsFile).
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html b/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html new file mode 100644 index 000000000000..0a87c053601f --- /dev/null +++ b/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
Can't run project because port is currently in use: 2020
+
+Assign a different port with the '--port <port>' argument or shut down the other running process.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PORT_IN_USE_SHORT.html b/packages/errors/__snapshot-html__/PORT_IN_USE_SHORT.html new file mode 100644 index 000000000000..65c95fd75dc3 --- /dev/null +++ b/packages/errors/__snapshot-html__/PORT_IN_USE_SHORT.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Port 2020 is already in use.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html b/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html new file mode 100644 index 000000000000..d319578fc76a --- /dev/null +++ b/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html @@ -0,0 +1,54 @@ + + + + + + + + + + + +
This project has been configured to record runs on our Dashboard.
+
+It currently has the projectId: abc123
+
+You also provided your Record Key, but you did not pass the --record flag.
+
+This run will not be recorded.
+
+If you meant to have this run recorded please additionally pass this flag.
+
+  cypress run --record
+
+If you don't want to record these runs, you can silence this warning:
+
+  cypress run --record false
+
+https://on.cypress.io/recording-project-runs
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RECORDING_FROM_FORK_PR.html b/packages/errors/__snapshot-html__/RECORDING_FROM_FORK_PR.html new file mode 100644 index 000000000000..bca10640cd91 --- /dev/null +++ b/packages/errors/__snapshot-html__/RECORDING_FROM_FORK_PR.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
Warning: It looks like you are trying to record this run from a forked PR.
+
+The 'Record Key' is missing. Your CI provider is likely not passing private environment variables to builds from forks.
+
+These results will not be recorded.
+
+This error will not alter the exit code.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html b/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html new file mode 100644 index 000000000000..92a0417aa2e0 --- /dev/null +++ b/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html @@ -0,0 +1,46 @@ + + + + + + + + + + + +
You passed the --record flag but did not provide us your Record Key.
+
+You can pass us your Record Key like this:
+
+  cypress run --record --key <record_key>
+
+You can also set the key as an environment variable with the name CYPRESS_RECORD_KEY.
+
+https://on.cypress.io/how-do-i-record-runs
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html b/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html new file mode 100644 index 000000000000..760372a7deda --- /dev/null +++ b/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag.
+
+The --parallel flag you passed was: true
+
+These flags can only be used when recording to the Cypress Dashboard service.
+
+https://on.cypress.io/record-params-without-recording
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html b/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html new file mode 100644 index 000000000000..3376a4b2e487 --- /dev/null +++ b/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
The oldName configuration option you have supplied has been renamed.
+
+Please rename oldName to newName
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RENDERER_CRASHED.html b/packages/errors/__snapshot-html__/RENDERER_CRASHED.html new file mode 100644 index 000000000000..895ad6c13957 --- /dev/null +++ b/packages/errors/__snapshot-html__/RENDERER_CRASHED.html @@ -0,0 +1,54 @@ + + + + + + + + + + + +
We detected that the Chromium Renderer process just crashed.
+
+This is the equivalent to seeing the 'sad face' when Chrome dies.
+
+This can happen for a number of different reasons:
+
+- You wrote an endless loop and you must fix your own code
+- There is a memory leak in Cypress (unlikely but possible)
+- You are running Docker (there is an easy fix for this: see link below)
+- You are running lots of tests on a memory intense application
+- You are running in a memory starved VM environment
+- There are problems with your GPU / GPU drivers
+- There are browser bugs in Chromium
+
+You can learn more including how to fix Docker here:
+
+https://on.cypress.io/renderer-process-crashed
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html b/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html new file mode 100644 index 000000000000..bad15566022c --- /dev/null +++ b/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Grouping is not included under your current billing plan.
+
+To run your tests with groups, please visit your billing and upgrade to another plan with grouping.
+
+https://on.cypress.io/set-up-billing
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html new file mode 100644 index 000000000000..07091d594a36 --- /dev/null +++ b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
We found an invalid value in the file: /path/to/file
+
+fail whale
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html new file mode 100644 index 000000000000..7c03062d196f --- /dev/null +++ b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html @@ -0,0 +1,46 @@ + + + + + + + + + + + +
The support file is missing or invalid.
+
+Your supportFile is set to /path/to/supportFile, but either the file is missing or it's invalid. The `supportFile` must be a `.js`, `.ts`, `.coffee` file or be supported by your preprocessor plugin (if configured).
+
+Correct your `/path/to/cypress.json`, create the appropriate file, or set `supportFile` to `false` if a support file is not necessary for your project.
+
+Or you might have renamed the extension of your `supportFile` to `.ts`. If that's the case, restart the test runner.
+
+Learn more at https://on.cypress.io/support-file-missing-or-invalid
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_FAILED.html b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_FAILED.html new file mode 100644 index 000000000000..5ed1b81c914c --- /dev/null +++ b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_FAILED.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
The browser never connected. Something is wrong. The tests cannot run. Aborting...
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING - retryingAgain.html b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING - retryingAgain.html new file mode 100644 index 000000000000..832d09988f1e --- /dev/null +++ b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING - retryingAgain.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Timed out waiting for the browser to connect. Retrying again...
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING.html b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING.html new file mode 100644 index 000000000000..59c368f884b7 --- /dev/null +++ b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Timed out waiting for the browser to connect. Retrying...
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html b/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html new file mode 100644 index 000000000000..76077f71fa5a --- /dev/null +++ b/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html @@ -0,0 +1,48 @@ + + + + + + + + + + + +
The `launchOptions` object returned by your plugin's `before:browser:launch` handler contained unexpected properties:
+
+- baz
+
+`launchOptions` may only contain the properties:
+
+- preferences
+- extensions
+- args
+
+https://on.cypress.io/browser-launch-api
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/UNSUPPORTED_BROWSER_VERSION.html b/packages/errors/__snapshot-html__/UNSUPPORTED_BROWSER_VERSION.html new file mode 100644 index 000000000000..8e116d004efb --- /dev/null +++ b/packages/errors/__snapshot-html__/UNSUPPORTED_BROWSER_VERSION.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
Cypress does not support running chrome version 64. To use chrome with Cypress, install a version of chrome newer than or equal to 64.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html new file mode 100644 index 000000000000..f759d21393bc --- /dev/null +++ b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
Warning: We failed processing this video.
+
+This error will not alter the exit code.
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at VIDEO_POST_PROCESSING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html new file mode 100644 index 000000000000..5e789ecbfeb8 --- /dev/null +++ b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html @@ -0,0 +1,45 @@ + + + + + + + + + + + +
Warning: We failed to record the video.
+
+This error will not alter the exit code.
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at VIDEO_RECORDING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file From c6232c665b9d82626e3f92ce52cc53b44b3fe1b9 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 11:58:25 -0500 Subject: [PATCH 050/165] Merge branch 'tgriesser/chore/refactor-errors' of https://github.com/cypress-io/cypress into tgriesser/chore/refactor-errors --- circle.yml | 1 + packages/errors/src/errors.ts | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 0995990fc8d9..135e60dfe96b 100644 --- a/circle.yml +++ b/circle.yml @@ -998,6 +998,7 @@ jobs: - run: name: Linting 🧹 command: | + yarn clean git clean -df yarn lint - run: diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 4e145d3f92bd..d5fdbd594e33 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -1037,8 +1037,7 @@ export const AllCypressErrors = { The error we received was: - ${details(arg2)} - ` + ${details(arg2)}` }, CT_NO_DEV_START_EVENT: (arg1: string) => { const pluginsFilePath = arg1 ? From 831f2d9e3733adb2933dfc9291778191fd26ce16 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 12:07:15 -0500 Subject: [PATCH 051/165] remove concurrency --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c777c6d5b83..de31579cb37c 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "stop-only": "npx stop-only --skip .cy,.publish,.projects,node_modules,dist,dist-test,fixtures,lib,bower_components,src,__snapshots__ --exclude e2e.ts,cypress-tests.ts,*only_spec.js", "stop-only-all": "yarn stop-only --folder packages", "pretest": "yarn ensure-deps", - "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,errors,electron,extension,https-proxy,launcher,net-stubbing,network,proxy,rewriter,runner,runner-shared,socket}'\" --concurrency=1", + "test": "yarn lerna exec yarn test --scope cypress --scope \"'@packages/{config,errors,electron,extension,https-proxy,launcher,net-stubbing,network,proxy,rewriter,runner,runner-shared,socket}'\"", "test-debug": "lerna exec yarn test-debug --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"", "pretest-e2e": "yarn ensure-deps", "test-integration": "lerna exec yarn test-integration --ignore \"'@packages/{desktop-gui,driver,root,static,web-config}'\"", From 17f1ce6857126fbcfac6302bb4a6b6d49f875d53 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Fri, 4 Feb 2022 12:19:36 -0500 Subject: [PATCH 052/165] fix assertion --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 5d5369604e88..0d279eb2a228 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -172,7 +172,7 @@ const testVisualErrors = (whichError: CypressErrorType[] | '*', errorsToTest: {[ // return fse.remove(pathToHtml) // })) - expect(uniqErrors).to.deep.eq(_.keys(errors.AllCypressErrors)) + expect(uniqErrors).to.have.all.members(_.keys(errors.AllCypressErrors)) } }) From d8c844611837fe838d20b1dd55f491f0bb142eea Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 4 Feb 2022 12:31:14 -0500 Subject: [PATCH 053/165] fixing ci --- circle.yml | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/circle.yml b/circle.yml index 135e60dfe96b..44c90f64ceb6 100644 --- a/circle.yml +++ b/circle.yml @@ -1087,7 +1087,7 @@ jobs: # run unit tests from each individual package - run: yarn test - verify-mocha-results: - expectedResultCount: 9 + expectedResultCount: 10 - store_test_results: path: /tmp/cypress # CLI tests generate HTML files with sample CLI command output diff --git a/package.json b/package.json index de31579cb37c..78c78a3ec1cb 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "bump": "node ./scripts/binary.js bump", "check-node-version": "node scripts/check-node-version.js", "check-terminal": "node scripts/check-terminal.js", - "clean": "lerna run clean --parallel --no-bail", + "clean": "lerna run clean --parallel --no-bail || echo 'ok, errors while cleaning'", "clean-deps": "find . -depth -name node_modules -type d -exec rm -rf {} \\;", "clean-untracked-files": "git clean -d -f", "precypress:open": "yarn ensure-deps", From b95c2a3146816cd7e40466ce89db15dbdfcfac43 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 4 Feb 2022 12:33:17 -0500 Subject: [PATCH 054/165] Link in readme --- packages/errors/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/errors/README.md b/packages/errors/README.md index ee4ea656181d..237ec22ab331 100644 --- a/packages/errors/README.md +++ b/packages/errors/README.md @@ -1,3 +1,5 @@ ## @packages/errors -Error definitions and utilities for Cypress \ No newline at end of file +Error definitions and utilities for Cypress + +See [Error Handling Guide](../../guides/error-handling.md) for more info \ No newline at end of file From 8935f18cb5016f91e74cc2afa6471fc87a8c20cd Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 4 Feb 2022 12:45:10 -0500 Subject: [PATCH 055/165] pass the browsers to listItems --- .../BROWSER_NOT_FOUND_BY_NAME - canary.html | 14 ++++++++++++-- .../BROWSER_NOT_FOUND_BY_NAME.html | 12 +++++++++++- packages/errors/src/errors.ts | 4 ++-- packages/server/lib/browsers/index.js | 4 +--- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html index 76e1e5e13570..054d0291119d 100644 --- a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html @@ -48,9 +48,19 @@ You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: -chrome,chromium,chrome:beta,chrome:canary,firefox,firefox:dev,firefox:nightly,edge,edge:canary,edge:beta,edge:dev +- chrome +- chromium +- chrome:beta +- chrome:canary +- firefox +- firefox:dev +- firefox:nightly +- edge +- edge:canary +- edge:beta +- edge:dev Note: In Cypress version 4.0.0, Canary must be launched as `chrome:canary`, not `canary`. -See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0. +See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0. \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html index c5575316eb39..61ad1d148204 100644 --- a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html @@ -48,5 +48,15 @@ You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: -chrome,chromium,chrome:beta,chrome:canary,firefox,firefox:dev,firefox:nightly,edge,edge:canary,edge:beta,edge:dev +- chrome +- chromium +- chrome:beta +- chrome:canary +- firefox +- firefox:dev +- firefox:nightly +- edge +- edge:canary +- edge:beta +- edge:dev \ No newline at end of file diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index d5fdbd594e33..f411f8374cf3 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -82,7 +82,7 @@ export const AllCypressErrors = { This option will not have an effect in ${guard(_.capitalize(browser))}. Tests that rely on web security being disabled will not run as expected.` }, - BROWSER_NOT_FOUND_BY_NAME: (browser: string, foundBrowsersStr: string) => { + BROWSER_NOT_FOUND_BY_NAME: (browser: string, foundBrowsersStr: string[]) => { let canarySuffix = '' if (browser === 'canary') { @@ -108,7 +108,7 @@ export const AllCypressErrors = { You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: - ${guard(foundBrowsersStr)}${guard(canarySuffix)}` + ${listItems(foundBrowsersStr)}${guard(canarySuffix)}` }, BROWSER_NOT_FOUND_BY_PATH: (arg1: string, arg2: string) => { return errTemplate`\ diff --git a/packages/server/lib/browsers/index.js b/packages/server/lib/browsers/index.js index 3ae32bc3ebeb..35b1377c9916 100644 --- a/packages/server/lib/browsers/index.js +++ b/packages/server/lib/browsers/index.js @@ -137,9 +137,7 @@ const formatBrowsersToOptions = (browsers) => { } const throwBrowserNotFound = function (browserName, browsers = []) { - const names = `- ${formatBrowsersToOptions(browsers).join('\n- ')}` - - return errors.throw('BROWSER_NOT_FOUND_BY_NAME', browserName, names) + return errors.throw('BROWSER_NOT_FOUND_BY_NAME', browserName, formatBrowsersToOptions(browsers)) } process.once('exit', () => kill(true, true)) From 7e9468d2391259f96507b47427a34d52bc5fa729 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 4 Feb 2022 14:23:42 -0500 Subject: [PATCH 056/165] fix: build @packages/errors in CI, defer import to prevent errors locally --- packages/errors/package.json | 3 +-- packages/server/lib/plugins/child/browser_launch.js | 3 +-- packages/server/lib/plugins/child/task.js | 3 +-- packages/server/test/integration/cypress_spec.js | 2 +- scripts/run-if-ci.sh | 6 ++++++ system-tests/__snapshots__/plugin_run_events_spec.ts.js | 2 -- 6 files changed, 10 insertions(+), 9 deletions(-) create mode 100755 scripts/run-if-ci.sh diff --git a/packages/errors/package.json b/packages/errors/package.json index 8fbb95e48ab6..d3acaa09dce0 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -6,9 +6,8 @@ "browser": "src/index.ts", "scripts": { "test": "yarn test-unit", - "build": "tsc || echo 'type errors'", + "build": "../../scripts/run-if-ci.sh tsc || echo 'type errors'", "build-prod": "tsc", - "postinstall": "rm -rf test/**/*.js", "check-ts": "tsc --noEmit", "clean-deps": "rm -rf node_modules", "clean": "rm -f ./src/*.js ./src/**/*.js ./src/**/**/*.js ./test/**/*.js || echo 'cleaned'", diff --git a/packages/server/lib/plugins/child/browser_launch.js b/packages/server/lib/plugins/child/browser_launch.js index c4f7152259df..c5680961e3d8 100644 --- a/packages/server/lib/plugins/child/browser_launch.js +++ b/packages/server/lib/plugins/child/browser_launch.js @@ -1,5 +1,4 @@ const util = require('../util') -const { getError } = require('@packages/errors') const ARRAY_METHODS = ['concat', 'push', 'unshift', 'slice', 'pop', 'shift', 'slice', 'splice', 'filter', 'map', 'forEach', 'reduce', 'reverse', 'splice', 'includes'] @@ -21,7 +20,7 @@ module.exports = { hasEmittedWarning = true - const warning = getError( + const warning = require('@packages/errors').getError( 'DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS', ) diff --git a/packages/server/lib/plugins/child/task.js b/packages/server/lib/plugins/child/task.js index 9aec891852b1..76590b5ab55f 100644 --- a/packages/server/lib/plugins/child/task.js +++ b/packages/server/lib/plugins/child/task.js @@ -1,6 +1,5 @@ const _ = require('lodash') const util = require('../util') -const errors = require('@packages/errors') const getBody = (ipc, events, ids, [event]) => { const taskEvent = _.find(events, { event: 'task' }).handler @@ -24,7 +23,7 @@ const merge = (prevEvents, events) => { const duplicates = _.intersection(_.keys(prevEvents), _.keys(events)) if (duplicates.length) { - errors.warning('DUPLICATE_TASK_KEY', duplicates.join(', ')) + require('@packages/errors').warning('DUPLICATE_TASK_KEY', duplicates.join(', ')) } return _.extend(prevEvents, events) diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index 43cb67fa0f3c..eb0d10540959 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -721,7 +721,7 @@ describe('lib/cypress', () => { const found3 = _.find(argsSet, (args) => { return _.find(args, (arg) => { - return arg.message && arg.message.includes( + return arg.message && stripAnsi(arg.message).includes( 'Available browsers found on your system are:\n- chrome\n- chromium\n- chrome:canary\n- electron', ) }) diff --git a/scripts/run-if-ci.sh b/scripts/run-if-ci.sh new file mode 100755 index 000000000000..6f5a38355fda --- /dev/null +++ b/scripts/run-if-ci.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +# If we're in CI, exit early +if [[ !$CI ]]; then exit 0; fi +# otherwise, run the supplied command +"${@:1}" diff --git a/system-tests/__snapshots__/plugin_run_events_spec.ts.js b/system-tests/__snapshots__/plugin_run_events_spec.ts.js index d89a29cd8d3c..0e5d0f56a1e3 100644 --- a/system-tests/__snapshots__/plugin_run_events_spec.ts.js +++ b/system-tests/__snapshots__/plugin_run_events_spec.ts.js @@ -166,6 +166,4 @@ The error we received was: Error: error thrown in before:spec [stack trace lines] - - ` From 9995f25bde44b5fb20be2819d1755d3876a35060 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 4 Feb 2022 14:27:23 -0500 Subject: [PATCH 057/165] 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) --- .../merge-develop-into-10.0-release.yml | 78 ----- .../workflows/merge-master-into-develop.yml | 3 +- __snapshots__/upload-npm-package-spec.js | 9 - __snapshots__/upload-unique-binary-spec.js | 26 -- assets/cypress-bot-pre-release-comment.png | Bin 0 -> 169345 bytes browser-versions.json | 4 +- circle.yml | 56 ++-- cli/lib/tasks/install.js | 2 +- cli/test/lib/tasks/install_spec.js | 6 +- guides/release-process.md | 17 +- .../commands/actions/selectFile_spec.js | 12 + .../commands/{ => querying}/querying_spec.js | 261 +-------------- .../shadow_dom_spec.js} | 30 +- .../commands/querying/within_spec.js | 300 ++++++++++++++++++ .../src/cy/commands/actions/selectFile.ts | 10 +- packages/driver/src/cy/commands/index.ts | 2 +- .../driver/src/cy/commands/querying/index.ts | 7 + .../cy/commands/{ => querying}/querying.ts | 110 +------ .../driver/src/cy/commands/querying/within.ts | 105 ++++++ packages/electron/app/{app.js => index.js} | 0 packages/electron/package.json | 2 +- packages/server/lib/video_capture.ts | 53 +++- scripts/binary/index.js | 33 +- scripts/binary/move-binaries.ts | 10 +- scripts/binary/upload-build-artifact.js | 145 +++++++++ scripts/binary/upload-npm-package.js | 122 ------- scripts/binary/upload-unique-binary.js | 197 ------------ scripts/binary/upload.js | 91 +++--- scripts/binary/util/upload.js | 1 - .../unit/binary/upload-build-artifact-spec.js | 140 ++++++++ .../unit/binary/upload-npm-package-spec.js | 23 -- scripts/unit/binary/upload-spec.js | 28 +- .../unit/binary/upload-unique-binary-spec.js | 62 ---- yarn.lock | 61 ++-- 34 files changed, 887 insertions(+), 1119 deletions(-) delete mode 100644 .github/workflows/merge-develop-into-10.0-release.yml delete mode 100644 __snapshots__/upload-npm-package-spec.js delete mode 100644 __snapshots__/upload-unique-binary-spec.js create mode 100644 assets/cypress-bot-pre-release-comment.png rename packages/driver/cypress/integration/commands/{ => querying}/querying_spec.js (89%) rename packages/driver/cypress/integration/commands/{querying_shadow_dom_spec.js => querying/shadow_dom_spec.js} (87%) create mode 100644 packages/driver/cypress/integration/commands/querying/within_spec.js create mode 100644 packages/driver/src/cy/commands/querying/index.ts rename packages/driver/src/cy/commands/{ => querying}/querying.ts (83%) create mode 100644 packages/driver/src/cy/commands/querying/within.ts rename packages/electron/app/{app.js => index.js} (100%) create mode 100644 scripts/binary/upload-build-artifact.js delete mode 100644 scripts/binary/upload-npm-package.js delete mode 100644 scripts/binary/upload-unique-binary.js create mode 100644 scripts/unit/binary/upload-build-artifact-spec.js delete mode 100644 scripts/unit/binary/upload-npm-package-spec.js delete mode 100644 scripts/unit/binary/upload-unique-binary-spec.js diff --git a/.github/workflows/merge-develop-into-10.0-release.yml b/.github/workflows/merge-develop-into-10.0-release.yml deleted file mode 100644 index 4fc28f10870e..000000000000 --- a/.github/workflows/merge-develop-into-10.0-release.yml +++ /dev/null @@ -1,78 +0,0 @@ -name: Merge develop into 10.0-release -on: - push: - branches: - - develop -jobs: - merge-develop-into-10-0-release: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - name: Set committer info - run: | - git config --local user.email "$(git log --format='%ae' HEAD^!)" - git config --local user.name "$(git log --format='%an' HEAD^!)" - - name: Checkout 10.0-release branch - run: git checkout 10.0-release - - name: Check for merge conflict - id: check-conflict - run: echo "::set-output name=merge_conflict::$(git merge-tree $(git merge-base HEAD develop) develop HEAD | egrep '<<<<<<<')" - - name: Merge develop into 10.0-release - id: merge-develop - run: git merge develop - if: ${{ !steps.check-conflict.outputs.merge_conflict }} - - name: Failed merge, set merged status as failed - run: echo "::set-output name=merge_conflict::'failed merge'" - if: ${{ steps.merge-develop.outcome != 'success' }} - - name: Push - run: git push - if: ${{ !steps.check-conflict.outputs.merge_conflict }} - - name: Checkout develop - run: git checkout develop - if: ${{ steps.check-conflict.outputs.merge_conflict }} - - name: Determine name of new branch - id: gen-names - run: | - echo "::set-output name=sha::$(git rev-parse --short HEAD)" - echo "::set-output name=branch_name::$(git rev-parse --short HEAD)-develop-into-10.0-release" - if: ${{ steps.check-conflict.outputs.merge_conflict }} - - name: Create a copy of develop on a new branch - run: git checkout -b ${{ steps.gen-names.outputs.branch_name }} develop - if: ${{ steps.check-conflict.outputs.merge_conflict }} - - name: Push branch to remote - run: git push origin ${{ steps.gen-names.outputs.branch_name }} - if: ${{ steps.check-conflict.outputs.merge_conflict }} - - name: Create Pull Request - uses: actions/github-script@v3 - with: - script: | - const pull = await github.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - base: '10.0-release', - head: '${{ steps.gen-names.outputs.branch_name }}', - title: 'chore: merge develop (${{ steps.gen-names.outputs.sha }}) into 10.0-release', - body: `There was a merge conflict when trying to automatically merge develop into 10.0-release. Please resolve the conflict and complete the merge. - - DO NOT SQUASH AND MERGE - - @${context.actor}`, - maintainer_can_modify: true, - }) - await github.pulls.requestReviewers({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pull.data.number, - reviewers: [context.actor], - }) - await github.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pull.data.number, - labels: ['auto-merge'], - }) - if: ${{ steps.check-conflict.outputs.merge_conflict }} diff --git a/.github/workflows/merge-master-into-develop.yml b/.github/workflows/merge-master-into-develop.yml index 28c1a9117125..2b15adabc19c 100644 --- a/.github/workflows/merge-master-into-develop.yml +++ b/.github/workflows/merge-master-into-develop.yml @@ -11,7 +11,8 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} + # the default `GITHUB_TOKEN` cannot push to protected branches, so use `cypress-app-bot`'s token instead + token: ${{ secrets.BOT_GITHUB_TOKEN }} - name: Set committer info run: | git config --local user.email "$(git log --format='%ae' HEAD^!)" diff --git a/__snapshots__/upload-npm-package-spec.js b/__snapshots__/upload-npm-package-spec.js deleted file mode 100644 index 5872c9c73424..000000000000 --- a/__snapshots__/upload-npm-package-spec.js +++ /dev/null @@ -1,9 +0,0 @@ -exports['getCDN npm package returns CDN s3 path 1'] = { - "input": { - "platform": "darwin-x64", - "filename": "cypress.tgz", - "version": "3.3.0", - "hash": "ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123" - }, - "result": "https://cdn.cypress.io/beta/npm/3.3.0/ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123/cypress.tgz" -} diff --git a/__snapshots__/upload-unique-binary-spec.js b/__snapshots__/upload-unique-binary-spec.js deleted file mode 100644 index 9f7c9488d49c..000000000000 --- a/__snapshots__/upload-unique-binary-spec.js +++ /dev/null @@ -1,26 +0,0 @@ -exports['getCDN for binary'] = { - "input": { - "platform": "darwin-x64", - "filename": "cypress.zip", - "version": "3.3.0", - "hash": "ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123" - }, - "result": "https://cdn.cypress.io/beta/binary/3.3.0/darwin-x64/ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123/cypress.zip" -} - -exports['upload binary folder'] = { - "input": { - "platformArch": "darwin-x64", - "version": "3.3.0", - "hash": "ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123" - }, - "result": "beta/binary/3.3.0/darwin-x64/ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123/" -} - -exports['upload binary folder for platform'] = { - "input": { - "platformArch": "darwin-x64", - "version": "3.3.0" - }, - "result": "beta/binary/3.3.0/darwin-x64" -} diff --git a/assets/cypress-bot-pre-release-comment.png b/assets/cypress-bot-pre-release-comment.png new file mode 100644 index 0000000000000000000000000000000000000000..28c8cccdc8f70c20d8feda0b1f0105ad74a53fd7 GIT binary patch literal 169345 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 R { }) } -const betaNpmUrlRe = /^\/beta\/npm\/(?[0-9.]+)\/(?.+?)\/cypress\.tgz$/ +const betaNpmUrlRe = /^\/beta\/npm\/(?[0-9.]+)\/(?.+?)\/(?.+?)\/cypress\.tgz$/ // convert a prerelease NPM package .tgz URL to the corresponding binary .zip URL const getBinaryUrlFromPrereleaseNpmUrl = (npmUrl) => { diff --git a/cli/test/lib/tasks/install_spec.js b/cli/test/lib/tasks/install_spec.js index f8a7d9f66e4f..ec1b91ac813f 100644 --- a/cli/test/lib/tasks/install_spec.js +++ b/cli/test/lib/tasks/install_spec.js @@ -467,13 +467,13 @@ describe('/lib/tasks/install', function () { }) it('returns binary url for prerelease npm url', function () { - expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/ciprovider-branchname-sha/cypress.tgz')) + expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/linux-x64/ciprovider-branchname-sha/cypress.tgz')) .to.eq('https://cdn.cypress.io/beta/binary/5.1.1/linux-x64/ciprovider-branchname-sha/cypress.zip') - expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/circle-develop-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz')) + expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/inux-x64/circle-develop-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz')) .to.eq('https://cdn.cypress.io/beta/binary/5.1.1/linux-x64/circle-develop-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.zip') - expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/circle-develop/some/branch-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz')) + expect(install._getBinaryUrlFromPrereleaseNpmUrl('https://cdn.cypress.io/beta/npm/5.1.1/inux-x64/circle-develop/some/branch-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.tgz')) .to.eq('https://cdn.cypress.io/beta/binary/5.1.1/linux-x64/circle-develop/some/branch-3fdfc3b453eb38ad3c0b079531e4dde6668e3dd0-436710/cypress.zip') }) diff --git a/guides/release-process.md b/guides/release-process.md index 86d342c0926b..ce2c0e8e5392 100644 --- a/guides/release-process.md +++ b/guides/release-process.md @@ -67,13 +67,7 @@ of Cypress. You can see the progress of the test projects by opening the status ![Screenshot of status checks](https://i.imgur.com/AsQwzgO.png) -#### :bangbang: Important :bangbang: - -The `linux x64`, `win32 x64`, and `darwin x64` artifacts produced by CI are all placed in the same directory on the CDN. The version that was built last will overwrite the other versions in the directory. Until work is done to complete [#19771](https://github.com/cypress-io/cypress/issues/19771), you must ensure that the `linux` workflow publishes its artifacts **after** the `windows`/`mac` workflows. To guarantee this, you can re-run the `create-build-artifacts` job for the `linux` workflow within CircleCI after the initial builds have completed. - - - -Once the `develop` branch for all test projects are reliably passing with the new changes and the `linux` binary is present at `https://cdn.cypress.io/beta/npm/X.Y.Z//cypress.tgz`, publishing can proceed. +Once the `develop` branch for all test projects are reliably passing with the new changes and the `linux-x64` binary is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64//cypress.zip`, and the `linux-x64` cypress npm package is present at `https://cdn.cypress.io/beta/binary/X.Y.Z/linux-x64//cypress.tgz`, publishing can proceed. ### Steps to Publish a New Version @@ -93,14 +87,15 @@ In the following instructions, "X.Y.Z" is used to denote the [next version of Cy - To find the link to the package file `cypress.tgz`: 1. In GitHub, go to the latest commit (the one whose sha you used in the last step). ![commit-link](https://user-images.githubusercontent.com/1157043/80608728-33fe6100-8a05-11ea-8b53-375303757b67.png) - 2. Scroll down past the changes to the comments. The first comment should be a `cypress-bot` comment that includes a line beginning `npm install ...`. Grab the `https://cdn.../npm/X.Y.Z//cypress.tgz` link. - ![cdn-tgz-link](https://user-images.githubusercontent.com/1157043/80608736-3791e800-8a05-11ea-8d75-e4f80128e857.png) - - Make sure the linux binaries are present at that location. See [Before Publishing a New Version](#before-publishing-a-new-version). - - Publish to the npm registry straight from the URL: + 2. Scroll down past the changes to the comments. The first comment should be a `cypress-bot` comment that includes a line beginning `npm install ...`. Grab the `https://cdn.../npm/X.Y.Z///cypress.tgz` link. + ![commit-bot-comment](../assets/cypress-bot-pre-release-comment.png) + - Make sure the `linux-x64` binary and npm package are present at the commented locations. See [Before Publishing a New Version](#before-publishing-a-new-version). + - Publish the `linux-x64` distribution to the npm registry straight from the URL: ```shell npm publish https://cdn.cypress.io/beta/npm/X.Y.Z//cypress.tgz --tag dev ``` + :bangbang: Important :bangbang: Be sure to release the `linux-x64` distribution. 5. Double-check that the new version has been published under the `dev` tag using `npm info cypress` or [available-versions](https://github.com/bahmutov/available-versions). `latest` should still point to the previous version. Example output: diff --git a/packages/driver/cypress/integration/commands/actions/selectFile_spec.js b/packages/driver/cypress/integration/commands/actions/selectFile_spec.js index cf73e534ed69..fdcb604a7d8e 100644 --- a/packages/driver/cypress/integration/commands/actions/selectFile_spec.js +++ b/packages/driver/cypress/integration/commands/actions/selectFile_spec.js @@ -185,6 +185,18 @@ describe('src/cy/commands/actions/selectFile', () => { }) }) + it('uses the AUT\'s File constructor', () => { + cy.window().then(($autWindow) => { + cy.get('#basic').selectFile('@foo', { action: 'select' }).then((input) => { + expect(input[0].files[0]).to.be.instanceOf($autWindow.File) + }) + + cy.get('#basic').selectFile('@foo', { action: 'drag-drop' }).then((input) => { + expect(input[0].files[0]).to.be.instanceOf($autWindow.File) + }) + }) + }) + describe('shorthands', () => { const validJsonString = `{ "foo": 1, diff --git a/packages/driver/cypress/integration/commands/querying_spec.js b/packages/driver/cypress/integration/commands/querying/querying_spec.js similarity index 89% rename from packages/driver/cypress/integration/commands/querying_spec.js rename to packages/driver/cypress/integration/commands/querying/querying_spec.js index e1a685776113..6882f42c8be4 100644 --- a/packages/driver/cypress/integration/commands/querying_spec.js +++ b/packages/driver/cypress/integration/commands/querying/querying_spec.js @@ -1,4 +1,4 @@ -const { assertLogLength } = require('../../support/utils') +const { assertLogLength } = require('../../../support/utils') const { _, $, Promise } = Cypress @@ -225,265 +225,6 @@ describe('src/cy/commands/querying', () => { }) }) - context('#within', () => { - it('invokes callback function with runnable.ctx', function () { - const ctx = this - - cy.get('div:first').within(function () { - expect(ctx === this).to.be.true - }) - }) - - it('scopes additional GET finders to the subject', () => { - const input = cy.$$('#by-name input:first') - - cy.get('#by-name').within(() => { - cy.get('input:first').then(($input) => { - expect($input.get(0)).to.eq(input.get(0)) - }) - }) - }) - - it('scopes additional CONTAINS finders to the subject', () => { - const span = cy.$$('#nested-div span:contains(foo)') - - cy.contains('foo').then(($span) => { - expect($span.get(0)).not.to.eq(span.get(0)) - }) - - cy.get('#nested-div').within(() => { - cy.contains('foo').then(($span) => { - expect($span.get(0)).to.eq(span.get(0)) - }) - }) - }) - - it('does not change the subject', () => { - const form = cy.$$('#by-name') - - cy.get('#by-name').within(() => {}).then(($form) => { - expect($form.get(0)).to.eq(form.get(0)) - }) - }) - - it('can call child commands after within on the same subject', () => { - const input = cy.$$('#by-name input:first') - - cy.get('#by-name').within(() => {}).find('input:first').then(($input) => { - expect($input.get(0)).to.eq(input.get(0)) - }) - }) - - it('supports nested withins', () => { - const span = cy.$$('#button-text button span') - - cy.get('#button-text').within(() => { - cy.get('button').within(() => { - cy.get('span').then(($span) => { - expect($span.get(0)).to.eq(span.get(0)) - }) - }) - }) - }) - - it('supports complicated nested withins', () => { - const span1 = cy.$$('#button-text a span') - const span2 = cy.$$('#button-text button span') - - cy.get('#button-text').within(() => { - cy.get('a').within(() => { - cy.get('span').then(($span) => { - expect($span.get(0)).to.eq(span1.get(0)) - }) - }) - - cy.get('button').within(() => { - cy.get('span').then(($span) => { - expect($span.get(0)).to.eq(span2.get(0)) - }) - }) - }) - }) - - it('clears withinSubject after within is over', () => { - const input = cy.$$('input:first') - const span = cy.$$('#button-text button span') - - cy.get('#button-text').within(() => { - cy.get('button').within(() => { - cy.get('span').then(($span) => { - expect($span.get(0)).to.eq(span.get(0)) - }) - }) - }) - - cy.get('input:first').then(($input) => { - expect($input.get(0)).to.eq(input.get(0)) - }) - }) - - it('removes command:start listeners after within is over', () => { - cy.get('#button-text').within(() => { - cy.get('button').within(() => { - cy.get('span') - }) - }) - - cy.then(() => { - expect(cy._events).not.to.have.property('command:start') - }) - }) - - it('clears withinSubject even if next is null', (done) => { - const span = cy.$$('#button-text button span') - - // should be defined here because next would have been - // null and withinSubject would not have been cleared - cy.once('command:queue:before:end', () => { - expect(cy.state('withinSubject')).not.to.be.undefined - }) - - cy.once('command:queue:end', () => { - expect(cy.state('withinSubject')).to.be.null - - done() - }) - - cy.get('#button-text').within(() => { - cy.get('button span').then(($span) => { - expect($span.get(0)).to.eq(span.get(0)) - }) - }) - }) - - // https://github.com/cypress-io/cypress/issues/4757 - it('subject is restored after within() call', () => { - cy.get('#wrapper').within(() => { - cy.get('#upper').should('contain.text', 'New York') - }) - .should('have.id', 'wrapper') - }) - - // https://github.com/cypress-io/cypress/issues/5183 - it('contains() works after within() call', () => { - cy.get(`#wrapper`).within(() => cy.get(`#upper`)).should(`contain.text`, `New York`) - cy.contains(`button`, `button`).should(`exist`) - }) - - describe('.log', () => { - beforeEach(function () { - this.logs = [] - - cy.on('log:added', (attrs, log) => { - if (attrs.name === 'within') { - this.lastLog = log - - this.logs.push(log) - } - }) - - return null - }) - - it('can silence logging', () => { - cy.get('div:first').within({ log: false }, () => {}).then(function () { - assertLogLength(this.logs, 0) - }) - }) - - it('logs immediately before resolving', (done) => { - const div = cy.$$('div:first') - - cy.on('log:added', (attrs, log) => { - if (log.get('name') === 'within') { - expect(log.get('state')).to.eq('pending') - expect(log.get('message')).to.eq('') - expect(log.get('$el').get(0)).to.eq(div.get(0)) - - done() - } - }) - - cy.get('div:first').within(() => {}) - }) - - it('snapshots after clicking', () => { - cy.get('div:first').within(() => {}) - .then(function () { - const { lastLog } = this - - expect(lastLog.get('snapshots').length).to.eq(1) - - expect(lastLog.get('snapshots')[0]).to.be.an('object') - }) - }) - }) - - describe('errors', { - defaultCommandTimeout: 100, - }, () => { - beforeEach(function () { - this.logs = [] - - cy.on('log:added', (attrs, log) => { - this.lastLog = log - - this.logs.push(log) - }) - - return null - }) - - it('logs once when not dom subject', function (done) { - cy.on('fail', (err) => { - const { lastLog } = this - - assertLogLength(this.logs, 1) - expect(lastLog.get('error')).to.eq(err) - - done() - }) - - cy.noop().within(() => {}) - }) - - it('throws when not a DOM subject', (done) => { - cy.on('fail', (err) => { - done() - }) - - cy.noop().within(() => {}) - }) - - _.each(['', [], {}, 1, null, undefined], (value) => { - it(`throws if passed anything other than a function, such as: ${value}`, (done) => { - cy.on('fail', (err) => { - expect(err.message).to.include('`cy.within()` must be called with a function.') - expect(err.docsUrl).to.eq('https://on.cypress.io/within') - - done() - }) - - cy.get('body').within(value) - }) - }) - - it('throws when subject is not in the document', (done) => { - cy.on('command:end', () => { - cy.$$('#list').remove() - }) - - cy.on('fail', (err) => { - expect(err.message).to.include('`cy.within()` failed because this element') - - done() - }) - - cy.get('#list').within(() => {}) - }) - }) - }) - context('#root', () => { it('returns html', () => { const html = cy.$$('html') diff --git a/packages/driver/cypress/integration/commands/querying_shadow_dom_spec.js b/packages/driver/cypress/integration/commands/querying/shadow_dom_spec.js similarity index 87% rename from packages/driver/cypress/integration/commands/querying_shadow_dom_spec.js rename to packages/driver/cypress/integration/commands/querying/shadow_dom_spec.js index 11c6e8e8d4c7..7e1e8fba3299 100644 --- a/packages/driver/cypress/integration/commands/querying_shadow_dom_spec.js +++ b/packages/driver/cypress/integration/commands/querying/shadow_dom_spec.js @@ -1,4 +1,4 @@ -const helpers = require('../../support/helpers') +const helpers = require('../../../support/helpers') const { _ } = Cypress @@ -7,34 +7,6 @@ describe('src/cy/commands/querying - shadow dom', () => { cy.visit('/fixtures/shadow-dom.html') }) - context('#within', () => { - it('finds element within shadow dom with includeShadowDom option', () => { - cy.get('#parent-of-shadow-container-0').within(() => { - cy - .get('p', { includeShadowDom: true }) - .should('have.length', 1) - .should('have.text', 'Shadow Content 3') - }) - }) - - it('when within subject is shadow root, finds element without needing includeShadowDom option', () => { - cy.get('#shadow-element-1').shadow().within(() => { - cy - .get('p') - .should('have.length', 1) - .should('have.text', 'Shadow Content 1') - }) - }) - - it('when within subject is already in shadow dom, finds element without needing includeShadowDom option', () => { - cy.get('.shadow-8-nested-1', { includeShadowDom: true }).within(() => { - cy - .get('.shadow-8-nested-5') - .should('have.text', '8') - }) - }) - }) - context('#get', () => { it('finds elements within shadow roots', () => { cy.get('.shadow-1', { includeShadowDom: true }) diff --git a/packages/driver/cypress/integration/commands/querying/within_spec.js b/packages/driver/cypress/integration/commands/querying/within_spec.js new file mode 100644 index 000000000000..bda7f8eaf5c6 --- /dev/null +++ b/packages/driver/cypress/integration/commands/querying/within_spec.js @@ -0,0 +1,300 @@ +const { assertLogLength } = require('../../../support/utils') + +const { _ } = Cypress + +describe('src/cy/commands/querying/within', () => { + context('#within', () => { + beforeEach(() => { + cy.visit('/fixtures/dom.html') + }) + + it('invokes callback function with runnable.ctx', function () { + const ctx = this + + cy.get('div:first').within(function () { + expect(ctx === this).to.be.true + }) + }) + + it('scopes additional GET finders to the subject', () => { + const input = cy.$$('#by-name input:first') + + cy.get('#by-name').within(() => { + cy.get('input:first').then(($input) => { + expect($input.get(0)).to.eq(input.get(0)) + }) + }) + }) + + it('scopes additional CONTAINS finders to the subject', () => { + const span = cy.$$('#nested-div span:contains(foo)') + + cy.contains('foo').then(($span) => { + expect($span.get(0)).not.to.eq(span.get(0)) + }) + + cy.get('#nested-div').within(() => { + cy.contains('foo').then(($span) => { + expect($span.get(0)).to.eq(span.get(0)) + }) + }) + }) + + it('does not change the subject', () => { + const form = cy.$$('#by-name') + + cy.get('#by-name').within(() => {}).then(($form) => { + expect($form.get(0)).to.eq(form.get(0)) + }) + }) + + it('can call child commands after within on the same subject', () => { + const input = cy.$$('#by-name input:first') + + cy.get('#by-name').within(() => {}).find('input:first').then(($input) => { + expect($input.get(0)).to.eq(input.get(0)) + }) + }) + + it('supports nested withins', () => { + const span = cy.$$('#button-text button span') + + cy.get('#button-text').within(() => { + cy.get('button').within(() => { + cy.get('span').then(($span) => { + expect($span.get(0)).to.eq(span.get(0)) + }) + }) + }) + }) + + it('supports complicated nested withins', () => { + const span1 = cy.$$('#button-text a span') + const span2 = cy.$$('#button-text button span') + + cy.get('#button-text').within(() => { + cy.get('a').within(() => { + cy.get('span').then(($span) => { + expect($span.get(0)).to.eq(span1.get(0)) + }) + }) + + cy.get('button').within(() => { + cy.get('span').then(($span) => { + expect($span.get(0)).to.eq(span2.get(0)) + }) + }) + }) + }) + + it('clears withinSubject after within is over', () => { + const input = cy.$$('input:first') + const span = cy.$$('#button-text button span') + + cy.get('#button-text').within(() => { + cy.get('button').within(() => { + cy.get('span').then(($span) => { + expect($span.get(0)).to.eq(span.get(0)) + }) + }) + }) + + cy.get('input:first').then(($input) => { + expect($input.get(0)).to.eq(input.get(0)) + }) + }) + + it('removes command:start listeners after within is over', () => { + cy.get('#button-text').within(() => { + cy.get('button').within(() => { + cy.get('span') + }) + }) + + cy.then(() => { + expect(cy._events).not.to.have.property('command:start') + }) + }) + + it('clears withinSubject even if next is null', (done) => { + const span = cy.$$('#button-text button span') + + // should be defined here because next would have been + // null and withinSubject would not have been cleared + cy.once('command:queue:before:end', () => { + expect(cy.state('withinSubject')).not.to.be.undefined + }) + + cy.once('command:queue:end', () => { + expect(cy.state('withinSubject')).to.be.null + + done() + }) + + cy.get('#button-text').within(() => { + cy.get('button span').then(($span) => { + expect($span.get(0)).to.eq(span.get(0)) + }) + }) + }) + + // https://github.com/cypress-io/cypress/issues/4757 + it('subject is restored after within() call', () => { + cy.get('#wrapper').within(() => { + cy.get('#upper').should('contain.text', 'New York') + }) + .should('have.id', 'wrapper') + }) + + // https://github.com/cypress-io/cypress/issues/5183 + it('contains() works after within() call', () => { + cy.get(`#wrapper`).within(() => cy.get(`#upper`)).should(`contain.text`, `New York`) + cy.contains(`button`, `button`).should(`exist`) + }) + + describe('.log', () => { + beforeEach(function () { + this.logs = [] + + cy.on('log:added', (attrs, log) => { + if (attrs.name === 'within') { + this.lastLog = log + + this.logs.push(log) + } + }) + + return null + }) + + it('can silence logging', () => { + cy.get('div:first').within({ log: false }, () => {}).then(function () { + assertLogLength(this.logs, 0) + }) + }) + + it('logs immediately before resolving', (done) => { + const div = cy.$$('div:first') + + cy.on('log:added', (attrs, log) => { + if (log.get('name') === 'within') { + expect(log.get('state')).to.eq('pending') + expect(log.get('message')).to.eq('') + expect(log.get('$el').get(0)).to.eq(div.get(0)) + + done() + } + }) + + cy.get('div:first').within(() => {}) + }) + + it('snapshots after clicking', () => { + cy.get('div:first').within(() => {}) + .then(function () { + const { lastLog } = this + + expect(lastLog.get('snapshots').length).to.eq(1) + + expect(lastLog.get('snapshots')[0]).to.be.an('object') + }) + }) + }) + + describe('errors', { + defaultCommandTimeout: 100, + }, () => { + beforeEach(function () { + this.logs = [] + + cy.on('log:added', (attrs, log) => { + this.lastLog = log + + this.logs.push(log) + }) + + return null + }) + + it('logs once when not dom subject', function (done) { + cy.on('fail', (err) => { + const { lastLog } = this + + assertLogLength(this.logs, 1) + expect(lastLog.get('error')).to.eq(err) + + done() + }) + + cy.noop().within(() => {}) + }) + + it('throws when not a DOM subject', (done) => { + cy.on('fail', (err) => { + done() + }) + + cy.noop().within(() => {}) + }) + + _.each(['', [], {}, 1, null, undefined], (value) => { + it(`throws if passed anything other than a function, such as: ${value}`, (done) => { + cy.on('fail', (err) => { + expect(err.message).to.include('`cy.within()` must be called with a function.') + expect(err.docsUrl).to.eq('https://on.cypress.io/within') + + done() + }) + + cy.get('body').within(value) + }) + }) + + it('throws when subject is not in the document', (done) => { + cy.on('command:end', () => { + cy.$$('#list').remove() + }) + + cy.on('fail', (err) => { + expect(err.message).to.include('`cy.within()` failed because this element') + + done() + }) + + cy.get('#list').within(() => {}) + }) + }) + }) + + context('#within - shadow dom', () => { + beforeEach(() => { + cy.visit('/fixtures/shadow-dom.html') + }) + + it('finds element within shadow dom with includeShadowDom option', () => { + cy.get('#parent-of-shadow-container-0').within(() => { + cy + .get('p', { includeShadowDom: true }) + .should('have.length', 1) + .should('have.text', 'Shadow Content 3') + }) + }) + + it('when within subject is shadow root, finds element without needing includeShadowDom option', () => { + cy.get('#shadow-element-1').shadow().within(() => { + cy + .get('p') + .should('have.length', 1) + .should('have.text', 'Shadow Content 1') + }) + }) + + it('when within subject is already in shadow dom, finds element without needing includeShadowDom option', () => { + cy.get('.shadow-8-nested-1', { includeShadowDom: true }).within(() => { + cy + .get('.shadow-8-nested-5') + .should('have.text', '8') + }) + }) + }) +}) diff --git a/packages/driver/src/cy/commands/actions/selectFile.ts b/packages/driver/src/cy/commands/actions/selectFile.ts index edbc7d4bad2c..a946250bc529 100644 --- a/packages/driver/src/cy/commands/actions/selectFile.ts +++ b/packages/driver/src/cy/commands/actions/selectFile.ts @@ -35,8 +35,10 @@ const tryMockWebkit = (item) => { return item } -const createDataTransfer = (files: Cypress.FileReferenceObject[]): DataTransfer => { - const dataTransfer = new DataTransfer() +const createDataTransfer = (files: Cypress.FileReferenceObject[], eventTarget: JQuery): DataTransfer => { + // obtain a reference to the `targetWindow` so we can use the right instances of the `File` and `DataTransfer` classes + const targetWindow = (eventTarget[0] as HTMLElement).ownerDocument.defaultView || window + const dataTransfer = new targetWindow.DataTransfer() files.forEach(({ contents, @@ -44,7 +46,7 @@ const createDataTransfer = (files: Cypress.FileReferenceObject[]): DataTransfer mimeType = mime.lookup(fileName) || '', lastModified = Date.now(), }) => { - const file = new File([contents], fileName, { lastModified, type: mimeType }) + const file = new targetWindow.File([contents], fileName, { lastModified, type: mimeType }) dataTransfer.items.add(file) }) @@ -302,7 +304,7 @@ export default (Commands, Cypress, cy, state, config) => { }) } - const dataTransfer = createDataTransfer(filesArray) + const dataTransfer = createDataTransfer(filesArray, eventTarget) ACTIONS[options.action as string](eventTarget.get(0), dataTransfer, coords, state) diff --git a/packages/driver/src/cy/commands/index.ts b/packages/driver/src/cy/commands/index.ts index 055d59fdf568..cc49499de962 100644 --- a/packages/driver/src/cy/commands/index.ts +++ b/packages/driver/src/cy/commands/index.ts @@ -71,7 +71,7 @@ export const allCommands = { Misc, Popups, Navigation, - Querying, + ...Querying, Request, Sessions, Screenshot, diff --git a/packages/driver/src/cy/commands/querying/index.ts b/packages/driver/src/cy/commands/querying/index.ts new file mode 100644 index 000000000000..2574bc7fb66d --- /dev/null +++ b/packages/driver/src/cy/commands/querying/index.ts @@ -0,0 +1,7 @@ +import * as Querying from './querying' +import * as Within from './within' + +export { + Querying, + Within, +} diff --git a/packages/driver/src/cy/commands/querying.ts b/packages/driver/src/cy/commands/querying/querying.ts similarity index 83% rename from packages/driver/src/cy/commands/querying.ts rename to packages/driver/src/cy/commands/querying/querying.ts index 7d1b2092c503..e372a9605b72 100644 --- a/packages/driver/src/cy/commands/querying.ts +++ b/packages/driver/src/cy/commands/querying/querying.ts @@ -1,12 +1,11 @@ import _ from 'lodash' import Promise from 'bluebird' -import { $Command } from '../../cypress/command' -import $dom from '../../dom' -import $elements from '../../dom/elements' -import $errUtils from '../../cypress/error_utils' -import { resolveShadowDomInclusion } from '../../cypress/shadow_dom_utils' -import { getAliasedRequests, isDynamicAliasingPossible } from '../net-stubbing/aliasing' +import $dom from '../../../dom' +import $elements from '../../../dom/elements' +import $errUtils from '../../../cypress/error_utils' +import { resolveShadowDomInclusion } from '../../../cypress/shadow_dom_utils' +import { getAliasedRequests, isDynamicAliasingPossible } from '../../net-stubbing/aliasing' export default (Commands, Cypress, cy, state) => { Commands.addAll({ @@ -603,105 +602,6 @@ export default (Commands, Cypress, cy, state) => { }, }) - Commands.addAll({ prevSubject: ['element', 'document'] }, { - within (subject, options, fn) { - let userOptions = options - const ctx = this - - if (_.isUndefined(fn)) { - fn = userOptions - userOptions = {} - } - - options = _.defaults({}, userOptions, { log: true }) - - if (options.log) { - options._log = Cypress.log({ - $el: subject, - message: '', - timeout: options.timeout, - }) - } - - if (!_.isFunction(fn)) { - $errUtils.throwErrByPath('within.invalid_argument', { onFail: options._log }) - } - - // reference the next command after this - // within. when that command runs we'll - // know to remove withinSubject - const next = state('current').get('next') - - // backup the current withinSubject - // this prevents a bug where we null out - // withinSubject when there are nested .withins() - // we want the inner within to restore the outer - // once its done - const prevWithinSubject = state('withinSubject') - - state('withinSubject', subject) - - // https://github.com/cypress-io/cypress/pull/8699 - // An internal command is inserted to create a divider between - // commands inside within() callback and commands chained to it. - const restoreCmdIndex = state('index') + 1 - - cy.queue.insert(restoreCmdIndex, $Command.create({ - args: [subject], - name: 'within-restore', - fn: (subject) => subject, - })) - - state('index', restoreCmdIndex) - - fn.call(ctx, subject) - - const cleanup = () => cy.removeListener('command:start', setWithinSubject) - - // we need a mechanism to know when we should remove - // our withinSubject so we dont accidentally keep it - // around after the within callback is done executing - // so when each command starts, check to see if this - // is the command which references our 'next' and - // if so, remove the within subject - const setWithinSubject = (obj) => { - if (obj !== next) { - return - } - - // okay so what we're doing here is creating a property - // which stores the 'next' command which will reset the - // withinSubject. If two 'within' commands reference the - // exact same 'next' command, then this prevents accidentally - // resetting withinSubject more than once. If they point - // to differnet 'next's then its okay - if (next !== state('nextWithinSubject')) { - state('withinSubject', prevWithinSubject || null) - state('nextWithinSubject', next) - } - - // regardless nuke this listeners - cleanup() - } - - // if next is defined then we know we'll eventually - // unbind these listeners - if (next) { - cy.on('command:start', setWithinSubject) - } else { - // remove our listener if we happen to reach the end - // event which will finalize cleanup if there was no next obj - cy.once('command:queue:before:end', () => { - cleanup() - - state('withinSubject', null) - }) - } - - return subject - }, - }) - Commands.add('shadow', { prevSubject: 'element' }, (subject, options) => { const userOptions = options || {} diff --git a/packages/driver/src/cy/commands/querying/within.ts b/packages/driver/src/cy/commands/querying/within.ts new file mode 100644 index 000000000000..ad5ed66c7da3 --- /dev/null +++ b/packages/driver/src/cy/commands/querying/within.ts @@ -0,0 +1,105 @@ +import _ from 'lodash' + +import { $Command } from '../../../cypress/command' +import $errUtils from '../../../cypress/error_utils' + +export default (Commands, Cypress, cy, state) => { + Commands.addAll({ prevSubject: ['element', 'document'] }, { + within (subject, options, fn) { + let userOptions = options + const ctx = this + + if (_.isUndefined(fn)) { + fn = userOptions + userOptions = {} + } + + options = _.defaults({}, userOptions, { log: true }) + + if (options.log) { + options._log = Cypress.log({ + $el: subject, + message: '', + timeout: options.timeout, + }) + } + + if (!_.isFunction(fn)) { + $errUtils.throwErrByPath('within.invalid_argument', { onFail: options._log }) + } + + // reference the next command after this + // within. when that command runs we'll + // know to remove withinSubject + const next = state('current').get('next') + + // backup the current withinSubject + // this prevents a bug where we null out + // withinSubject when there are nested .withins() + // we want the inner within to restore the outer + // once its done + const prevWithinSubject = state('withinSubject') + + state('withinSubject', subject) + + // https://github.com/cypress-io/cypress/pull/8699 + // An internal command is inserted to create a divider between + // commands inside within() callback and commands chained to it. + const restoreCmdIndex = state('index') + 1 + + cy.queue.insert(restoreCmdIndex, $Command.create({ + args: [subject], + name: 'within-restore', + fn: (subject) => subject, + })) + + state('index', restoreCmdIndex) + + fn.call(ctx, subject) + + const cleanup = () => cy.removeListener('command:start', setWithinSubject) + + // we need a mechanism to know when we should remove + // our withinSubject so we dont accidentally keep it + // around after the within callback is done executing + // so when each command starts, check to see if this + // is the command which references our 'next' and + // if so, remove the within subject + const setWithinSubject = (obj) => { + if (obj !== next) { + return + } + + // okay so what we're doing here is creating a property + // which stores the 'next' command which will reset the + // withinSubject. If two 'within' commands reference the + // exact same 'next' command, then this prevents accidentally + // resetting withinSubject more than once. If they point + // to differnet 'next's then its okay + if (next !== state('nextWithinSubject')) { + state('withinSubject', prevWithinSubject || null) + state('nextWithinSubject', next) + } + + // regardless nuke this listeners + cleanup() + } + + // if next is defined then we know we'll eventually + // unbind these listeners + if (next) { + cy.on('command:start', setWithinSubject) + } else { + // remove our listener if we happen to reach the end + // event which will finalize cleanup if there was no next obj + cy.once('command:queue:before:end', () => { + cleanup() + + state('withinSubject', null) + }) + } + + return subject + }, + }) +} diff --git a/packages/electron/app/app.js b/packages/electron/app/index.js similarity index 100% rename from packages/electron/app/app.js rename to packages/electron/app/index.js diff --git a/packages/electron/package.json b/packages/electron/package.json index 67af3d3faa8f..bf431cfa6dbd 100644 --- a/packages/electron/package.json +++ b/packages/electron/package.json @@ -24,7 +24,7 @@ }, "devDependencies": { "electron": "15.3.4", - "electron-packager": "15.1.0", + "electron-packager": "15.4.0", "execa": "4.1.0", "mocha": "3.5.3" }, diff --git a/packages/server/lib/video_capture.ts b/packages/server/lib/video_capture.ts index 27a0a8eaf3ce..14c2b7d6625f 100644 --- a/packages/server/lib/video_capture.ts +++ b/packages/server/lib/video_capture.ts @@ -274,30 +274,53 @@ export function start (name, options: StartOptions = {}) { type OnProgress = (p: number) => void export async function process (name, cname, videoCompression, ffmpegchaptersConfig, onProgress: OnProgress = function () {}) { - const metaFileName = `${name}.meta` - - const maybeGenerateMetaFile = Bluebird.method(() => { - if (!ffmpegchaptersConfig) { - return false - } - - // Writing the metadata to filesystem is necessary because fluent-ffmpeg is just a wrapper of ffmpeg command. - return fs.writeFile(metaFileName, ffmpegchaptersConfig).then(() => true) - }) - - const addChaptersMeta = await maybeGenerateMetaFile() - let total = null + const metaFileName = `${name}.meta` + const addChaptersMeta = ffmpegchaptersConfig && await fs.writeFile(metaFileName, ffmpegchaptersConfig).then(() => true) + return new Bluebird((resolve, reject) => { debug('processing video from %s to %s video compression %o', name, cname, videoCompression) const command = ffmpeg() + .addOptions([ + // These flags all serve to reduce initial buffering, especially important + // when dealing with very short videos (such as during component tests). + // See https://ffmpeg.org/ffmpeg-formats.html#Format-Options for details. + '-avioflags direct', + + // Because we're passing in a slideshow of still frames, there's no + // fps metadata to be found in the video stream. This ensures that ffmpeg + // isn't buffering a lot of data waiting for information that's not coming. + '-fpsprobesize 0', + + // Tells ffmpeg to read only the first 32 bytes of the stream for information + // (resolution, stream format, etc). + // Some videos can have long metadata (eg, lots of chapters) or spread out, + // but our streams are always predictable; No need to wait / buffer data before + // starting encoding + '-probesize 32', + + // By default ffmpeg buffers the first 5 seconds of video to analyze it before + // it starts encoding. We're basically telling it "there is no metadata coming, + // start encoding as soon as we give you frames." + '-analyzeduration 0', + ]) + + // See https://trac.ffmpeg.org/wiki/Encode/H.264 for details about h264 options. const outputOptions = [ + // Preset is a tradeoff between encoding speed and filesize. It does not determine video + // quality; It's just a tradeoff between CPU vs size. '-preset fast', - `-crf ${videoCompression}`, - '-pix_fmt yuv420p', + // Compression Rate Factor is essentially the quality dial; 0 would be lossless + // (big files), while 51 (the maximum) would lead to low quality (and small files). + `-crf ${videoCompression}`, + + // Discussion of pixel formats is beyond the scope of these comments. See + // https://en.wikipedia.org/wiki/Chroma_subsampling if you want the gritty details. + // Short version: yuv420p is a standard video format supported everywhere. + '-pix_fmt yuv420p', ] if (addChaptersMeta) { diff --git a/scripts/binary/index.js b/scripts/binary/index.js index 30efde126023..7e1571167038 100644 --- a/scripts/binary/index.js +++ b/scripts/binary/index.js @@ -21,8 +21,7 @@ const meta = require('./meta') const build = require('./build') const upload = require('./upload') const uploadUtils = require('./util/upload') -const { uploadNpmPackage } = require('./upload-npm-package') -const { uploadUniqueBinary } = require('./upload-unique-binary') +const { uploadArtifactToS3 } = require('./upload-build-artifact') const { moveBinaries } = require('./move-binaries') // initialize on existing repo @@ -252,18 +251,11 @@ const deploy = { }) }, - // upload Cypress NPM package file - 'upload-npm-package' (args = process.argv) { - console.log('#packageUpload') + // upload Cypress binary or NPM Package zip file under unique hash + 'upload-build-artifact' (args = process.argv) { + console.log('#uploadBuildArtifact') - return uploadNpmPackage(args) - }, - - // upload Cypress binary zip file under unique hash - 'upload-unique-binary' (args = process.argv) { - console.log('#uniqueBinaryUpload') - - return uploadUniqueBinary(args) + return uploadArtifactToS3(args) }, // uploads a single built Cypress binary ZIP file @@ -288,10 +280,21 @@ const deploy = { console.log('for platform %s version %s', options.platform, options.version) - return upload.toS3({ - zipFile: options.zip, + const uploadPath = upload.getFullUploadPath({ version: options.version, platform: options.platform, + name: upload.zipName, + }) + + return upload.toS3({ + file: options.zip, + uploadPath, + }).then(() => { + return uploadUtils.purgeDesktopAppFromCache({ + version: options.version, + platform: options.platform, + zipName: options.zip, + }) }) }) }, diff --git a/scripts/binary/move-binaries.ts b/scripts/binary/move-binaries.ts index fafbf82542a1..c458e0b1cc3e 100644 --- a/scripts/binary/move-binaries.ts +++ b/scripts/binary/move-binaries.ts @@ -18,9 +18,9 @@ import confirm from 'inquirer-confirm' import uploadUtils from './util/upload' // @ts-ignore -import { getUploadDirForPlatform } from './upload-unique-binary' +import { getUploadDirForPlatform } from './upload-build-artifact' // @ts-ignore -import { zipName, getFullUploadName } from './upload' +import { zipName, getFullUploadPath } from './upload' /** * 40 character full sha commit string @@ -160,7 +160,9 @@ export const moveBinaries = async (args = []) => { const uploadDir = getUploadDirForPlatform({ version: releaseOptions.version, - }, platformArch) + uploadFolder: 'binary', + platformArch, + }) console.log('finding binary for %s in %s', platformArch, uploadDir) @@ -216,7 +218,7 @@ export const moveBinaries = async (args = []) => { platformArch: lastBuild.platformArch, name: zipName, } - const destinationPath = getFullUploadName(options) + const destinationPath = getFullUploadPath(options) console.log('copying test runner %s to %s', lastBuild.platformArch, destinationPath) diff --git a/scripts/binary/upload-build-artifact.js b/scripts/binary/upload-build-artifact.js new file mode 100644 index 000000000000..8cecb52815d7 --- /dev/null +++ b/scripts/binary/upload-build-artifact.js @@ -0,0 +1,145 @@ +const minimist = require('minimist') +const la = require('lazy-ass') +const check = require('check-more-types') +const fs = require('fs') +const hasha = require('hasha') +const _ = require('lodash') + +const upload = require('./upload') +const uploadUtils = require('./util/upload') +const { s3helpers } = require('./s3-api') + +const uploadTypes = { + binary: { + uploadFolder: 'binary', + uploadFileName: 'cypress.zip', + }, + 'npm-package': { + uploadFolder: 'npm', + uploadFileName: 'cypress.tgz', + }, +} + +const getCDN = function (uploadPath) { + return [uploadUtils.getUploadUrl(), uploadPath].join('/') +} + +const getUploadDirForPlatform = function (options) { + const { version, uploadFolder, platformArch } = options + + return ['beta', uploadFolder, version, platformArch].join('/') +} +// the artifact will be uploaded for every platform and uploaded into under a unique folder +// https://cdn.cypress.io/beta/(binary|npm)////cypress.zip +// For binary: +// beta/binary/9.4.2/win32-x64/circle-develop-219138ca4e952edc4af831f2ae16ce659ebdb50b/cypress.zip +// For NPM package: +// beta/npm/9.4.2/circle-develop-219138ca4e952edc4af831f2ae16ce659ebdb50b/cypress.tgz +const getUploadPath = function (options) { + const { hash, uploadFileName } = options + + return [getUploadDirForPlatform(options), hash, uploadFileName].join('/') +} + +const setChecksum = (filename, key) => { + console.log('setting checksum for file %s', filename) + console.log('on s3 object %s', key) + + la(check.unemptyString(filename), 'expected filename', filename) + la(check.unemptyString(key), 'expected uploaded S3 key', key) + + const checksum = hasha.fromFileSync(filename, { algorithm: 'sha512' }) + const { + size, + } = fs.statSync(filename) + + console.log('SHA256 checksum %s', checksum) + console.log('size', size) + + const aws = uploadUtils.getS3Credentials() + const s3 = s3helpers.makeS3(aws) + // S3 object metadata can only have string values + const metadata = { + checksum, + size: String(size), + } + + // by default s3.copyObject does not preserve ACL when copying + // thus we need to reset it for our public files + return s3helpers.setUserMetadata(aws.bucket, key, metadata, + 'application/zip', 'public-read', s3) +} + +const validateOptions = (options) => { + const { type, version, platform } = options + const supportedUploadTypes = Object.keys(uploadTypes) + + la(check.defined(type) && supportedUploadTypes.includes(type), + `specify which upload type you\'d like to upload. One of ${supportedUploadTypes.join(',')}`, type) + + const { uploadFolder, uploadFileName } = uploadTypes[type] + + options.uploadFolder = uploadFolder + options.uploadFileName = uploadFileName + + la(check.unemptyString(version) && check.semver(version), 'invalid version', version) + + if (!options.hash) { + options.hash = uploadUtils.formHashFromEnvironment() + } + + la(check.unemptyString(options.hash), 'missing hash to give', options) + + options.platformArch = uploadUtils.getUploadNameByOsAndArch(platform || process.platform) + + return options +} + +const uploadArtifactToS3 = function (args = []) { + const supportedOptions = ['type', 'version', 'file', 'hash', 'platform'] + let options = minimist(args, { + string: supportedOptions, + }) + + console.log('Upload options') + console.log(_.pick(options, supportedOptions)) + + validateOptions(options) + + const uploadPath = getUploadPath(options) + + return upload.toS3({ file: options.file, uploadPath }) + .then(() => { + return setChecksum(options.file, uploadPath) + }) + .then(() => { + const cdnUrl = getCDN(uploadPath) + + if (options.type === 'binary') { + console.log('Binary can be downloaded using URL') + console.log(cdnUrl) + } else { + console.log('NPM package can be installed using URL') + console.log('npm install %s', cdnUrl) + } + + return cdnUrl + }) + .then(uploadUtils.saveUrl(`${options.type}-url.json`)) + .catch((e) => { + console.error('There was an issue uploading the artifact.') + console.error(e) + }) +} + +module.exports = { + getCDN, + getUploadDirForPlatform, + getUploadPath, + setChecksum, + uploadArtifactToS3, +} + +if (!module.parent) { + uploadArtifactToS3(process.argv) +} diff --git a/scripts/binary/upload-npm-package.js b/scripts/binary/upload-npm-package.js deleted file mode 100644 index 141661fee4d1..000000000000 --- a/scripts/binary/upload-npm-package.js +++ /dev/null @@ -1,122 +0,0 @@ -const minimist = require('minimist') -const Promise = require('bluebird') -const la = require('lazy-ass') -const check = require('check-more-types') -const fs = require('fs') -const path = require('path') -const awspublish = require('gulp-awspublish') -const rename = require('gulp-rename') -const gulpDebug = require('gulp-debug') -const gulp = require('gulp') -const uploadUtils = require('./util/upload') - -const npmPackageExtension = '.tgz' -const uploadFileName = 'cypress.tgz' - -const isNpmPackageFile = check.extension(npmPackageExtension) - -// the package tgz file will be uploaded into unique folder -// in our case something like this -// https://cdn.cypress.io/beta/npm///cypress.tgz -const rootFolder = 'beta' -const npmFolder = 'npm' - -const getCDN = function ({ version, hash, filename }) { - la(check.semver(version), 'invalid version', version) - la(check.unemptyString(hash), 'missing hash', hash) - la(check.unemptyString(filename), 'missing filename', filename) - la(isNpmPackageFile(filename), 'wrong extension for file', filename) - const url = uploadUtils.getUploadUrl() - - la(check.url(url), 'could not get upload url', url) - - return [url, rootFolder, npmFolder, version, hash, filename].join('/') -} - -const getUploadDirName = function (options) { - la(check.unemptyString(options.version), 'missing version', options) - la(check.unemptyString(options.hash), 'missing hash', options) - const dir = [rootFolder, npmFolder, options.version, options.hash, null].join('/') - - return dir -} - -const uploadFile = (options) => { - return new Promise((resolve, reject) => { - const publisher = uploadUtils.getPublisher() - - const headers = {} - - headers['Cache-Control'] = 'no-cache' - - return gulp.src(options.file) - .pipe(rename((p) => { - p.basename = path.basename(uploadFileName, npmPackageExtension) - p.dirname = getUploadDirName(options) - console.log('renaming upload to', p.dirname, p.basename) - la(check.unemptyString(p.basename), 'missing basename') - la(check.unemptyString(p.dirname), 'missing dirname') - - return p - })).pipe(gulpDebug()) - .pipe(publisher.publish(headers)) - .pipe(awspublish.reporter()) - .on('error', reject) - .on('end', resolve) - }) -} - -const uploadNpmPackage = function (args = []) { - console.log(args) - const options = minimist(args, { - string: ['version', 'file', 'hash'], - alias: { - version: 'v', - file: 'f', - hash: 'h', - }, - }) - - console.log('Upload NPM package options') - console.log(options) - - la(check.unemptyString(options.file), 'missing file to upload', options) - la(isNpmPackageFile(options.file), - 'invalid file to upload extension', options.file) - - if (!options.hash) { - options.hash = uploadUtils.formHashFromEnvironment() - } - - la(check.unemptyString(options.hash), 'missing hash to give', options) - la(check.unemptyString(options.version), 'missing version', options) - - la(fs.existsSync(options.file), 'cannot find file', options.file) - - return uploadFile(options) - .then(() => { - const cdnUrl = getCDN({ - version: options.version, - hash: options.hash, - filename: uploadFileName, - }) - - console.log('NPM package can be installed using URL') - console.log('npm install %s', cdnUrl) - - return cdnUrl - }).then(uploadUtils.saveUrl('npm-package-url.json')) -} - -// for now disable purging from CDN cache -// because each upload should be unique by hash -// .then R.tap(uploadUtils.purgeCache) - -module.exports = { - uploadNpmPackage, - getCDN, -} - -if (!module.parent) { - uploadNpmPackage(process.argv) -} diff --git a/scripts/binary/upload-unique-binary.js b/scripts/binary/upload-unique-binary.js deleted file mode 100644 index f2e381f2a368..000000000000 --- a/scripts/binary/upload-unique-binary.js +++ /dev/null @@ -1,197 +0,0 @@ -const minimist = require('minimist') -const Promise = require('bluebird') -const la = require('lazy-ass') -const check = require('check-more-types') -const fs = require('fs') -const path = require('path') -const awspublish = require('gulp-awspublish') -const rename = require('gulp-rename') -const gulpDebug = require('gulp-debug') -const gulp = require('gulp') -const hasha = require('hasha') -const _ = require('lodash') - -const uploadUtils = require('./util/upload') -const { - s3helpers, -} = require('./s3-api') - -// we zip the binary on every platform and upload under same name -const binaryExtension = '.zip' -const uploadFileName = 'cypress.zip' - -const isBinaryFile = check.extension(binaryExtension) - -const rootFolder = 'beta' -const folder = 'binary' - -// the binary will be uploaded into unique folder -// in our case something like this -// https://cdn.cypress.io/desktop/binary/0.20.2///cypress.zip -const getCDN = function ({ version, hash, filename, platform }) { - la(check.semver(version), 'invalid version', version) - la(check.unemptyString(hash), 'missing hash', hash) - la(check.unemptyString(filename), 'missing filename', filename) - la(isBinaryFile(filename), 'wrong extension for file', filename) - la(check.unemptyString(platform), 'missing platform', platform) - - const cdnUrl = uploadUtils.getUploadUrl() - - la(check.url(cdnUrl), 'could not get cdn url', cdnUrl) - - return [cdnUrl, rootFolder, folder, version, platform, hash, filename].join('/') -} - -// returns folder that contains beta (unreleased) binaries for given version -// -const getUploadVersionDirName = function (options) { - la(check.unemptyString(options.version), 'missing version', options) - - const dir = [rootFolder, folder, options.version].join('/') - - return dir -} - -const getUploadDirForPlatform = function (options, platformArch) { - la(uploadUtils.isValidPlatformArch(platformArch), - 'missing or invalid platformArch', platformArch) - - const versionDir = getUploadVersionDirName(options) - - la(check.unemptyString(versionDir), 'could not form folder from', options) - - const dir = [versionDir, platformArch].join('/') - - return dir -} - -const getUploadDirName = function (options) { - la(check.unemptyString(options.hash), 'missing hash', options) - - const uploadFolder = getUploadDirForPlatform(options, options.platformArch) - - la(check.unemptyString(uploadFolder), 'could not form folder from', options) - - const dir = [uploadFolder, options.hash, null].join('/') - - return dir -} - -const uploadFile = (options) => { - return new Promise((resolve, reject) => { - const publisher = uploadUtils.getPublisher() - - const headers = {} - - headers['Cache-Control'] = 'no-cache' - - let key = null - - return gulp.src(options.file) - .pipe(rename((p) => { - p.basename = path.basename(uploadFileName, binaryExtension) - p.dirname = getUploadDirName(options) - console.log('renaming upload to', p.dirname, p.basename) - la(check.unemptyString(p.basename), 'missing basename') - la(check.unemptyString(p.dirname), 'missing dirname') - key = p.dirname + uploadFileName - - return p - })).pipe(gulpDebug()) - .pipe(publisher.publish(headers)) - .pipe(awspublish.reporter()) - .on('error', reject) - .on('end', () => { - return resolve(key) - }) - }) -} - -const setChecksum = (filename, key) => { - console.log('setting checksum for file %s', filename) - console.log('on s3 object %s', key) - - la(check.unemptyString(filename), 'expected filename', filename) - la(check.unemptyString(key), 'expected uploaded S3 key', key) - - const checksum = hasha.fromFileSync(filename, { algorithm: 'sha512' }) - const { - size, - } = fs.statSync(filename) - - console.log('SHA256 checksum %s', checksum) - console.log('size', size) - - const aws = uploadUtils.getS3Credentials() - const s3 = s3helpers.makeS3(aws) - // S3 object metadata can only have string values - const metadata = { - checksum, - size: String(size), - } - - // by default s3.copyObject does not preserve ACL when copying - // thus we need to reset it for our public files - return s3helpers.setUserMetadata(aws.bucket, key, metadata, - 'application/zip', 'public-read', s3) -} - -const uploadUniqueBinary = function (args = []) { - const options = minimist(args, { - string: ['version', 'file', 'hash', 'platform'], - alias: { - version: 'v', - file: 'f', - hash: 'h', - }, - }) - - console.log('Upload unique binary options') - - console.log(_.pick(options, ['file', 'version', 'hash'])) - - la(check.unemptyString(options.file), 'missing file to upload', options) - la(isBinaryFile(options.file), - 'invalid file to upload extension', options.file) - - if (!options.hash) { - options.hash = uploadUtils.formHashFromEnvironment() - } - - la(check.unemptyString(options.hash), 'missing hash to give', options) - la(check.unemptyString(options.version), 'missing version', options) - - la(fs.existsSync(options.file), 'cannot find file', options.file) - - const platform = options.platform != null ? options.platform : process.platform - - options.platformArch = uploadUtils.getUploadNameByOsAndArch(platform) - - return uploadFile(options) - .then((key) => { - return setChecksum(options.file, key) - }).then(() => { - const cdnUrl = getCDN({ - version: options.version, - hash: options.hash, - filename: uploadFileName, - platform: options.platformArch, - }) - - console.log('Binary can be downloaded using URL') - console.log(cdnUrl) - - return cdnUrl - }).then(uploadUtils.saveUrl('binary-url.json')) -} - -module.exports = { - getUploadDirName, - getUploadDirForPlatform, - uploadUniqueBinary, - getCDN, -} - -if (!module.parent) { - uploadUniqueBinary(process.argv) -} diff --git a/scripts/binary/upload.js b/scripts/binary/upload.js index 247e0a785436..0db0d12e306f 100644 --- a/scripts/binary/upload.js +++ b/scripts/binary/upload.js @@ -5,9 +5,9 @@ let fs = require('fs-extra') const path = require('path') const gulp = require('gulp') const Promise = require('bluebird') -const meta = require('./meta') const la = require('lazy-ass') const check = require('check-more-types') + const uploadUtils = require('./util/upload') fs = Promise.promisifyAll(fs) @@ -30,17 +30,25 @@ module.exports = { // returns desktop folder for a given folder without platform // something like desktop/0.20.1 - getUploadeVersionFolder (aws, version) { + getUploadVersionFolder (aws, version) { la(check.unemptyString(aws.folder), 'aws object is missing desktop folder', aws.folder) const dirName = [aws.folder, version].join('/') return dirName }, - getFullUploadName ({ folder, version, platformArch, name }) { - la(check.unemptyString(folder), 'missing folder', folder) - la(check.semver(version), 'missing or invalid version', version) - la(check.unemptyString(name), 'missing file name', name) + // store uploaded application in subfolders by version and platform + // something like desktop/0.20.1/darwin-x64/ + getFullUploadPath (options) { + let { folder, version, platformArch, name } = options + + if (!folder) { + folder = this.getAwsObj().folder + } + + la(check.unemptyString(folder), 'missing folder', options) + la(check.semver(version), 'missing or invalid version', options) + la(check.unemptyString(name), 'missing file name', options) la(uploadUtils.isValidPlatformArch(platformArch), 'invalid platform and arch', platformArch) @@ -49,20 +57,6 @@ module.exports = { return fileName }, - // store uploaded application in subfolders by platform and version - // something like desktop/0.20.1/darwin-x64/ - getUploadDirName ({ version, platform }) { - const aws = this.getAwsObj() - const platformArch = uploadUtils.getUploadNameByOsAndArch(platform) - - const versionFolder = this.getUploadeVersionFolder(aws, version) - const dirName = [versionFolder, platformArch, null].join('/') - - console.log('target directory %s', dirName) - - return dirName - }, - getManifestUrl (folder, version, uploadOsName) { const url = uploadUtils.getUploadUrl() @@ -141,48 +135,35 @@ module.exports = { }) }, - toS3 ({ zipFile, version, platform }) { + toS3 ({ file, uploadPath }) { console.log('#uploadToS3 ⏳') + console.log('uploading', file, 'to', uploadPath) - la(check.unemptyString(version), 'expected version string', version) - la(check.unemptyString(zipFile), 'expected zip filename', zipFile) - la(check.extension('zip', zipFile), - 'zip filename should end with .zip', zipFile) - - la(meta.isValidPlatform(platform), 'invalid platform', platform) + la(check.unemptyString(file), 'missing file to upload', file) + la(fs.existsSync(file), 'cannot find file', file) + la(check.extension(path.extname(uploadPath))(file), + 'invalid file to upload extension', file) - console.log(`zip filename ${zipFile}`) - - if (!fs.existsSync(zipFile)) { - throw new Error(`Cannot find zip file ${zipFile}`) - } - - const upload = () => { - return new Promise((resolve, reject) => { - const publisher = this.getPublisher() - - const headers = {} + return new Promise((resolve, reject) => { + const publisher = this.getPublisher() - headers['Cache-Control'] = 'no-cache' + const headers = {} - return gulp.src(zipFile) - .pipe(rename((p) => { - // rename to standard filename zipName - p.basename = path.basename(zipName, p.extname) - p.dirname = this.getUploadDirName({ version, platform }) + headers['Cache-Control'] = 'no-cache' - return p - })).pipe(gulpDebug()) - .pipe(publisher.publish(headers)) - .pipe(awspublish.reporter()) - .on('error', reject) - .on('end', resolve) - }) - } + return gulp.src(file) + .pipe(rename((p) => { + // rename to standard filename for upload + p.basename = path.basename(uploadPath, path.extname(uploadPath)) + p.dirname = path.dirname(uploadPath) - return upload() - .then(() => { - return uploadUtils.purgeDesktopAppFromCache({ version, platform, zipName }) + return p + })) + .pipe(gulpDebug()) + .pipe(publisher.publish(headers)) + .pipe(awspublish.reporter()) + .on('error', reject) + .on('end', resolve) }) }, } diff --git a/scripts/binary/util/upload.js b/scripts/binary/util/upload.js index 2d0d24d67501..597f89469872 100644 --- a/scripts/binary/util/upload.js +++ b/scripts/binary/util/upload.js @@ -15,7 +15,6 @@ const getUploadUrl = function () { const url = konfig('cdn_url') la(check.url(url), 'could not get CDN url', url) - console.log('upload url', url) return url } diff --git a/scripts/unit/binary/upload-build-artifact-spec.js b/scripts/unit/binary/upload-build-artifact-spec.js new file mode 100644 index 000000000000..c09f9a9a4d1f --- /dev/null +++ b/scripts/unit/binary/upload-build-artifact-spec.js @@ -0,0 +1,140 @@ +const { sinon } = require('@packages/https-proxy/test/spec_helper') +const { expect } = require('chai') +const hasha = require('hasha') +const fs = require('fs') + +const { + getCDN, + getUploadDirForPlatform, + getUploadPath, + uploadArtifactToS3, +} = require('../../binary/upload-build-artifact') +const upload = require('../../binary/upload') +const uploadUtils = require('../../binary/util/upload') +const { s3helpers } = require('../../binary/s3-api') + +/* eslint-env mocha */ +describe('upload-release-artifact', () => { + describe('.getCDN', () => { + it('returns CDN s3 url', () => { + const uploadUrl = 'dir/path/file' + const result = getCDN(uploadUrl) + + expect(result).to.eq('https://cdn.cypress.io/dir/path/file') + }) + }) + + describe('.getUploadDirForPlatform', () => { + it('returns folder for given version and platform', () => { + const options = { + uploadFolder: 'binary', + platformArch: 'darwin-x64', + version: '3.3.0', + } + const result = getUploadDirForPlatform(options) + + expect(result).to.eq('beta/binary/3.3.0/darwin-x64') + }) + }) + + describe('.getUploadPath', () => { + it('returns s3 upload path', () => { + const options = { + uploadFolder: 'binary', + platformArch: 'darwin-x64', + version: '3.3.0', + hash: 'hash', + uploadFileName: 'file', + } + const result = getUploadPath(options) + + expect(result).to.eq('beta/binary/3.3.0/darwin-x64/hash/file') + }) + }) + + describe('.uploadArtifactToS3', () => { + let sandbox + + beforeEach(function () { + sandbox = sinon.sandbox.create() + sandbox.stub(hasha, 'fromFileSync').returns('checksum') + sandbox.stub(fs, 'statSync').returns('size') + sandbox.stub(s3helpers, 'makeS3').returns('size') + sandbox.stub(s3helpers, 'setUserMetadata') + sandbox.stub(upload, 'toS3') + sandbox.stub(uploadUtils, 'formHashFromEnvironment') + sandbox.stub(uploadUtils, 'getS3Credentials').returns({ bucket: 'beta' }) + sandbox.stub(uploadUtils, 'getUploadNameByOsAndArch') + sandbox.stub(uploadUtils, 'saveUrl') + }) + + afterEach(function () { + sandbox.restore() + }) + + it('throws error if type argument is missing', () => { + expect(() => uploadArtifactToS3()).to.throw(/specify which upload type you'd like to upload/) + }) + + it('throws error if type argument is not binary or npm-package', () => { + expect(() => uploadArtifactToS3(['--type', 'npm'])).to.throw(/specify which upload type you'd like to upload/) + }) + + it('throws error if version argument is missing', () => { + expect(() => uploadArtifactToS3(['--type', 'binary'])).to.throw(/invalid version/) + }) + + it('throws error if version argument is not a semver', () => { + expect(() => uploadArtifactToS3(['--type', 'npm-package', '--version', '.1'])).to.throw(/invalid version/) + }) + + it('throws error if not ran in CircleCI to generate unique hash', () => { + uploadUtils.formHashFromEnvironment.throws() + expect(() => uploadArtifactToS3(['--type', 'npm-package', '--version', '1.0.0'])).to.throw() + }) + + it('uploads binary to s3 and saves url to json', () => { + uploadUtils.formHashFromEnvironment.returns('hash') + uploadUtils.getUploadNameByOsAndArch.returns('darwin-x64') + upload.toS3.resolves(true) + + const args = ['--file', 'my.zip', '--type', 'binary', '--version', '1.0.0'] + + uploadArtifactToS3(args) + + expect(uploadUtils.formHashFromEnvironment).to.have.calledOnce + expect(uploadUtils.getUploadNameByOsAndArch).to.have.calledOnce + + expect(upload.toS3).to.have.been.calledOnce + expect(upload.toS3.lastCall.args).to.have.lengthOf(1) + expect(upload.toS3.lastCall.args[0]).to.have.property('file', 'my.zip') + expect(upload.toS3.lastCall.args[0]).to.have.property('uploadPath', 'beta/binary/1.0.0/darwin-x64/hash/cypress.zip') + + expect(uploadUtils.saveUrl).to.have.calledOnce + expect(uploadUtils.saveUrl.lastCall.args).to.have.lengthOf(1) + expect(uploadUtils.saveUrl.lastCall.args[0]).to.eq('binary-url.json') + }) + + it('uploads npm-package to s3 and saves url to json', () => { + uploadUtils.formHashFromEnvironment.returns('hash') + uploadUtils.getUploadNameByOsAndArch.returns('darwin-x64') + upload.toS3.resolves(true) + + const args = ['--file', 'my.zip', '--type', 'npm-package', '--version', '1.0.0'] + + uploadArtifactToS3(args) + + expect(uploadUtils.formHashFromEnvironment).to.have.calledOnce + expect(uploadUtils.getUploadNameByOsAndArch).to.have.calledOnce + + expect(upload.toS3).to.have.been.calledOnce + expect(upload.toS3.lastCall.args).to.have.lengthOf(1) + expect(upload.toS3.lastCall.args[0]).to.have.property('file', 'my.zip') + expect(upload.toS3.lastCall.args[0]).to.have.property('uploadPath', 'beta/npm/1.0.0/darwin-x64/hash/cypress.tgz') + + expect(uploadUtils.saveUrl).to.have.calledOnce + expect(uploadUtils.saveUrl.lastCall.args).to.have.lengthOf(1) + expect(uploadUtils.saveUrl.lastCall.args[0]).to.eq('npm-package-url.json') + }) + }) +}) diff --git a/scripts/unit/binary/upload-npm-package-spec.js b/scripts/unit/binary/upload-npm-package-spec.js deleted file mode 100644 index b8035aed1e28..000000000000 --- a/scripts/unit/binary/upload-npm-package-spec.js +++ /dev/null @@ -1,23 +0,0 @@ -const snapshot = require('snap-shot-it') - -/* eslint-env mocha */ -describe('getCDN', () => { - context('npm package', () => { - const { getCDN } = require('../../binary/upload-npm-package') - - it('returns CDN s3 path', () => { - const options = { - platform: 'darwin-x64', - filename: 'cypress.tgz', - version: '3.3.0', - // ci name + commit sha + build number - hash: 'ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123', - } - - snapshot({ - input: options, - result: getCDN(options), - }) - }) - }) -}) diff --git a/scripts/unit/binary/upload-spec.js b/scripts/unit/binary/upload-spec.js index 3309da47a21d..9d6e92c25b55 100644 --- a/scripts/unit/binary/upload-spec.js +++ b/scripts/unit/binary/upload-spec.js @@ -2,10 +2,8 @@ require('../../spec-helper') const snapshot = require('snap-shot-it') const la = require('lazy-ass') -const os = require('os') /* eslint-env mocha */ -/* global sinon */ describe('upload', () => { const upload = require('../../binary/upload') @@ -19,36 +17,14 @@ describe('upload', () => { }) }) - context('getUploadeVersionFolder', () => { + context('getUploadVersionFolder', () => { it('returns folder', () => { const aws = { folder: 'desktop', } - const folder = upload.getUploadeVersionFolder(aws, '3.3.0') + const folder = upload.getUploadVersionFolder(aws, '3.3.0') la(folder === 'desktop/3.3.0', 'wrong desktop folder', folder) }) }) - - context('getUploadDirName', () => { - it('returns folder with platform', () => { - const aws = { - folder: 'desktop', - } - - sinon.stub(upload, 'getAwsObj').returns(aws) - sinon.stub(os, 'arch').returns('x64') - - const folder = upload.getUploadDirName({ - platform: 'darwin', - version: '3.3.0', - }) - - la( - folder === 'desktop/3.3.0/darwin-x64/', - 'wrong upload desktop folder', - folder, - ) - }) - }) }) diff --git a/scripts/unit/binary/upload-unique-binary-spec.js b/scripts/unit/binary/upload-unique-binary-spec.js deleted file mode 100644 index 3f401593ede4..000000000000 --- a/scripts/unit/binary/upload-unique-binary-spec.js +++ /dev/null @@ -1,62 +0,0 @@ -const snapshot = require('snap-shot-it') - -/* eslint-env mocha */ -describe('upload-unique-binary', () => { - describe('getUploadDirName', () => { - const { getUploadDirName } = require('../../binary/upload-unique-binary') - - it('returns folder for given version', () => { - const options = { - platformArch: 'darwin-x64', - version: '3.3.0', - // ci name + commit sha + build number - hash: 'ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123', - } - - snapshot('upload binary folder', { - input: options, - result: getUploadDirName(options), - }) - }) - }) - - describe('getUploadDirForPlatform', () => { - const { - getUploadDirForPlatform, - } = require('../../binary/upload-unique-binary') - - it('returns folder for given version and platform', () => { - const options = { - platformArch: 'darwin-x64', - version: '3.3.0', - } - const result = getUploadDirForPlatform(options, options.platformArch) - - snapshot('upload binary folder for platform', { - input: options, - result, - }) - }) - }) - - describe('getCDN', () => { - context('binary', () => { - const { getCDN } = require('../../binary/upload-unique-binary') - - it('returns CDN s3 path', () => { - const options = { - platform: 'darwin-x64', - filename: 'cypress.zip', - version: '3.3.0', - // ci name + commit sha + build number - hash: 'ci-name-e154a40f3f76abd39a1d85c0ebc0ff9565015706-123', - } - - snapshot('getCDN for binary', { - input: options, - result: getCDN(options), - }) - }) - }) - }) -}) diff --git a/yarn.lock b/yarn.lock index 60f603c97bdb..0a3aa78c7147 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10822,7 +10822,7 @@ asap@^2.0.0, asap@~2.0.3, asap@~2.0.6: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -asar@^3.0.0, asar@^3.0.3: +asar@^3.0.3, asar@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/asar/-/asar-3.1.0.tgz#70b0509449fe3daccc63beb4d3c7d2e24d3c6473" integrity sha512-vyxPxP5arcAqN4F/ebHd/HhwnAiZtwhglvdmc7BR2f0ywbVNTOpSeyhLDbGXtE/y58hv1oC75TaNIXutnsOZsQ== @@ -15345,6 +15345,15 @@ cross-spawn-async@^2.1.1: lru-cache "^4.0.0" which "^1.2.8" +cross-spawn-windows-exe@^1.1.0, cross-spawn-windows-exe@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/cross-spawn-windows-exe/-/cross-spawn-windows-exe-1.2.0.tgz#46253b0f497676e766faf4a7061004618b5ac5ec" + integrity sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw== + dependencies: + "@malept/cross-spawn-promise" "^1.1.0" + is-wsl "^2.2.0" + which "^2.0.2" + cross-spawn@6.0.5, cross-spawn@^6.0.0, cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -17428,7 +17437,7 @@ electron-mocha@^11.0.2: which "^2.0.2" yargs "^16.2.0" -electron-notarize@^1.0.0, electron-notarize@^1.1.1: +electron-notarize@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/electron-notarize/-/electron-notarize-1.1.1.tgz#3ed274b36158c1beb1dbef14e7faf5927e028629" integrity sha512-kufsnqh86CTX89AYNG3NCPoboqnku/+32RxeJ2+7A4Rbm4bbOx0Nc7XTy3/gAlBfpj9xPAxHfhZLOHgfi6cJVw== @@ -17436,18 +17445,6 @@ electron-notarize@^1.0.0, electron-notarize@^1.1.1: debug "^4.1.1" fs-extra "^9.0.1" -electron-osx-sign@^0.4.11: - version "0.4.17" - resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.4.17.tgz#2727ca0c79e1e4e5ccd3861fb3da9c3c913b006c" - integrity sha512-wUJPmZJQCs1zgdlQgeIpRcvrf7M5/COQaOV68Va1J/SgmWx5KL2otgg+fAae7luw6qz9R8Gvu/Qpe9tAOu/3xQ== - dependencies: - bluebird "^3.5.0" - compare-version "^0.1.2" - debug "^2.6.8" - isbinaryfile "^3.0.2" - minimist "^1.2.0" - plist "^3.0.1" - electron-osx-sign@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/electron-osx-sign/-/electron-osx-sign-0.5.0.tgz#fc258c5e896859904bbe3d01da06902c04b51c3a" @@ -17460,16 +17457,17 @@ electron-osx-sign@^0.5.0: minimist "^1.2.0" plist "^3.0.1" -electron-packager@15.1.0: - version "15.1.0" - resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-15.1.0.tgz#16a3733e4cad26112a2ac36f0b0f35c3b0170eff" - integrity sha512-THNm4bz1DfvR9f0g51+NjuAYELflM8+1vhQ/iv/G8vyZNKzSMuFd5doobngQKq3rRsLdPNZVnGqDdgS884d7Og== +electron-packager@15.4.0: + version "15.4.0" + resolved "https://registry.yarnpkg.com/electron-packager/-/electron-packager-15.4.0.tgz#07ea036b70cde2062d4c8dce4d907d793b303998" + integrity sha512-JrrLcBP15KGrPj0cZ/ALKGmaQ4gJkn3mocf0E3bRKdR3kxKWYcDRpCvdhksYDXw/r3I6tMEcZ7XzyApWFXdVpw== dependencies: "@electron/get" "^1.6.0" - asar "^3.0.0" + asar "^3.1.0" + cross-spawn-windows-exe "^1.2.0" debug "^4.0.1" - electron-notarize "^1.0.0" - electron-osx-sign "^0.4.11" + electron-notarize "^1.1.1" + electron-osx-sign "^0.5.0" extract-zip "^2.0.0" filenamify "^4.1.0" fs-extra "^9.0.0" @@ -17478,10 +17476,10 @@ electron-packager@15.1.0: junk "^3.1.0" parse-author "^2.0.0" plist "^3.0.0" - rcedit "^2.0.0" + rcedit "^3.0.1" resolve "^1.1.6" semver "^7.1.3" - yargs-parser "^19.0.1" + yargs-parser "^20.0.0" electron-publish@22.13.1: version "22.13.1" @@ -32799,10 +32797,12 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -rcedit@^2.0.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-2.3.0.tgz#951685a079db98a4cc8c21ebab75e374d5a0b108" - integrity sha512-h1gNEl9Oai1oijwyJ1WYqYSXTStHnOcv1KYljg/8WM4NAg3H1KBK3azIaKkQ1WQl+d7PoJpcBMscPfLXVKgCLQ== +rcedit@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/rcedit/-/rcedit-3.0.1.tgz#ae21b43e49c075f4d84df1929832a12c302f3c90" + integrity sha512-XM0Jv40/y4hVAqj/MO70o/IWs4uOsaSoo2mLyk3klFDW+SStLnCtzuQu+1OBTIMGlM8CvaK9ftlYCp6DJ+cMsw== + dependencies: + cross-spawn-windows-exe "^1.1.0" "react-15.6.1@npm:react@15.6.1": version "15.6.1" @@ -41937,12 +41937,7 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^19.0.1: - version "19.0.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-19.0.4.tgz#99183a3a59268b205c6b04177f2a5bfb46e79ba7" - integrity sha512-eXeQm7yXRjPFFyf1voPkZgXQZJjYfjgQUmGPbD2TLtZeIYzvacgWX7sQ5a1HsRgVP+pfKAkRZDNtTGev4h9vhw== - -yargs-parser@^20.2.2, yargs-parser@^20.2.3: +yargs-parser@^20.0.0, yargs-parser@^20.2.2, yargs-parser@^20.2.3: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== From fddc4f01a442c4067a11340387bb282b71bb674a Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Fri, 4 Feb 2022 14:52:01 -0500 Subject: [PATCH 058/165] fix run-if-ci.sh --- scripts/run-if-ci.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/run-if-ci.sh b/scripts/run-if-ci.sh index 6f5a38355fda..901bf670361c 100755 --- a/scripts/run-if-ci.sh +++ b/scripts/run-if-ci.sh @@ -1,6 +1,6 @@ #!/bin/bash # If we're in CI, exit early -if [[ !$CI ]]; then exit 0; fi +if [[ $CI == '' ]]; then exit 0; fi # otherwise, run the supplied command "${@:1}" From b44400745a61155ceed9af8268bf63e769943b7f Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sat, 5 Feb 2022 01:24:30 -0500 Subject: [PATCH 059/165] remove dead code --- packages/errors/package.json | 2 -- packages/errors/test/support/utils.ts | 2 -- yarn.lock | 16 ++-------------- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/packages/errors/package.json b/packages/errors/package.json index d3acaa09dce0..ab09cec89a20 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -27,11 +27,9 @@ "@types/strip-ansi": "^5.2.1", "ansi-styles": "^5", "chai": "4.2.0", - "electron-mocha": "^11.0.2", "globby": "^11.1.0", "is-ci": "^3.0.1", "mocha": "7.0.1", - "pixelmatch": "^5.2.1", "pngjs": "^6.0.0", "sinon": "7.5.0", "terminal-banner": "^1.1.0", diff --git a/packages/errors/test/support/utils.ts b/packages/errors/test/support/utils.ts index ee08a5e307dd..92053185ad19 100644 --- a/packages/errors/test/support/utils.ts +++ b/packages/errors/test/support/utils.ts @@ -6,8 +6,6 @@ import { PNG } from 'pngjs' const isCi = require('is-ci') -// const outputHtmlFolder = path.join(__dirname, '..', '..', '__snapshot-images__') -// const baseImageFolder = path.join(__dirname, '..', '..', '__snapshot-bases__') app.on('window-all-closed', () => { }) diff --git a/yarn.lock b/yarn.lock index 812f78f68a31..cf9c575d82b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17487,13 +17487,6 @@ electron-to-chromium@^1.3.247, electron-to-chromium@^1.3.378, electron-to-chromi resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.727.tgz#857e310ca00f0b75da4e1db6ff0e073cc4a91ddf" integrity sha512-Mfz4FIB4FSvEwBpDfdipRIrwd6uo8gUDoRDF4QEYb4h4tSuI3ov594OrjU6on042UlFHouIJpClDODGkPcBSbg== -electron-window@^0.8.0: - version "0.8.1" - resolved "https://registry.yarnpkg.com/electron-window/-/electron-window-0.8.1.tgz#16ca187eb4870b0679274fc8299c5960e6ab2c5e" - integrity sha1-FsoYfrSHCwZ5J0/IKZxZYOarLF4= - dependencies: - is-electron-renderer "^2.0.0" - electron@15.3.4: version "15.3.4" resolved "https://registry.yarnpkg.com/electron/-/electron-15.3.4.tgz#811e8872f4500b88ad49e005cbe8f93e10676f6d" @@ -23046,11 +23039,6 @@ is-dotfile@^1.0.0: resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= -is-electron-renderer@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-electron-renderer/-/is-electron-renderer-2.0.1.tgz#a469d056f975697c58c98c6023eb0aa79af895a2" - integrity sha1-pGnQVvl1aXxYyYxgI+sKp5r4laI= - is-equal-shallow@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" @@ -27730,7 +27718,7 @@ mocha@7.1.2, mocha@^7.1.0: yargs-parser "13.1.2" yargs-unparser "1.6.0" -mocha@>=1.13.0, mocha@^9.1.1: +mocha@>=1.13.0: version "9.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.0.tgz#2bfba73d46e392901f877ab9a47b7c9c5d0275cc" integrity sha512-kNn7E8g2SzVcq0a77dkphPsDSN7P+iYkqE0ZsGCYWRsoiKjOt+NvXfaagik8vuDa6W5Zw3qxe8Jfpt5qKf+6/Q== @@ -30599,7 +30587,7 @@ pixelmatch@^4.0.2: dependencies: pngjs "^3.0.0" -pixelmatch@^5.1.0, pixelmatch@^5.2.1: +pixelmatch@^5.1.0: version "5.2.1" resolved "https://registry.yarnpkg.com/pixelmatch/-/pixelmatch-5.2.1.tgz#9e4e4f4aa59648208a31310306a5bed5522b0d65" integrity sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ== From 6df0f201c15d0ac6ce537f2c0eea5353fbc64242 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Sat, 5 Feb 2022 11:02:56 -0500 Subject: [PATCH 060/165] Mark the .html files as generated in gitattributes --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitattributes b/.gitattributes index 04179d5018a4..ebbd9782ff01 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,5 @@ * text=auto *.json text eol=lf + +packages/errors/__snapshot-html__/** linguist-generated=true \ No newline at end of file From 5f4bdf50e11eb41ff9380d849a81eeed342dad8d Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sat, 5 Feb 2022 14:45:43 -0500 Subject: [PATCH 061/165] fix running single error case, slice out more of the brittle stack --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 0d279eb2a228..e46e82ec10c6 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -136,13 +136,11 @@ const testVisualError = (errorGeneratorFn: () => Er }).timeout(5000) } -const testVisualErrors = (whichError: CypressErrorType[] | '*', errorsToTest: {[K in CypressErrorType]: () => ErrorGenerator}) => { +const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K in CypressErrorType]: () => ErrorGenerator}) => { // if we aren't testing all the errors if (whichError !== '*') { - for (const errorToTest of whichError) { - // then just test this individual error - return testVisualError(errorsToTest[errorToTest], errorToTest) - } + // then just test this individual error + return testVisualError(errorsToTest[whichError], whichError) } // otherwise test all the errors @@ -205,7 +203,7 @@ const testVisualErrors = (whichError: CypressErrorType[] | '*', errorsToTest: {[ const makeErr = () => { const err = new Error('fail whale') - err.stack = err.stack?.split('\n').slice(0, 4).join('\n') ?? '' + err.stack = err.stack?.split('\n').slice(0, 3).join('\n') ?? '' return err as Error & {stack: string} } From 0d4f1ec4b291c68853aaa23116d44d89881d1b50 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sat, 5 Feb 2022 14:46:03 -0500 Subject: [PATCH 062/165] remove additional brittle stack line --- .../__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html | 3 +-- packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html | 3 +-- packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html | 3 +-- packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html | 3 +-- packages/errors/__snapshot-html__/ERROR_READING_FILE.html | 3 +-- packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html | 3 +-- .../errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html | 3 +-- .../errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html | 3 +-- packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html | 3 +-- packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html | 3 +-- packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html | 3 +-- .../errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html | 3 +-- .../errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html | 3 +-- .../errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html | 3 +-- packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html | 3 +-- 15 files changed, 15 insertions(+), 30 deletions(-) diff --git a/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html index cbc96e23c351..54b2c9dfc352 100644 --- a/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html +++ b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html @@ -40,6 +40,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at CANNOT_REMOVE_OLD_BROWSER_PROFILES (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at CANNOT_REMOVE_OLD_BROWSER_PROFILES (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html index ed33e7c29eb2..7c0dea27a7b8 100644 --- a/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html +++ b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html @@ -40,6 +40,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at CANNOT_TRASH_ASSETS (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at CANNOT_TRASH_ASSETS (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html index c7211a48ff0d..c1a7d1c0a6a6 100644 --- a/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html @@ -44,6 +44,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at CDP_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at CDP_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html index 04ef0a7eb5a1..be41e13a511b 100644 --- a/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html @@ -38,6 +38,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at CDP_COULD_NOT_RECONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at CDP_COULD_NOT_RECONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_READING_FILE.html b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html index c85380fb1e5f..042709d384bf 100644 --- a/packages/errors/__snapshot-html__/ERROR_READING_FILE.html +++ b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html @@ -38,6 +38,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at ERROR_READING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at ERROR_READING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html index f01d8f5fa52a..49a8415e599e 100644 --- a/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html +++ b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html @@ -38,6 +38,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at ERROR_WRITING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at ERROR_WRITING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html index cdec24ad31e0..9fc77bbe6397 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html +++ b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html @@ -42,6 +42,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at FIREFOX_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at FIREFOX_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html index e0656bed01f5..119de9d111e0 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html +++ b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html @@ -42,6 +42,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at FIREFOX_MARIONETTE_FAILURE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at FIREFOX_MARIONETTE_FAILURE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html index f6279e4ba2f1..3b991e30bf4f 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html @@ -44,6 +44,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at PLUGINS_FILE_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at PLUGINS_FILE_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html index d6f9dcc1276c..2a679c2acfd0 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html @@ -40,6 +40,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at PLUGINS_FUNCTION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at PLUGINS_FUNCTION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html index 3b8e56684a1d..ac6736145abf 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html @@ -40,6 +40,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at PLUGINS_RUN_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at PLUGINS_RUN_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html index 0a11737af5d8..ef12abce3199 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html @@ -38,6 +38,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html index fcb0ccb9b976..3d64ad0f8c1e 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html @@ -38,6 +38,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html index f759d21393bc..14609ed9b289 100644 --- a/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html +++ b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html @@ -40,6 +40,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at VIDEO_POST_PROCESSING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at VIDEO_POST_PROCESSING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html index 5e789ecbfeb8..407ee18c3d06 100644 --- a/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html +++ b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html @@ -40,6 +40,5 @@ Error: fail whale at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at VIDEO_RECORDING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at testVisualError (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at VIDEO_RECORDING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) \ No newline at end of file From f99fb05e8514c0a64c4963c0b6e861c14703f2f7 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sat, 5 Feb 2022 15:04:46 -0500 Subject: [PATCH 063/165] firefox web security error --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index e46e82ec10c6..6e7f62192b7a 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -240,7 +240,7 @@ testVisualErrors('*', { }, CHROME_WEB_SECURITY_NOT_SUPPORTED: () => { return { - default: ['electron'], + default: ['firefox'], } }, BROWSER_NOT_FOUND_BY_NAME: () => { From e53647cf9d9df90cf41fc6d3804ef4fefafa915c Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sat, 5 Feb 2022 15:06:04 -0500 Subject: [PATCH 064/165] nest inside of describe --- .../test/unit/visualSnapshotErrors_spec.ts | 1186 +++++++++-------- 1 file changed, 594 insertions(+), 592 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 6e7f62192b7a..395bfb54cf97 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -208,615 +208,617 @@ const makeErr = () => { return err as Error & {stack: string} } -testVisualErrors('*', { -// testVisualErrors('CANNOT_TRASH_ASSETS', { - CANNOT_TRASH_ASSETS: () => { - const err = makeErr() +describe('visual error templates', () => { + // testVisualErrors('CANNOT_TRASH_ASSETS', { + testVisualErrors('*', { + CANNOT_TRASH_ASSETS: () => { + const err = makeErr() - return { - default: [err.stack], - } - }, - CANNOT_REMOVE_OLD_BROWSER_PROFILES: () => { - const err = makeErr() + return { + default: [err.stack], + } + }, + CANNOT_REMOVE_OLD_BROWSER_PROFILES: () => { + const err = makeErr() - return { - default: [err.stack], - } - }, - VIDEO_RECORDING_FAILED: () => { - const err = makeErr() + return { + default: [err.stack], + } + }, + VIDEO_RECORDING_FAILED: () => { + const err = makeErr() - return { - default: [err.stack], - } - }, - VIDEO_POST_PROCESSING_FAILED: () => { - const err = makeErr() + return { + default: [err.stack], + } + }, + VIDEO_POST_PROCESSING_FAILED: () => { + const err = makeErr() - return { - default: [err.stack], - } - }, - CHROME_WEB_SECURITY_NOT_SUPPORTED: () => { - return { - default: ['firefox'], - } - }, - BROWSER_NOT_FOUND_BY_NAME: () => { - return { - default: ['invalid-browser', browsers.formatBrowsersToOptions(launcherBrowsers.browsers)], - canary: ['canary', browsers.formatBrowsersToOptions(launcherBrowsers.browsers)], - } - }, - BROWSER_NOT_FOUND_BY_PATH: () => { - const err = makeErr() + return { + default: [err.stack], + } + }, + CHROME_WEB_SECURITY_NOT_SUPPORTED: () => { + return { + default: ['firefox'], + } + }, + BROWSER_NOT_FOUND_BY_NAME: () => { + return { + default: ['invalid-browser', browsers.formatBrowsersToOptions(launcherBrowsers.browsers)], + canary: ['canary', browsers.formatBrowsersToOptions(launcherBrowsers.browsers)], + } + }, + BROWSER_NOT_FOUND_BY_PATH: () => { + const err = makeErr() - return { - default: ['/path/does/not/exist', err.message], - } - }, - NOT_LOGGED_IN: () => { - return { - default: [], - } - }, - TESTS_DID_NOT_START_RETRYING: () => { - return { - default: ['Retrying...'], - retryingAgain: ['Retrying again...'], - } - }, - TESTS_DID_NOT_START_FAILED: () => { - return { - default: [], - } - }, - DASHBOARD_CANCEL_SKIPPED_SPEC: () => { - return { - default: [], - } - }, - DASHBOARD_API_RESPONSE_FAILED_RETRYING: () => { - return { - default: [{ tries: 3, delay: 5000, response: '500 server down' }], - } - }, - DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: () => { - return { - default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Invalid CI Build ID' }], - } - }, - DASHBOARD_CANNOT_PROCEED_IN_SERIAL: () => { - return { - default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Invalid CI Build ID' }], - } - }, - DASHBOARD_UNKNOWN_INVALID_REQUEST: () => { - return { - default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Unexpected 500 Error' }], - } - }, - DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: () => { - return { - default: [{ props: { ciBuildId: 'invalid', group: 'foo' }, message: 'You have been warned' }], - } - }, - DASHBOARD_STALE_RUN: () => { - return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], - } - }, - DASHBOARD_ALREADY_COMPLETE: () => { - return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], - } - }, - DASHBOARD_PARALLEL_REQUIRED: () => { - return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], - } - }, - DASHBOARD_PARALLEL_DISALLOWED: () => { - return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], - } - }, - DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: () => { - return { - default: [ - { - group: 'foo', - runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', - ciBuildId: 'test-ciBuildId-123', - parameters: { - osName: 'darwin', - osVersion: 'v1', - browserName: 'Electron', - browserVersion: '59.1.2.3', - specs: [ - 'cypress/integration/app_spec.js', - ], + return { + default: ['/path/does/not/exist', err.message], + } + }, + NOT_LOGGED_IN: () => { + return { + default: [], + } + }, + TESTS_DID_NOT_START_RETRYING: () => { + return { + default: ['Retrying...'], + retryingAgain: ['Retrying again...'], + } + }, + TESTS_DID_NOT_START_FAILED: () => { + return { + default: [], + } + }, + DASHBOARD_CANCEL_SKIPPED_SPEC: () => { + return { + default: [], + } + }, + DASHBOARD_API_RESPONSE_FAILED_RETRYING: () => { + return { + default: [{ tries: 3, delay: 5000, response: '500 server down' }], + } + }, + DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: () => { + return { + default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Invalid CI Build ID' }], + } + }, + DASHBOARD_CANNOT_PROCEED_IN_SERIAL: () => { + return { + default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Invalid CI Build ID' }], + } + }, + DASHBOARD_UNKNOWN_INVALID_REQUEST: () => { + return { + default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Unexpected 500 Error' }], + } + }, + DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: () => { + return { + default: [{ props: { ciBuildId: 'invalid', group: 'foo' }, message: 'You have been warned' }], + } + }, + DASHBOARD_STALE_RUN: () => { + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } + }, + DASHBOARD_ALREADY_COMPLETE: () => { + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } + }, + DASHBOARD_PARALLEL_REQUIRED: () => { + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } + }, + DASHBOARD_PARALLEL_DISALLOWED: () => { + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } + }, + DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: () => { + return { + default: [ + { + group: 'foo', + runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', + ciBuildId: 'test-ciBuildId-123', + parameters: { + osName: 'darwin', + osVersion: 'v1', + browserName: 'Electron', + browserVersion: '59.1.2.3', + specs: [ + 'cypress/integration/app_spec.js', + ], + }, }, - }, - ], - } - }, - DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: () => { - return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], - } - }, - DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { - return { - default: [], - } - }, - DUPLICATE_TASK_KEY: () => { - return { - default: ['foo, bar, baz'], - } - }, - INDETERMINATE_CI_BUILD_ID: () => { - return { - default: [ - { group: 'foo', parallel: 'false' }, - ciProvider.detectableCiBuildIdProviders(), - ], - } - }, - RECORD_PARAMS_WITHOUT_RECORDING: () => { - return { - default: [{ parallel: 'true' }], - } - }, - INCORRECT_CI_BUILD_ID_USAGE: () => { - return { - default: [{ ciBuildId: 'ciBuildId123' }], - } - }, - RECORD_KEY_MISSING: () => { - return { - default: [], - } - }, - CANNOT_RECORD_NO_PROJECT_ID: () => { - return { - default: ['/path/to/cypress.json'], - } - }, - PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION: () => { - return { - default: ['abc123'], - } - }, - DASHBOARD_INVALID_RUN_REQUEST: () => { - return { - default: [{ message: 'Error on Run Request', errors: [], object: {} }], - } - }, - RECORDING_FROM_FORK_PR: () => { - return { - default: [], - } - }, - DASHBOARD_CANNOT_UPLOAD_RESULTS: () => { - const err = makeErr() + ], + } + }, + DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: () => { + return { + default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + } + }, + DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { + return { + default: [], + } + }, + DUPLICATE_TASK_KEY: () => { + return { + default: ['foo, bar, baz'], + } + }, + INDETERMINATE_CI_BUILD_ID: () => { + return { + default: [ + { group: 'foo', parallel: 'false' }, + ciProvider.detectableCiBuildIdProviders(), + ], + } + }, + RECORD_PARAMS_WITHOUT_RECORDING: () => { + return { + default: [{ parallel: 'true' }], + } + }, + INCORRECT_CI_BUILD_ID_USAGE: () => { + return { + default: [{ ciBuildId: 'ciBuildId123' }], + } + }, + RECORD_KEY_MISSING: () => { + return { + default: [], + } + }, + CANNOT_RECORD_NO_PROJECT_ID: () => { + return { + default: ['/path/to/cypress.json'], + } + }, + PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION: () => { + return { + default: ['abc123'], + } + }, + DASHBOARD_INVALID_RUN_REQUEST: () => { + return { + default: [{ message: 'Error on Run Request', errors: [], object: {} }], + } + }, + RECORDING_FROM_FORK_PR: () => { + return { + default: [], + } + }, + DASHBOARD_CANNOT_UPLOAD_RESULTS: () => { + const err = makeErr() - return { - default: [err], - } - }, - DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: () => { - const err = makeErr() + return { + default: [err], + } + }, + DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: () => { + const err = makeErr() - return { - default: [err], - } - }, - DASHBOARD_RECORD_KEY_NOT_VALID: () => { - return { - default: ['record-key-1234', 'projectId'], - } - }, - DASHBOARD_PROJECT_NOT_FOUND: () => { - return { - default: ['abc123', '/path/to/cypress.json'], - } - }, - NO_PROJECT_ID: () => { - return { - default: ['cypress.json', '/path/to/project'], - } - }, - NO_PROJECT_FOUND_AT_PROJECT_ROOT: () => { - return { - default: ['/path/to/project'], - } - }, - CANNOT_FETCH_PROJECT_TOKEN: () => { - return { - default: [], - } - }, - CANNOT_CREATE_PROJECT_TOKEN: () => { - return { - default: [], - } - }, - PORT_IN_USE_SHORT: () => { - return { - default: [2020], - } - }, - PORT_IN_USE_LONG: () => { - return { - default: [2020], - } - }, - ERROR_READING_FILE: () => { - return { - default: ['/path/to/read/file.ts', makeErr()], - } - }, - ERROR_WRITING_FILE: () => { - return { - default: ['path/to/write/file.ts', makeErr()], - } - }, - NO_SPECS_FOUND: () => { - return { - default: ['/path/to/project/root', '**_spec.js'], - noPattern: ['/path/to/project/root'], - } - }, - RENDERER_CRASHED: () => { - return { - default: [], - } - }, - AUTOMATION_SERVER_DISCONNECTED: () => { - return { - default: [], - } - }, - SUPPORT_FILE_NOT_FOUND: () => { - return { - default: ['/path/to/supportFile', '/path/to/cypress.json'], - } - }, - PLUGINS_FILE_ERROR: () => { - const err = makeErr() + return { + default: [err], + } + }, + DASHBOARD_RECORD_KEY_NOT_VALID: () => { + return { + default: ['record-key-1234', 'projectId'], + } + }, + DASHBOARD_PROJECT_NOT_FOUND: () => { + return { + default: ['abc123', '/path/to/cypress.json'], + } + }, + NO_PROJECT_ID: () => { + return { + default: ['cypress.json', '/path/to/project'], + } + }, + NO_PROJECT_FOUND_AT_PROJECT_ROOT: () => { + return { + default: ['/path/to/project'], + } + }, + CANNOT_FETCH_PROJECT_TOKEN: () => { + return { + default: [], + } + }, + CANNOT_CREATE_PROJECT_TOKEN: () => { + return { + default: [], + } + }, + PORT_IN_USE_SHORT: () => { + return { + default: [2020], + } + }, + PORT_IN_USE_LONG: () => { + return { + default: [2020], + } + }, + ERROR_READING_FILE: () => { + return { + default: ['/path/to/read/file.ts', makeErr()], + } + }, + ERROR_WRITING_FILE: () => { + return { + default: ['path/to/write/file.ts', makeErr()], + } + }, + NO_SPECS_FOUND: () => { + return { + default: ['/path/to/project/root', '**_spec.js'], + noPattern: ['/path/to/project/root'], + } + }, + RENDERER_CRASHED: () => { + return { + default: [], + } + }, + AUTOMATION_SERVER_DISCONNECTED: () => { + return { + default: [], + } + }, + SUPPORT_FILE_NOT_FOUND: () => { + return { + default: ['/path/to/supportFile', '/path/to/cypress.json'], + } + }, + PLUGINS_FILE_ERROR: () => { + const err = makeErr() - return { - default: ['/path/to/pluginsFile', err], - } - }, - PLUGINS_DIDNT_EXPORT_FUNCTION: () => { - return { - default: ['/path/to/pluginsFile', () => 'some function'], - } - }, - PLUGINS_FUNCTION_ERROR: () => { - const err = makeErr() + return { + default: ['/path/to/pluginsFile', err], + } + }, + PLUGINS_DIDNT_EXPORT_FUNCTION: () => { + return { + default: ['/path/to/pluginsFile', () => 'some function'], + } + }, + PLUGINS_FUNCTION_ERROR: () => { + const err = makeErr() - return { - default: ['/path/to/pluginsFile', err], - } - }, - PLUGINS_UNEXPECTED_ERROR: () => { - const err = makeErr() + return { + default: ['/path/to/pluginsFile', err], + } + }, + PLUGINS_UNEXPECTED_ERROR: () => { + const err = makeErr() - return { - default: ['/path/to/pluginsFile', err], - } - }, - PLUGINS_VALIDATION_ERROR: () => { - const err = makeErr() + return { + default: ['/path/to/pluginsFile', err], + } + }, + PLUGINS_VALIDATION_ERROR: () => { + const err = makeErr() - return { - default: ['/path/to/pluginsFile', err], - } - }, - BUNDLE_ERROR: () => { - const err = makeErr() + return { + default: ['/path/to/pluginsFile', err], + } + }, + BUNDLE_ERROR: () => { + const err = makeErr() - return { - default: ['/path/to/file', err.message], - } - }, - SETTINGS_VALIDATION_ERROR: () => { - const err = makeErr() + return { + default: ['/path/to/file', err.message], + } + }, + SETTINGS_VALIDATION_ERROR: () => { + const err = makeErr() - return { - default: ['/path/to/file', err.message], - } - }, - PLUGINS_CONFIG_VALIDATION_ERROR: () => { - const err = makeErr() + return { + default: ['/path/to/file', err.message], + } + }, + PLUGINS_CONFIG_VALIDATION_ERROR: () => { + const err = makeErr() - return { - default: ['/path/to/pluginsFile', err.message], - } - }, - CONFIG_VALIDATION_ERROR: () => { - const err = makeErr() + return { + default: ['/path/to/pluginsFile', err.message], + } + }, + CONFIG_VALIDATION_ERROR: () => { + const err = makeErr() - return { - default: [err.message], - } - }, - RENAMED_CONFIG_OPTION: () => { - return { - default: [{ name: 'oldName', newName: 'newName' }], - } - }, - CANNOT_CONNECT_BASE_URL: () => { - return { - default: [], - } - }, - CANNOT_CONNECT_BASE_URL_WARNING: () => { - return { - default: ['http://localhost:3000'], - } - }, - CANNOT_CONNECT_BASE_URL_RETRYING: () => { - return { - default: [{ attempt: 0, baseUrl: 'http://localhost:3000', remaining: 60, delay: 500 }], - } - }, - INVALID_REPORTER_NAME: () => { - return { - default: [{ - name: 'missing-reporter-name', - paths: ['/path/to/reporter', '/path/reporter'], - error: `stack-trace`, - }], - } - }, - NO_DEFAULT_CONFIG_FILE_FOUND: () => { - return { - default: ['/path/to/project/root'], - } - }, - CONFIG_FILES_LANGUAGE_CONFLICT: () => { - return { - default: [ - 'cypress.config.js', - 'cypress.config.ts', - '/path/to/project/root', - ], - } - }, - CONFIG_FILE_NOT_FOUND: () => { - return { - default: ['cypress.json', '/path/to/project/root'], - } - }, - INVOKED_BINARY_OUTSIDE_NPM_MODULE: () => { - return { - default: [], - } - }, - FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { - return { - default: [{ - link: 'https://dashboard.cypress.io/project/abcd', - planType: 'Test Plan', - usedTestsMessage: 'The limit is 500 free results', - }], - } - }, - FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { - return { - default: [{ - link: 'https://dashboard.cypress.io/project/abcd', - planType: 'Grace Period Plan', - usedTestsMessage: 'The limit is 500 free results', - gracePeriodMessage: 'You are on a grace period for 20 days', - }], - } - }, - PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { - return { - default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], - } - }, - FREE_PLAN_EXCEEDS_MONTHLY_TESTS: () => { - return { - default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], - } - }, - FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: () => { - return { - default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results', gracePeriodMessage: 'Feb 1, 2022' }], - } - }, - PLAN_EXCEEDS_MONTHLY_TESTS: () => { - return { - default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], - } - }, - FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: () => { - return { - default: [{ link: 'https://on.cypress.io/set-up-billing', gracePeriodMessage: 'Feb 1, 2022' }], - } - }, - PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { - return { - default: [{ link: 'https://on.cypress.io/set-up-billing' }], - } - }, - PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: () => { - return { - default: [{ link: 'https://on.cypress.io/set-up-billing', gracePeriodMessage: 'Feb 1, 2022' }], - } - }, - RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { - return { - default: [{ link: 'https://on.cypress.io/set-up-billing' }], - } - }, - FIXTURE_NOT_FOUND: () => { - return { - default: ['file', ['js', 'ts', 'json']], - } - }, - AUTH_COULD_NOT_LAUNCH_BROWSER: () => { - return { - default: ['http://dashboard.cypress.io/login'], - } - }, - AUTH_BROWSER_LAUNCHED: () => { - return { - default: [], - } - }, - BAD_POLICY_WARNING: () => { - return { - default: [[ - 'HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome\\ProxyServer', - 'HKEY_CURRENT_USER\\Software\\Policies\\Google\\Chromium\\ExtensionSettings', - ]], - } - }, - BAD_POLICY_WARNING_TOOLTIP: () => { - return { - default: [], - } - }, - EXTENSION_NOT_LOADED: () => { - return { - default: ['Electron', '/path/to/extension'], - } - }, - COULD_NOT_FIND_SYSTEM_NODE: () => { - return { - default: ['16.2.1'], - } - }, - INVALID_CYPRESS_INTERNAL_ENV: () => { - return { - default: ['foo'], - } - }, - CDP_VERSION_TOO_OLD: () => { - return { - default: ['89', { major: 90, minor: 2 }], - } - }, - CDP_COULD_NOT_CONNECT: () => { - return { - default: ['chrome', '2345', makeErr()], - } - }, - FIREFOX_COULD_NOT_CONNECT: () => { - const err = makeErr() + return { + default: [err.message], + } + }, + RENAMED_CONFIG_OPTION: () => { + return { + default: [{ name: 'oldName', newName: 'newName' }], + } + }, + CANNOT_CONNECT_BASE_URL: () => { + return { + default: [], + } + }, + CANNOT_CONNECT_BASE_URL_WARNING: () => { + return { + default: ['http://localhost:3000'], + } + }, + CANNOT_CONNECT_BASE_URL_RETRYING: () => { + return { + default: [{ attempt: 0, baseUrl: 'http://localhost:3000', remaining: 60, delay: 500 }], + } + }, + INVALID_REPORTER_NAME: () => { + return { + default: [{ + name: 'missing-reporter-name', + paths: ['/path/to/reporter', '/path/reporter'], + error: `stack-trace`, + }], + } + }, + NO_DEFAULT_CONFIG_FILE_FOUND: () => { + return { + default: ['/path/to/project/root'], + } + }, + CONFIG_FILES_LANGUAGE_CONFLICT: () => { + return { + default: [ + 'cypress.config.js', + 'cypress.config.ts', + '/path/to/project/root', + ], + } + }, + CONFIG_FILE_NOT_FOUND: () => { + return { + default: ['cypress.json', '/path/to/project/root'], + } + }, + INVOKED_BINARY_OUTSIDE_NPM_MODULE: () => { + return { + default: [], + } + }, + FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { + return { + default: [{ + link: 'https://dashboard.cypress.io/project/abcd', + planType: 'Test Plan', + usedTestsMessage: 'The limit is 500 free results', + }], + } + }, + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { + return { + default: [{ + link: 'https://dashboard.cypress.io/project/abcd', + planType: 'Grace Period Plan', + usedTestsMessage: 'The limit is 500 free results', + gracePeriodMessage: 'You are on a grace period for 20 days', + }], + } + }, + PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], + } + }, + FREE_PLAN_EXCEEDS_MONTHLY_TESTS: () => { + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], + } + }, + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: () => { + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results', gracePeriodMessage: 'Feb 1, 2022' }], + } + }, + PLAN_EXCEEDS_MONTHLY_TESTS: () => { + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], + } + }, + FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: () => { + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', gracePeriodMessage: 'Feb 1, 2022' }], + } + }, + PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { + return { + default: [{ link: 'https://on.cypress.io/set-up-billing' }], + } + }, + PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: () => { + return { + default: [{ link: 'https://on.cypress.io/set-up-billing', gracePeriodMessage: 'Feb 1, 2022' }], + } + }, + RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { + return { + default: [{ link: 'https://on.cypress.io/set-up-billing' }], + } + }, + FIXTURE_NOT_FOUND: () => { + return { + default: ['file', ['js', 'ts', 'json']], + } + }, + AUTH_COULD_NOT_LAUNCH_BROWSER: () => { + return { + default: ['http://dashboard.cypress.io/login'], + } + }, + AUTH_BROWSER_LAUNCHED: () => { + return { + default: [], + } + }, + BAD_POLICY_WARNING: () => { + return { + default: [[ + 'HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome\\ProxyServer', + 'HKEY_CURRENT_USER\\Software\\Policies\\Google\\Chromium\\ExtensionSettings', + ]], + } + }, + BAD_POLICY_WARNING_TOOLTIP: () => { + return { + default: [], + } + }, + EXTENSION_NOT_LOADED: () => { + return { + default: ['Electron', '/path/to/extension'], + } + }, + COULD_NOT_FIND_SYSTEM_NODE: () => { + return { + default: ['16.2.1'], + } + }, + INVALID_CYPRESS_INTERNAL_ENV: () => { + return { + default: ['foo'], + } + }, + CDP_VERSION_TOO_OLD: () => { + return { + default: ['89', { major: 90, minor: 2 }], + } + }, + CDP_COULD_NOT_CONNECT: () => { + return { + default: ['chrome', '2345', makeErr()], + } + }, + FIREFOX_COULD_NOT_CONNECT: () => { + const err = makeErr() - return { - default: [err], - } - }, - CDP_COULD_NOT_RECONNECT: () => { - const err = makeErr() + return { + default: [err], + } + }, + CDP_COULD_NOT_RECONNECT: () => { + const err = makeErr() - return { - default: [err], - } - }, - CDP_RETRYING_CONNECTION: () => { - return { - default: [1, 'chrome'], - } - }, - UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: () => { - return { - default: [ - ['baz'], ['preferences', 'extensions', 'args'], - ], - } - }, - COULD_NOT_PARSE_ARGUMENTS: () => { - return { - default: ['spec', '1', 'spec must be a string or comma-separated list'], - } - }, - FIREFOX_MARIONETTE_FAILURE: () => { - const err = makeErr() + return { + default: [err], + } + }, + CDP_RETRYING_CONNECTION: () => { + return { + default: [1, 'chrome'], + } + }, + UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: () => { + return { + default: [ + ['baz'], ['preferences', 'extensions', 'args'], + ], + } + }, + COULD_NOT_PARSE_ARGUMENTS: () => { + return { + default: ['spec', '1', 'spec must be a string or comma-separated list'], + } + }, + FIREFOX_MARIONETTE_FAILURE: () => { + const err = makeErr() - return { - default: ['connection', err], - } - }, - FOLDER_NOT_WRITABLE: () => { - return { - default: ['/path/to/folder'], - } - }, - EXPERIMENTAL_SAMESITE_REMOVED: () => { - return { - default: [], - } - }, - EXPERIMENTAL_COMPONENT_TESTING_REMOVED: () => { - return { - default: [{ configFile: '/path/to/configFile.json' }], - } - }, - EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { - return { - default: [], - } - }, - EXPERIMENTAL_NETWORK_STUBBING_REMOVED: () => { - return { - default: [], - } - }, - EXPERIMENTAL_RUN_EVENTS_REMOVED: () => { - return { - default: [], - } - }, - FIREFOX_GC_INTERVAL_REMOVED: () => { - return { - default: [], - } - }, - INCOMPATIBLE_PLUGIN_RETRIES: () => { - return { - default: ['./path/to/cypress-plugin-retries'], - } - }, - NODE_VERSION_DEPRECATION_BUNDLED: () => { - return { - default: [{ name: 'nodeVersion', value: 'bundled', 'configFile': 'cypress.json' }], - } - }, - NODE_VERSION_DEPRECATION_SYSTEM: () => { - return { - default: [{ name: 'nodeVersion', value: 'system', 'configFile': 'cypress.json' }], - } - }, - CT_NO_DEV_START_EVENT: () => { - return { - default: ['/path/to/plugins/file.js'], - } - }, - PLUGINS_RUN_EVENT_ERROR: () => { - return { - default: ['before:spec', makeErr()], - } - }, - INVALID_CONFIG_OPTION: () => { - return { - default: [['test', 'foo']], - } - }, - UNSUPPORTED_BROWSER_VERSION: () => { - return { - default: [`Cypress does not support running chrome version 64. To use chrome with Cypress, install a version of chrome newer than or equal to 64.`], - } - }, + return { + default: ['connection', err], + } + }, + FOLDER_NOT_WRITABLE: () => { + return { + default: ['/path/to/folder'], + } + }, + EXPERIMENTAL_SAMESITE_REMOVED: () => { + return { + default: [], + } + }, + EXPERIMENTAL_COMPONENT_TESTING_REMOVED: () => { + return { + default: [{ configFile: '/path/to/configFile.json' }], + } + }, + EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { + return { + default: [], + } + }, + EXPERIMENTAL_NETWORK_STUBBING_REMOVED: () => { + return { + default: [], + } + }, + EXPERIMENTAL_RUN_EVENTS_REMOVED: () => { + return { + default: [], + } + }, + FIREFOX_GC_INTERVAL_REMOVED: () => { + return { + default: [], + } + }, + INCOMPATIBLE_PLUGIN_RETRIES: () => { + return { + default: ['./path/to/cypress-plugin-retries'], + } + }, + NODE_VERSION_DEPRECATION_BUNDLED: () => { + return { + default: [{ name: 'nodeVersion', value: 'bundled', 'configFile': 'cypress.json' }], + } + }, + NODE_VERSION_DEPRECATION_SYSTEM: () => { + return { + default: [{ name: 'nodeVersion', value: 'system', 'configFile': 'cypress.json' }], + } + }, + CT_NO_DEV_START_EVENT: () => { + return { + default: ['/path/to/plugins/file.js'], + } + }, + PLUGINS_RUN_EVENT_ERROR: () => { + return { + default: ['before:spec', makeErr()], + } + }, + INVALID_CONFIG_OPTION: () => { + return { + default: [['test', 'foo']], + } + }, + UNSUPPORTED_BROWSER_VERSION: () => { + return { + default: [`Cypress does not support running chrome version 64. To use chrome with Cypress, install a version of chrome newer than or equal to 64.`], + } + }, + }) }) From e62d1a97843cbdb584afe893ffb6f640616234b1 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sun, 6 Feb 2022 16:21:02 -0500 Subject: [PATCH 065/165] reformat and redesign errors --- packages/errors/src/errTemplate.ts | 127 +++++- packages/errors/src/errorUtils.ts | 39 +- packages/errors/src/errors.ts | 368 +++++++++--------- packages/errors/test/support/utils.ts | 2 +- packages/errors/test/unit/errors_spec.ts | 11 +- .../test/unit/visualSnapshotErrors_spec.ts | 178 +++++++-- packages/server/lib/api.js | 3 +- packages/server/lib/fixture.js | 3 +- packages/server/lib/plugins/child/task.js | 2 +- packages/server/lib/project-base.ts | 28 +- packages/server/lib/util/settings.ts | 8 +- 11 files changed, 475 insertions(+), 294 deletions(-) diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index f440a9d0c4e7..4beb0110da99 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -1,14 +1,87 @@ import assert from 'assert' -import { stripIndent } from './stripIndent' - -/** - * Guarding the value, involves - */ import chalk from 'chalk' +import _ from 'lodash' import stripAnsi from 'strip-ansi' import { trimMultipleNewLines } from './errorUtils' +import { stripIndent } from './stripIndent' + import type { ErrTemplateResult, SerializedError } from './errorTypes' +interface ListOptions { + prefix?: string + color?: Function +} + +const theme = { + blue: chalk.blueBright, + gray: chalk.gray, + yellow: chalk.yellow, + magenta: chalk.magenta, +} + +export const fmt = { + meta: theme.gray, + path: theme.blue, + url: theme.blue, + flag: theme.magenta, + prop: theme.yellow, + value: theme.blue, + highlight: theme.yellow, + highlightSecondary: theme.magenta, + terminal, + listItem, + listItems, + listFlags, + cypressVersion, +} + +function terminal (str: string) { + return guard(`${theme.gray('$')} ${theme.blue(str)}`) +} + +function cypressVersion (version: string) { + const parts = version.split('.') + + if (parts.length !== 3) { + throw new Error('Cypress version provided must be in x.x.x format') + } + + // TODO: come back to this to work on formatting + return guard(`Cypress version ${version}`) +} + +function _item (item: string, { prefix = ' - ', color = theme.blue }: ListOptions) { + return stripIndent`${theme.gray(prefix)}${color(item)}` +} + +function listItem (item: string, options: ListOptions = {}) { + return guard(_item(item, options)) +} + +function listItems (items: string[], options: ListOptions = {}) { + return guard(items + .map((item) => _item(item, options)) + .join('\n')) +} + +function listFlags ( + obj: Record, + mapper: Record, +) { + return guard(_ + .chain(mapper) + .map((flag, key) => { + const v = obj[key] + + if (v) { + return `The ${flag} flag you passed was: ${theme.yellow(v)}` + } + }) + .compact() + .join('\n') + .value()) +} + export class Guard { constructor (readonly val: string | number) {} } @@ -29,19 +102,27 @@ export function backtick (val: string) { return new Backtick(val) } +export class Secondary { + constructor (readonly val: string | number) {} +} + +export function secondary (val: string) { + return new Secondary(val) +} + /** * Marks the value as "details". This is when we print out the stack trace to the console * (if it's an error), or use the stack trace as the originalError */ -export class Details { +export class StackTrace { /** - * @param {string | Error | object} details + * @param {string | Error | object} stackTrace */ constructor (readonly val: string | Error | object) {} } -export function details (val: string | Error | object) { - return new Details(val) +export function stackTrace (val: string | Error | object) { + return new StackTrace(val) } export function isErrorLike (err: any): err is SerializedError | Error { @@ -52,14 +133,14 @@ export function isErrorLike (err: any): err is SerializedError | Error { * Creates a consistently formatted object to return from the error call. * * For the console: - * - By default, wrap every arg in chalk.blue, unless it's "guarded" or is a "details" - * - Details stack gets logged at the end of the message in yellow + * - By default, wrap every arg in yellow, unless it's "guarded" or is a "details" + * - Details stack gets logged at the end of the message in gray | magenta * * For the browser: * - Wrap every arg in backticks, for better rendering in markdown * - If details is an error, it gets provided as originalError */ -export const errTemplate = (strings: TemplateStringsArray, ...args: Array): ErrTemplateResult => { +export const errTemplate = (strings: TemplateStringsArray, ...args: Array): ErrTemplateResult => { let originalError: Error | undefined = undefined let messageDetails: string | undefined @@ -69,13 +150,17 @@ export const errTemplate = (strings: TemplateStringsArray, ...args: Array = [] @@ -110,19 +197,19 @@ export const errTemplate = (strings: TemplateStringsArray, ...args: Array { - return Boolean(err.isCypressErr) -} +import type { CypressError, ErrorLike } from './errorTypes' -export const listItems = (paths: string[]) => { - return guard(_ - .chain(paths) - .map((p) => { - return stripIndent`- ${chalk.blue(p)}` - }).join('\n') - .value()) +export { + pluralize, + humanTime, } -export const displayFlags = (obj: Record, mapper: Record) => { - return guard(_ - .chain(mapper) - .map((flag, key) => { - let v - - v = obj[key] - - if (v) { - return `The ${flag} flag you passed was: ${chalk.blue(v)}` - } - - return undefined - }).compact() - .join('\n') - .value()) +export const isCypressErr = (err: ErrorLike): err is CypressError => { + return Boolean(err.isCypressErr) } const twoOrMoreNewLinesRe = /\n{2,}/ @@ -60,7 +39,7 @@ export const logError = function (err: CypressError | ErrorLike, color: AllowedC console.log(chalk[color](err.message)) if (err.details) { - console.log(`\n${chalk['yellow'](err.details)}`) + console.log(`\n${err.details}`) } // bail if this error came from known diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index f411f8374cf3..ddf26716f066 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -1,14 +1,15 @@ +import AU from 'ansi_up' /* eslint-disable no-console */ import chalk from 'chalk' import _ from 'lodash' +import path from 'path' import stripAnsi from 'strip-ansi' -import AU from 'ansi_up' - -import { backtick, details, errTemplate, guard } from './errTemplate' +import { humanTime, logError, pluralize } from './errorUtils' +import { backtick, errTemplate, fmt, guard, stackTrace } from './errTemplate' +import { stackWithoutMessage } from './stackUtils' import { stripIndent } from './stripIndent' -import { displayFlags, listItems, logError } from './errorUtils' + import type { ClonedError, CypressError, ErrorLike, ErrTemplateResult } from './errorTypes' -import { stackWithoutMessage } from './stackUtils' const ansi_up = new AU() @@ -50,7 +51,7 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${details(arg1)}` + ${stackTrace(arg1)}` }, CANNOT_REMOVE_OLD_BROWSER_PROFILES: (arg1: string) => { return errTemplate`\ @@ -58,7 +59,7 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${details(arg1)}` + ${stackTrace(arg1)}` }, VIDEO_RECORDING_FAILED: (arg1: string) => { return errTemplate`\ @@ -66,7 +67,7 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${details(arg1)}` + ${stackTrace(arg1)}` }, VIDEO_POST_PROCESSING_FAILED: (arg1: string) => { return errTemplate`\ @@ -74,11 +75,11 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${details(arg1)}` + ${stackTrace(arg1)}` }, CHROME_WEB_SECURITY_NOT_SUPPORTED: (browser: string) => { return errTemplate`\ - Your project has set the configuration option: \`chromeWebSecurity: false\` + Your project has set the configuration option: ${fmt.prop(`chromeWebSecurity`)} to ${fmt.value(`false`)} This option will not have an effect in ${guard(_.capitalize(browser))}. Tests that rely on web security being disabled will not run as expected.` }, @@ -88,8 +89,8 @@ export const AllCypressErrors = { if (browser === 'canary') { canarySuffix += '\n\n' canarySuffix += stripIndent`\ - Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. - + Note: In ${fmt.cypressVersion(`4.0.0`)}, Canary must be launched as ${chalk.magentaBright(`chrome:canary`)}, not ${chalk.magentaBright(`canary`)}. +_ See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.` } @@ -99,30 +100,26 @@ export const AllCypressErrors = { Browser: ${browser} was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - - chrome - - chromium - - edge - - electron - - firefox + ${fmt.listItems(['electron', 'chrome', 'chromium', 'edge', 'firefox'])} You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: - ${listItems(foundBrowsersStr)}${guard(canarySuffix)}` + ${fmt.listItems(foundBrowsersStr)}${guard(canarySuffix)}` }, BROWSER_NOT_FOUND_BY_PATH: (arg1: string, arg2: string) => { return errTemplate`\ - We could not identify a known browser at the path you provided: ${arg1} + We could not identify a known browser at the path you provided: ${fmt.path(arg1)} The output from the command we ran was: - ${details(arg2)}` + ${stackTrace(arg2)}` }, NOT_LOGGED_IN: () => { return errTemplate`\ You're not logged in. - Run \`cypress open\` to open the Desktop App and log in.` + Run ${`cypress open`} to open the Desktop App and log in.` }, TESTS_DID_NOT_START_RETRYING: (arg1: string) => { return errTemplate`Timed out waiting for the browser to connect. ${guard(arg1)}` @@ -133,25 +130,28 @@ export const AllCypressErrors = { DASHBOARD_CANCEL_SKIPPED_SPEC: () => { return errTemplate`${guard(`\n `)}This spec and its tests were skipped because the run has been canceled.` }, - DASHBOARD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delay: number, response: string}) => { + DASHBOARD_API_RESPONSE_FAILED_RETRYING: (arg1: {tries: number, delay: number, response: Error}) => { + const time = pluralize('time', arg1.tries) + const delay = humanTime.long(arg1.delay, false) + return errTemplate`\ We encountered an unexpected error talking to our servers. - We will retry ${arg1.tries} more ${guard(arg1.tries === 1 ? 'time' : 'times')} in ${guard(arg1.delay)}... + We will retry ${guard(arg1.tries)} more ${guard(time)} in ${guard(delay)}... The server's response was: ${arg1.response}` - /* Because of displayFlags() and listItems() */ + /* Because of fmt.listFlags() and fmt.listItems() */ /* eslint-disable indent */ }, - DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: (arg1: {flags: any, response: string}) => { + DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: (arg1: {flags: any, response: Error}) => { return errTemplate`\ We encountered an unexpected error talking to our servers. - Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers. + Because you passed the ${fmt.highlightSecondary(`--parallel`)} flag, this run cannot proceed because it requires a valid response from our servers. - ${displayFlags(arg1.flags, { + ${fmt.listFlags(arg1.flags, { group: '--group', ciBuildId: '--ciBuildId', })} @@ -160,11 +160,11 @@ export const AllCypressErrors = { ${arg1.response}` }, - DASHBOARD_CANNOT_PROCEED_IN_SERIAL: (arg1: {flags: any, response: string}) => { + DASHBOARD_CANNOT_PROCEED_IN_SERIAL: (arg1: {flags: any, response: Error}) => { return errTemplate`\ We encountered an unexpected error talking to our servers. - ${displayFlags(arg1.flags, { + ${fmt.listFlags(arg1.flags, { group: '--group', ciBuildId: '--ciBuildId', })} @@ -173,13 +173,13 @@ export const AllCypressErrors = { ${arg1.response}` }, - DASHBOARD_UNKNOWN_INVALID_REQUEST: (arg1: {flags: any, response: string}) => { + DASHBOARD_UNKNOWN_INVALID_REQUEST: (arg1: {flags: any, response: Error}) => { return errTemplate`\ We encountered an unexpected error talking to our servers. There is likely something wrong with the request. - ${displayFlags(arg1.flags, { + ${fmt.listFlags(arg1.flags, { tags: '--tag', group: '--group', parallel: '--parallel', @@ -190,6 +190,7 @@ export const AllCypressErrors = { ${arg1.response}` }, + // TODO: fix DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: (arg1: {props: any, message: string}) => { return errTemplate`\ Warning from Cypress Dashboard: ${arg1.message} @@ -199,13 +200,13 @@ export const AllCypressErrors = { }, DASHBOARD_STALE_RUN: (arg1: {runUrl: string, [key: string]: any}) => { return errTemplate`\ - You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago. + You are attempting to pass the ${fmt.highlightSecondary(`--parallel`)} flag to a run that was completed over 24 hours ago. - The existing run is: ${arg1.runUrl} + The existing run is: ${fmt.url(arg1.runUrl)} You cannot parallelize a run that has been complete for that long. - ${displayFlags(arg1, { + ${fmt.listFlags(arg1, { tags: '--tag', group: '--group', parallel: '--parallel', @@ -214,15 +215,15 @@ export const AllCypressErrors = { https://on.cypress.io/stale-run` }, - DASHBOARD_ALREADY_COMPLETE: (arg1: {runUrl: string}) => { + DASHBOARD_ALREADY_COMPLETE: (props: {runUrl: string}) => { return errTemplate`\ The run you are attempting to access is already complete and will not accept new groups. - The existing run is: ${arg1.runUrl} + The existing run is: ${fmt.url(props.runUrl)} When a run finishes all of its groups, it waits for a configurable set of time before finally completing. You must add more groups during that time period. - ${displayFlags(arg1, { + ${fmt.listFlags(props, { tags: '--tag', group: '--group', parallel: '--parallel', @@ -233,11 +234,11 @@ export const AllCypressErrors = { }, DASHBOARD_PARALLEL_REQUIRED: (arg1: {runUrl: string}) => { return errTemplate`\ - You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag. + You did not pass the ${fmt.highlightSecondary(`--parallel`)} flag, but this run's group was originally created with the --parallel flag. - The existing run is: ${arg1.runUrl} + The existing run is: ${fmt.url(arg1.runUrl)} - ${displayFlags(arg1, { + ${fmt.listFlags(arg1, { tags: '--tag', group: '--group', parallel: '--parallel', @@ -250,11 +251,11 @@ export const AllCypressErrors = { }, DASHBOARD_PARALLEL_DISALLOWED: (arg1: {runUrl: string}) => { return errTemplate`\ - You passed the --parallel flag, but this run group was originally created without the --parallel flag. + You passed the ${fmt.highlightSecondary(`--parallel`)} flag, but this run group was originally created without the --parallel flag. - The existing run is: ${arg1.runUrl} + The existing run is: ${fmt.url(arg1.runUrl)} - ${displayFlags(arg1, { + ${fmt.listFlags(arg1, { group: '--group', parallel: '--parallel', ciBuildId: '--ciBuildId', @@ -266,15 +267,15 @@ export const AllCypressErrors = { }, DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: (arg1: {runUrl: string, parameters: any}) => { return errTemplate`\ - You passed the --parallel flag, but we do not parallelize tests across different environments. + You passed the ${fmt.highlightSecondary(`--parallel`)} flag, but we do not parallelize tests across different environments. This machine is sending different environment parameters than the first machine that started this parallel run. - The existing run is: ${arg1.runUrl} + The existing run is: ${fmt.url(arg1.runUrl)} In order to run in parallel mode each machine must send identical environment parameters such as: - ${listItems([ + ${fmt.listItems([ 'specs', 'osName', 'osVersion', @@ -290,41 +291,45 @@ export const AllCypressErrors = { }, DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: (arg1: {runUrl: string, ciBuildId?: string | null}) => { return errTemplate`\ - You passed the --group flag, but this group name has already been used for this run. + You passed the ${fmt.flag(`--group`)} flag, but this group name has already been used for this run. - The existing run is: ${arg1.runUrl} + The existing run is: ${fmt.url(arg1.runUrl)} - ${displayFlags(arg1, { + ${fmt.listFlags(arg1, { group: '--group', parallel: '--parallel', ciBuildId: '--ciBuildId', })} - If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name. + If you are trying to parallelize this run, then also pass the ${fmt.highlightSecondary(`--parallel`)} flag, else pass a different group name. ${warnIfExplicitCiBuildId(arg1.ciBuildId)} https://on.cypress.io/run-group-name-not-unique` }, + // TODO: fix DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { return errTemplate`\ Deprecation Warning: The \`before:browser:launch\` plugin event changed its signature in version \`4.0.0\` - + The \`before:browser:launch\` plugin event switched from yielding the second argument as an \`array\` of browser arguments to an options \`object\` with an \`args\` property. - + We've detected that your code is still using the previous, deprecated interface signature. - - This code will not work in a future version of Cypress. Please see the upgrade guide: ${chalk.yellow('https://on.cypress.io/deprecated-before-browser-launch-args')}` + + This code will not work in a future version of Cypress. Please see the upgrade guide: https://on.cypress.io/deprecated-before-browser-launch-args` }, - DUPLICATE_TASK_KEY: (arg1: string) => { + DUPLICATE_TASK_KEY: (arg1: string[]) => { return errTemplate`\ - Warning: Multiple attempts to register the following task(s): ${arg1}. Only the last attempt will be registered.` + Warning: Multiple attempts to register the following task(s): + ${fmt.listItems(arg1, { color: fmt.highlight })} + + Only the last attempt will be registered.` }, INDETERMINATE_CI_BUILD_ID: (arg1: Record, arg2: string[]) => { return errTemplate`\ - You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId. + You passed the ${fmt.flag(`--group`)} or ${fmt.flag(`--parallel`)} flag but we could not automatically determine or generate a ciBuildId. - ${displayFlags(arg1, { + ${fmt.listFlags(arg1, { group: '--group', parallel: '--parallel', })} @@ -333,7 +338,7 @@ export const AllCypressErrors = { The ciBuildId is automatically detected if you are running Cypress in any of the these CI providers: - ${listItems(arg2)} + ${fmt.listItems(arg2)} Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually. @@ -341,9 +346,9 @@ export const AllCypressErrors = { }, RECORD_PARAMS_WITHOUT_RECORDING: (arg1: Record) => { return errTemplate`\ - You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag. + You passed the ${fmt.flag(`--ci-build-id`)}, ${fmt.flag(`--group`)}, ${fmt.flag(`--tag`)}, or ${fmt.flag(`--parallel`)} flag without also passing the ${fmt.flag(`--record`)} flag. - ${displayFlags(arg1, { + ${fmt.listFlags(arg1, { ciBuildId: '--ci-build-id', tags: '--tag', group: '--group', @@ -356,9 +361,9 @@ export const AllCypressErrors = { }, INCORRECT_CI_BUILD_ID_USAGE: (arg1: Record) => { return errTemplate`\ - You passed the --ci-build-id flag but did not provide either a --group or --parallel flag. + You passed the ${fmt.flag(`--ci-build-id`)} flag but did not provide either a ${fmt.flag(`--group`)} or ${fmt.flag(`--parallel`)} flag. - ${displayFlags(arg1, { + ${fmt.listFlags(arg1, { ciBuildId: '--ci-build-id', })} @@ -369,27 +374,27 @@ export const AllCypressErrors = { }, RECORD_KEY_MISSING: () => { return errTemplate`\ - You passed the --record flag but did not provide us your Record Key. + You passed the ${fmt.flag(`--record`)} flag but did not provide us your Record Key. You can pass us your Record Key like this: - ${chalk.blue('cypress run --record --key ')} + ${fmt.terminal(`cypress run --record --key `)} You can also set the key as an environment variable with the name CYPRESS_RECORD_KEY. https://on.cypress.io/how-do-i-record-runs` }, - CANNOT_RECORD_NO_PROJECT_ID: (arg1: string) => { + CANNOT_RECORD_NO_PROJECT_ID: (configFilePath: string) => { return errTemplate`\ - You passed the --record flag but this project has not been setup to record. + You passed the ${fmt.flag(`--record`)} flag but this project has not been setup to record. - This project is missing the 'projectId' inside of '${arg1}'. + This project is missing the ${`projectId`} inside of: ${fmt.path(configFilePath)} We cannot uniquely identify this project without this id. - You need to setup this project to record. This will generate a unique 'projectId'. + You need to setup this project to record. This will generate a unique projectId. - Alternatively if you omit the --record flag this project will run without recording. + Alternatively if you omit the ${fmt.flag(`--record`)} flag this project will run without recording. https://on.cypress.io/recording-project-runs` }, @@ -397,22 +402,23 @@ export const AllCypressErrors = { return errTemplate`\ This project has been configured to record runs on our Dashboard. - It currently has the projectId: ${chalk.green(arg1)} + It currently has the projectId: ${arg1} - You also provided your Record Key, but you did not pass the --record flag. + You also provided your Record Key, but you did not pass the ${fmt.flag(`--record`)} flag. This run will not be recorded. - If you meant to have this run recorded please additionally pass this flag. + If you meant to have this run recorded please additionally pass this flag: - ${'cypress run --record'} + ${fmt.terminal('cypress run --record')} If you don't want to record these runs, you can silence this warning: - ${chalk.yellow('cypress run --record false')} + ${fmt.terminal('cypress run --record false')} https://on.cypress.io/recording-project-runs` }, + // TODO: fix DASHBOARD_INVALID_RUN_REQUEST: (arg1: {message: string, errors: any, object: any}) => { return errTemplate`\ Recording this run failed because the request was invalid. @@ -427,6 +433,7 @@ export const AllCypressErrors = { ${JSON.stringify(arg1.object, null, 2)}` }, + // TODO: fix RECORDING_FROM_FORK_PR: () => { return errTemplate`\ Warning: It looks like you are trying to record this run from a forked PR. @@ -437,7 +444,7 @@ export const AllCypressErrors = { This error will not alter the exit code.` }, - DASHBOARD_CANNOT_UPLOAD_RESULTS: (arg1: string | Error) => { + DASHBOARD_CANNOT_UPLOAD_RESULTS: (apiErr: Error) => { return errTemplate`\ Warning: We encountered an error while uploading results from your run. @@ -445,9 +452,9 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${arg1}` + ${apiErr}` }, - DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: (arg1: string | Error) => { + DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: (apiErr: Error) => { return errTemplate`\ Warning: We encountered an error talking to our servers. @@ -455,11 +462,11 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${arg1}` + ${apiErr}` }, DASHBOARD_RECORD_KEY_NOT_VALID: (recordKey: string, projectId: string) => { return errTemplate`\ - Your Record Key ${chalk.yellow(recordKey)} is not valid with this project: ${chalk.blue(projectId)} + Your Record Key ${recordKey} is not valid with this projectId: ${fmt.highlightSecondary(projectId)} It may have been recently revoked by you or another user. @@ -467,11 +474,11 @@ export const AllCypressErrors = { https://on.cypress.io/dashboard/projects/${guard(projectId)}` }, - DASHBOARD_PROJECT_NOT_FOUND: (arg1: string, arg2: string) => { + DASHBOARD_PROJECT_NOT_FOUND: (projectId: string, configFileBaseName: string) => { return errTemplate`\ - We could not find a project with the ID: ${chalk.yellow(arg1)} + We could not find a Dashboard project with the projectId: ${projectId} - This projectId came from your '${arg2}' file or an environment variable. + This projectId came from your ${fmt.path(configFileBaseName)} file or an environment variable. Please log into the Dashboard and find your project. @@ -481,11 +488,11 @@ export const AllCypressErrors = { https://on.cypress.io/dashboard` }, - NO_PROJECT_ID: (arg1: string, arg2: string) => { - return errTemplate`Can't find 'projectId' in the '${arg1}' file for this project: ${chalk.blue(arg2)}` + NO_PROJECT_ID: (configFilePath: string) => { + return errTemplate`Can't find ${`projectId`} in the config file: ${fmt.path(configFilePath)}` }, - NO_PROJECT_FOUND_AT_PROJECT_ROOT: (arg1: string) => { - return errTemplate`Can't find project at the path: ${chalk.blue(arg1)}` + NO_PROJECT_FOUND_AT_PROJECT_ROOT: (projectRoot: string) => { + return errTemplate`Can't find a project at the path: ${fmt.path(projectRoot)}` }, CANNOT_FETCH_PROJECT_TOKEN: () => { return errTemplate`Can't find project's secret key.` @@ -500,29 +507,29 @@ export const AllCypressErrors = { return errTemplate`\ Can't run project because port is currently in use: ${arg1} - ${chalk.yellow('Assign a different port with the \'--port \' argument or shut down the other running process.')}` + Assign a different port with the ${fmt.flag(`--port `)} argument or shut down the other running process.')}` }, ERROR_READING_FILE: (filePath: string, err: Error) => { return errTemplate`\ - Error reading from: ${filePath} + Error reading from: ${fmt.path(filePath)} - ${details(err)}` + ${stackTrace(err)}` }, ERROR_WRITING_FILE: (filePath: string, err: Error) => { return errTemplate`\ - Error writing to: ${filePath} + Error writing to: ${fmt.path(filePath)} - ${details(err)}` + ${stackTrace(err)}` }, - NO_SPECS_FOUND: (arg1: string, arg2?: string | null) => { + NO_SPECS_FOUND: (folderPath: string, globPattern?: string | null) => { // no glob provided, searched all specs - if (!arg2) { + if (!globPattern) { return errTemplate`\ Can't run because no spec files were found. We searched for any files inside of this folder: - ${chalk.blue(arg1)}` + ${fmt.path(folderPath)}` } return errTemplate`\ @@ -530,11 +537,7 @@ export const AllCypressErrors = { We searched for any files matching this glob pattern: - ${chalk.blue(arg2)} - - Relative to the project root folder: - - ${chalk.blue(arg1)}` + ${path.join(fmt.path(folderPath), globPattern)}` }, RENDERER_CRASHED: () => { return errTemplate`\ @@ -559,31 +562,32 @@ export const AllCypressErrors = { AUTOMATION_SERVER_DISCONNECTED: () => { return errTemplate`The automation client disconnected. Cannot continue running tests.` }, - SUPPORT_FILE_NOT_FOUND: (arg1: string, arg2: string) => { + SUPPORT_FILE_NOT_FOUND: (supportFilePath: string) => { return errTemplate`\ - The support file is missing or invalid. + Your ${`supportFile`} file is missing or invalid: ${fmt.path(supportFilePath)} - Your ${'supportFile'} is set to ${arg1}, but either the file is missing or it's invalid. The \`supportFile\` must be a \`.js\`, \`.ts\`, \`.coffee\` file or be supported by your preprocessor plugin (if configured). + The supportFile must be a .js, .ts, .coffee file or be supported by your preprocessor plugin (if configured). - Correct your ${backtick(arg2)}, create the appropriate file, or set \`supportFile\` to \`false\` if a support file is not necessary for your project. + Fix your support file, or set supportFile to ${fmt.highlightSecondary(`false`)} if a support file is not necessary for your project. - Or you might have renamed the extension of your \`supportFile\` to \`.ts\`. If that's the case, restart the test runner. + If you have just renamed the extension of your supportFile, restart Cypress. Learn more at https://on.cypress.io/support-file-missing-or-invalid` }, - PLUGINS_FILE_ERROR: (arg1: string, arg2: Error) => { + PLUGINS_FILE_ERROR: (pluginsFilePath: string, err: Error) => { return errTemplate`\ - The plugins file is missing or invalid. + Your ${`pluginsFile`} file is missing or invalid: ${fmt.path(pluginsFilePath)} - Your \`pluginsFile\` is set to ${arg1}, but either the file is missing, it contains a syntax error, or threw an error when required. The \`pluginsFile\` must be a \`.js\`, \`.ts\`, or \`.coffee\` file. + It may have thrown an error when required, check the stack trace below. - Or you might have renamed the extension of your \`pluginsFile\`. If that's the case, restart the test runner. + Fix your plugins file, or set pluginsFile to ${fmt.highlightSecondary(`false`)} if a plugins file is not necessary for your project. - Please fix this, or set \`pluginsFile\` to \`false\` if a plugins file is not necessary for your project. + If you have just renamed the extension of your pluginsFile, restart Cypress. - ${details(arg2)} + ${stackTrace(err)} ` }, + // TODO: fix PLUGINS_DIDNT_EXPORT_FUNCTION: (arg1: string, arg2: any) => { return errTemplate`\ The \`pluginsFile\` must export a function with the following signature: @@ -600,39 +604,40 @@ export const AllCypressErrors = { It exported: - ${details(arg2)} + ${stackTrace(arg2)} ` }, PLUGINS_FUNCTION_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate`\ - The function exported by the plugins file threw an error. + The function exported by the plugins file threw an error: ${fmt.path(arg1)} - We invoked the function exported by ${arg1}, but it threw an error. - - ${details(arg2)} + ${stackTrace(arg2)} ` }, PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` - The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (${arg1}) + The following error was thrown by your plugins file: ${fmt.path(arg1)} + + We stopped running your tests because a plugin crashed. - ${details(arg2)} + ${stackTrace(arg2)} ` }, PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` - The following validation error was thrown by your plugins file (${arg1}). + The following validation error was thrown by your plugins file: ${fmt.path(arg1)} - ${details(arg2)} + ${stackTrace(arg2)} ` }, - BUNDLE_ERROR: (arg1: string, arg2: string) => { + // TODO: look at the listItem prefix + BUNDLE_ERROR: (filePath: string, arg2: string) => { // IF YOU MODIFY THIS MAKE SURE TO UPDATE // THE ERROR MESSAGE IN THE RUNNER TOO return errTemplate`\ Oops...we found an error preparing this test file: - ${chalk.blue(arg1)} + ${fmt.listItem(filePath, { prefix: '> ' })} The error was: @@ -646,19 +651,22 @@ export const AllCypressErrors = { Fix the error in your code and re-run your tests.` // happens when there is an error in configuration file like "cypress.json" }, - SETTINGS_VALIDATION_ERROR: (arg1: string, arg2: string) => { + // TODO: should this be basename or absolute path? + // TODO: what should details be here? isn't it an error? + SETTINGS_VALIDATION_ERROR: (configFileBaseName: string, errMessage: string) => { return errTemplate`\ - We found an invalid value in the file: ${arg1} + We found an invalid value in the file: ${fmt.path(configFileBaseName)} - ${chalk.yellow(arg2)}` + ${errMessage}` // happens when there is an invalid config value is returned from the // project's plugins file like "cypress/plugins.index.js" }, + // TODO: should this be relative or absolute? PLUGINS_CONFIG_VALIDATION_ERROR: (arg1: string, arg2: string) => { let filePath = `${arg1}` return errTemplate`\ - An invalid configuration value returned from the plugins file: ${chalk.blue(filePath)} + An invalid configuration value returned from the plugins file: ${fmt.path(filePath)} ${chalk.yellow(arg2)}` // general configuration error not-specific to configuration or plugins files @@ -671,9 +679,9 @@ export const AllCypressErrors = { }, RENAMED_CONFIG_OPTION: (arg1: {name: string, newName: string}) => { return errTemplate`\ - The ${chalk.yellow(arg1.name)} configuration option you have supplied has been renamed. + The ${arg1.name} configuration option you have supplied has been renamed. - Please rename ${chalk.yellow(arg1.name)} to ${chalk.blue(arg1.newName)}` + Please rename ${arg1.name} to ${fmt.highlightSecondary(arg1.newName)}` }, CANNOT_CONNECT_BASE_URL: () => { return errTemplate`\ @@ -685,7 +693,7 @@ export const AllCypressErrors = { return errTemplate`\ Cypress could not verify that this server is running: - > ${chalk.blue(arg1)} + ${fmt.listItem(arg1, { prefix: '> ' })} This server has been configured as your \`baseUrl\`, and tests will likely fail if it is not running.` }, @@ -712,7 +720,7 @@ export const AllCypressErrors = { We searched for the reporter in these paths: - ${listItems(arg1.paths)} + ${fmt.listItems(arg1.paths)} The error we received was: @@ -725,23 +733,24 @@ export const AllCypressErrors = { return errTemplate`\ Could not find a Cypress configuration file, exiting. - We looked but did not find a default config file in this folder: ${chalk.blue(arg1)}` + We looked but did not find a default config file in this folder: ${fmt.path(arg1)}` // TODO: update with vetted cypress language }, - CONFIG_FILES_LANGUAGE_CONFLICT: (arg1: string, arg2: string, arg3: string) => { + // TODO: verify these are configBaseName and not configPath + CONFIG_FILES_LANGUAGE_CONFLICT: (projectRoot: string, configFileBaseName1: string, configFileBaseName2: string) => { return errTemplate` - There is both a ${backtick(arg2)} and a ${backtick(arg3)} at the location below: + There is both a ${configFileBaseName1} and a ${configFileBaseName2} at the location below: - ${arg1} + ${fmt.listItem(projectRoot, { prefix: '> ' })} Cypress does not know which one to read for config. Please remove one of the two and try again. ` }, - CONFIG_FILE_NOT_FOUND: (arg1: string, arg2: string) => { + CONFIG_FILE_NOT_FOUND: (configFileBaseName: string, projectRoot: string) => { return errTemplate`\ Could not find a Cypress configuration file, exiting. - We looked but did not find a ${chalk.blue(arg1)} file in this folder: ${chalk.blue(arg2)}` + We looked but did not find a ${fmt.path(configFileBaseName)} file in this folder: ${fmt.path(projectRoot)}` }, INVOKED_BINARY_OUTSIDE_NPM_MODULE: () => { return errTemplate`\ @@ -749,7 +758,7 @@ export const AllCypressErrors = { This is not the recommended approach, and Cypress may not work correctly. - Please install the 'cypress' NPM package and follow the instructions here: + Please install the ${`cypress`} NPM package and follow the instructions here: https://on.cypress.io/installing-cypress` }, @@ -840,7 +849,7 @@ export const AllCypressErrors = { A fixture file could not be found at any of the following paths: > ${arg1} - > ${arg1}{{extension}} + > ${arg1}.[ext] Cypress looked for these file extensions at the provided path: @@ -848,49 +857,47 @@ export const AllCypressErrors = { Provide a path to an existing fixture file.` }, - AUTH_COULD_NOT_LAUNCH_BROWSER: (arg1: string) => { + AUTH_COULD_NOT_LAUNCH_BROWSER: (loginUrl: string) => { return errTemplate`\ Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser: - ${arg1}` + ${fmt.url(loginUrl)}` }, AUTH_BROWSER_LAUNCHED: () => { return errTemplate`Check your browser to continue logging in.` }, - BAD_POLICY_WARNING: (arg1: string[]) => { + BAD_POLICY_WARNING: (policyKeys: string[]) => { return errTemplate`\ Cypress detected policy settings on your computer that may cause issues. The following policies were detected that may prevent Cypress from automating Chrome: - ${guard(arg1.map((line) => ` > ${line}`).join('\n'))} + ${fmt.listItems(policyKeys)} For more information, see https://on.cypress.io/bad-browser-policy` }, BAD_POLICY_WARNING_TOOLTIP: () => { return errTemplate`Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy` }, - EXTENSION_NOT_LOADED: (arg1: string, arg2: string) => { + EXTENSION_NOT_LOADED: (browserName: string, extensionPath: string) => { return errTemplate`\ - ${guard(arg1)} could not install the extension at path: - - > ${arg2} + ${guard(browserName)} could not install the extension at path: ${fmt.path(extensionPath)} Please verify that this is the path to a valid, unpacked WebExtension.` }, - COULD_NOT_FIND_SYSTEM_NODE: (arg1: string) => { + COULD_NOT_FIND_SYSTEM_NODE: (nodeVersion: string) => { return errTemplate`\ - \`nodeVersion\` is set to \`system\`, but Cypress could not find a usable Node executable on your PATH. + ${fmt.prop(`nodeVersion`)} is set to ${fmt.value(`system`)}, but Cypress could not find a usable Node executable on your PATH. Make sure that your Node executable exists and can be run by the current user. - Cypress will use the built-in Node version (v${arg1}) instead.` + Cypress will use the built-in Node version ${fmt.highlightSecondary(nodeVersion)} instead.` }, - INVALID_CYPRESS_INTERNAL_ENV: (arg1: string) => { + INVALID_CYPRESS_INTERNAL_ENV: (val: string) => { return errTemplate`\ We have detected an unknown or unsupported "CYPRESS_INTERNAL_ENV" value - ${chalk.yellow(arg1)} + ${fmt.listItem(val, { prefix: '> ' })} "CYPRESS_INTERNAL_ENV" is reserved and should only be used internally. @@ -907,9 +914,9 @@ export const AllCypressErrors = { The CDP port requested was ${guard(chalk.yellow(arg2))}. - Error details: + Error stackTrace: - ${details(arg3)}` + ${stackTrace(arg3)}` }, FIREFOX_COULD_NOT_CONNECT: (arg1: Error) => { return errTemplate`\ @@ -917,28 +924,28 @@ export const AllCypressErrors = { This usually indicates there was a problem opening the Firefox browser. - Error details: + Error stackTrace: - ${details(arg1)}` + ${stackTrace(arg1)}` }, CDP_COULD_NOT_RECONNECT: (arg1: Error) => { return errTemplate`\ There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser. - ${details(arg1)}` + ${stackTrace(arg1)}` }, CDP_RETRYING_CONNECTION: (attempt: string | number, browserType: string) => { return errTemplate`Still waiting to connect to ${guard(browserType)}, retrying in 1 second (attempt ${chalk.yellow(`${attempt}`)}/62)` }, UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: (arg1: string[], arg2: string[]) => { return errTemplate`\ - The \`launchOptions\` object returned by your plugin's \`before:browser:launch\` handler contained unexpected properties: + The ${`launchOptions`} object returned by your plugin's ${fmt.highlightSecondary(`before:browser:launch`)} handler contained unexpected properties: - ${listItems(arg1)} + ${fmt.listItems(arg1)} - \`launchOptions\` may only contain the properties: + launchOptions may only contain the properties: - ${listItems(arg2)} + ${fmt.listItems(arg2)} https://on.cypress.io/browser-launch-api` }, @@ -958,11 +965,11 @@ export const AllCypressErrors = { To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running. - ${details(arg2)}` + ${stackTrace(arg2)}` }, FOLDER_NOT_WRITABLE: (arg1: string) => { return errTemplate`\ - Folder ${arg1} is not writable. + Folder ${fmt.path(arg1)} is not writable. Writing to this directory is required by Cypress in order to store screenshots and videos. @@ -972,42 +979,44 @@ export const AllCypressErrors = { }, EXPERIMENTAL_SAMESITE_REMOVED: () => { return errTemplate`\ - The \`experimentalGetCookiesSameSite\` configuration option was removed in Cypress version \`5.0.0\`. Yielding the \`sameSite\` property is now the default behavior of the \`cy.cookie\` commands. + The ${`experimentalGetCookiesSameSite`} configuration option was removed in ${fmt.cypressVersion(`5.0.0`)}. Yielding the ${fmt.highlightSecondary(`sameSite`)} property is now the default behavior of the ${fmt.highlightSecondary(`cy.cookie`)} commands. You can safely remove this option from your config.` }, + // TODO: verify configFile is absolute path EXPERIMENTAL_COMPONENT_TESTING_REMOVED: (arg1: {configFile: string}) => { return errTemplate`\ - The ${'experimentalComponentTesting'} configuration option was removed in Cypress version \`7.0.0\`. Please remove this flag from ${arg1.configFile}. + The ${'experimentalComponentTesting'} configuration option was removed in ${fmt.cypressVersion(`7.0.0`)}. + + Please remove this flag from: ${fmt.path(arg1.configFile)} Cypress Component Testing is now a standalone command. You can now run your component tests with: - ${chalk.yellow(`\`cypress open-ct\``)} + ${fmt.terminal(`cypress open-ct`)} https://on.cypress.io/migration-guide` }, EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { return errTemplate`\ - The \`experimentalShadowDomSupport\` configuration option was removed in Cypress version \`5.2.0\`. It is no longer necessary when utilizing the \`includeShadowDom\` option. + The ${`experimentalShadowDomSupport`} configuration option was removed in ${fmt.cypressVersion(`5.2.0`)}. It is no longer necessary when utilizing the ${fmt.highlightSecondary(`includeShadowDom`)} option. You can safely remove this option from your config.` }, EXPERIMENTAL_NETWORK_STUBBING_REMOVED: () => { return errTemplate`\ - The \`experimentalNetworkStubbing\` configuration option was removed in Cypress version \`6.0.0\`. - It is no longer necessary for using \`cy.intercept()\` (formerly \`cy.route2()\`). + The ${`experimentalNetworkStubbing`} configuration option was removed in ${fmt.cypressVersion(`6.0.0`)}. - You can safely remove this option from your config.` + It is no longer necessary for using ${fmt.highlightSecondary(`cy.intercept()`)}. You can safely remove this option from your config.` }, EXPERIMENTAL_RUN_EVENTS_REMOVED: () => { return errTemplate`\ - The \`experimentalRunEvents\` configuration option was removed in Cypress version \`6.7.0\`. It is no longer necessary when listening to run events in the plugins file. + The ${`experimentalRunEvents`} configuration option was removed in ${fmt.cypressVersion(`6.7.0`)}. It is no longer necessary when listening to run events in the plugins file. You can safely remove this option from your config.` }, FIREFOX_GC_INTERVAL_REMOVED: () => { return errTemplate`\ - The \`firefoxGcInterval\` configuration option was removed in Cypress version \`8.0.0\`. It was introduced to work around a bug in Firefox 79 and below. + The ${`firefoxGcInterval`} configuration option was removed in ${fmt.cypressVersion(`8.0.0`)}. It was introduced to work around a bug in Firefox 79 and below. Since Cypress no longer supports Firefox 85 and below in Cypress 8, this option was removed. @@ -1015,9 +1024,9 @@ export const AllCypressErrors = { }, INCOMPATIBLE_PLUGIN_RETRIES: (arg1: string) => { return errTemplate`\ - We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at ${arg1}. + We've detected that the incompatible plugin ${`cypress-plugin-retries`} is installed at ${fmt.path(arg1)}. - Test retries is now supported in Cypress version \`5.0.0\`. + Test retries is now supported in ${fmt.cypressVersion(`5.0.0`)}. Remove the plugin from your dependencies to silence this warning. @@ -1025,19 +1034,22 @@ export const AllCypressErrors = { ` }, INVALID_CONFIG_OPTION: (arg1: string[]) => { + const phrase = arg1.length > 1 ? 'options are' : 'option is' + return errTemplate`\ - ${arg1.map((arg) => `\`${arg}\` is not a valid configuration option`).join('\n')} + The following configuration ${guard(phrase)} invalid: + ${fmt.listItems(arg1)} https://on.cypress.io/configuration ` }, PLUGINS_RUN_EVENT_ERROR: (arg1: string, arg2: Error) => { return errTemplate`\ - An error was thrown in your plugins file while executing the handler for the '${chalk.blue(arg1)}' event. + An error was thrown in your plugins file while executing the handler for the ${arg1} event. The error we received was: - ${details(arg2)}` + ${stackTrace(arg2)}` }, CT_NO_DEV_START_EVENT: (arg1: string) => { const pluginsFilePath = arg1 ? @@ -1064,7 +1076,7 @@ export const AllCypressErrors = { return errTemplate`\ Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. - As of Cypress version \`9.0.0\` the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. + As of ${fmt.cypressVersion(`9.0.0`)} the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. Please remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. ` @@ -1073,7 +1085,7 @@ export const AllCypressErrors = { return errTemplate`\ Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. - As of Cypress version \`9.0.0\` the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. + As of ${fmt.cypressVersion(`9.0.0`)} the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. When ${backtick(arg1.name)} is set to ${backtick(arg1.value)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. diff --git a/packages/errors/test/support/utils.ts b/packages/errors/test/support/utils.ts index 92053185ad19..340583c2e027 100644 --- a/packages/errors/test/support/utils.ts +++ b/packages/errors/test/support/utils.ts @@ -10,7 +10,7 @@ app.on('window-all-closed', () => { }) -const HEIGHT = 550 +const HEIGHT = 600 const WIDTH = 1200 const EXT = '.png' const debug = Debug(isCi ? '*' : 'visualSnapshotErrors') diff --git a/packages/errors/test/unit/errors_spec.ts b/packages/errors/test/unit/errors_spec.ts index df22e5b4a508..ac9285965a53 100644 --- a/packages/errors/test/unit/errors_spec.ts +++ b/packages/errors/test/unit/errors_spec.ts @@ -1,12 +1,11 @@ +import 'sinon-chai' +import style from 'ansi-styles' +import chai, { expect } from 'chai' /* eslint-disable no-console */ import chalk from 'chalk' -import style from 'ansi-styles' -import snapshot from 'snap-shot-it' import sinon from 'sinon' -import 'sinon-chai' - +import snapshot from 'snap-shot-it' import * as errors from '../../src' -import chai, { expect } from 'chai' chai.use(require('@cypress/sinon-chai')) @@ -50,7 +49,7 @@ describe('lib/errors', () => { }) it('logs err.message', () => { - const err = errors.getError('NO_PROJECT_ID', 'cypress.json', 'foo/bar/baz') + const err = errors.getError('NO_PROJECT_ID', '/path/to/project/cypress.json') const ret = errors.log(err) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 395bfb54cf97..19f9e785888c 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -200,6 +200,14 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K }) } +const makeApiErr = () => { + const err = new Error('500 - "Internal Server Error"') + + err.name = 'StatusCodeError' + + return err +} + const makeErr = () => { const err = new Error('fail whale') @@ -209,8 +217,10 @@ const makeErr = () => { } describe('visual error templates', () => { - // testVisualErrors('CANNOT_TRASH_ASSETS', { - testVisualErrors('*', { + const errorType = (process.env.ERROR_TYPE || '*') as CypressErrorType + + // testVisualErrors('CANNOT_RECORD_NO_PROJECT_ID', { + testVisualErrors(errorType, { CANNOT_TRASH_ASSETS: () => { const err = makeErr() @@ -280,47 +290,100 @@ describe('visual error templates', () => { }, DASHBOARD_API_RESPONSE_FAILED_RETRYING: () => { return { - default: [{ tries: 3, delay: 5000, response: '500 server down' }], + default: [{ + tries: 3, + delay: 5000, + response: makeApiErr(), + }], + lastTry: [{ + tries: 1, + delay: 5000, + response: makeApiErr(), + }], } }, DASHBOARD_CANNOT_PROCEED_IN_PARALLEL: () => { return { - default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Invalid CI Build ID' }], + default: [{ + flags: { + ciBuildId: 'invalid', + group: 'foo', + }, + response: makeApiErr(), + }], } }, DASHBOARD_CANNOT_PROCEED_IN_SERIAL: () => { return { - default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Invalid CI Build ID' }], + default: [{ + flags: { + ciBuildId: 'invalid', + group: 'foo', + }, + response: makeApiErr(), + }], } }, DASHBOARD_UNKNOWN_INVALID_REQUEST: () => { return { - default: [{ flags: { ciBuildId: 'invalid', group: 'foo' }, response: 'Unexpected 500 Error' }], + default: [{ + flags: { + ciBuildId: 'invalid', + group: 'foo', + }, + response: makeApiErr(), + }], } }, DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: () => { return { - default: [{ props: { ciBuildId: 'invalid', group: 'foo' }, message: 'You have been warned' }], + default: [{ + props: { + ciBuildId: 'invalid', + group: 'foo', + }, + message: 'You have been warned', + }], } }, DASHBOARD_STALE_RUN: () => { return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + default: [{ + runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', + tag: '123', + group: 'foo', + parallel: true, + }], } }, DASHBOARD_ALREADY_COMPLETE: () => { return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + default: [{ + runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', + tag: '123', + group: 'foo', + parallel: true, + }], } }, DASHBOARD_PARALLEL_REQUIRED: () => { return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + default: [{ + runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', + tag: '123', + group: 'foo', + parallel: true, + }], } }, DASHBOARD_PARALLEL_DISALLOWED: () => { return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + default: [{ + runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', + tag: '123', + group: 'foo', + parallel: true, + }], } }, DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH: () => { @@ -345,7 +408,12 @@ describe('visual error templates', () => { }, DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE: () => { return { - default: [{ runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', tag: '123', group: 'foo', parallel: true }], + default: [{ + runUrl: 'https://dashboard.cypress.io/project/abcd/runs/1', + tag: '123', + group: 'foo', + parallel: true, + }], } }, DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { @@ -354,16 +422,19 @@ describe('visual error templates', () => { } }, DUPLICATE_TASK_KEY: () => { + const tasks = ['foo', 'bar', 'baz'] + return { - default: ['foo, bar, baz'], + default: [tasks], } }, INDETERMINATE_CI_BUILD_ID: () => { return { - default: [ - { group: 'foo', parallel: 'false' }, - ciProvider.detectableCiBuildIdProviders(), - ], + default: [{ + group: 'foo', + parallel: 'false', + }, + ciProvider.detectableCiBuildIdProviders()], } }, RECORD_PARAMS_WITHOUT_RECORDING: () => { @@ -388,12 +459,16 @@ describe('visual error templates', () => { }, PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION: () => { return { - default: ['abc123'], + default: ['project-id-123'], } }, DASHBOARD_INVALID_RUN_REQUEST: () => { return { - default: [{ message: 'Error on Run Request', errors: [], object: {} }], + default: [{ + message: 'Error on Run Request', + errors: [], + object: {}, + }], } }, RECORDING_FROM_FORK_PR: () => { @@ -402,14 +477,14 @@ describe('visual error templates', () => { } }, DASHBOARD_CANNOT_UPLOAD_RESULTS: () => { - const err = makeErr() + const err = makeApiErr() return { default: [err], } }, DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: () => { - const err = makeErr() + const err = makeApiErr() return { default: [err], @@ -417,17 +492,17 @@ describe('visual error templates', () => { }, DASHBOARD_RECORD_KEY_NOT_VALID: () => { return { - default: ['record-key-1234', 'projectId'], + default: ['record-key-123', 'project-id-123'], } }, DASHBOARD_PROJECT_NOT_FOUND: () => { return { - default: ['abc123', '/path/to/cypress.json'], + default: ['project-id-123', '/path/to/cypress.json'], } }, NO_PROJECT_ID: () => { return { - default: ['cypress.json', '/path/to/project'], + default: ['/path/to/project/cypress.json'], } }, NO_PROJECT_FOUND_AT_PROJECT_ROOT: () => { @@ -462,7 +537,7 @@ describe('visual error templates', () => { }, ERROR_WRITING_FILE: () => { return { - default: ['path/to/write/file.ts', makeErr()], + default: ['/path/to/write/file.ts', makeErr()], } }, NO_SPECS_FOUND: () => { @@ -483,7 +558,7 @@ describe('visual error templates', () => { }, SUPPORT_FILE_NOT_FOUND: () => { return { - default: ['/path/to/supportFile', '/path/to/cypress.json'], + default: ['/path/to/supportFile'], } }, PLUGINS_FILE_ERROR: () => { @@ -530,7 +605,7 @@ describe('visual error templates', () => { const err = makeErr() return { - default: ['/path/to/file', err.message], + default: ['cypress.json', err.message], } }, PLUGINS_CONFIG_VALIDATION_ERROR: () => { @@ -564,7 +639,12 @@ describe('visual error templates', () => { }, CANNOT_CONNECT_BASE_URL_RETRYING: () => { return { - default: [{ attempt: 0, baseUrl: 'http://localhost:3000', remaining: 60, delay: 500 }], + default: [{ + attempt: 0, + baseUrl: 'http://localhost:3000', + remaining: 60, + delay: 500, + }], } }, INVALID_REPORTER_NAME: () => { @@ -584,9 +664,9 @@ describe('visual error templates', () => { CONFIG_FILES_LANGUAGE_CONFLICT: () => { return { default: [ + '/path/to/project/root', 'cypress.config.js', 'cypress.config.ts', - '/path/to/project/root', ], } }, @@ -621,27 +701,47 @@ describe('visual error templates', () => { }, PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: () => { return { - default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], + default: [{ + link: 'https://on.cypress.io/set-up-billing', + planType: 'Test Plan', + usedTestsMessage: 'The limit is 500 free results', + }], } }, FREE_PLAN_EXCEEDS_MONTHLY_TESTS: () => { return { - default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], + default: [{ + link: 'https://on.cypress.io/set-up-billing', + planType: 'Test Plan', + usedTestsMessage: 'The limit is 500 free results', + }], } }, FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: () => { return { - default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results', gracePeriodMessage: 'Feb 1, 2022' }], + default: [{ + link: 'https://on.cypress.io/set-up-billing', + planType: 'Test Plan', + usedTestsMessage: 'The limit is 500 free results', + gracePeriodMessage: 'Feb 1, 2022', + }], } }, PLAN_EXCEEDS_MONTHLY_TESTS: () => { return { - default: [{ link: 'https://on.cypress.io/set-up-billing', planType: 'Test Plan', usedTestsMessage: 'The limit is 500 free results' }], + default: [{ + link: 'https://on.cypress.io/set-up-billing', + planType: 'Test Plan', + usedTestsMessage: 'The limit is 500 free results', + }], } }, FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: () => { return { - default: [{ link: 'https://on.cypress.io/set-up-billing', gracePeriodMessage: 'Feb 1, 2022' }], + default: [{ + link: 'https://on.cypress.io/set-up-billing', + gracePeriodMessage: 'Feb 1, 2022', + }], } }, PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { @@ -651,7 +751,10 @@ describe('visual error templates', () => { }, PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: () => { return { - default: [{ link: 'https://on.cypress.io/set-up-billing', gracePeriodMessage: 'Feb 1, 2022' }], + default: [{ + link: 'https://on.cypress.io/set-up-billing', + gracePeriodMessage: 'Feb 1, 2022', + }], } }, RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: () => { @@ -666,7 +769,7 @@ describe('visual error templates', () => { }, AUTH_COULD_NOT_LAUNCH_BROWSER: () => { return { - default: ['http://dashboard.cypress.io/login'], + default: ['https://dashboard.cypress.io/login'], } }, AUTH_BROWSER_LAUNCHED: () => { @@ -812,7 +915,8 @@ describe('visual error templates', () => { }, INVALID_CONFIG_OPTION: () => { return { - default: [['test', 'foo']], + default: [['foo']], + plural: [['foo', 'bar']], } }, UNSUPPORTED_BROWSER_VERSION: () => { diff --git a/packages/server/lib/api.js b/packages/server/lib/api.js index 17b06940bc0c..862ba91d2483 100644 --- a/packages/server/lib/api.js +++ b/packages/server/lib/api.js @@ -8,7 +8,6 @@ const humanInterval = require('human-interval') const { agent } = require('@packages/network') const pkg = require('@packages/root') const machineId = require('./util/machine_id') -const humanTime = require('./util/human_time') const errors = require('./errors') const { apiRoutes, onRoutes } = require('./util/routes') @@ -116,7 +115,7 @@ const retryWithBackoff = (fn) => { errors.warning( 'DASHBOARD_API_RESPONSE_FAILED_RETRYING', { - delay: humanTime.long(delay, false), + delay, tries: DELAYS.length - retryIndex, response: err, }, diff --git a/packages/server/lib/fixture.js b/packages/server/lib/fixture.js index 32d13557c608..ade9babd01d2 100644 --- a/packages/server/lib/fixture.js +++ b/packages/server/lib/fixture.js @@ -54,7 +54,8 @@ module.exports = { return glob(pattern, { nosort: true, nodir: true, - }).bind(this) + }) + .bind(this) .then(function (matches) { if (matches.length === 0) { const relativePath = path.relative('.', p) diff --git a/packages/server/lib/plugins/child/task.js b/packages/server/lib/plugins/child/task.js index 76590b5ab55f..b7c7f435d0a5 100644 --- a/packages/server/lib/plugins/child/task.js +++ b/packages/server/lib/plugins/child/task.js @@ -23,7 +23,7 @@ const merge = (prevEvents, events) => { const duplicates = _.intersection(_.keys(prevEvents), _.keys(events)) if (duplicates.length) { - require('@packages/errors').warning('DUPLICATE_TASK_KEY', duplicates.join(', ')) + require('@packages/errors').warning('DUPLICATE_TASK_KEY', duplicates) } return _.extend(prevEvents, events) diff --git a/packages/server/lib/project-base.ts b/packages/server/lib/project-base.ts index 722faa008bed..a22a5c526996 100644 --- a/packages/server/lib/project-base.ts +++ b/packages/server/lib/project-base.ts @@ -3,35 +3,35 @@ import Debug from 'debug' import EE from 'events' import _ from 'lodash' import path from 'path' - -import browsers from './browsers' -import pkg from '@packages/root' import { allowed } from '@packages/config' -import { ServerCt } from './server-ct' -import { SocketCt } from './socket-ct' -import { SocketE2E } from './socket-e2e' +import pkg from '@packages/root' import api from './api' import { Automation } from './automation' +import browsers from './browsers' import * as config from './config' import cwd from './cwd' import errors from './errors' -import Reporter from './reporter' +import plugins from './plugins' +import devServer from './plugins/dev-server' +import preprocessor from './plugins/preprocessor' import runEvents from './plugins/run_events' +import { checkSupportFile, getDefaultConfigFilePath } from './project_utils' +import Reporter from './reporter' import savedState from './saved_state' import scaffold from './scaffold' +import { ServerCt } from './server-ct' import { ServerE2E } from './server-e2e' -import system from './util/system' +import { SocketCt } from './socket-ct' +import { SocketE2E } from './socket-e2e' +import { SpecsStore } from './specs-store' import user from './user' import { ensureProp } from './util/class-helpers' import { fs } from './util/fs' import * as settings from './util/settings' -import plugins from './plugins' import specsUtil from './util/specs' +import system from './util/system' import Watchers from './watchers' -import devServer from './plugins/dev-server' -import preprocessor from './plugins/preprocessor' -import { SpecsStore } from './specs-store' -import { checkSupportFile, getDefaultConfigFilePath } from './project_utils' + import type { LaunchArgs } from './open_project' // Cannot just use RuntimeConfigOptions as is because some types are not complete. @@ -849,7 +849,7 @@ export class ProjectBase extends EE { return readSettings.projectId } - errors.throw('NO_PROJECT_ID', settings.configFile(this.options) || '', this.projectRoot) + errors.throw('NO_PROJECT_ID', settings.pathToConfigFile(this.projectRoot, this.options)) } async verifyExistence () { diff --git a/packages/server/lib/util/settings.ts b/packages/server/lib/util/settings.ts index 2368388ba434..dc2a64db6587 100644 --- a/packages/server/lib/util/settings.ts +++ b/packages/server/lib/util/settings.ts @@ -1,11 +1,10 @@ -import _ from 'lodash' import Promise from 'bluebird' +import Debug from 'debug' +import _ from 'lodash' import path from 'path' - import errors from '../errors' import { fs } from '../util/fs' import { requireAsync } from './require_async' -import Debug from 'debug' const debug = Debug('cypress:server:settings') @@ -72,7 +71,8 @@ const renameSupportFolder = (obj) => { } function _pathToFile (projectRoot, file) { - return path.isAbsolute(file) ? file : path.join(projectRoot, file) + return path.resolve(projectRoot, file) + // return path.isAbsolute(file) ? file : path.join(projectRoot, file) } function _err (type, file, err) { From a1deba7dd3df92dbd126b395fdcf7035b9299faf Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Sun, 6 Feb 2022 22:34:49 -0500 Subject: [PATCH 066/165] more error cleanup and standardization --- packages/errors/src/errTemplate.ts | 8 + packages/errors/src/errors.ts | 150 ++++++++++-------- .../test/unit/visualSnapshotErrors_spec.ts | 7 +- 3 files changed, 93 insertions(+), 72 deletions(-) diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index 4beb0110da99..4ef8fe2f9194 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -15,6 +15,7 @@ interface ListOptions { const theme = { blue: chalk.blueBright, gray: chalk.gray, + white: chalk.white, yellow: chalk.yellow, magenta: chalk.magenta, } @@ -22,12 +23,15 @@ const theme = { export const fmt = { meta: theme.gray, path: theme.blue, + code: theme.blue, url: theme.blue, flag: theme.magenta, prop: theme.yellow, value: theme.blue, highlight: theme.yellow, highlightSecondary: theme.magenta, + off: guard, + object, terminal, listItem, listItems, @@ -35,6 +39,10 @@ export const fmt = { cypressVersion, } +function object (obj: object) { + return theme.white(obj as any) +} + function terminal (str: string) { return guard(`${theme.gray('$')} ${theme.blue(str)}`) } diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index ddf26716f066..cbfe171fee28 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -196,7 +196,7 @@ _ Warning from Cypress Dashboard: ${arg1.message} Details: - ${JSON.stringify(arg1.props, null, 2)}` + ${fmt.object(arg1.props)}` }, DASHBOARD_STALE_RUN: (arg1: {runUrl: string, [key: string]: any}) => { return errTemplate`\ @@ -285,7 +285,7 @@ _ This machine sent the following parameters: - ${JSON.stringify(arg1.parameters, null, 2)} + ${fmt.object(arg1.parameters)} https://on.cypress.io/parallel-group-params-mismatch` }, @@ -310,9 +310,9 @@ _ // TODO: fix DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { return errTemplate`\ - Deprecation Warning: The \`before:browser:launch\` plugin event changed its signature in version \`4.0.0\` + Deprecation Warning: The ${`before:browser:launch`} plugin event changed its signature in ${fmt.cypressVersion(`4.0.0`)} - The \`before:browser:launch\` plugin event switched from yielding the second argument as an \`array\` of browser arguments to an options \`object\` with an \`args\` property. + The event switched from yielding the second argument as an ${fmt.highlightSecondary(`array`)} of browser arguments to an options ${fmt.highlightSecondary(`object`)} with an ${fmt.highlightSecondary(`args`)} property. We've detected that your code is still using the previous, deprecated interface signature. @@ -438,7 +438,7 @@ _ return errTemplate`\ Warning: It looks like you are trying to record this run from a forked PR. - The 'Record Key' is missing. Your CI provider is likely not passing private environment variables to builds from forks. + The ${`Record Key`} is missing. Your CI provider is likely not passing private environment variables to builds from forks. These results will not be recorded. @@ -588,23 +588,23 @@ _ ` }, // TODO: fix - PLUGINS_DIDNT_EXPORT_FUNCTION: (arg1: string, arg2: any) => { - return errTemplate`\ - The \`pluginsFile\` must export a function with the following signature: - - \`\`\` - module.exports = function (on, config) { + PLUGINS_DIDNT_EXPORT_FUNCTION: (pluginsFilePath: string, exported: any) => { + const code = stripIndent` + module.exports = (on, config) => { // configure plugins here - } - \`\`\` + }` - Learn more: https://on.cypress.io/plugins-api + return errTemplate`\ + The ${fmt.highlightSecondary(`pluginsFile`)} must export a function with the following signature: - We loaded the \`pluginsFile\` from: ${arg1} + ${fmt.meta(`// ${pluginsFilePath}`)} + ${fmt.code(code)} - It exported: + Instead it exported: - ${stackTrace(arg2)} + ${exported} + + Learn more: https://on.cypress.io/plugins-api ` }, PLUGINS_FUNCTION_ERROR: (arg1: string, arg2: string | Error) => { @@ -614,12 +614,13 @@ _ ${stackTrace(arg2)} ` }, + // TODO: use this for whimsical example PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` - The following error was thrown by your plugins file: ${fmt.path(arg1)} - We stopped running your tests because a plugin crashed. + The following error was thrown by your plugins file: ${fmt.path(arg1)} + ${stackTrace(arg2)} ` }, @@ -631,6 +632,7 @@ _ ` }, // TODO: look at the listItem prefix + // TODO: update the error message in the runner too BUNDLE_ERROR: (filePath: string, arg2: string) => { // IF YOU MODIFY THIS MAKE SURE TO UPDATE // THE ERROR MESSAGE IN THE RUNNER TOO @@ -671,11 +673,12 @@ _ ${chalk.yellow(arg2)}` // general configuration error not-specific to configuration or plugins files }, - CONFIG_VALIDATION_ERROR: (arg1: string) => { + // TODO: test this + CONFIG_VALIDATION_ERROR: (errMsg: string) => { return errTemplate`\ We found an invalid configuration value: - ${chalk.yellow(arg1)}` + ${errMsg}` }, RENAMED_CONFIG_OPTION: (arg1: {name: string, newName: string}) => { return errTemplate`\ @@ -695,8 +698,9 @@ _ ${fmt.listItem(arg1, { prefix: '> ' })} - This server has been configured as your \`baseUrl\`, and tests will likely fail if it is not running.` + This server has been configured as your ${fmt.highlightSecondary(`baseUrl`)}, and tests will likely fail if it is not running.` }, + // TODO: test this CANNOT_CONNECT_BASE_URL_RETRYING: (arg1: {attempt: number, baseUrl: string, remaining: number, delay: number}) => { switch (arg1.attempt) { case 1: @@ -705,7 +709,7 @@ _ > ${chalk.blue(arg1.baseUrl)} - We are verifying this server because it has been configured as your \`baseUrl\`. + We are verifying this server because it has been configured as your ${`baseUrl`}. Cypress automatically waits until your server is accessible before running tests. @@ -714,26 +718,26 @@ _ return errTemplate`${guard(displayRetriesRemaining(arg1.remaining))}` } }, + // TODO: test this INVALID_REPORTER_NAME: (arg1: {name: string, paths: string[], error: string}) => { return errTemplate`\ - Could not load reporter by name: ${chalk.yellow(arg1.name)} + Error loading the reporter: ${chalk.yellow(arg1.name)} We searched for the reporter in these paths: ${fmt.listItems(arg1.paths)} - The error we received was: + The error was: ${chalk.yellow(arg1.error)} Learn more at https://on.cypress.io/reporters` // TODO: update with vetted cypress language }, + // TODO: test this out NO_DEFAULT_CONFIG_FILE_FOUND: (arg1: string) => { return errTemplate`\ - Could not find a Cypress configuration file, exiting. - - We looked but did not find a default config file in this folder: ${fmt.path(arg1)}` + Could not find a Cypress configuration file in this folder: ${fmt.path(arg1)}` // TODO: update with vetted cypress language }, // TODO: verify these are configBaseName and not configPath @@ -748,7 +752,7 @@ _ }, CONFIG_FILE_NOT_FOUND: (configFileBaseName: string, projectRoot: string) => { return errTemplate`\ - Could not find a Cypress configuration file, exiting. + Could not find a Cypress configuration file. We looked but did not find a ${fmt.path(configFileBaseName)} file in this folder: ${fmt.path(projectRoot)}` }, @@ -887,7 +891,7 @@ _ }, COULD_NOT_FIND_SYSTEM_NODE: (nodeVersion: string) => { return errTemplate`\ - ${fmt.prop(`nodeVersion`)} is set to ${fmt.value(`system`)}, but Cypress could not find a usable Node executable on your PATH. + ${fmt.prop(`nodeVersion`)} is set to ${fmt.value(`system`)} but Cypress could not find a usable Node executable on your PATH. Make sure that your Node executable exists and can be run by the current user. @@ -895,37 +899,41 @@ _ }, INVALID_CYPRESS_INTERNAL_ENV: (val: string) => { return errTemplate`\ - We have detected an unknown or unsupported "CYPRESS_INTERNAL_ENV" value + We have detected an unknown or unsupported ${fmt.highlightSecondary(`CYPRESS_INTERNAL_ENV`)} value: - ${fmt.listItem(val, { prefix: '> ' })} + ${fmt.listItem(val, { prefix: '> ', color: fmt.highlight })} - "CYPRESS_INTERNAL_ENV" is reserved and should only be used internally. - - Do not modify the "CYPRESS_INTERNAL_ENV" value.` + CYPRESS_INTERNAL_ENV is reserved for internal use and cannot be modified.` }, - CDP_VERSION_TOO_OLD: (arg1: string, arg2: {major: number, minor: string | number}) => { - return errTemplate`A minimum CDP version of v${guard(arg1)} is required, but the current browser has ${guard(arg2.major !== 0 ? `v${arg2.major}.${arg2.minor}` : 'an older version')}.` + CDP_VERSION_TOO_OLD: (minimumVersion: string, currentVersion: {major: number, minor: string | number}) => { + const phrase = currentVersion.major !== 0 + ? fmt.highlightSecondary(`${currentVersion.major}.${currentVersion.minor}`) + : 'an older version' + + return errTemplate`A minimum CDP version of ${minimumVersion} is required, but the current browser has ${fmt.off(phrase)}.` }, - CDP_COULD_NOT_CONNECT: (arg1: string, arg2: string, arg3: Error) => { + CDP_COULD_NOT_CONNECT: (browserName: string, port: number, err: Error) => { + // we include a stack trace here because it may contain useful information + // to debug since this is an "uncontrolled" error even though it doesn't + // come from a user return errTemplate`\ Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds. - This usually indicates there was a problem opening the ${guard(arg1)} browser. - - The CDP port requested was ${guard(chalk.yellow(arg2))}. + This usually indicates there was a problem opening the ${guard(_.capitalize(browserName))} browser. - Error stackTrace: + The CDP port requested was ${`${port}`}. - ${stackTrace(arg3)}` + ${stackTrace(err)}` }, FIREFOX_COULD_NOT_CONNECT: (arg1: Error) => { + // we include a stack trace here because it may contain useful information + // to debug since this is an "uncontrolled" error even though it doesn't + // come from a user return errTemplate`\ Cypress failed to make a connection to Firefox. This usually indicates there was a problem opening the Firefox browser. - Error stackTrace: - ${stackTrace(arg1)}` }, CDP_COULD_NOT_RECONNECT: (arg1: Error) => { @@ -934,8 +942,8 @@ _ ${stackTrace(arg1)}` }, - CDP_RETRYING_CONNECTION: (attempt: string | number, browserType: string) => { - return errTemplate`Still waiting to connect to ${guard(browserType)}, retrying in 1 second (attempt ${chalk.yellow(`${attempt}`)}/62)` + CDP_RETRYING_CONNECTION: (attempt: string | number, browserName: string) => { + return errTemplate`Still waiting to connect to ${fmt.off(_.capitalize(browserName))}, retrying in 1 second ${fmt.meta(`(attempt ${attempt}/62)`)}` }, UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: (arg1: string[], arg2: string[]) => { return errTemplate`\ @@ -957,19 +965,19 @@ _ The error was: ${arg3}` }, - FIREFOX_MARIONETTE_FAILURE: (arg1: string, arg2: Error) => { + FIREFOX_MARIONETTE_FAILURE: (origin: string, err: Error) => { return errTemplate`\ Cypress could not connect to Firefox. - An unexpected error was received from Marionette ${guard(arg1)} + An unexpected error was received from Marionette: ${fmt.highlightSecondary(origin)} To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running. - ${stackTrace(arg2)}` + ${stackTrace(err)}` }, FOLDER_NOT_WRITABLE: (arg1: string) => { return errTemplate`\ - Folder ${fmt.path(arg1)} is not writable. + This folder is not writable: ${fmt.path(arg1)} Writing to this directory is required by Cypress in order to store screenshots and videos. @@ -979,7 +987,9 @@ _ }, EXPERIMENTAL_SAMESITE_REMOVED: () => { return errTemplate`\ - The ${`experimentalGetCookiesSameSite`} configuration option was removed in ${fmt.cypressVersion(`5.0.0`)}. Yielding the ${fmt.highlightSecondary(`sameSite`)} property is now the default behavior of the ${fmt.highlightSecondary(`cy.cookie`)} commands. + The ${`experimentalGetCookiesSameSite`} configuration option was removed in ${fmt.cypressVersion(`5.0.0`)}. + + Returning the ${fmt.highlightSecondary(`sameSite`)} property is now the default behavior of the ${fmt.highlightSecondary(`cy.cookie`)} commands. You can safely remove this option from your config.` }, @@ -990,7 +1000,7 @@ _ Please remove this flag from: ${fmt.path(arg1.configFile)} - Cypress Component Testing is now a standalone command. You can now run your component tests with: + Component Testing is now a standalone command. You can now run your component tests with: ${fmt.terminal(`cypress open-ct`)} @@ -1018,27 +1028,28 @@ _ return errTemplate`\ The ${`firefoxGcInterval`} configuration option was removed in ${fmt.cypressVersion(`8.0.0`)}. It was introduced to work around a bug in Firefox 79 and below. - Since Cypress no longer supports Firefox 85 and below in Cypress 8, this option was removed. + Since Cypress no longer supports Firefox 85 and below in Cypress ${fmt.cypressVersion(`8.0.0`)}, this option was removed. You can safely remove this option from your config.` }, INCOMPATIBLE_PLUGIN_RETRIES: (arg1: string) => { return errTemplate`\ - We've detected that the incompatible plugin ${`cypress-plugin-retries`} is installed at ${fmt.path(arg1)}. + We've detected that the incompatible plugin ${`cypress-plugin-retries`} is installed at: ${fmt.path(arg1)} - Test retries is now supported in ${fmt.cypressVersion(`5.0.0`)}. + Test retries is now natively supported in ${fmt.cypressVersion(`5.0.0`)}. Remove the plugin from your dependencies to silence this warning. https://on.cypress.io/test-retries ` }, + // TODO: test this INVALID_CONFIG_OPTION: (arg1: string[]) => { const phrase = arg1.length > 1 ? 'options are' : 'option is' return errTemplate`\ The following configuration ${guard(phrase)} invalid: - ${fmt.listItems(arg1)} + ${fmt.listItems(arg1, { color: fmt.highlight })} https://on.cypress.io/configuration ` @@ -1051,23 +1062,21 @@ _ ${stackTrace(arg2)}` }, - CT_NO_DEV_START_EVENT: (arg1: string) => { - const pluginsFilePath = arg1 ? - stripIndent`\ - You can find the \'pluginsFile\' at the following path: - - ${arg1} - ` : '' + CT_NO_DEV_START_EVENT: (pluginsFilePath: string) => { + const code = stripIndent` + module.exports = (on, config) => { + on('dev-server:start', () => startDevServer(...) + }` return errTemplate`\ - To run component-testing, cypress needs the \`dev-server:start\` event. + To run component-testing, cypress needs the ${`dev-server:start`} event. - Implement it by adding a \`on('dev-server:start', () => startDevServer())\` call in your pluginsFile. - ${pluginsFilePath} - Learn how to set up component testing: + Please implement it by adding this code to your ${fmt.highlightSecondary(`pluginsFile`)}. - https://on.cypress.io/component-testing - ` + ${fmt.meta(`// ${pluginsFilePath}`)} + ${fmt.code(code)} + + See https://on.cypress.io/component-testing for help on setting up component testing.` }, UNSUPPORTED_BROWSER_VERSION: (errorMsg: string) => { return errTemplate`${guard(errorMsg)}` @@ -1081,6 +1090,7 @@ _ Please remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. ` }, + // TODO: does this need to change since its a warning? NODE_VERSION_DEPRECATION_BUNDLED: (arg1: {name: string, value: any, configFile: string}) => { return errTemplate`\ Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 19f9e785888c..3120348d4791 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -570,7 +570,9 @@ describe('visual error templates', () => { }, PLUGINS_DIDNT_EXPORT_FUNCTION: () => { return { - default: ['/path/to/pluginsFile', () => 'some function'], + default: ['/path/to/pluginsFile', { some: 'object' }], + string: ['/path/to/pluginsFile', 'some string'], + array: ['/path/to/pluginsFile', ['some', 'array']], } }, PLUGINS_FUNCTION_ERROR: () => { @@ -807,7 +809,8 @@ describe('visual error templates', () => { }, CDP_VERSION_TOO_OLD: () => { return { - default: ['89', { major: 90, minor: 2 }], + default: ['1.3', { major: 1, minor: 2 }], + older: ['1.3', { major: 0, minor: 0 }], } }, CDP_COULD_NOT_CONNECT: () => { From 941ac214f27a1a8ccbefbfcfd144c45b96176622 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 7 Feb 2022 01:24:48 -0500 Subject: [PATCH 067/165] additional formatting of errors, code cleanup, refactoring --- packages/errors/src/errTemplate.ts | 61 +++++----- packages/errors/src/errors.ts | 104 +++++++++--------- .../test/unit/visualSnapshotErrors_spec.ts | 27 ++--- packages/server/lib/modes/record.js | 29 +++-- packages/server/lib/util/args.js | 8 +- 5 files changed, 121 insertions(+), 108 deletions(-) diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index 4ef8fe2f9194..fdbcf3caf24d 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -31,7 +31,7 @@ export const fmt = { highlight: theme.yellow, highlightSecondary: theme.magenta, off: guard, - object, + stringify, terminal, listItem, listItems, @@ -39,10 +39,6 @@ export const fmt = { cypressVersion, } -function object (obj: object) { - return theme.white(obj as any) -} - function terminal (str: string) { return guard(`${theme.gray('$')} ${theme.blue(str)}`) } @@ -110,12 +106,12 @@ export function backtick (val: string) { return new Backtick(val) } -export class Secondary { - constructor (readonly val: string | number) {} +export class Stringify { + constructor (readonly val: any) {} } -export function secondary (val: string) { - return new Secondary(val) +export function stringify (val: object) { + return new Stringify(val) } /** @@ -137,6 +133,32 @@ export function isErrorLike (err: any): err is SerializedError | Error { return err && typeof err === 'object' && Boolean('name' in err && 'message' in err) } +function jsonStringify (obj: object) { + return JSON.stringify(obj, null, 2) +} + +function isScalar (val: any): val is string | number | null | boolean { + return typeof val === 'string' || + typeof val === 'number' || + typeof val === 'boolean' || + val == null +} + +/** + * Formats the value passed in via details(), but does not color the value here, since it + * is printed separately in the console. + * + * @param val + * @returns + */ +function formatMsgDetails (val: any): string { + return isScalar(val) + ? `${val}` + : isErrorLike(val) + ? val.stack || val.message || val.name + : jsonStringify(val) +} + /** * Creates a consistently formatted object to return from the error call. * @@ -153,10 +175,6 @@ export const errTemplate = (strings: TemplateStringsArray, ...args: Array = [] let detailsSeen = false @@ -205,6 +210,8 @@ export const errTemplate = (strings: TemplateStringsArray, ...args: Array { + return _.isFinite(limit) + ? fmt.off(`The limit is ${fmt.highlight(`${limit}`)} ${usedTestsMessage} results.`) + : '' +} + export const warnIfExplicitCiBuildId = function (ciBuildId?: string | null) { if (!ciBuildId) { return '' @@ -79,7 +85,7 @@ export const AllCypressErrors = { }, CHROME_WEB_SECURITY_NOT_SUPPORTED: (browser: string) => { return errTemplate`\ - Your project has set the configuration option: ${fmt.prop(`chromeWebSecurity`)} to ${fmt.value(`false`)} + Your project has set the configuration option: ${fmt.prop(`chromeWebSecurity`)} to ${`false`} This option will not have an effect in ${guard(_.capitalize(browser))}. Tests that rely on web security being disabled will not run as expected.` }, @@ -196,7 +202,7 @@ _ Warning from Cypress Dashboard: ${arg1.message} Details: - ${fmt.object(arg1.props)}` + ${fmt.stringify(arg1.props)}` }, DASHBOARD_STALE_RUN: (arg1: {runUrl: string, [key: string]: any}) => { return errTemplate`\ @@ -285,7 +291,7 @@ _ This machine sent the following parameters: - ${fmt.object(arg1.parameters)} + ${fmt.stringify(arg1.parameters)} https://on.cypress.io/parallel-group-params-mismatch` }, @@ -380,7 +386,7 @@ _ ${fmt.terminal(`cypress run --record --key `)} - You can also set the key as an environment variable with the name CYPRESS_RECORD_KEY. + You can also set the key as an environment variable with the name: ${fmt.highlightSecondary(`CYPRESS_RECORD_KEY`)} https://on.cypress.io/how-do-i-record-runs` }, @@ -431,7 +437,7 @@ _ Request Sent: - ${JSON.stringify(arg1.object, null, 2)}` + ${JSON.stringify(arg1.stringify, null, 2)}` }, // TODO: fix RECORDING_FROM_FORK_PR: () => { @@ -478,7 +484,7 @@ _ return errTemplate`\ We could not find a Dashboard project with the projectId: ${projectId} - This projectId came from your ${fmt.path(configFileBaseName)} file or an environment variable. + This ${fmt.highlightSecondary(`projectId`)} came from your ${fmt.path(configFileBaseName)} file or an environment variable. Please log into the Dashboard and find your project. @@ -507,7 +513,7 @@ _ return errTemplate`\ Can't run project because port is currently in use: ${arg1} - Assign a different port with the ${fmt.flag(`--port `)} argument or shut down the other running process.')}` + Assign a different port with the ${fmt.flag(`--port `)} argument or shut down the other running process.` }, ERROR_READING_FILE: (filePath: string, err: Error) => { return errTemplate`\ @@ -591,25 +597,25 @@ _ PLUGINS_DIDNT_EXPORT_FUNCTION: (pluginsFilePath: string, exported: any) => { const code = stripIndent` module.exports = (on, config) => { - // configure plugins here + ${fmt.meta(`// configure plugins here`)} }` return errTemplate`\ - The ${fmt.highlightSecondary(`pluginsFile`)} must export a function with the following signature: + The ${`pluginsFile`} must export a function with the following signature: ${fmt.meta(`// ${pluginsFilePath}`)} ${fmt.code(code)} Instead it exported: - ${exported} + ${fmt.stringify(exported)} Learn more: https://on.cypress.io/plugins-api ` }, PLUGINS_FUNCTION_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate`\ - The function exported by the plugins file threw an error: ${fmt.path(arg1)} + The function exported by the ${`pluginsFile`} threw an error: ${fmt.path(arg1)} ${stackTrace(arg2)} ` @@ -624,9 +630,10 @@ _ ${stackTrace(arg2)} ` }, + // TODO: test this PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` - The following validation error was thrown by your plugins file: ${fmt.path(arg1)} + Your ${`pluginsFile`} threw a validation error: ${fmt.path(arg1)} ${stackTrace(arg2)} ` @@ -698,7 +705,7 @@ _ ${fmt.listItem(arg1, { prefix: '> ' })} - This server has been configured as your ${fmt.highlightSecondary(`baseUrl`)}, and tests will likely fail if it is not running.` + This server has been configured as your ${`baseUrl`}, and tests will likely fail if it is not running.` }, // TODO: test this CANNOT_CONNECT_BASE_URL_RETRYING: (arg1: {attempt: number, baseUrl: string, remaining: number, delay: number}) => { @@ -766,87 +773,87 @@ _ https://on.cypress.io/installing-cypress` }, - FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, usedTestsMessage: string, limit: number}) => { return errTemplate`\ - You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} + You've exceeded the limit of private test results under your free plan this month. ${getUsedTestsMessage(arg1.limit, arg1.usedTestsMessage)} To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, - FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, usedTestsMessage: string, gracePeriodMessage: string, limit: number}) => { return errTemplate`\ - You've exceeded the limit of private test results under your free plan this month. ${arg1.usedTestsMessage} + You've exceeded the limit of private test results under your free plan this month. ${getUsedTestsMessage(arg1.limit, arg1.usedTestsMessage)} - Your plan is now in a grace period, which means your tests will still be recorded until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue recording tests on the Cypress Dashboard in the future. + Your plan is now in a grace period, which means your tests will still be recorded until ${fmt.off(arg1.gracePeriodMessage)}. Please upgrade your plan to continue recording tests on the Cypress Dashboard in the future. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, - PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS: (arg1: {link: string, usedTestsMessage: string, limit: number}) => { return errTemplate`\ - You've exceeded the limit of private test results under your current billing plan this month. ${arg1.usedTestsMessage} + You've exceeded the limit of private test results under your current billing plan this month. ${getUsedTestsMessage(arg1.limit, arg1.usedTestsMessage)} To upgrade your account, please visit your billing to upgrade to another billing plan. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, - FREE_PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + FREE_PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, usedTestsMessage: string, limit: number}) => { return errTemplate`\ - You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} + You've exceeded the limit of test results under your free plan this month. ${getUsedTestsMessage(arg1.limit, arg1.usedTestsMessage)} To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, - FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, gracePeriodMessage: string}) => { + FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, usedTestsMessage: string, gracePeriodMessage: string, limit: number}) => { return errTemplate`\ - You've exceeded the limit of test results under your free plan this month. ${arg1.usedTestsMessage} + You've exceeded the limit of test results under your free plan this month. ${getUsedTestsMessage(arg1.limit, arg1.usedTestsMessage)} Your plan is now in a grace period, which means you will have the full benefits of your current plan until ${arg1.gracePeriodMessage}. Please visit your billing to upgrade your plan. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, - PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string}) => { + PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, limit: number}) => { return errTemplate`\ - You've exceeded the limit of test results under your ${arg1.planType} billing plan this month. ${arg1.usedTestsMessage} + You've exceeded the limit of test results under your ${arg1.planType} billing plan this month. ${getUsedTestsMessage(arg1.limit, arg1.usedTestsMessage)} To continue getting the full benefits of your current plan, please visit your billing to upgrade. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE: (arg1: {link: string, gracePeriodMessage: string}) => { return errTemplate`\ - Parallelization is not included under your free plan. + ${fmt.highlightSecondary(`Parallelization`)} is not included under your free plan. Your plan is now in a grace period, which means your tests will still run in parallel until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests in parallel in the future. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { return errTemplate`\ - Parallelization is not included under your current billing plan. + ${fmt.highlightSecondary(`Parallelization`)} is not included under your current billing plan. To run your tests in parallel, please visit your billing and upgrade to another plan with parallelization. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED: (arg1: {link: string, gracePeriodMessage: string}) => { return errTemplate`\ - Grouping is not included under your free plan. + ${fmt.highlightSecondary(`Grouping`)} is not included under your free plan. Your plan is now in a grace period, which means your tests will still run with groups until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests with groups in the future. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN: (arg1: {link: string}) => { return errTemplate`\ - Grouping is not included under your current billing plan. + ${fmt.highlightSecondary(`Grouping`)} is not included under your current billing plan. To run your tests with groups, please visit your billing and upgrade to another plan with grouping. - ${guard(arg1.link)}` + ${fmt.off(arg1.link)}` }, FIXTURE_NOT_FOUND: (arg1: string, arg2: string[]) => { return errTemplate`\ @@ -891,7 +898,7 @@ _ }, COULD_NOT_FIND_SYSTEM_NODE: (nodeVersion: string) => { return errTemplate`\ - ${fmt.prop(`nodeVersion`)} is set to ${fmt.value(`system`)} but Cypress could not find a usable Node executable on your PATH. + ${fmt.prop(`nodeVersion`)} is set to ${`system`} but Cypress could not find a usable Node executable on your ${fmt.highlightSecondary(`PATH`)}. Make sure that your Node executable exists and can be run by the current user. @@ -899,15 +906,13 @@ _ }, INVALID_CYPRESS_INTERNAL_ENV: (val: string) => { return errTemplate`\ - We have detected an unknown or unsupported ${fmt.highlightSecondary(`CYPRESS_INTERNAL_ENV`)} value: - - ${fmt.listItem(val, { prefix: '> ', color: fmt.highlight })} + We have detected an unknown or unsupported ${fmt.highlightSecondary(`CYPRESS_INTERNAL_ENV`)} value: ${val} CYPRESS_INTERNAL_ENV is reserved for internal use and cannot be modified.` }, CDP_VERSION_TOO_OLD: (minimumVersion: string, currentVersion: {major: number, minor: string | number}) => { const phrase = currentVersion.major !== 0 - ? fmt.highlightSecondary(`${currentVersion.major}.${currentVersion.minor}`) + ? fmt.highlight(`${currentVersion.major}.${currentVersion.minor}`) : 'an older version' return errTemplate`A minimum CDP version of ${minimumVersion} is required, but the current browser has ${fmt.off(phrase)}.` @@ -957,13 +962,14 @@ _ https://on.cypress.io/browser-launch-api` }, - COULD_NOT_PARSE_ARGUMENTS: (arg1: string, arg2: string, arg3: string) => { + // TODO: test this + COULD_NOT_PARSE_ARGUMENTS: (argName: string, argValue: string, errMsg: string) => { return errTemplate`\ - Cypress encountered an error while parsing the argument ${chalk.gray(arg1)} + Cypress encountered an error while parsing the argument: ${`--${argName}`} - You passed: ${arg2} + You passed: ${fmt.value(argValue)} - The error was: ${arg3}` + The error was: ${fmt.highlightSecondary(errMsg)}` }, FIREFOX_MARIONETTE_FAILURE: (origin: string, err: Error) => { return errTemplate`\ diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 3120348d4791..8717dde5463f 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -686,8 +686,8 @@ describe('visual error templates', () => { return { default: [{ link: 'https://dashboard.cypress.io/project/abcd', - planType: 'Test Plan', - usedTestsMessage: 'The limit is 500 free results', + limit: 500, + usedTestsMessage: 'test', }], } }, @@ -695,9 +695,9 @@ describe('visual error templates', () => { return { default: [{ link: 'https://dashboard.cypress.io/project/abcd', - planType: 'Grace Period Plan', - usedTestsMessage: 'The limit is 500 free results', - gracePeriodMessage: 'You are on a grace period for 20 days', + limit: 500, + usedTestsMessage: 'test', + gracePeriodMessage: 'the grace period ends', }], } }, @@ -705,8 +705,8 @@ describe('visual error templates', () => { return { default: [{ link: 'https://on.cypress.io/set-up-billing', - planType: 'Test Plan', - usedTestsMessage: 'The limit is 500 free results', + limit: 25000, + usedTestsMessage: 'private test', }], } }, @@ -714,8 +714,8 @@ describe('visual error templates', () => { return { default: [{ link: 'https://on.cypress.io/set-up-billing', - planType: 'Test Plan', - usedTestsMessage: 'The limit is 500 free results', + limit: 500, + usedTestsMessage: 'test', }], } }, @@ -723,8 +723,8 @@ describe('visual error templates', () => { return { default: [{ link: 'https://on.cypress.io/set-up-billing', - planType: 'Test Plan', - usedTestsMessage: 'The limit is 500 free results', + limit: 500, + usedTestsMessage: 'test', gracePeriodMessage: 'Feb 1, 2022', }], } @@ -733,8 +733,9 @@ describe('visual error templates', () => { return { default: [{ link: 'https://on.cypress.io/set-up-billing', - planType: 'Test Plan', - usedTestsMessage: 'The limit is 500 free results', + planType: 'Sprout', + limit: 25000, + usedTestsMessage: 'test', }], } }, diff --git a/packages/server/lib/modes/record.js b/packages/server/lib/modes/record.js index 4d5defda475d..1a652d224fdb 100644 --- a/packages/server/lib/modes/record.js +++ b/packages/server/lib/modes/record.js @@ -253,14 +253,6 @@ const getCommitFromGitOrCi = (git) => { }) } -const usedTestsMessage = (limit, phrase) => { - if (_.isFinite(limit)) { - return `The limit is ${chalk.blue(limit)} ${phrase} results.` - } - - return '' -} - const billingLink = (orgId) => { if (orgId) { return `https://on.cypress.io/dashboard/organizations/${orgId}/billing` @@ -346,13 +338,15 @@ const createRun = Promise.method((options = {}) => { switch (warning.code) { case 'FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS': return errors.warning('FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS', { - usedTestsMessage: usedTestsMessage(warning.limit, 'private test'), + limit: warning.limit, + usedTestsMessage: 'private test', gracePeriodMessage: gracePeriodMessage(warning.gracePeriodEnds), link: billingLink(warning.orgId), }) case 'FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS': return errors.warning('FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS', { - usedTestsMessage: usedTestsMessage(warning.limit, 'test'), + limit: warning.limit, + usedTestsMessage: 'test', gracePeriodMessage: gracePeriodMessage(warning.gracePeriodEnds), link: billingLink(warning.orgId), }) @@ -364,19 +358,22 @@ const createRun = Promise.method((options = {}) => { case 'FREE_PLAN_EXCEEDS_MONTHLY_TESTS_V2': return errors.warning('PLAN_EXCEEDS_MONTHLY_TESTS', { planType: 'free', - usedTestsMessage: usedTestsMessage(warning.limit, 'test'), + limit: warning.limit, + usedTestsMessage: 'test', link: billingLink(warning.orgId), }) case 'PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS': return errors.warning('PLAN_EXCEEDS_MONTHLY_TESTS', { planType: 'current', - usedTestsMessage: usedTestsMessage(warning.limit, 'private test'), + limit: warning.limit, + usedTestsMessage: 'private test', link: billingLink(warning.orgId), }) case 'PAID_PLAN_EXCEEDS_MONTHLY_TESTS': return errors.warning('PLAN_EXCEEDS_MONTHLY_TESTS', { planType: 'current', - usedTestsMessage: usedTestsMessage(warning.limit, 'test'), + limit: warning.limit, + usedTestsMessage: 'test', link: billingLink(warning.orgId), }) case 'PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED': @@ -416,12 +413,14 @@ const createRun = Promise.method((options = {}) => { switch (code) { case 'FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS': return errors.throw('FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS', { - usedTestsMessage: usedTestsMessage(limit, 'private test'), + limit, + usedTestsMessage: 'private test', link: billingLink(orgId), }) case 'FREE_PLAN_EXCEEDS_MONTHLY_TESTS': return errors.throw('FREE_PLAN_EXCEEDS_MONTHLY_TESTS', { - usedTestsMessage: usedTestsMessage(limit, 'test'), + limit, + usedTestsMessage: 'test', link: billingLink(orgId), }) case 'PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN': diff --git a/packages/server/lib/util/args.js b/packages/server/lib/util/args.js index f4bc20f530c5..bc53d12f7242 100644 --- a/packages/server/lib/util/args.js +++ b/packages/server/lib/util/args.js @@ -163,8 +163,8 @@ const JSONOrCoerce = (str) => { return coerceUtil.coerce(str) } -const sanitizeAndConvertNestedArgs = (str, argname) => { - la(is.unemptyString(argname), 'missing config argname to be parsed') +const sanitizeAndConvertNestedArgs = (str, argName) => { + la(is.unemptyString(argName), 'missing config argName to be parsed') try { if (typeof str === 'object') { @@ -197,10 +197,10 @@ const sanitizeAndConvertNestedArgs = (str, argname) => { .mapValues(JSONOrCoerce) .value() } catch (err) { - debug('could not pass config %s value %s', argname, str) + debug('could not pass config %s value %s', argName, str) debug('error %o', err) - return errors.throw('COULD_NOT_PARSE_ARGUMENTS', argname, str, err.message) + return errors.throw('COULD_NOT_PARSE_ARGUMENTS', argName, str, err.message) } } From f6a00c0fbcb38207408aae7ba9ec19a3485b6bb5 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 7 Feb 2022 02:21:36 -0500 Subject: [PATCH 068/165] update ansi colors to match terminal colors --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 8717dde5463f..5152bbff5043 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -62,9 +62,13 @@ const snapshotErrorConsoleLogs = function (errorFileName: string) { // if the sanitized snapshot matches, let's save the ANSI colors converted into HTML const html = termToHtml .strings(logs, termToHtml.themes.dark.name) - .split('color:#00A').join('color:#2472c7') // replace blue colors - .split('color:#00A').join('color:#e05561') // replace red colors + .split('color:#55F').join('color:#4ec4ff') // replace blueBright colors + .split('color:#A00').join('color:#e05561') // replace red colors .split('color:#A50').join('color:#e5e510') // replace yellow colors + .split('color:#555').join('color:#4f5666') // replace gray colors + .split('color:#eee').join('color:#e6e6e6') // replace white colors + .split('color:#A0A').join('color:#c062de') // replace magenta colors + .split('color:#F5F').join('color:#de73ff') // replace magentaBright colors .split('"Courier New", ').join('"Courier Prime", ') // replace font .split(' -
Check your browser to continue logging in.
+    
Check your browser to continue logging in.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html b/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html index 01814a1cbb30..334c12975f53 100644 --- a/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html +++ b/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html @@ -34,7 +34,7 @@ -
Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser:
-
-http://dashboard.cypress.io/login
+    
Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser:
+
+https://dashboard.cypress.io/login
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/AUTOMATION_SERVER_DISCONNECTED.html b/packages/errors/__snapshot-html__/AUTOMATION_SERVER_DISCONNECTED.html index f2ac614f8c08..170d59bacaab 100644 --- a/packages/errors/__snapshot-html__/AUTOMATION_SERVER_DISCONNECTED.html +++ b/packages/errors/__snapshot-html__/AUTOMATION_SERVER_DISCONNECTED.html @@ -34,5 +34,5 @@ -
The automation client disconnected. Cannot continue running tests.
+    
The automation client disconnected. Cannot continue running tests.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BAD_POLICY_WARNING.html b/packages/errors/__snapshot-html__/BAD_POLICY_WARNING.html index 0b3b4d7aab3f..617575adb031 100644 --- a/packages/errors/__snapshot-html__/BAD_POLICY_WARNING.html +++ b/packages/errors/__snapshot-html__/BAD_POLICY_WARNING.html @@ -34,12 +34,12 @@ -
Cypress detected policy settings on your computer that may cause issues.
-
-The following policies were detected that may prevent Cypress from automating Chrome:
-
- > HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\ProxyServer
- > HKEY_CURRENT_USER\Software\Policies\Google\Chromium\ExtensionSettings
-
-For more information, see https://on.cypress.io/bad-browser-policy
+    
Cypress detected policy settings on your computer that may cause issues.
+
+The following policies were detected that may prevent Cypress from automating Chrome:
+
+ - HKEY_LOCAL_MACHINE\Software\Policies\Google\Chrome\ProxyServer
+ - HKEY_CURRENT_USER\Software\Policies\Google\Chromium\ExtensionSettings
+
+For more information, see https://on.cypress.io/bad-browser-policy
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BAD_POLICY_WARNING_TOOLTIP.html b/packages/errors/__snapshot-html__/BAD_POLICY_WARNING_TOOLTIP.html index e7be5ad7fae0..a0c6acb1901f 100644 --- a/packages/errors/__snapshot-html__/BAD_POLICY_WARNING_TOOLTIP.html +++ b/packages/errors/__snapshot-html__/BAD_POLICY_WARNING_TOOLTIP.html @@ -34,5 +34,5 @@ -
Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy
+    
Cypress detected policy settings on your computer that may cause issues with using this browser. For more information, see https://on.cypress.io/bad-browser-policy
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html index 054d0291119d..cf2946ed304c 100644 --- a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html @@ -34,33 +34,33 @@ -
Can't run because you've entered an invalid browser name.
-
-Browser: canary was not found on your system or is not supported by Cypress.
-
-Cypress supports the following browsers:
-- chrome
-- chromium
-- edge
-- electron
-- firefox
-
-You can also use a custom browser: https://on.cypress.io/customize-browsers
-
-Available browsers found on your system are:
-- chrome
-- chromium
-- chrome:beta
-- chrome:canary
-- firefox
-- firefox:dev
-- firefox:nightly
-- edge
-- edge:canary
-- edge:beta
-- edge:dev
-
-Note: In Cypress version 4.0.0, Canary must be launched as `chrome:canary`, not `canary`.
-
-See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.
+    
Can't run because you've entered an invalid browser name.
+
+Browser: canary was not found on your system or is not supported by Cypress.
+
+Cypress supports the following browsers:
+ - electron
+ - chrome
+ - chromium
+ - edge
+ - firefox
+
+You can also use a custom browser: https://on.cypress.io/customize-browsers
+
+Available browsers found on your system are:
+ - chrome
+ - chromium
+ - chrome:beta
+ - chrome:canary
+ - firefox
+ - firefox:dev
+ - firefox:nightly
+ - edge
+ - edge:canary
+ - edge:beta
+ - edge:dev
+
+Note: In Cypress version 4.0.0, Canary must be launched as chrome:canary, not canary.
+
+See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html index 61ad1d148204..3cb6658c2617 100644 --- a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html @@ -34,29 +34,29 @@ -
Can't run because you've entered an invalid browser name.
-
-Browser: invalid-browser was not found on your system or is not supported by Cypress.
-
-Cypress supports the following browsers:
-- chrome
-- chromium
-- edge
-- electron
-- firefox
-
-You can also use a custom browser: https://on.cypress.io/customize-browsers
-
-Available browsers found on your system are:
-- chrome
-- chromium
-- chrome:beta
-- chrome:canary
-- firefox
-- firefox:dev
-- firefox:nightly
-- edge
-- edge:canary
-- edge:beta
-- edge:dev
+    
Can't run because you've entered an invalid browser name.
+
+Browser: invalid-browser was not found on your system or is not supported by Cypress.
+
+Cypress supports the following browsers:
+ - electron
+ - chrome
+ - chromium
+ - edge
+ - firefox
+
+You can also use a custom browser: https://on.cypress.io/customize-browsers
+
+Available browsers found on your system are:
+ - chrome
+ - chromium
+ - chrome:beta
+ - chrome:canary
+ - firefox
+ - firefox:dev
+ - firefox:nightly
+ - edge
+ - edge:canary
+ - edge:beta
+ - edge:dev
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html index 576599e0f35f..f018567f8b70 100644 --- a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html @@ -34,9 +34,9 @@ -
We could not identify a known browser at the path you provided: /path/does/not/exist
-
-The output from the command we ran was:
+    
We could not identify a known browser at the path you provided: /path/does/not/exist
+
+The output from the command we ran was:
 
-fail whale
+fail whale
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BUNDLE_ERROR.html b/packages/errors/__snapshot-html__/BUNDLE_ERROR.html index 334f9c72095f..e64518f7a92c 100644 --- a/packages/errors/__snapshot-html__/BUNDLE_ERROR.html +++ b/packages/errors/__snapshot-html__/BUNDLE_ERROR.html @@ -34,18 +34,18 @@ -
Oops...we found an error preparing this test file:
-
-  /path/to/file
-
-The error was:
-
-fail whale
-
-This occurred while Cypress was compiling and bundling your test code. This is usually caused by:
-
-- A missing file or dependency
-- A syntax error in the file or one of its dependencies
-
-Fix the error in your code and re-run your tests.
+    
Oops...we found an error preparing this test file:
+
+  > /path/to/file
+
+The error was:
+
+fail whale
+
+This occurred while Cypress was compiling and bundling your test code. This is usually caused by:
+
+- A missing file or dependency
+- A syntax error in the file or one of its dependencies
+
+Fix the error in your code and re-run your tests.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL.html b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL.html index 6fb05776bd7b..2708c07753de 100644 --- a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL.html +++ b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL.html @@ -34,7 +34,7 @@ -
Cypress failed to verify that your server is running.
-
-Please start this server and then run Cypress again.
+    
Cypress failed to verify that your server is running.
+
+Please start this server and then run Cypress again.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING - retrying.html b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING - retrying.html new file mode 100644 index 000000000000..d0d57752b810 --- /dev/null +++ b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING - retrying.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
We will try connecting to it 60 more times...
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html index e2ec0ae2c1b8..f58e0d1408e0 100644 --- a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html +++ b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html @@ -34,5 +34,13 @@ -
We will try connecting to it 60 more times...
+    
Cypress could not verify that this server is running:
+
+  > http://localhost:3000
+
+We are verifying this server because it has been configured as your baseUrl.
+
+Cypress automatically waits until your server is accessible before running tests.
+
+We will try connecting to it 60 more times...
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_WARNING.html b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_WARNING.html index 77da743609e4..555dfe4a1431 100644 --- a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_WARNING.html +++ b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_WARNING.html @@ -34,9 +34,9 @@ -
Cypress could not verify that this server is running:
-
-  > http://localhost:3000
-
-This server has been configured as your `baseUrl`, and tests will likely fail if it is not running.
+    
Cypress could not verify that this server is running:
+
+  > http://localhost:3000
+
+This server has been configured as your baseUrl, and tests will likely fail if it is not running.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CREATE_PROJECT_TOKEN.html b/packages/errors/__snapshot-html__/CANNOT_CREATE_PROJECT_TOKEN.html index 6c9b5af2909b..59fd38858196 100644 --- a/packages/errors/__snapshot-html__/CANNOT_CREATE_PROJECT_TOKEN.html +++ b/packages/errors/__snapshot-html__/CANNOT_CREATE_PROJECT_TOKEN.html @@ -34,5 +34,5 @@ -
Can't create project's secret key.
+    
Can't create project's secret key.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_FETCH_PROJECT_TOKEN.html b/packages/errors/__snapshot-html__/CANNOT_FETCH_PROJECT_TOKEN.html index 4a7521ffc1c2..c4df34380cab 100644 --- a/packages/errors/__snapshot-html__/CANNOT_FETCH_PROJECT_TOKEN.html +++ b/packages/errors/__snapshot-html__/CANNOT_FETCH_PROJECT_TOKEN.html @@ -34,5 +34,5 @@ -
Can't find project's secret key.
+    
Can't find project's secret key.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html b/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html index ffecb223ca6b..29ebdf2bcdbc 100644 --- a/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html +++ b/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html @@ -34,15 +34,15 @@ -
You passed the --record flag but this project has not been setup to record.
-
-This project is missing the 'projectId' inside of '/path/to/cypress.json'.
-
-We cannot uniquely identify this project without this id.
-
-You need to setup this project to record. This will generate a unique 'projectId'.
-
-Alternatively if you omit the --record flag this project will run without recording.
-
-https://on.cypress.io/recording-project-runs
+    
You passed the --record flag but this project has not been setup to record.
+
+This project is missing the projectId inside of: /path/to/cypress.json
+
+We cannot uniquely identify this project without this id.
+
+You need to setup this project to record. This will generate a unique projectId.
+
+Alternatively if you omit the --record flag this project will run without recording.
+
+https://on.cypress.io/recording-project-runs
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html index 54b2c9dfc352..44f3469253d9 100644 --- a/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html +++ b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html @@ -34,11 +34,11 @@ -
Warning: We failed to remove old browser profiles from previous runs.
-
-This error will not alter the exit code.
+    
Warning: We failed to remove old browser profiles from previous runs.
+
+This error will not alter the exit code.
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CANNOT_REMOVE_OLD_BROWSER_PROFILES (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CANNOT_REMOVE_OLD_BROWSER_PROFILES (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html index 7c0dea27a7b8..3ddd725d21ab 100644 --- a/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html +++ b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html @@ -34,11 +34,11 @@ -
Warning: We failed to trash the existing run results.
-
-This error will not alter the exit code.
+    
Warning: We failed to trash the existing run results.
+
+This error will not alter the exit code.
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CANNOT_TRASH_ASSETS (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CANNOT_TRASH_ASSETS (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html index c1a7d1c0a6a6..2970c79ecdd2 100644 --- a/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html @@ -34,15 +34,13 @@ -
Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds.
-
-This usually indicates there was a problem opening the chrome browser.
-
-The CDP port requested was 2345.
-
-Error details:
+    
Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds.
+
+This usually indicates there was a problem opening the Chrome browser.
+
+The CDP port requested was 2345.
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CDP_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CDP_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html index be41e13a511b..76c512ae60cf 100644 --- a/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html @@ -34,9 +34,9 @@ -
There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser.
+    
There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser.
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CDP_COULD_NOT_RECONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CDP_COULD_NOT_RECONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html b/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html index 6ca121df29eb..2075198d652e 100644 --- a/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html +++ b/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html @@ -34,5 +34,5 @@ -
Still waiting to connect to chrome, retrying in 1 second (attempt 1/62)
+    
Still waiting to connect to Chrome, retrying in 1 second (attempt 1/62)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD - older.html b/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD - older.html new file mode 100644 index 000000000000..401ca5b3a607 --- /dev/null +++ b/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD - older.html @@ -0,0 +1,38 @@ + + + + + + + + + + + +
A minimum CDP version of 1.3 is required, but the current browser has an older version.
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD.html b/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD.html index f4c345cf408f..b2f35a65c725 100644 --- a/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD.html +++ b/packages/errors/__snapshot-html__/CDP_VERSION_TOO_OLD.html @@ -34,5 +34,5 @@ -
A minimum CDP version of v89 is required, but the current browser has v90.2.
+    
A minimum CDP version of 1.3 is required, but the current browser has 1.2.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html b/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html index 6e888b47b152..0939fc1992c9 100644 --- a/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html +++ b/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html @@ -34,7 +34,7 @@ -
Your project has set the configuration option: `chromeWebSecurity: false`
-
-This option will not have an effect in Electron. Tests that rely on web security being disabled will not run as expected.
+    
Your project has set the configuration option: chromeWebSecurity to false
+
+This option will not have an effect in Firefox. Tests that rely on web security being disabled will not run as expected.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_FILES_LANGUAGE_CONFLICT.html b/packages/errors/__snapshot-html__/CONFIG_FILES_LANGUAGE_CONFLICT.html index 750fd109b9e0..dd29bf076f3c 100644 --- a/packages/errors/__snapshot-html__/CONFIG_FILES_LANGUAGE_CONFLICT.html +++ b/packages/errors/__snapshot-html__/CONFIG_FILES_LANGUAGE_CONFLICT.html @@ -34,10 +34,10 @@ -
There is both a `cypress.config.ts` and a `/path/to/project/root` at the location below:
-
-cypress.config.js
-
-Cypress does not know which one to read for config. Please remove one of the two and try again.
-
+    
There is both a cypress.config.js and a cypress.config.ts at the location below:
+
+  > /path/to/project/root
+
+Cypress does not know which one to read for config. Please remove one of the two and try again.
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html index 2415364ba2f0..42192a78108d 100644 --- a/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html @@ -34,7 +34,7 @@ -
Could not find a Cypress configuration file, exiting.
-
-We looked but did not find a cypress.json file in this folder: /path/to/project/root
+    
Could not find a Cypress configuration file.
+
+We looked but did not find a cypress.json file in this folder: /path/to/project/root
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html index df27d789ad0c..1d210f776571 100644 --- a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html @@ -34,7 +34,7 @@ -
We found an invalid configuration value:
-
-fail whale
+    
We found an invalid configuration value:
+
+fail whale
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html b/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html index cf5fa6e35787..e4a3a86eef5b 100644 --- a/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html +++ b/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html @@ -34,9 +34,9 @@ -
`nodeVersion` is set to `system`, but Cypress could not find a usable Node executable on your PATH.
-
-Make sure that your Node executable exists and can be run by the current user.
-
-Cypress will use the built-in Node version (v16.2.1) instead.
+    
nodeVersion is set to system but Cypress could not find a usable Node executable on your PATH.
+
+Make sure that your Node executable exists and can be run by the current user.
+
+Cypress will use the built-in Node version 16.2.1 instead.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html b/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html index a794086f9ba0..37326e3d23cd 100644 --- a/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html +++ b/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html @@ -34,9 +34,9 @@ -
Cypress encountered an error while parsing the argument spec
-
-You passed: 1
-
-The error was: spec must be a string or comma-separated list
+    
Cypress encountered an error while parsing the argument: --spec
+
+You passed: 1
+
+The error was: spec must be a string or comma-separated list
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html b/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html index 68a5fb364441..4e5ba627f82e 100644 --- a/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html +++ b/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html @@ -34,15 +34,14 @@ -
To run component-testing, cypress needs the `dev-server:start` event.
-
-Implement it by adding a `on('dev-server:start', () => startDevServer())` call in your pluginsFile.
-You can find the 'pluginsFile' at the following path:
-
-/path/to/plugins/file.js
-
-Learn how to set up component testing:
-
-https://on.cypress.io/component-testing
-
+    
To run component-testing, cypress needs the dev-server:start event.
+
+Please implement it by adding this code to your pluginsFile.
+
+// /path/to/plugins/file.js
+module.exports = (on, config) => {
+  on('dev-server:start', () => startDevServer(...)
+}
+
+See https://on.cypress.io/component-testing for help on setting up component testing.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html b/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html index ec658ad9b729..68fbf4b453b1 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html @@ -34,14 +34,14 @@ -
The run you are attempting to access is already complete and will not accept new groups.
-
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
-
-When a run finishes all of its groups, it waits for a configurable set of time before finally completing. You must add more groups during that time period.
-
-The --group flag you passed was: foo
-The --parallel flag you passed was: true
-
-https://on.cypress.io/already-complete
+    
The run you are attempting to access is already complete and will not accept new groups.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+When a run finishes all of its groups, it waits for a configurable set of time before finally completing. You must add more groups during that time period.
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+https://on.cypress.io/already-complete
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING - lastTry.html b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING - lastTry.html new file mode 100644 index 000000000000..5ab475c8471c --- /dev/null +++ b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING - lastTry.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
We encountered an unexpected error talking to our servers.
+
+We will retry 1 more time in 5 seconds...
+
+The server's response was:
+
+StatusCodeError: 500 - "Internal Server Error"
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html index d9db94ba2b68..5997a735a72f 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html @@ -34,11 +34,11 @@ -
We encountered an unexpected error talking to our servers.
-
-We will retry 3 more times in 5000...
-
-The server's response was:
-
-500 server down
+    
We encountered an unexpected error talking to our servers.
+
+We will retry 3 more times in 5 seconds...
+
+The server's response was:
+
+StatusCodeError: 500 - "Internal Server Error"
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANCEL_SKIPPED_SPEC.html b/packages/errors/__snapshot-html__/DASHBOARD_CANCEL_SKIPPED_SPEC.html index 29121e39a9b3..6d454e3c40b4 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_CANCEL_SKIPPED_SPEC.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANCEL_SKIPPED_SPEC.html @@ -34,6 +34,6 @@ -

-  This spec and its tests were skipped because the run has been canceled.
+    

+  This spec and its tests were skipped because the run has been canceled.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html index 0a675bf5b7eb..8377b5e917b6 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html @@ -34,11 +34,11 @@ -
Warning: We encountered an error talking to our servers.
-
-This run will not be recorded.
-
-This error will not alter the exit code.
-
-Error: fail whale
+    
Warning: We encountered an error talking to our servers.
+
+This run will not be recorded.
+
+This error will not alter the exit code.
+
+StatusCodeError: 500 - "Internal Server Error"
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html index 556c942117cc..19b8f5e4755a 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html @@ -34,14 +34,14 @@ -
We encountered an unexpected error talking to our servers.
-
-Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
-
-The --group flag you passed was: foo
-The --ciBuildId flag you passed was: invalid
-
-The server's response was:
-
-Invalid CI Build ID
+    
We encountered an unexpected error talking to our servers.
+
+Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
+
+The --group flag you passed was: foo
+The --ciBuildId flag you passed was: invalid
+
+The server's response was:
+
+StatusCodeError: 500 - "Internal Server Error"
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html index 28fc227487c6..e2da0eaa4a32 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html @@ -34,12 +34,12 @@ -
We encountered an unexpected error talking to our servers.
-
-The --group flag you passed was: foo
-The --ciBuildId flag you passed was: invalid
-
-The server's response was:
-
-Invalid CI Build ID
+    
We encountered an unexpected error talking to our servers.
+
+The --group flag you passed was: foo
+The --ciBuildId flag you passed was: invalid
+
+The server's response was:
+
+StatusCodeError: 500 - "Internal Server Error"
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html index e9d5e67b88ed..8ab343c06edb 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html @@ -34,11 +34,11 @@ -
Warning: We encountered an error while uploading results from your run.
-
-These results will not be recorded.
-
-This error will not alter the exit code.
-
-Error: fail whale
+    
Warning: We encountered an error while uploading results from your run.
+
+These results will not be recorded.
+
+This error will not alter the exit code.
+
+StatusCodeError: 500 - "Internal Server Error"
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html b/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html index 707fc131d29d..03d21e3df134 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html @@ -34,15 +34,22 @@ -
Recording this run failed because the request was invalid.
-
-Error on Run Request
-
-Errors:
-
-[]
-
-Request Sent:
-
-{}
+    
Recording this run failed because the request was invalid.
+
+request should follow postRunRequest@2.0.0 schema
+
+Errors:
+
+[
+  "data.commit has additional properties",
+  "data.ci.buildNumber is required"
+]
+
+Request Sent:
+
+{
+  "foo": "foo",
+  "bar": "bar",
+  "baz": "baz"
+}
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html index 4535d42bfc2f..96dc50d29d2e 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html @@ -34,14 +34,14 @@ -
You passed the --parallel flag, but this run group was originally created without the --parallel flag.
-
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
-
-The --group flag you passed was: foo
-The --parallel flag you passed was: true
-
-You can not use the --parallel flag with this group.
-
-https://on.cypress.io/parallel-disallowed
+    
You passed the --parallel flag, but this run group was originally created without the --parallel flag.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+You can not use the --parallel flag with this group.
+
+https://on.cypress.io/parallel-disallowed
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html index f7b5aff4196d..992876f18b88 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html @@ -34,31 +34,31 @@ -
You passed the --parallel flag, but we do not parallelize tests across different environments.
-
-This machine is sending different environment parameters than the first machine that started this parallel run.
-
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
-
-In order to run in parallel mode each machine must send identical environment parameters such as:
-
-- specs
-- osName
-- osVersion
-- browserName
-- browserVersion (major)
-
-This machine sent the following parameters:
-
-{
-  "osName": "darwin",
-  "osVersion": "v1",
-  "browserName": "Electron",
-  "browserVersion": "59.1.2.3",
-  "specs": [
-    "cypress/integration/app_spec.js"
-  ]
-}
-
-https://on.cypress.io/parallel-group-params-mismatch
+    
You passed the --parallel flag, but we do not parallelize tests across different environments.
+
+This machine is sending different environment parameters than the first machine that started this parallel run.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+In order to run in parallel mode each machine must send identical environment parameters such as:
+
+ - specs
+ - osName
+ - osVersion
+ - browserName
+ - browserVersion (major)
+
+This machine sent the following parameters:
+
+{
+  "osName": "darwin",
+  "osVersion": "v1",
+  "browserName": "Electron",
+  "browserVersion": "59.1.2.3",
+  "specs": [
+    "cypress/integration/app_spec.js"
+  ]
+}
+
+https://on.cypress.io/parallel-group-params-mismatch
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html index 78bb8bb4402b..127e48121ae8 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html @@ -34,14 +34,14 @@ -
You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag.
-
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
-
-The --group flag you passed was: foo
-The --parallel flag you passed was: true
-
-You must use the --parallel flag with this group.
-
-https://on.cypress.io/parallel-required
+    
You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+You must use the --parallel flag with this group.
+
+https://on.cypress.io/parallel-required
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html b/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html index a63f6d5f91d2..3f9612b72bdf 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html @@ -34,15 +34,15 @@ -
We could not find a project with the ID: abc123
-
-This projectId came from your '/path/to/cypress.json' file or an environment variable.
-
-Please log into the Dashboard and find your project.
-
-We will list the correct projectId in the 'Settings' tab.
-
-Alternatively, you can create a new project using the Desktop Application.
-
-https://on.cypress.io/dashboard
+    
We could not find a Dashboard project with the projectId: project-id-123
+
+This projectId came from your /path/to/cypress.json file or an environment variable.
+
+Please log into the Dashboard and find your project.
+
+We will list the correct projectId in the 'Settings' tab.
+
+Alternatively, you can create a new project using the Desktop Application.
+
+https://on.cypress.io/dashboard
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html b/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html index 923712640a7e..c7d02879a890 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html @@ -34,11 +34,11 @@ -
Your Record Key record-key-1234 is not valid with this project: projectId
-
-It may have been recently revoked by you or another user.
-
-Please log into the Dashboard to see the valid record keys.
-
-https://on.cypress.io/dashboard/projects/projectId
+    
Your Record Key record-key-123 is not valid with this projectId: project-id-123
+
+It may have been recently revoked by you or another user.
+
+Please log into the Dashboard to see the valid record keys.
+
+https://on.cypress.io/dashboard/projects/project-id-123
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html b/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html index 4affb21085b8..33b4180accf9 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html @@ -34,14 +34,14 @@ -
You passed the --group flag, but this group name has already been used for this run.
-
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
-
-The --group flag you passed was: foo
-The --parallel flag you passed was: true
-
-If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name.
-
-https://on.cypress.io/run-group-name-not-unique
+    
You passed the --group flag, but this group name has already been used for this run.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name.
+
+https://on.cypress.io/run-group-name-not-unique
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html b/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html index a656b852c31a..82411f629741 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html @@ -34,14 +34,14 @@ -
You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago.
-
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
-
-You cannot parallelize a run that has been complete for that long.
-
-The --group flag you passed was: foo
-The --parallel flag you passed was: true
-
-https://on.cypress.io/stale-run
+    
You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago.
+
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+
+You cannot parallelize a run that has been complete for that long.
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: true
+
+https://on.cypress.io/stale-run
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html index 577198165ed1..41bad8435799 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html @@ -34,11 +34,13 @@ -
Warning from Cypress Dashboard: You have been warned
-
-Details:
-{
-  "ciBuildId": "invalid",
-  "group": "foo"
-}
+    
Warning from Cypress Dashboard: You are almost out of time
+
+Details:
+{
+  "code": "OUT_OF_TIME",
+  "name": "OutOfTime",
+  "hadTime": 1000,
+  "spentTime": 999
+}
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html index d206c65e9adb..6c948cd4023a 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html @@ -34,14 +34,14 @@ -
We encountered an unexpected error talking to our servers.
-
-There is likely something wrong with the request.
-
-The --group flag you passed was: foo
-The --ciBuildId flag you passed was: invalid
-
-The server's response was:
-
-Unexpected 500 Error
+    
We encountered an unexpected error talking to our servers.
+
+There is likely something wrong with the request.
+
+The --group flag you passed was: foo
+The --ciBuildId flag you passed was: invalid
+
+The server's response was:
+
+StatusCodeError: 500 - "Internal Server Error"
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html b/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html index f7bc7e68beb5..401d55c00cb5 100644 --- a/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html +++ b/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html @@ -34,11 +34,11 @@ -
Deprecation Warning: The `before:browser:launch` plugin event changed its signature in version `4.0.0`
-
-The `before:browser:launch` plugin event switched from yielding the second argument as an `array` of browser arguments to an options `object` with an `args` property.
-
-We've detected that your code is still using the previous, deprecated interface signature.
-
-This code will not work in a future version of Cypress. Please see the upgrade guide: https://on.cypress.io/deprecated-before-browser-launch-args
+    
Deprecation Warning: The before:browser:launch plugin event changed its signature in Cypress version 4.0.0
+
+The event switched from yielding the second argument as an array of browser arguments to an options object with an args property.
+
+We've detected that your code is still using the previous, deprecated interface signature.
+
+This code will not work in a future version of Cypress. Please see the upgrade guide: https://on.cypress.io/deprecated-before-browser-launch-args
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DUPLICATE_TASK_KEY.html b/packages/errors/__snapshot-html__/DUPLICATE_TASK_KEY.html index 2b5f01a41def..b06b8fa527ca 100644 --- a/packages/errors/__snapshot-html__/DUPLICATE_TASK_KEY.html +++ b/packages/errors/__snapshot-html__/DUPLICATE_TASK_KEY.html @@ -34,5 +34,11 @@ -
Warning: Multiple attempts to register the following task(s): foo, bar, baz. Only the last attempt will be registered.
+    
Warning: Multiple attempts to register the following task(s):
+
+ - foo
+ - bar
+ - baz
+
+Only the last attempt will be registered.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_READING_FILE.html b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html index 042709d384bf..d0498dca20fa 100644 --- a/packages/errors/__snapshot-html__/ERROR_READING_FILE.html +++ b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html @@ -34,9 +34,9 @@ -
Error reading from: /path/to/read/file.ts
+    
Error reading from: /path/to/read/file.ts
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at ERROR_READING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at ERROR_READING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html index 49a8415e599e..816e24b9cdf1 100644 --- a/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html +++ b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html @@ -34,9 +34,9 @@ -
Error writing to: path/to/write/file.ts
+    
Error writing to: /path/to/write/file.ts
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at ERROR_WRITING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at ERROR_WRITING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html index 61ba4c65e439..645603204de6 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html @@ -34,11 +34,13 @@ -
The experimentalComponentTesting configuration option was removed in Cypress version `7.0.0`. Please remove this flag from /path/to/configFile.json.
-
-Cypress Component Testing is now a standalone command. You can now run your component tests with:
-
-`cypress open-ct`
-
-https://on.cypress.io/migration-guide
+    
The experimentalComponentTesting configuration option was removed in Cypress version 7.0.0.
+
+Please remove this flag from: /path/to/configFile.json
+
+Component Testing is now a standalone command. You can now run your component tests with:
+
+  $ cypress open-ct
+
+https://on.cypress.io/migration-guide
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html index d1273dfe4b71..20a5ebf473bf 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html @@ -34,8 +34,7 @@ -
The `experimentalNetworkStubbing` configuration option was removed in Cypress version `6.0.0`.
-It is no longer necessary for using `cy.intercept()` (formerly `cy.route2()`).
-
-You can safely remove this option from your config.
+    
The experimentalNetworkStubbing configuration option was removed in Cypress version 6.0.0.
+
+It is no longer necessary for using cy.intercept(). You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html index 355b138d91c9..b2c96c6dc4e2 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html @@ -34,7 +34,7 @@ -
The `experimentalRunEvents` configuration option was removed in Cypress version `6.7.0`. It is no longer necessary when listening to run events in the plugins file.
-
-You can safely remove this option from your config.
+    
The experimentalRunEvents configuration option was removed in Cypress version 6.7.0. It is no longer necessary when listening to run events in the plugins file.
+
+You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html index f16bbd21b097..fed97205421d 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html @@ -34,7 +34,9 @@ -
The `experimentalGetCookiesSameSite` configuration option was removed in Cypress version `5.0.0`. Yielding the `sameSite` property is now the default behavior of the `cy.cookie` commands.
-
-You can safely remove this option from your config.
+    
The experimentalGetCookiesSameSite configuration option was removed in Cypress version 5.0.0.
+
+Returning the sameSite property is now the default behavior of the cy.cookie commands.
+
+You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html index 44708092c63c..759de1a5be3f 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html @@ -34,7 +34,7 @@ -
The `experimentalShadowDomSupport` configuration option was removed in Cypress version `5.2.0`. It is no longer necessary when utilizing the `includeShadowDom` option.
-
-You can safely remove this option from your config.
+    
The experimentalShadowDomSupport configuration option was removed in Cypress version 5.2.0. It is no longer necessary when utilizing the includeShadowDom option.
+
+You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html b/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html index 18e918f34b96..923846012aca 100644 --- a/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html +++ b/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html @@ -34,9 +34,7 @@ -
Electron could not install the extension at path:
-
-> /path/to/extension
-
-Please verify that this is the path to a valid, unpacked WebExtension.
+    
Electron could not install the extension at path: /path/to/extension
+
+Please verify that this is the path to a valid, unpacked WebExtension.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html index 9fc77bbe6397..90ac362a0644 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html +++ b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html @@ -34,13 +34,11 @@ -
Cypress failed to make a connection to Firefox.
-
-This usually indicates there was a problem opening the Firefox browser.
-
-Error details:
+    
Cypress failed to make a connection to Firefox.
+
+This usually indicates there was a problem opening the Firefox browser.
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at FIREFOX_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at FIREFOX_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html b/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html index fba58f5cfe58..7ec17dcd33b5 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html +++ b/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html @@ -34,9 +34,9 @@ -
The `firefoxGcInterval` configuration option was removed in Cypress version `8.0.0`. It was introduced to work around a bug in Firefox 79 and below.
-
-Since Cypress no longer supports Firefox 85 and below in Cypress 8, this option was removed.
-
-You can safely remove this option from your config.
+    
The firefoxGcInterval configuration option was removed in Cypress version 8.0.0. It was introduced to work around a bug in Firefox 79 and below.
+
+Since Cypress no longer supports Firefox 85 and below in Cypress Cypress version 8.0.0, this option was removed.
+
+You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html index 119de9d111e0..101d37ab5484 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html +++ b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html @@ -34,13 +34,13 @@ -
Cypress could not connect to Firefox.
-
-An unexpected error was received from Marionette connection
-
-To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running.
+    
Cypress could not connect to Firefox.
+
+An unexpected error was received from Marionette: connection
+
+To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running.
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at FIREFOX_MARIONETTE_FAILURE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at FIREFOX_MARIONETTE_FAILURE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html b/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html index d95064c2c419..6de2c6a39215 100644 --- a/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html @@ -34,14 +34,14 @@ -
A fixture file could not be found at any of the following paths:
-
-> file
-> file{{extension}}
-
-Cypress looked for these file extensions at the provided path:
-
-> js, ts, json
-
-Provide a path to an existing fixture file.
+    
A fixture file could not be found at any of the following paths:
+
+  > file
+  > file.[ext]
+
+Cypress looked for these file extensions at the provided path:
+
+  > js, ts, json
+
+Provide a path to an existing fixture file.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html b/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html index 831fd4cc73ca..c58265328b29 100644 --- a/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html +++ b/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html @@ -34,11 +34,11 @@ -
Folder /path/to/folder is not writable.
-
-Writing to this directory is required by Cypress in order to store screenshots and videos.
-
-Enable write permissions to this directory to ensure screenshots and videos are stored.
-
-If you don't require screenshots or videos to be stored you can safely ignore this warning.
+    
This folder is not writable: /path/to/folder
+
+Writing to this directory is required by Cypress in order to store screenshots and videos.
+
+Enable write permissions to this directory to ensure screenshots and videos are stored.
+
+If you don't require screenshots or videos to be stored you can safely ignore this warning.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html b/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html index 01124be756e7..d87c629efd39 100644 --- a/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html +++ b/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html @@ -34,9 +34,9 @@ -
You've exceeded the limit of private test results under your free plan this month. The limit is 500 free results
-
-To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan.
-
-https://dashboard.cypress.io/project/abcd
+    
You've exceeded the limit of private test results under your free plan this month. The limit is 500 test results.
+
+To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan.
+
+https://dashboard.cypress.io/project/abcd
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_TESTS.html b/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_TESTS.html index 0831de83e81f..d2d1d791177d 100644 --- a/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_TESTS.html +++ b/packages/errors/__snapshot-html__/FREE_PLAN_EXCEEDS_MONTHLY_TESTS.html @@ -34,9 +34,9 @@ -
You've exceeded the limit of test results under your free plan this month. The limit is 500 free results
-
-To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan.
-
-https://on.cypress.io/set-up-billing
+    
You've exceeded the limit of test results under your free plan this month. The limit is 500 test results.
+
+To continue recording tests this month you must upgrade your account. Please visit your billing to upgrade to another billing plan.
+
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS.html b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS.html index 5b691f602af4..f828ad90b96c 100644 --- a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS.html +++ b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_PRIVATE_TESTS.html @@ -34,9 +34,9 @@ -
You've exceeded the limit of private test results under your free plan this month. The limit is 500 free results
-
-Your plan is now in a grace period, which means your tests will still be recorded until You are on a grace period for 20 days. Please upgrade your plan to continue recording tests on the Cypress Dashboard in the future.
-
-https://dashboard.cypress.io/project/abcd
+    
You've exceeded the limit of private test results under your free plan this month. The limit is 500 test results.
+
+Your plan is now in a grace period, which means your tests will still be recorded until the grace period ends. Please upgrade your plan to continue recording tests on the Cypress Dashboard in the future.
+
+https://dashboard.cypress.io/project/abcd
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS.html b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS.html index 264a947c3830..4fab940d27b4 100644 --- a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS.html +++ b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_EXCEEDS_MONTHLY_TESTS.html @@ -34,11 +34,11 @@ -
You've exceeded the limit of test results under your free plan this month. The limit is 500 free results
-
-Your plan is now in a grace period, which means you will have the full benefits of your current plan until Feb 1, 2022.
-
-Please visit your billing to upgrade your plan.
-
-https://on.cypress.io/set-up-billing
+    
You've exceeded the limit of test results under your free plan this month. The limit is 500 test results.
+
+Your plan is now in a grace period, which means you will have the full benefits of your current plan until Feb 1, 2022.
+
+Please visit your billing to upgrade your plan.
+
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html index ebb2527f5153..e22dbe1d25af 100644 --- a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html +++ b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html @@ -34,9 +34,9 @@ -
Parallelization is not included under your free plan.
-
-Your plan is now in a grace period, which means your tests will still run in parallel until Feb 1, 2022. Please upgrade your plan to continue running your tests in parallel in the future.
-
-https://on.cypress.io/set-up-billing
+    
Parallelization is not included under your free plan.
+
+Your plan is now in a grace period, which means your tests will still run in parallel until Feb 1, 2022. Please upgrade your plan to continue running your tests in parallel in the future.
+
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html b/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html index c3c70d514912..685190a9bb51 100644 --- a/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html +++ b/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html @@ -34,12 +34,12 @@ -
We've detected that the incompatible plugin `cypress-plugin-retries` is installed at ./path/to/cypress-plugin-retries.
-
-Test retries is now supported in Cypress version `5.0.0`.
-
-Remove the plugin from your dependencies to silence this warning.
-
-https://on.cypress.io/test-retries
-
+    
We've detected that the incompatible plugin cypress-plugin-retries is installed at: ./path/to/cypress-plugin-retries
+
+Test retries is now natively supported in Cypress version 5.0.0.
+
+Remove the plugin from your dependencies to silence this warning.
+
+https://on.cypress.io/test-retries
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html b/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html index 201b802906ce..8ab52b574863 100644 --- a/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html +++ b/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html @@ -34,11 +34,11 @@ -
You passed the --ci-build-id flag but did not provide either a --group or --parallel flag.
-
-The --ci-build-id flag you passed was: ciBuildId123
-
-The --ci-build-id flag is used to either group or parallelize multiple runs together.
-
-https://on.cypress.io/incorrect-ci-build-id-usage
+    
You passed the --ci-build-id flag but did not provide either a --group or --parallel flag.
+
+The --ci-build-id flag you passed was: ciBuildId123
+
+The --ci-build-id flag is used to either group or parallelize multiple runs together.
+
+https://on.cypress.io/incorrect-ci-build-id-usage
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html b/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html index 0b609011279c..ccdc5f366797 100644 --- a/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html +++ b/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html @@ -34,40 +34,40 @@ -
You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId.
-
-The --group flag you passed was: foo
-The --parallel flag you passed was: false
-
-In order to use either of these features a ciBuildId must be determined.
-
-The ciBuildId is automatically detected if you are running Cypress in any of the these CI providers:
-
-- appveyor
-- azure
-- awsCodeBuild
-- bamboo
-- bitbucket
-- buildkite
-- circle
-- codeshipBasic
-- codeshipPro
-- concourse
-- codeFresh
-- drone
-- githubActions
-- gitlab
-- goCD
-- googleCloud
-- jenkins
-- semaphore
-- shippable
-- teamfoundation
-- travis
-- netlify
-- layerci
-
-Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually.
-
-https://on.cypress.io/indeterminate-ci-build-id
+    
You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId.
+
+The --group flag you passed was: foo
+The --parallel flag you passed was: false
+
+In order to use either of these features a ciBuildId must be determined.
+
+The ciBuildId is automatically detected if you are running Cypress in any of the these CI providers:
+
+ - appveyor
+ - azure
+ - awsCodeBuild
+ - bamboo
+ - bitbucket
+ - buildkite
+ - circle
+ - codeshipBasic
+ - codeshipPro
+ - concourse
+ - codeFresh
+ - drone
+ - githubActions
+ - gitlab
+ - goCD
+ - googleCloud
+ - jenkins
+ - semaphore
+ - shippable
+ - teamfoundation
+ - travis
+ - netlify
+ - layerci
+
+Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually.
+
+https://on.cypress.io/indeterminate-ci-build-id
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION - plural.html b/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION - plural.html new file mode 100644 index 000000000000..56233ce5858f --- /dev/null +++ b/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION - plural.html @@ -0,0 +1,44 @@ + + + + + + + + + + + +
The following configuration options are invalid:
+
+ - foo
+ - bar
+
+https://on.cypress.io/configuration
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION.html b/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION.html index dfd4c9467607..ae37189fd589 100644 --- a/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION.html +++ b/packages/errors/__snapshot-html__/INVALID_CONFIG_OPTION.html @@ -34,9 +34,10 @@ -
`test` is not a valid configuration option
-`foo` is not a valid configuration option
-
-https://on.cypress.io/configuration
-
+    
The following configuration option is invalid:
+
+ - foo
+
+https://on.cypress.io/configuration
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html b/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html index 3d0f56ca0001..ff9126ba2c98 100644 --- a/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html +++ b/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html @@ -34,11 +34,7 @@ -
We have detected an unknown or unsupported "CYPRESS_INTERNAL_ENV" value
-
-  foo
-
-"CYPRESS_INTERNAL_ENV" is reserved and should only be used internally.
-
-Do not modify the "CYPRESS_INTERNAL_ENV" value.
+    
We have detected an unknown or unsupported CYPRESS_INTERNAL_ENV value: foo
+
+CYPRESS_INTERNAL_ENV is reserved for internal use and cannot be modified.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html b/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html index 0c7d16628118..a526ac7d00a1 100644 --- a/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html +++ b/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html @@ -34,16 +34,16 @@ -
Could not load reporter by name: missing-reporter-name
-
-We searched for the reporter in these paths:
-
-- /path/to/reporter
-- /path/reporter
-
-The error we received was:
-
-stack-trace
-
-Learn more at https://on.cypress.io/reporters
+    
Error loading the reporter: missing-reporter-name
+
+We searched for the reporter in these paths:
+
+ - /path/to/reporter
+ - /path/reporter
+
+The error was:
+
+stack-trace
+
+Learn more at https://on.cypress.io/reporters
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVOKED_BINARY_OUTSIDE_NPM_MODULE.html b/packages/errors/__snapshot-html__/INVOKED_BINARY_OUTSIDE_NPM_MODULE.html index 837ba33218cc..5246b2581724 100644 --- a/packages/errors/__snapshot-html__/INVOKED_BINARY_OUTSIDE_NPM_MODULE.html +++ b/packages/errors/__snapshot-html__/INVOKED_BINARY_OUTSIDE_NPM_MODULE.html @@ -34,11 +34,11 @@ -
It looks like you are running the Cypress binary directly.
-
-This is not the recommended approach, and Cypress may not work correctly.
-
-Please install the 'cypress' NPM package and follow the instructions here:
-
-https://on.cypress.io/installing-cypress
+    
It looks like you are running the Cypress binary directly.
+
+This is not the recommended approach, and Cypress may not work correctly.
+
+Please install the cypress NPM package and follow the instructions here:
+
+https://on.cypress.io/installing-cypress
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html index e140265d7b0c..b34c8449a136 100644 --- a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html +++ b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html @@ -34,12 +34,12 @@ -
Deprecation Warning: `nodeVersion` is currently set to `bundled` in the `cypress.json` configuration file.
-
-As of Cypress version `9.0.0` the default behavior of `nodeVersion` has changed to always use the version of Node used to start cypress via the cli.
-
-When `nodeVersion` is set to `bundled`, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations.
-
-As the `nodeVersion` configuration option will be removed in a future release, it is recommended to remove the `nodeVersion` configuration option from `cypress.json`.
-
+    
Deprecation Warning: `nodeVersion` is currently set to `bundled` in the `cypress.json` configuration file.
+
+As of Cypress version 9.0.0 the default behavior of `nodeVersion` has changed to always use the version of Node used to start cypress via the cli.
+
+When `nodeVersion` is set to `bundled`, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations.
+
+As the `nodeVersion` configuration option will be removed in a future release, it is recommended to remove the `nodeVersion` configuration option from `cypress.json`.
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html index dd454e40330e..f1f0cbf674e8 100644 --- a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html +++ b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html @@ -34,10 +34,10 @@ -
Deprecation Warning: `nodeVersion` is currently set to `system` in the `cypress.json` configuration file.
-
-As of Cypress version `9.0.0` the default behavior of `nodeVersion` has changed to always use the version of Node used to start cypress via the cli.
-
-Please remove the `nodeVersion` configuration option from `cypress.json`.
-
+    
Deprecation Warning: `nodeVersion` is currently set to `system` in the `cypress.json` configuration file.
+
+As of Cypress version 9.0.0 the default behavior of `nodeVersion` has changed to always use the version of Node used to start cypress via the cli.
+
+Please remove the `nodeVersion` configuration option from `cypress.json`.
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NOT_LOGGED_IN.html b/packages/errors/__snapshot-html__/NOT_LOGGED_IN.html index d3c51d8435f0..ac98f99b559a 100644 --- a/packages/errors/__snapshot-html__/NOT_LOGGED_IN.html +++ b/packages/errors/__snapshot-html__/NOT_LOGGED_IN.html @@ -34,7 +34,7 @@ -
You're not logged in.
-
-Run `cypress open` to open the Desktop App and log in.
+    
You're not logged in.
+
+Run cypress open to open the Desktop App and log in.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html b/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html index 5d1abfb10317..6c5020ef1710 100644 --- a/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html +++ b/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html @@ -34,7 +34,5 @@ -
Could not find a Cypress configuration file, exiting.
-
-We looked but did not find a default config file in this folder: /path/to/project/root
+    
Could not find a Cypress configuration file in this folder: /path/to/project/root
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html b/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html index 37ec6b829594..ab286c77bb59 100644 --- a/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html +++ b/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html @@ -34,5 +34,5 @@ -
Can't find project at the path: /path/to/project
+    
Can't find a project at the path: /path/to/project
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_PROJECT_ID.html b/packages/errors/__snapshot-html__/NO_PROJECT_ID.html index 0f2d2c1bd3b1..a36b5c26ca3c 100644 --- a/packages/errors/__snapshot-html__/NO_PROJECT_ID.html +++ b/packages/errors/__snapshot-html__/NO_PROJECT_ID.html @@ -34,5 +34,5 @@ -
Can't find 'projectId' in the 'cypress.json' file for this project: /path/to/project
+    
Can't find projectId in the config file: /path/to/project/cypress.json
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html index 69143a3055da..aaf59f28170c 100644 --- a/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html +++ b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html @@ -34,9 +34,9 @@ -
Can't run because no spec files were found.
-
-We searched for any files inside of this folder:
-
-/path/to/project/root
+    
Can't run because no spec files were found.
+
+We searched for specs inside of this folder:
+
+  > /path/to/project/root
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html b/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html index 4e93dacee17c..862f9b0dc241 100644 --- a/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html +++ b/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html @@ -34,13 +34,9 @@ -
Can't run because no spec files were found.
-
-We searched for any files matching this glob pattern:
-
-**_spec.js
-
-Relative to the project root folder:
-
-/path/to/project/root
+    
Can't run because no spec files were found.
+
+We searched for specs matching this glob pattern:
+
+  > /path/to/project/root/**_spec.js
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html b/packages/errors/__snapshot-html__/PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html index 0bf8f34d7deb..0c434815d3fc 100644 --- a/packages/errors/__snapshot-html__/PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html +++ b/packages/errors/__snapshot-html__/PAID_PLAN_EXCEEDS_MONTHLY_PRIVATE_TESTS.html @@ -34,9 +34,9 @@ -
You've exceeded the limit of private test results under your current billing plan this month. The limit is 500 free results
-
-To upgrade your account, please visit your billing to upgrade to another billing plan.
-
-https://on.cypress.io/set-up-billing
+    
You've exceeded the limit of private test results under your current billing plan this month. The limit is 25000 private test results.
+
+To upgrade your account, please visit your billing to upgrade to another billing plan.
+
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html b/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html index 43fe54bbcbc2..57fbc5da8c99 100644 --- a/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html +++ b/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html @@ -34,9 +34,9 @@ -
Parallelization is not included under your current billing plan.
-
-To run your tests in parallel, please visit your billing and upgrade to another plan with parallelization.
-
-https://on.cypress.io/set-up-billing
+    
Parallelization is not included under your current billing plan.
+
+To run your tests in parallel, please visit your billing and upgrade to another plan with parallelization.
+
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLAN_EXCEEDS_MONTHLY_TESTS.html b/packages/errors/__snapshot-html__/PLAN_EXCEEDS_MONTHLY_TESTS.html index 6b6a853d67f5..15297714defc 100644 --- a/packages/errors/__snapshot-html__/PLAN_EXCEEDS_MONTHLY_TESTS.html +++ b/packages/errors/__snapshot-html__/PLAN_EXCEEDS_MONTHLY_TESTS.html @@ -34,9 +34,9 @@ -
You've exceeded the limit of test results under your Test Plan billing plan this month. The limit is 500 free results
-
-To continue getting the full benefits of your current plan, please visit your billing to upgrade.
-
-https://on.cypress.io/set-up-billing
+    
You've exceeded the limit of test results under your Sprout billing plan this month. The limit is 25000 test results.
+
+To continue getting the full benefits of your current plan, please visit your billing to upgrade.
+
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html b/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html index b9ea4790eacc..5abe869dabf2 100644 --- a/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html +++ b/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html @@ -34,9 +34,9 @@ -
Grouping is not included under your free plan.
-
-Your plan is now in a grace period, which means your tests will still run with groups until Feb 1, 2022. Please upgrade your plan to continue running your tests with groups in the future.
-
-https://on.cypress.io/set-up-billing
+    
Grouping is not included under your free plan.
+
+Your plan is now in a grace period, which means your tests will still run with groups until Feb 1, 2022. Please upgrade your plan to continue running your tests with groups in the future.
+
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html index e352e75c6b08..646361fbfd02 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html @@ -34,7 +34,7 @@ -
An invalid configuration value returned from the plugins file: /path/to/pluginsFile
-
-fail whale
+    
An invalid configuration value returned from the plugins file: /path/to/pluginsFile
+
+fail whale
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html new file mode 100644 index 000000000000..c527444f7343 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html @@ -0,0 +1,53 @@ + + + + + + + + + + + +
The pluginsFile must export a function with the following signature:
+
+// /path/to/pluginsFile
+module.exports = (on, config) => {
+  // configure plugins here
+}
+
+Instead it exported:
+
+[
+  "some",
+  "array"
+]
+
+Learn more: https://on.cypress.io/plugins-api
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html new file mode 100644 index 000000000000..8719a597fa14 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html @@ -0,0 +1,50 @@ + + + + + + + + + + + +
The pluginsFile must export a function with the following signature:
+
+// /path/to/pluginsFile
+module.exports = (on, config) => {
+  // configure plugins here
+}
+
+Instead it exported:
+
+"some string"
+
+Learn more: https://on.cypress.io/plugins-api
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html index 223514b5d6b6..11dd132b2b0a 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html @@ -34,17 +34,19 @@ -
The `pluginsFile` must export a function with the following signature:
-
-```
-module.exports = function (on, config) {
-  // configure plugins here
-}
-```
-
-Learn more: https://on.cypress.io/plugins-api
-
-We loaded the `pluginsFile` from: /path/to/pluginsFile
-
-It exported:
+    
The pluginsFile must export a function with the following signature:
+
+// /path/to/pluginsFile
+module.exports = (on, config) => {
+  // configure plugins here
+}
+
+Instead it exported:
+
+{
+  "some": "object"
+}
+
+Learn more: https://on.cypress.io/plugins-api
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html index 3b991e30bf4f..2f031e550638 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html @@ -34,15 +34,11 @@ -
The plugins file is missing or invalid.
-
-Your `pluginsFile` is set to /path/to/pluginsFile, but either the file is missing, it contains a syntax error, or threw an error when required. The `pluginsFile` must be a `.js`, `.ts`, or `.coffee` file.
-
-Or you might have renamed the extension of your `pluginsFile`. If that's the case, restart the test runner.
-
-Please fix this, or set `pluginsFile` to `false` if a plugins file is not necessary for your project.
+    
Your pluginsFile file is invalid: /path/to/pluginsFile
+
+It threw an error when required, check the stack trace below:
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_FILE_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_FILE_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html new file mode 100644 index 000000000000..22ee56f6f676 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
Your pluginsFile file was not found at path: /path/to/pluginsFile
+
+Create this file, or set pluginsFile to false if a plugins file is not necessary for your project.
+
+If you have just renamed the extension of your pluginsFile, restart Cypress.
+
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html index 2a679c2acfd0..33a7833ab9de 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html @@ -34,11 +34,9 @@ -
The function exported by the plugins file threw an error.
-
-We invoked the function exported by /path/to/pluginsFile, but it threw an error.
+    
The function exported by the pluginsFile threw an error: /path/to/pluginsFile
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_FUNCTION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_FUNCTION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html new file mode 100644 index 000000000000..b052b4359f04 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html @@ -0,0 +1,52 @@ + + + + + + + + + + + +
Your pluginsFile threw a validation error: /path/to/pluginsFile
+
+You must pass a valid event name when registering a plugin.
+
+You passed: invalid:event
+
+The following are valid events:
+
+ - foo
+ - bar
+ - baz
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_INVALID_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html index ac6736145abf..be988155e26a 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html @@ -34,11 +34,11 @@ -
An error was thrown in your plugins file while executing the handler for the 'before:spec' event.
-
-The error we received was:
+    
An error was thrown in your plugins file while executing the handler for the before:spec event.
+
+The error we received was:
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_RUN_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_RUN_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html index ef12abce3199..8a955fa32d86 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html @@ -34,9 +34,11 @@ -
The following error was thrown by a plugin. We stopped running your tests because a plugin crashed. Please check your plugins file (/path/to/pluginsFile)
+    
We stopped running your tests because a plugin crashed.
+
+The following error was thrown by your plugins file: /path/to/pluginsFile
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html index 3d64ad0f8c1e..99a1effcb3a3 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html @@ -34,9 +34,9 @@ -
The following validation error was thrown by your plugins file (/path/to/pluginsFile).
+    
Your pluginsFile threw a validation error: /path/to/pluginsFile
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html b/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html index 0a87c053601f..dca0726efd80 100644 --- a/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html +++ b/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html @@ -34,7 +34,7 @@ -
Can't run project because port is currently in use: 2020
-
-Assign a different port with the '--port <port>' argument or shut down the other running process.
+    
Can't run project because port is currently in use: 2020
+
+Assign a different port with the --port <port> argument or shut down the other running process.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PORT_IN_USE_SHORT.html b/packages/errors/__snapshot-html__/PORT_IN_USE_SHORT.html index 65c95fd75dc3..d5bf44ec55a4 100644 --- a/packages/errors/__snapshot-html__/PORT_IN_USE_SHORT.html +++ b/packages/errors/__snapshot-html__/PORT_IN_USE_SHORT.html @@ -34,5 +34,5 @@ -
Port 2020 is already in use.
+    
Port 2020 is already in use.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html b/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html index d319578fc76a..24506afdcccc 100644 --- a/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html +++ b/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html @@ -34,21 +34,21 @@ -
This project has been configured to record runs on our Dashboard.
-
-It currently has the projectId: abc123
-
-You also provided your Record Key, but you did not pass the --record flag.
-
-This run will not be recorded.
-
-If you meant to have this run recorded please additionally pass this flag.
-
-  cypress run --record
-
-If you don't want to record these runs, you can silence this warning:
-
-  cypress run --record false
-
-https://on.cypress.io/recording-project-runs
+    
This project has been configured to record runs on our Dashboard.
+
+It currently has the projectId: project-id-123
+
+You also provided your Record Key, but you did not pass the --record flag.
+
+This run will not be recorded.
+
+If you meant to have this run recorded please additionally pass this flag:
+
+  $ cypress run --record
+
+If you don't want to record these runs, you can silence this warning:
+
+  $ cypress run --record false
+
+https://on.cypress.io/recording-project-runs
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RECORDING_FROM_FORK_PR.html b/packages/errors/__snapshot-html__/RECORDING_FROM_FORK_PR.html index bca10640cd91..9b5793382784 100644 --- a/packages/errors/__snapshot-html__/RECORDING_FROM_FORK_PR.html +++ b/packages/errors/__snapshot-html__/RECORDING_FROM_FORK_PR.html @@ -34,11 +34,11 @@ -
Warning: It looks like you are trying to record this run from a forked PR.
-
-The 'Record Key' is missing. Your CI provider is likely not passing private environment variables to builds from forks.
-
-These results will not be recorded.
-
-This error will not alter the exit code.
+    
Warning: It looks like you are trying to record this run from a forked PR.
+
+The Record Key is missing. Your CI provider is likely not passing private environment variables to builds from forks.
+
+These results will not be recorded.
+
+This error will not alter the exit code.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html b/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html index 92a0417aa2e0..f312a70eea3c 100644 --- a/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html +++ b/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html @@ -34,13 +34,13 @@ -
You passed the --record flag but did not provide us your Record Key.
-
-You can pass us your Record Key like this:
-
-  cypress run --record --key <record_key>
-
-You can also set the key as an environment variable with the name CYPRESS_RECORD_KEY.
-
-https://on.cypress.io/how-do-i-record-runs
+    
You passed the --record flag but did not provide us your Record Key.
+
+You can pass us your Record Key like this:
+
+  $ cypress run --record --key <record_key>
+
+You can also set the key as an environment variable with the name: CYPRESS_RECORD_KEY
+
+https://on.cypress.io/how-do-i-record-runs
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html b/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html index 760372a7deda..1511f56bea7b 100644 --- a/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html +++ b/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html @@ -34,11 +34,11 @@ -
You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag.
-
-The --parallel flag you passed was: true
-
-These flags can only be used when recording to the Cypress Dashboard service.
-
-https://on.cypress.io/record-params-without-recording
+    
You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag.
+
+The --parallel flag you passed was: true
+
+These flags can only be used when recording to the Cypress Dashboard service.
+
+https://on.cypress.io/record-params-without-recording
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html b/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html index 3376a4b2e487..c5d54192c4d9 100644 --- a/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html +++ b/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html @@ -34,7 +34,7 @@ -
The oldName configuration option you have supplied has been renamed.
-
-Please rename oldName to newName
+    
The oldName configuration option you have supplied has been renamed.
+
+Please rename oldName to newName
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RENDERER_CRASHED.html b/packages/errors/__snapshot-html__/RENDERER_CRASHED.html index 895ad6c13957..e3912fc901d3 100644 --- a/packages/errors/__snapshot-html__/RENDERER_CRASHED.html +++ b/packages/errors/__snapshot-html__/RENDERER_CRASHED.html @@ -34,21 +34,21 @@ -
We detected that the Chromium Renderer process just crashed.
-
-This is the equivalent to seeing the 'sad face' when Chrome dies.
-
-This can happen for a number of different reasons:
-
-- You wrote an endless loop and you must fix your own code
-- There is a memory leak in Cypress (unlikely but possible)
-- You are running Docker (there is an easy fix for this: see link below)
-- You are running lots of tests on a memory intense application
-- You are running in a memory starved VM environment
-- There are problems with your GPU / GPU drivers
-- There are browser bugs in Chromium
-
-You can learn more including how to fix Docker here:
-
-https://on.cypress.io/renderer-process-crashed
+    
We detected that the Chromium Renderer process just crashed.
+
+This is the equivalent to seeing the 'sad face' when Chrome dies.
+
+This can happen for a number of different reasons:
+
+- You wrote an endless loop and you must fix your own code
+- There is a memory leak in Cypress (unlikely but possible)
+- You are running Docker (there is an easy fix for this: see link below)
+- You are running lots of tests on a memory intense application
+- You are running in a memory starved VM environment
+- There are problems with your GPU / GPU drivers
+- There are browser bugs in Chromium
+
+You can learn more including how to fix Docker here:
+
+https://on.cypress.io/renderer-process-crashed
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html b/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html index bad15566022c..d497a729e8a1 100644 --- a/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html +++ b/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html @@ -34,9 +34,9 @@ -
Grouping is not included under your current billing plan.
-
-To run your tests with groups, please visit your billing and upgrade to another plan with grouping.
-
-https://on.cypress.io/set-up-billing
+    
Grouping is not included under your current billing plan.
+
+To run your tests with groups, please visit your billing and upgrade to another plan with grouping.
+
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html index 07091d594a36..a9c9e4eb89ab 100644 --- a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html @@ -34,7 +34,7 @@ -
We found an invalid value in the file: /path/to/file
-
-fail whale
+    
We found an invalid value in the file: cypress.json
+
+fail whale
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html index 7c03062d196f..065dab71610a 100644 --- a/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html @@ -34,13 +34,13 @@ -
The support file is missing or invalid.
-
-Your supportFile is set to /path/to/supportFile, but either the file is missing or it's invalid. The `supportFile` must be a `.js`, `.ts`, `.coffee` file or be supported by your preprocessor plugin (if configured).
-
-Correct your `/path/to/cypress.json`, create the appropriate file, or set `supportFile` to `false` if a support file is not necessary for your project.
-
-Or you might have renamed the extension of your `supportFile` to `.ts`. If that's the case, restart the test runner.
-
-Learn more at https://on.cypress.io/support-file-missing-or-invalid
+    
Your supportFile file is missing or invalid: /path/to/supportFile
+
+The supportFile must be a .js, .ts, .coffee file or be supported by your preprocessor plugin (if configured).
+
+Fix your support file, or set supportFile to false if a support file is not necessary for your project.
+
+If you have just renamed the extension of your supportFile, restart Cypress.
+
+Learn more at https://on.cypress.io/support-file-missing-or-invalid
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_FAILED.html b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_FAILED.html index 5ed1b81c914c..a1e6589e5562 100644 --- a/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_FAILED.html +++ b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_FAILED.html @@ -34,5 +34,5 @@ -
The browser never connected. Something is wrong. The tests cannot run. Aborting...
+    
The browser never connected. Something is wrong. The tests cannot run. Aborting...
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING - retryingAgain.html b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING - retryingAgain.html index 832d09988f1e..8b0610392d57 100644 --- a/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING - retryingAgain.html +++ b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING - retryingAgain.html @@ -34,5 +34,5 @@ -
Timed out waiting for the browser to connect. Retrying again...
+    
Timed out waiting for the browser to connect. Retrying again...
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING.html b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING.html index 59c368f884b7..87a1d43abd73 100644 --- a/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING.html +++ b/packages/errors/__snapshot-html__/TESTS_DID_NOT_START_RETRYING.html @@ -34,5 +34,5 @@ -
Timed out waiting for the browser to connect. Retrying...
+    
Timed out waiting for the browser to connect. Retrying...
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html b/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html index 76077f71fa5a..ef45cb19ad88 100644 --- a/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html +++ b/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html @@ -34,15 +34,15 @@ -
The `launchOptions` object returned by your plugin's `before:browser:launch` handler contained unexpected properties:
-
-- baz
-
-`launchOptions` may only contain the properties:
-
-- preferences
-- extensions
-- args
-
-https://on.cypress.io/browser-launch-api
+    
The launchOptions object returned by your plugin's before:browser:launch handler contained unexpected properties:
+
+ - baz
+
+launchOptions may only contain the properties:
+
+ - preferences
+ - extensions
+ - args
+
+https://on.cypress.io/browser-launch-api
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/UNSUPPORTED_BROWSER_VERSION.html b/packages/errors/__snapshot-html__/UNSUPPORTED_BROWSER_VERSION.html index 8e116d004efb..fd88bc7d472c 100644 --- a/packages/errors/__snapshot-html__/UNSUPPORTED_BROWSER_VERSION.html +++ b/packages/errors/__snapshot-html__/UNSUPPORTED_BROWSER_VERSION.html @@ -34,5 +34,5 @@ -
Cypress does not support running chrome version 64. To use chrome with Cypress, install a version of chrome newer than or equal to 64.
+    
Cypress does not support running chrome version 64. To use chrome with Cypress, install a version of chrome newer than or equal to 64.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html index 14609ed9b289..74547800c5ff 100644 --- a/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html +++ b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html @@ -34,11 +34,11 @@ -
Warning: We failed processing this video.
-
-This error will not alter the exit code.
+    
Warning: We failed processing this video.
+
+This error will not alter the exit code.
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at VIDEO_POST_PROCESSING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at VIDEO_POST_PROCESSING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html index 407ee18c3d06..6620a45d8802 100644 --- a/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html +++ b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html @@ -34,11 +34,11 @@ -
Warning: We failed to record the video.
-
-This error will not alter the exit code.
+    
Warning: We failed to record the video.
+
+This error will not alter the exit code.
 
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at VIDEO_RECORDING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at VIDEO_RECORDING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file From bab19eef06e53a03b74e95039cc309c4a82b8809 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 7 Feb 2022 10:28:55 -0500 Subject: [PATCH 085/165] fix tests, types --- packages/errors/test/unit/errors_spec.ts | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/packages/errors/test/unit/errors_spec.ts b/packages/errors/test/unit/errors_spec.ts index ac9285965a53..6641648cb20e 100644 --- a/packages/errors/test/unit/errors_spec.ts +++ b/packages/errors/test/unit/errors_spec.ts @@ -4,7 +4,6 @@ import chai, { expect } from 'chai' /* eslint-disable no-console */ import chalk from 'chalk' import sinon from 'sinon' -import snapshot from 'snap-shot-it' import * as errors from '../../src' chai.use(require('@cypress/sinon-chai')) @@ -59,7 +58,7 @@ describe('lib/errors', () => { }) it('logs err.details', () => { - const err = errors.get('PLUGINS_FUNCTION_ERROR', 'foo/bar/baz', 'details huh') + const err = errors.get('PLUGINS_FUNCTION_ERROR', 'foo/bar/baz', new Error('asdf')) const ret = errors.log(err) @@ -98,23 +97,4 @@ describe('lib/errors', () => { expect(obj.message).to.eq('foo\u001b[34mbar\u001b[39m\u001b[33mbaz\u001b[39m') }) }) - - context('.displayFlags', () => { - it('returns string formatted from selected keys', () => { - const options = { - tags: 'nightly,staging', - name: 'my tests', - unused: 'some other value', - } - // we are only interested in showig tags and name values - // and prepending them with custom prefixes - const mapping = { - tags: '--tag', - name: '--name', - } - const text = errors.errorUtils.displayFlags(options, mapping) - - return snapshot('tags and name only', text.val) - }) - }) }) From 75929aebec83b90d9974497d263a66985fdf1eb7 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 7 Feb 2022 10:31:00 -0500 Subject: [PATCH 086/165] fix test --- packages/server/test/unit/api_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/test/unit/api_spec.js b/packages/server/test/unit/api_spec.js index 96e6d33a1694..9535ac4d0801 100644 --- a/packages/server/test/unit/api_spec.js +++ b/packages/server/test/unit/api_spec.js @@ -1333,19 +1333,19 @@ describe('lib/api', () => { expect(errors.warning).to.be.calledThrice expect(errors.warning.firstCall.args[0]).to.eql('DASHBOARD_API_RESPONSE_FAILED_RETRYING') expect(errors.warning.firstCall.args[1]).to.eql({ - delay: '30 seconds', + delay: 30000, tries: 3, response: err, }) expect(errors.warning.secondCall.args[1]).to.eql({ - delay: '1 minute', + delay: 60000, tries: 2, response: err, }) expect(errors.warning.thirdCall.args[1]).to.eql({ - delay: '2 minutes', + delay: 120000, tries: 1, response: err, }) From d48d3f96b4dde4f2320a5af905480d35c79aab07 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 7 Feb 2022 10:38:34 -0500 Subject: [PATCH 087/165] fix failing tests --- .../server/__snapshots__/browsers_spec.js | 32 +++++++++++-------- .../test/unit/browsers/browsers_spec.js | 2 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/server/__snapshots__/browsers_spec.js b/packages/server/__snapshots__/browsers_spec.js index 6da3ba73f7fe..3c7dbf19111a 100644 --- a/packages/server/__snapshots__/browsers_spec.js +++ b/packages/server/__snapshots__/browsers_spec.js @@ -4,11 +4,11 @@ Can't run because you've entered an invalid browser name. Browser: browserNotGonnaBeFound was not found on your system or is not supported by Cypress. Cypress supports the following browsers: -- chrome -- chromium -- edge -- electron -- firefox + - electron + - chrome + - chromium + - edge + - firefox You can also use a custom browser: https://on.cypress.io/customize-browsers @@ -16,6 +16,10 @@ Available browsers found on your system are: - chrome - firefox - electron + - chrome + - chromium + - firefox + - electron ` exports['lib/browsers/index .ensureAndGetByNameOrPath throws a special error when canary is passed 1'] = ` @@ -24,20 +28,20 @@ Can't run because you've entered an invalid browser name. Browser: canary was not found on your system or is not supported by Cypress. Cypress supports the following browsers: -- chrome -- chromium -- edge -- electron -- firefox + - electron + - chrome + - chromium + - edge + - firefox You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: -- chrome -- chrome:canary -- firefox + - chrome + - chrome:canary + - firefox -Note: In Cypress version 4.0.0, Canary must be launched as \`chrome:canary\`, not \`canary\`. +Note: In Cypress version 4.0.0, Canary must be launched as chrome:canary, not canary. See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0. ` diff --git a/packages/server/test/unit/browsers/browsers_spec.js b/packages/server/test/unit/browsers/browsers_spec.js index cc5114512b41..7a8a16cf54fc 100644 --- a/packages/server/test/unit/browsers/browsers_spec.js +++ b/packages/server/test/unit/browsers/browsers_spec.js @@ -104,7 +104,7 @@ describe('lib/browsers/index', () => { // we will get good error message that includes the "err" object expect(err).to.have.property('type').to.eq('BROWSER_NOT_FOUND_BY_NAME') - expect(err).to.have.property('message').to.contain(`${chalk.blue('foo-bad-bang')} was not found on your system`) + expect(err).to.have.property('message').to.contain(`Browser: ${chalk.yellow('foo-bad-bang')} was not found on your system`) }) }) }) From f9b474f603f36fc562c162927caf985e89c5ef69 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 7 Feb 2022 10:40:54 -0500 Subject: [PATCH 088/165] fix tests --- packages/server/test/unit/browsers/cri-client_spec.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/server/test/unit/browsers/cri-client_spec.ts b/packages/server/test/unit/browsers/cri-client_spec.ts index eeb45bcfb520..2d8038fae8c0 100644 --- a/packages/server/test/unit/browsers/cri-client_spec.ts +++ b/packages/server/test/unit/browsers/cri-client_spec.ts @@ -1,6 +1,7 @@ import Bluebird from 'bluebird' -import { create } from '../../../lib/browsers/cri-client' +import chalk from 'chalk' import EventEmitter from 'events' +import { create } from '../../../lib/browsers/cri-client' const { expect, proxyquire, sinon } = require('../../spec_helper') @@ -113,12 +114,12 @@ describe('lib/browsers/cri-client', function () { .rejects() return expect(withProtocolVersion(null, '1.3')).to.be - .rejectedWith('A minimum CDP version of v1.3 is required, but the current browser has an older version.') + .rejectedWith(`A minimum CDP version of ${chalk.yellow(`1.3`)} is required, but the current browser has an older version.`) }) it('rejects if protocolVersion < current', function () { return expect(withProtocolVersion('1.2', '1.3')).to.be - .rejectedWith('A minimum CDP version of v1.3 is required, but the current browser has v1.2.') + .rejectedWith(`A minimum CDP version of ${chalk.yellow(`1.3`)} is required, but the current browser has ${chalk.yellow(`1.2`)}.`) }) }) }) From f7e7c303316b2bb32fb3e097039901bb5067bfa2 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Mon, 7 Feb 2022 10:58:53 -0500 Subject: [PATCH 089/165] fixes lots of broken tests --- .../__snapshot-html__/PLUGINS_FILE_ERROR.html | 2 +- .../PLUGINS_FILE_NOT_FOUND.html | 2 +- .../SUPPORT_FILE_NOT_FOUND.html | 2 +- packages/errors/src/errors.ts | 6 ++-- .../server/test/unit/browsers/firefox_spec.ts | 9 ++--- .../test/unit/browsers/protocol_spec.ts | 35 +++++++++---------- packages/server/test/unit/config_spec.js | 5 +-- system-tests/__snapshots__/plugins_spec.js | 4 +-- 8 files changed, 32 insertions(+), 33 deletions(-) diff --git a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html index 2f031e550638..14e1f504a64b 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html @@ -34,7 +34,7 @@ -
Your pluginsFile file is invalid: /path/to/pluginsFile
+    
Your pluginsFile is invalid: /path/to/pluginsFile
 
 It threw an error when required, check the stack trace below:
 
diff --git a/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html
index 22ee56f6f676..49946905dfdf 100644
--- a/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html
+++ b/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html
@@ -34,7 +34,7 @@
     
   
     
-    
Your pluginsFile file was not found at path: /path/to/pluginsFile
+    
Your pluginsFile was not found at path: /path/to/pluginsFile
 
 Create this file, or set pluginsFile to false if a plugins file is not necessary for your project.
 
diff --git a/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html
index 065dab71610a..165b304fb95e 100644
--- a/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html
+++ b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html
@@ -34,7 +34,7 @@
     
   
     
-    
Your supportFile file is missing or invalid: /path/to/supportFile
+    
Your supportFile is missing or invalid: /path/to/supportFile
 
 The supportFile must be a .js, .ts, .coffee file or be supported by your preprocessor plugin (if configured).
 
diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts
index 6f11a69bbbf4..e61d0e5e83ac 100644
--- a/packages/errors/src/errors.ts
+++ b/packages/errors/src/errors.ts
@@ -569,7 +569,7 @@ export const AllCypressErrors = {
   },
   SUPPORT_FILE_NOT_FOUND: (supportFilePath: string) => {
     return errTemplate`\
-        Your ${`supportFile`} file is missing or invalid: ${fmt.path(supportFilePath)}
+        Your ${`supportFile`} is missing or invalid: ${fmt.path(supportFilePath)}
 
         The supportFile must be a .js, .ts, .coffee file or be supported by your preprocessor plugin (if configured).
 
@@ -581,7 +581,7 @@ export const AllCypressErrors = {
   },
   PLUGINS_FILE_ERROR: (pluginsFilePath: string, err: Error) => {
     return errTemplate`\
-        Your ${`pluginsFile`} file is invalid: ${fmt.path(pluginsFilePath)}
+        Your ${`pluginsFile`} is invalid: ${fmt.path(pluginsFilePath)}
 
         It threw an error when required, check the stack trace below:
 
@@ -590,7 +590,7 @@ export const AllCypressErrors = {
   },
   PLUGINS_FILE_NOT_FOUND: (pluginsFilePath: string) => {
     return errTemplate`\
-        Your ${`pluginsFile`} file was not found at path: ${fmt.path(pluginsFilePath)}
+        Your ${`pluginsFile`} was not found at path: ${fmt.path(pluginsFilePath)}
 
         Create this file, or set pluginsFile to ${fmt.highlightSecondary(`false`)} if a plugins file is not necessary for your project.
 
diff --git a/packages/server/test/unit/browsers/firefox_spec.ts b/packages/server/test/unit/browsers/firefox_spec.ts
index 57375a6fdc25..85222e9e9aa4 100644
--- a/packages/server/test/unit/browsers/firefox_spec.ts
+++ b/packages/server/test/unit/browsers/firefox_spec.ts
@@ -3,10 +3,11 @@ require('../../spec_helper')
 import 'chai-as-promised'
 import { expect } from 'chai'
 import { EventEmitter } from 'events'
-import Foxdriver from '@benmalka/foxdriver'
 import Marionette from 'marionette-client'
 import os from 'os'
 import sinon from 'sinon'
+import stripAnsi from 'strip-ansi'
+import Foxdriver from '@benmalka/foxdriver'
 import * as firefox from '../../../lib/browsers/firefox'
 import firefoxUtil from '../../../lib/browsers/firefox-util'
 
@@ -413,7 +414,7 @@ describe('lib/browsers/firefox', () => {
 
         await expect(firefoxUtil.setupMarionette([], '', port))
         .to.be.rejected.then((err) => {
-          expect(err.message).to.include('An unexpected error was received from Marionette Socket')
+          expect(stripAnsi(err.message)).to.include(`An unexpected error was received from Marionette: Socket`)
           expect(err.details).to.include('Error: foo error')
           expect(err.originalError.message).to.eq('foo error')
         })
@@ -426,7 +427,7 @@ describe('lib/browsers/firefox', () => {
 
         await expect(firefoxUtil.setupMarionette([], '', port))
         .to.be.rejected.then((err) => {
-          expect(err.message).to.include('An unexpected error was received from Marionette commands')
+          expect(stripAnsi(err.message)).to.include('An unexpected error was received from Marionette: commands')
           expect(err.details).to.include('Error: foo error')
         })
       })
@@ -436,7 +437,7 @@ describe('lib/browsers/firefox', () => {
 
         await expect(firefoxUtil.setupMarionette([], '', port))
         .to.be.rejected.then((err) => {
-          expect(err.message).to.include('An unexpected error was received from Marionette connection')
+          expect(stripAnsi(err.message)).to.include('An unexpected error was received from Marionette: connection')
           expect(err.details).to.include('Error: not connectable')
         })
       })
diff --git a/packages/server/test/unit/browsers/protocol_spec.ts b/packages/server/test/unit/browsers/protocol_spec.ts
index f54b3e35d805..1339c7bb9ef9 100644
--- a/packages/server/test/unit/browsers/protocol_spec.ts
+++ b/packages/server/test/unit/browsers/protocol_spec.ts
@@ -1,17 +1,16 @@
 import '../../spec_helper'
-import _ from 'lodash'
-import Bluebird from 'bluebird'
 import 'chai-as-promised' // for the types!
-import chalk from 'chalk'
-import { connect } from '@packages/network'
-import CRI from 'chrome-remote-interface'
+import Bluebird from 'bluebird'
 import { expect } from 'chai'
+import CRI from 'chrome-remote-interface'
+import { stripIndents } from 'common-tags'
 import humanInterval from 'human-interval'
-import * as protocol from '../../../lib/browsers/protocol'
+import _ from 'lodash'
 import sinon from 'sinon'
 import snapshot from 'snap-shot-it'
 import stripAnsi from 'strip-ansi'
-import { stripIndents } from 'common-tags'
+import { connect } from '@packages/network'
+import * as protocol from '../../../lib/browsers/protocol'
 
 describe('lib/browsers/protocol', () => {
   // protocol connects explicitly to this host
@@ -25,7 +24,7 @@ describe('lib/browsers/protocol', () => {
       let delay: number
       let i = 0
 
-      while ((delay = protocol._getDelayMsForRetry(i, 'FooBrowser'))) {
+      while ((delay = protocol._getDelayMsForRetry(i, 'foobrowser'))) {
         delays.push(delay)
         i++
       }
@@ -35,7 +34,7 @@ describe('lib/browsers/protocol', () => {
       log.getCalls().forEach((log, i) => {
         const line = stripAnsi(log.args[0])
 
-        expect(line).to.include(`Still waiting to connect to FooBrowser, retrying in 1 second (attempt ${i + 18}/62)`)
+        expect(line).to.include(`Still waiting to connect to Foobrowser, retrying in 1 second (attempt ${i + 18}/62)`)
       })
 
       snapshot(delays)
@@ -46,22 +45,20 @@ describe('lib/browsers/protocol', () => {
     const expectedCdpFailedError = stripIndents`
       Cypress failed to make a connection to the Chrome DevTools Protocol after retrying for 50 seconds.
 
-      This usually indicates there was a problem opening the FooBrowser browser.
-
-      The CDP port requested was ${chalk.yellow('12345')}.
+      This usually indicates there was a problem opening the Foobrowser browser.
 
-      Error details:
+      The CDP port requested was 12345.
     `
 
     it('rejects if CDP connection fails', () => {
       const innerErr = new Error('cdp connection failure')
 
       sinon.stub(connect, 'createRetryingSocket').callsArgWith(1, innerErr)
-      const p = protocol.getWsTargetFor(12345, 'FooBrowser')
+      const p = protocol.getWsTargetFor(12345, 'foobrowser')
 
       return expect(p).to.eventually.be.rejected.then((val) => {
-        expect(val).property('message').include(expectedCdpFailedError)
-        expect(val).property('details').include(innerErr.message)
+        expect(stripAnsi(val.message)).include(expectedCdpFailedError)
+        expect(stripAnsi(val.details)).include(innerErr.message)
       })
     })
 
@@ -81,8 +78,8 @@ describe('lib/browsers/protocol', () => {
       const p = protocol.getWsTargetFor(12345, 'FooBrowser')
 
       return expect(p).to.eventually.be.rejected.then((val) => {
-        expect(val).property('message').include(expectedCdpFailedError)
-        expect(val).property('details').include(innerErr.message)
+        expect(stripAnsi(val.message)).include(expectedCdpFailedError)
+        expect(stripAnsi(val.details)).include(innerErr.message)
       })
     })
 
@@ -182,7 +179,7 @@ describe('lib/browsers/protocol', () => {
       log.getCalls().forEach((log, i) => {
         const line = stripAnsi(log.args[0])
 
-        expect(line).to.include(`Still waiting to connect to FooBrowser, retrying in 1 second (attempt ${i + 18}/62)`)
+        expect(line).to.include(`Still waiting to connect to Foobrowser, retrying in 1 second (attempt ${i + 18}/62)`)
       })
     })
   })
diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js
index b5b1bc9e900a..a4e6f4f32028 100644
--- a/packages/server/test/unit/config_spec.js
+++ b/packages/server/test/unit/config_spec.js
@@ -2,6 +2,7 @@ require('../spec_helper')
 
 const _ = require('lodash')
 const debug = require('debug')('test')
+const stripAnsi = require('strip-ansi')
 const Fixtures = require('@tooling/system-tests/lib/fixtures')
 
 const config = require(`${root}lib/config`)
@@ -2095,7 +2096,7 @@ describe('lib/config', () => {
 
       return config.setSupportFileAndFolder(obj, mockSupportDefaults)
       .catch((err) => {
-        expect(err.message).to.include('The support file is missing or invalid.')
+        expect(stripAnsi(err.message)).to.include('Your supportFile is missing or invalid:')
       })
     })
 
@@ -2256,7 +2257,7 @@ describe('lib/config', () => {
 
       return config.setPluginsFile(obj, mockPluginDefaults)
       .catch((err) => {
-        expect(err.message).to.include('The plugins file is missing or invalid.')
+        expect(stripAnsi(err.message)).to.include('Your pluginsFile was not found at path:')
       })
     })
 
diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js
index 038700e6555d..4683e940776a 100644
--- a/system-tests/__snapshots__/plugins_spec.js
+++ b/system-tests/__snapshots__/plugins_spec.js
@@ -534,7 +534,7 @@ Learn more: https://on.cypress.io/plugins-api
 `
 
 exports['e2e plugins fails when its set from config but does not exist 1'] = `
-Your pluginsFile file was not found at path: /foo/bar/.projects/plugin-missing/cypress/plugins/does-not-exist.js
+Your pluginsFile was not found at path: /foo/bar/.projects/plugin-missing/cypress/plugins/does-not-exist.js
 
 Create this file, or set pluginsFile to false if a plugins file is not necessary for your project.
 
@@ -544,7 +544,7 @@ If you have just renamed the extension of your pluginsFile, restart Cypress.
 `
 
 exports['e2e plugins fails when require throws synchronously 1'] = `
-Your pluginsFile file is invalid: /foo/bar/.projects/plugins-root-sync-error/cypress/plugins/index.js
+Your pluginsFile is invalid: /foo/bar/.projects/plugins-root-sync-error/cypress/plugins/index.js
 
 It threw an error when required, check the stack trace below:
 

From a3ee5ac50d9dc785a3164ae7de76dbc1a6c47480 Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Mon, 7 Feb 2022 11:38:20 -0500
Subject: [PATCH 090/165] more test fixes

---
 packages/errors/test/unit/errTemplate_spec.ts | 48 +++++++++++--------
 packages/errors/test/unit/errors_spec.ts      | 10 ++--
 .../unit/plugins/child/validate_event_spec.js | 32 +------------
 3 files changed, 36 insertions(+), 54 deletions(-)

diff --git a/packages/errors/test/unit/errTemplate_spec.ts b/packages/errors/test/unit/errTemplate_spec.ts
index 08aeb25000f4..394ea4cbedc9 100644
--- a/packages/errors/test/unit/errTemplate_spec.ts
+++ b/packages/errors/test/unit/errTemplate_spec.ts
@@ -1,7 +1,6 @@
 import { expect } from 'chai'
 import chalk from 'chalk'
-
-import { details, errTemplate, guard } from '../../src/errTemplate'
+import { errTemplate, fmt } from '../../src/errTemplate'
 import { stripIndent } from '../../src/stripIndent'
 
 describe('errTemplate', () => {
@@ -12,15 +11,15 @@ describe('errTemplate', () => {
     expect(obj.forBrowser()).to.include({ message: 'Hello world' })
   })
 
-  it('colors blue by default for the console, backticks passed arguments for the browser,', () => {
+  it('colors yellow by default for the console, backticks passed arguments for the browser,', () => {
     const obj = errTemplate`Hello world ${'special'}`
 
-    expect(obj).to.include({ message: `Hello world ${chalk.blue('special')}` })
+    expect(obj).to.include({ message: `Hello world ${chalk.yellow('special')}` })
     expect(obj.forBrowser()).to.include({ message: 'Hello world `special`' })
   })
 
-  it('uses guard to guard passed values', () => {
-    const obj = errTemplate`Hello world ${guard('special')}`
+  it('uses fmt.off to guard passed values', () => {
+    const obj = errTemplate`Hello world ${fmt.off('special')}`
 
     expect(obj).to.include({ message: `Hello world special` })
     expect(obj.forBrowser()).to.include({ message: `Hello world special` })
@@ -30,18 +29,21 @@ describe('errTemplate', () => {
     const errStack = new Error().stack ?? ''
     const obj = errTemplate`
       This was an error
-      
-      ${details(errStack)}
+
+      ${fmt.stackTrace(errStack)}
     `
 
-    expect(obj).to.include({ message: `This was an error`, details: errStack })
+    expect(obj).to.include({
+      message: `This was an error`,
+      details: chalk.magenta(errStack),
+    })
   })
 
   it('will stringify non scalar values', () => {
     const someObj = { a: 1, b: 2, c: 3 }
     const obj = errTemplate`
       This was returned from the app:
-      
+
       ${someObj}
     `
 
@@ -67,12 +69,15 @@ describe('errTemplate', () => {
     const someObj = { a: 1, b: 2, c: 3 }
     const obj = errTemplate`
       This was returned from the app:
-      
-      ${details(someObj)}
+
+      ${fmt.stackTrace(someObj)}
     `
 
     expect(obj.forBrowser()).to.include({ message: `This was returned from the app:` })
-    expect(obj).to.include({ message: `This was returned from the app:`, details: JSON.stringify(someObj, null, 2) })
+    expect(obj).to.include({
+      message: `This was returned from the app:`,
+      details: chalk.magenta(JSON.stringify(someObj, null, 2)),
+    })
   })
 
   it('uses details to set originalError, for toErrorProps, highlight stack for console', () => {
@@ -80,22 +85,25 @@ describe('errTemplate', () => {
     const err = new Error()
     const obj = errTemplate`
       This was an error in ${specFile}
-      
-      ${details(err)}
+
+      ${fmt.stackTrace(err)}
     `
 
     expect(obj.forBrowser()).to.include({ message: `This was an error in \`specFile.js\`` })
-    expect(obj).to.include({ message: `This was an error in ${chalk.blue(specFile)}`, details: err.stack })
+    expect(obj).to.include({
+      message: `This was an error in ${chalk.yellow(specFile)}`,
+      details: chalk.magenta(err.stack ?? ''),
+    })
   })
 
   it('throws if multiple details are used in the same template', () => {
     expect(() => {
       errTemplate`
-        Hello world 
-        
-        ${details(new Error())}
+        Hello world
+
+        ${fmt.stackTrace(new Error())}
 
-        ${details(new Error())}
+        ${fmt.stackTrace(new Error())}
       `
     }).to.throw(/Cannot use details\(\) multiple times in the same errTemplate/)
   })
diff --git a/packages/errors/test/unit/errors_spec.ts b/packages/errors/test/unit/errors_spec.ts
index 6641648cb20e..ffa08aef8591 100644
--- a/packages/errors/test/unit/errors_spec.ts
+++ b/packages/errors/test/unit/errors_spec.ts
@@ -11,7 +11,7 @@ chai.use(require('@cypress/sinon-chai'))
 describe('lib/errors', () => {
   beforeEach(() => {
     sinon.restore()
-    sinon.stub(console, 'log')
+    sinon.spy(console, 'log')
   })
 
   context('.log', () => {
@@ -54,11 +54,13 @@ describe('lib/errors', () => {
 
       expect(ret).to.be.undefined
 
-      expect(console.log).to.be.calledWithMatch('foo/bar/baz')
+      expect(console.log).to.be.calledWithMatch('/path/to/project/cypress.json')
     })
 
     it('logs err.details', () => {
-      const err = errors.get('PLUGINS_FUNCTION_ERROR', 'foo/bar/baz', new Error('asdf'))
+      const userError = new Error('asdf')
+
+      const err = errors.get('PLUGINS_FUNCTION_ERROR', 'foo/bar/baz', userError)
 
       const ret = errors.log(err)
 
@@ -66,7 +68,7 @@ describe('lib/errors', () => {
 
       expect(console.log).to.be.calledWithMatch('foo/bar/baz')
 
-      expect(console.log).to.be.calledWithMatch(`\n${ chalk.yellow('details huh')}`)
+      expect(console.log).to.be.calledWithMatch(chalk.magenta(userError.stack ?? ''))
     })
 
     it('logs err.stack in development', () => {
diff --git a/packages/server/test/unit/plugins/child/validate_event_spec.js b/packages/server/test/unit/plugins/child/validate_event_spec.js
index c58ead318a86..4fb5cbfbf4b5 100644
--- a/packages/server/test/unit/plugins/child/validate_event_spec.js
+++ b/packages/server/test/unit/plugins/child/validate_event_spec.js
@@ -20,21 +20,7 @@ describe('lib/plugins/child/validate_event', () => {
     const { isValid, error } = validateEvent()
 
     expect(isValid).to.be.false
-    expect(error.message).to.equal(`You must pass a valid event name when registering a plugin.
-
-You passed: \`undefined\`
-
-The following are valid events:
-- after:run
-- after:screenshot
-- after:spec
-- before:browser:launch
-- before:run
-- before:spec
-- dev-server:start
-- file:preprocessor
-- task
-`)
+    expect(error.message).to.equal(`invalid event name`)
   })
 
   it('returns error when called with no event handler', () => {
@@ -48,21 +34,7 @@ The following are valid events:
     const { isValid, error } = validateEvent('invalid:event:name', {})
 
     expect(isValid).to.be.false
-    expect(error.message).to.equal(`You must pass a valid event name when registering a plugin.
-
-You passed: \`invalid:event:name\`
-
-The following are valid events:
-- after:run
-- after:screenshot
-- after:spec
-- before:browser:launch
-- before:run
-- before:spec
-- dev-server:start
-- file:preprocessor
-- task
-`)
+    expect(error.message).to.equal(`invalid event name`)
   })
 
   _.each(events, ([event, type]) => {

From d580755cdacfe471890d353ba86cff3e98e7a648 Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Mon, 7 Feb 2022 12:07:15 -0500
Subject: [PATCH 091/165] fixes more tests

---
 packages/server/__snapshots__/args_spec.js    |   8 +-
 packages/server/__snapshots__/cypress_spec.js | 154 +++++++++---------
 .../server/test/integration/cypress_spec.js   |  22 +--
 3 files changed, 92 insertions(+), 92 deletions(-)

diff --git a/packages/server/__snapshots__/args_spec.js b/packages/server/__snapshots__/args_spec.js
index cdf8434f60eb..d0828f60cfe7 100644
--- a/packages/server/__snapshots__/args_spec.js
+++ b/packages/server/__snapshots__/args_spec.js
@@ -1,5 +1,5 @@
 exports['invalid env error'] = `
-Cypress encountered an error while parsing the argument env
+Cypress encountered an error while parsing the argument: --env
 
 You passed: nonono
 
@@ -7,7 +7,7 @@ The error was: Cannot read property 'split' of undefined
 `
 
 exports['invalid reporter options error'] = `
-Cypress encountered an error while parsing the argument reporterOptions
+Cypress encountered an error while parsing the argument: --reporterOptions
 
 You passed: abc
 
@@ -15,7 +15,7 @@ The error was: Cannot read property 'split' of undefined
 `
 
 exports['invalid config error'] = `
-Cypress encountered an error while parsing the argument config
+Cypress encountered an error while parsing the argument: --config
 
 You passed: xyz
 
@@ -23,7 +23,7 @@ The error was: Cannot read property 'split' of undefined
 `
 
 exports['invalid spec error'] = `
-Cypress encountered an error while parsing the argument spec
+Cypress encountered an error while parsing the argument: --spec
 
 You passed: {}
 
diff --git a/packages/server/__snapshots__/cypress_spec.js b/packages/server/__snapshots__/cypress_spec.js
index c5f11d360496..b26dcf8b407b 100644
--- a/packages/server/__snapshots__/cypress_spec.js
+++ b/packages/server/__snapshots__/cypress_spec.js
@@ -58,29 +58,29 @@ In order to use either of these features a ciBuildId must be determined.
 
 The ciBuildId is automatically detected if you are running Cypress in any of the these CI providers:
 
-- appveyor
-- azure
-- awsCodeBuild
-- bamboo
-- bitbucket
-- buildkite
-- circle
-- codeshipBasic
-- codeshipPro
-- concourse
-- codeFresh
-- drone
-- githubActions
-- gitlab
-- goCD
-- googleCloud
-- jenkins
-- semaphore
-- shippable
-- teamfoundation
-- travis
-- netlify
-- layerci
+ - appveyor
+ - azure
+ - awsCodeBuild
+ - bamboo
+ - bitbucket
+ - buildkite
+ - circle
+ - codeshipBasic
+ - codeshipPro
+ - concourse
+ - codeFresh
+ - drone
+ - githubActions
+ - gitlab
+ - goCD
+ - googleCloud
+ - jenkins
+ - semaphore
+ - shippable
+ - teamfoundation
+ - travis
+ - netlify
+ - layerci
 
 Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually.
 
@@ -96,29 +96,29 @@ In order to use either of these features a ciBuildId must be determined.
 
 The ciBuildId is automatically detected if you are running Cypress in any of the these CI providers:
 
-- appveyor
-- azure
-- awsCodeBuild
-- bamboo
-- bitbucket
-- buildkite
-- circle
-- codeshipBasic
-- codeshipPro
-- concourse
-- codeFresh
-- drone
-- githubActions
-- gitlab
-- goCD
-- googleCloud
-- jenkins
-- semaphore
-- shippable
-- teamfoundation
-- travis
-- netlify
-- layerci
+ - appveyor
+ - azure
+ - awsCodeBuild
+ - bamboo
+ - bitbucket
+ - buildkite
+ - circle
+ - codeshipBasic
+ - codeshipPro
+ - concourse
+ - codeFresh
+ - drone
+ - githubActions
+ - gitlab
+ - goCD
+ - googleCloud
+ - jenkins
+ - semaphore
+ - shippable
+ - teamfoundation
+ - travis
+ - netlify
+ - layerci
 
 Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually.
 
@@ -135,29 +135,29 @@ In order to use either of these features a ciBuildId must be determined.
 
 The ciBuildId is automatically detected if you are running Cypress in any of the these CI providers:
 
-- appveyor
-- azure
-- awsCodeBuild
-- bamboo
-- bitbucket
-- buildkite
-- circle
-- codeshipBasic
-- codeshipPro
-- concourse
-- codeFresh
-- drone
-- githubActions
-- gitlab
-- goCD
-- googleCloud
-- jenkins
-- semaphore
-- shippable
-- teamfoundation
-- travis
-- netlify
-- layerci
+ - appveyor
+ - azure
+ - awsCodeBuild
+ - bamboo
+ - bitbucket
+ - buildkite
+ - circle
+ - codeshipBasic
+ - codeshipPro
+ - concourse
+ - codeFresh
+ - drone
+ - githubActions
+ - gitlab
+ - goCD
+ - googleCloud
+ - jenkins
+ - semaphore
+ - shippable
+ - teamfoundation
+ - travis
+ - netlify
+ - layerci
 
 Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually.
 
@@ -192,11 +192,11 @@ The existing run is: https://dashboard.cypress.io/runs/12345
 
 In order to run in parallel mode each machine must send identical environment parameters such as:
 
-- specs
-- osName
-- osVersion
-- browserName
-- browserVersion (major)
+ - specs
+ - osName
+ - osVersion
+ - browserName
+ - browserVersion (major)
 
 This machine sent the following parameters:
 
@@ -278,7 +278,7 @@ https://on.cypress.io/record-params-without-recording
 `
 
 exports['could not parse config error'] = `
-Cypress encountered an error while parsing the argument config
+Cypress encountered an error while parsing the argument: --config
 
 You passed: xyz
 
@@ -286,7 +286,7 @@ The error was: Cannot read property 'split' of undefined
 `
 
 exports['could not parse env error'] = `
-Cypress encountered an error while parsing the argument env
+Cypress encountered an error while parsing the argument: --env
 
 You passed: a123
 
@@ -294,7 +294,7 @@ The error was: Cannot read property 'split' of undefined
 `
 
 exports['could not parse reporter options error'] = `
-Cypress encountered an error while parsing the argument reporterOptions
+Cypress encountered an error while parsing the argument: --reporterOptions
 
 You passed: nonono
 
diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js
index eb0d10540959..c12f2925ef6a 100644
--- a/packages/server/test/integration/cypress_spec.js
+++ b/packages/server/test/integration/cypress_spec.js
@@ -1,7 +1,6 @@
 /* eslint-disable no-restricted-properties */
 require('../spec_helper')
 
-const chalk = require('chalk')
 const _ = require('lodash')
 const path = require('path')
 const EE = require('events')
@@ -159,11 +158,11 @@ describe('lib/cypress', () => {
       const err = errors.log.getCall(0).args[0]
 
       if (msg1) {
-        expect(err.message, 'error text').to.include(msg1)
+        expect(stripAnsi(err.message), 'error text').to.include(msg1)
       }
 
       if (msg2) {
-        expect(err.message, 'second error text').to.include(msg2)
+        expect(stripAnsi(err.message), 'second error text').to.include(msg2)
       }
 
       return err
@@ -680,12 +679,13 @@ describe('lib/cypress', () => {
       })
     })
 
-    it('logs error when supportFile doesn\'t exist', function () {
+    it(`logs error when supportFile doesn't exist`, function () {
       return settings.write(this.idsPath, { supportFile: '/does/not/exist' })
       .then(() => {
         return cypress.start([`--run-project=${this.idsPath}`])
-      }).then(() => {
-        this.expectExitWithErr('SUPPORT_FILE_NOT_FOUND', `Your ${chalk.blue('supportFile')} is set to ${chalk.blue('/does/not/exist')}`)
+      })
+      .then(() => {
+        this.expectExitWithErr('SUPPORT_FILE_NOT_FOUND', `Your supportFile is missing or invalid: /does/not/exist`)
       })
     })
 
@@ -702,7 +702,7 @@ describe('lib/cypress', () => {
         const found1 = _.find(argsSet, (args) => {
           return _.find(args, (arg) => {
             return arg.message && arg.message.includes(
-              `Browser: ${chalk.blue('foo')} was not found on your system or is not supported by Cypress.`,
+              `Browser: foo was not found on your system or is not supported by Cypress.`,
             )
           })
         })
@@ -737,9 +737,9 @@ describe('lib/cypress', () => {
         .then(() => {
           // includes the search spec
           this.expectExitWithErr('NO_SPECS_FOUND', 'path/to/spec')
-          this.expectExitWithErr('NO_SPECS_FOUND', 'We searched for any files matching this glob pattern:')
+          this.expectExitWithErr('NO_SPECS_FOUND', 'We searched for specs matching this glob pattern:')
           // includes the project path
-          this.expectExitWithErr('NO_SPECS_FOUND', this.todosPath)
+          this.expectExitWithErr('NO_SPECS_FOUND', path.join(this.todosPath, 'path/to/spec'))
         })
       })
 
@@ -762,8 +762,8 @@ describe('lib/cypress', () => {
           '--config=integrationFolder=cypress/specs',
         ])
         .then(() => {
-          this.expectExitWithErr('NO_SPECS_FOUND', 'We searched for any files inside of this folder:')
-          this.expectExitWithErr('NO_SPECS_FOUND', 'cypress/specs')
+          this.expectExitWithErr('NO_SPECS_FOUND', 'We searched for specs inside of this folder:')
+          this.expectExitWithErr('NO_SPECS_FOUND', path.join(this.todosPath, 'cypress/specs'))
         })
       })
     })

From 38c3e8327d65dcb7a80eb02afa72d20cb9b94177 Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Mon, 7 Feb 2022 12:30:35 -0500
Subject: [PATCH 092/165] fix type checking

---
 packages/errors/src/errTemplate.ts   |  2 ++
 packages/errors/src/errors.ts        |  4 ++--
 packages/server/lib/config.ts        |  6 ++----
 packages/server/lib/project_utils.ts |  8 +++-----
 packages/server/lib/util/settings.ts | 12 ++++++++----
 5 files changed, 17 insertions(+), 15 deletions(-)

diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts
index fb30d9537a5c..7545ab279f78 100644
--- a/packages/errors/src/errTemplate.ts
+++ b/packages/errors/src/errTemplate.ts
@@ -92,6 +92,8 @@ function listFlags (
     if (v) {
       return `The ${flag} flag you passed was: ${theme.yellow(v)}`
     }
+
+    return undefined
   })
   .compact()
   .join('\n')
diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts
index e61d0e5e83ac..e9e3b47c60ed 100644
--- a/packages/errors/src/errors.ts
+++ b/packages/errors/src/errors.ts
@@ -491,8 +491,8 @@ export const AllCypressErrors = {
 
         https://on.cypress.io/dashboard`
   },
-  NO_PROJECT_ID: (configFilePath: string) => {
-    return errTemplate`Can't find ${`projectId`} in the config file: ${fmt.path(configFilePath)}`
+  NO_PROJECT_ID: (configFilePath: string | false) => {
+    return errTemplate`Can't find ${`projectId`} in the config file: ${fmt.path(configFilePath || '')}`
   },
   NO_PROJECT_FOUND_AT_PROJECT_ROOT: (projectRoot: string) => {
     return errTemplate`Can't find a project at the path: ${fmt.path(projectRoot)}`
diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts
index 804271739124..c7a3b2304001 100644
--- a/packages/server/lib/config.ts
+++ b/packages/server/lib/config.ts
@@ -448,7 +448,7 @@ export function setSupportFileAndFolder (obj, defaults) {
     return fs.pathExists(obj.supportFile)
     .then((found) => {
       if (!found) {
-        errors.throw('SUPPORT_FILE_NOT_FOUND', obj.supportFile, obj.configFile || defaults.configFile)
+        errors.throw('SUPPORT_FILE_NOT_FOUND', obj.supportFile)
       }
 
       return debug('switching to found file %s', obj.supportFile)
@@ -465,9 +465,7 @@ export function setSupportFileAndFolder (obj, defaults) {
     })
     .then((result) => {
       if (result === null) {
-        const configFile = obj.configFile || defaults.configFile
-
-        return errors.throw('SUPPORT_FILE_NOT_FOUND', path.resolve(obj.projectRoot, sf), configFile)
+        return errors.throw('SUPPORT_FILE_NOT_FOUND', path.resolve(obj.projectRoot, sf))
       }
 
       debug('setting support file to %o', { result })
diff --git a/packages/server/lib/project_utils.ts b/packages/server/lib/project_utils.ts
index 07ad6e08a0a7..b646c3c70785 100644
--- a/packages/server/lib/project_utils.ts
+++ b/packages/server/lib/project_utils.ts
@@ -1,11 +1,9 @@
 import Debug from 'debug'
 import path from 'path'
-
-import * as settings from './util/settings'
+import { CYPRESS_CONFIG_FILES } from './configFiles'
 import errors from './errors'
-import { fs } from './util/fs'
 import { escapeFilenameInUrl } from './util/escape_filename'
-import { CYPRESS_CONFIG_FILES } from './configFiles'
+import { fs } from './util/fs'
 
 const debug = Debug('cypress:server:project_utils')
 
@@ -126,7 +124,7 @@ export const checkSupportFile = async ({
     const found = await fs.pathExists(supportFile)
 
     if (!found) {
-      errors.throw('SUPPORT_FILE_NOT_FOUND', supportFile, settings.configFile({ configFile }) || '')
+      errors.throw('SUPPORT_FILE_NOT_FOUND', supportFile)
     }
   }
 
diff --git a/packages/server/lib/util/settings.ts b/packages/server/lib/util/settings.ts
index dc2a64db6587..ec69672ab396 100644
--- a/packages/server/lib/util/settings.ts
+++ b/packages/server/lib/util/settings.ts
@@ -130,9 +130,13 @@ export function configFile (options: SettingsOptions = {}) {
   return options.configFile === false ? false : (options.configFile || 'cypress.json')
 }
 
-export function id (projectRoot, options = {}) {
+export function id (projectRoot, options: SettingsOptions = {}) {
   const file = pathToConfigFile(projectRoot, options)
 
+  if (!file) {
+    return Promise.resolve(null)
+  }
+
   return fs.readJson(file)
   .then((config) => config.projectId)
   .catch(() => {
@@ -141,12 +145,12 @@ export function id (projectRoot, options = {}) {
 }
 
 export function read (projectRoot, options: SettingsOptions = {}) {
-  if (options.configFile === false) {
+  const file = pathToConfigFile(projectRoot, options)
+
+  if (!file) {
     return Promise.resolve({})
   }
 
-  const file = pathToConfigFile(projectRoot, options)
-
   const readPromise = /\.json$/.test(file) ? fs.readJSON(path.resolve(projectRoot, file)) : requireAsync(file, {
     projectRoot,
     loadErrorCode: 'CONFIG_FILE_ERROR',

From e600767e9236fad8943276d6506493200b5e7c4a Mon Sep 17 00:00:00 2001
From: Tim Griesser 
Date: Mon, 7 Feb 2022 16:49:13 -0500
Subject: [PATCH 093/165] wip: consistent handling of interpolated values

---
 .gitignore                                    |   2 +
 guides/error-handling.md                      |   9 +-
 .../AUTH_COULD_NOT_LAUNCH_BROWSER.html        |   2 +-
 packages/errors/src/errTemplate.ts            | 243 ++++++++++--------
 packages/errors/src/errorTypes.ts             |   7 +-
 packages/errors/src/errorUtils.ts             |   4 +-
 packages/errors/src/errors.ts                 | 207 ++++++++-------
 packages/errors/test/unit/errTemplate_spec.ts |  20 +-
 .../test/unit/visualSnapshotErrors_spec.ts    |  34 ++-
 packages/server/lib/plugins/util.js           |   2 +-
 10 files changed, 285 insertions(+), 245 deletions(-)

diff --git a/.gitignore b/.gitignore
index f55d29e87acf..d0c3a004ebc4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -87,6 +87,8 @@ system-tests/fixtures/large-img
 
 # from errors
 /packages/errors/__snapshot-images__
+/packages/errors/__snapshot-md__
+/packages/errors/__snapshot-html-local__
 
 # graphql, auto-generated
 /packages/launchpad/src/generated
diff --git a/guides/error-handling.md b/guides/error-handling.md
index d5b2a60dbda1..8bb2505b80c2 100644
--- a/guides/error-handling.md
+++ b/guides/error-handling.md
@@ -25,12 +25,13 @@ Return Value of `errTemplate` (`ErrTemplateResult`):
 
 ```ts
 {
-  message: string, // Will always exist, this is the terminal-formatted error message
+  // Will always exist, this is the terminal-formatted error message
+  message: string, 
+  // Will always exist, this is the browser-formatted error message
+  messageMarkdown: string, 
   details?: string, // Exists if there is `details()` call in the errTemplate
   originalError?: ErrorLike // Exists if an error was passed into the `details()`
-  forBrowser(): {
-    message: string // Ansi stripped message for rendering in the browser, with the variables wrapped in backticks
-  }
+
 }
 ```
 
diff --git a/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html b/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html
index 334c12975f53..0508eea7e611 100644
--- a/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html
+++ b/packages/errors/__snapshot-html__/AUTH_COULD_NOT_LAUNCH_BROWSER.html
@@ -36,5 +36,5 @@
     
     
Cypress was unable to open your installed browser. To continue logging in, please open this URL in your web browser:
 
-https://dashboard.cypress.io/login
+https://dashboard.cypress.io/login
 
\ No newline at end of file diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index 7545ab279f78..037bfecd60c1 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -1,11 +1,10 @@ -import assert from 'assert' import chalk from 'chalk' import _ from 'lodash' -import stripAnsi from 'strip-ansi' import { trimMultipleNewLines } from './errorUtils' import { stripIndent } from './stripIndent' import type { ErrTemplateResult, SerializedError } from './errorTypes' +import assert from 'assert' interface ListOptions { prefix?: string @@ -20,8 +19,83 @@ const theme = { magenta: chalk.magentaBright, } -export const fmt = { +type AllowedPartialArg = Guard | Format | PartialErr | null + +type AllowedTemplateArg = StackTrace | AllowedPartialArg + +class PartialErr { + constructor (readonly strArr: TemplateStringsArray, readonly args: AllowedTemplateArg[]) {} +} + +interface FormatConfig { + block: true +} + +type ToFormat = string | number | Error | object | null | Guard | AllowedTemplateArg + +class Format { + constructor (readonly type: keyof typeof fmtHighlight, readonly val: ToFormat, readonly config?: FormatConfig) {} + + formatVal (target: 'ansi' | 'markdown') { + if (this.val instanceof Guard) { + return this.val.val + } + + return target === 'ansi' ? this.formatAnsi() : this.formatMarkdown() + } + + private formatAnsi () { + const val = this.prepVal('ansi') + + return fmtHighlight[this.type](val) + } + + private formatMarkdown () { + if (this.type === 'code') { + return this.prepVal('markdown') + } + + return mdFence(this.prepVal('markdown')) + } + + private prepVal (target: 'ansi' | 'markdown'): string { + if (this.val instanceof PartialErr) { + return prepMessage(this.val.strArr, this.val.args, target) + } + + if (isErrorLike(this.val)) { + return `\n${this.val.name}: ${this.val.message}` + } + + if (this.val && (typeof this.val === 'object' || Array.isArray(this.val))) { + return JSON.stringify(this.val, null, 2) + } + + return `${this.val}` + } +} + +function mdFence (val: string) { + if (isMultiLine(val)) { + return `\`\`\`${val}\`\`\`` + } + + return `\`${val}\`` +} + +function isMultiLine (val: string) { + return Boolean(val.split('\n').length > 1) +} + +function makeFormat (type: keyof typeof fmtHighlight, config?: FormatConfig) { + return (val: ToFormat) => { + return new Format(type, val, config) + } +} + +const fmtHighlight = { meta: theme.gray, + comment: theme.gray, path: theme.blue, code: theme.blue, url: theme.blue, @@ -29,8 +103,19 @@ export const fmt = { highlight: theme.yellow, highlightSecondary: theme.magenta, highlightTertiary: theme.blue, +} as const + +export const fmt = { + meta: makeFormat('meta'), + comment: makeFormat('comment'), + path: makeFormat('path'), + code: makeFormat('code', { block: true }), + url: makeFormat('url'), + flag: makeFormat('flag'), + highlight: makeFormat('highlight'), + highlightSecondary: makeFormat('highlightSecondary'), + highlightTertiary: makeFormat('highlightTertiary'), off: guard, - stringify, terminal, listItem, listItems, @@ -50,7 +135,7 @@ function cypressVersion (version: string) { throw new Error('Cypress version provided must be in x.x.x format') } - return `Cypress version ${version}` + return guard(`Cypress version ${version}`) } function _item (item: string, options: ListOptions = {}) { @@ -112,22 +197,6 @@ export function guard (val: string | number) { return new Guard(val) } -export class Backtick { - constructor (readonly val: string | number) {} -} - -export function backtick (val: string) { - return new Backtick(val) -} - -export class Stringify { - constructor (readonly val: any) {} -} - -export function stringify (val: object) { - return new Stringify(val) -} - /** * Marks the value as "details". This is when we print out the stack trace to the console * (if it's an error), or use the stack trace as the originalError @@ -147,30 +216,12 @@ export function isErrorLike (err: any): err is SerializedError | Error { return err && typeof err === 'object' && Boolean('name' in err && 'message' in err) } -function jsonStringify (obj: object) { - return JSON.stringify(obj, null, 2) -} - -function isScalar (val: any): val is string | number | null | boolean { - return typeof val === 'string' || - typeof val === 'number' || - typeof val === 'boolean' || - val == null -} - /** - * Formats the value passed in via details(), but does not color the value here, since it - * is printed separately in the console. - * - * @param val - * @returns + * Creates a "partial" that can be interpolated into the full Error template. The partial runs through + * stripIndent prior to being added into the template */ -function formatMsgDetails (val: any): string { - return isScalar(val) - ? `${val}` - : isErrorLike(val) - ? val.stack || val.message || val.name - : jsonStringify(val) +export const errPartial = (templateStr: TemplateStringsArray, ...args: AllowedPartialArg[]) => { + return new PartialErr(templateStr, args) } /** @@ -184,81 +235,53 @@ function formatMsgDetails (val: any): string { * - Wrap every arg in backticks, for better rendering in markdown * - If details is an error, it gets provided as originalError */ -export const errTemplate = (strings: TemplateStringsArray, ...args: Array): ErrTemplateResult => { +export const errTemplate = (strings: TemplateStringsArray, ...args: AllowedTemplateArg[]): ErrTemplateResult => { let originalError: Error | undefined = undefined - let messageDetails: string | undefined - - function prepMessage (forTerminal = true) { - function formatVal (val: string | number | Error | object | null) { - // if (isErrorLike(val)) { - // return `${val.name}: ${val.message}` - // } - - if (isScalar(val)) { - // If it's for the terminal, wrap in blue, otherwise wrap in backticks if we don't see any backticks - if (forTerminal) { - return theme.yellow(`${val}`) - } - - return String(val).includes('`') ? String(val) : `\`${val}\`` - } - try { - const objJson = jsonStringify(val) + const msg = trimMultipleNewLines(prepMessage(strings, args, 'ansi')) - return forTerminal ? objJson : stripIndent` - \`\`\` - ${objJson} - \`\`\` - ` - } catch { - return String(val) - } - } + return { + message: msg, + originalError, + messageMarkdown: trimMultipleNewLines(prepMessage(strings, args, 'markdown')), + } +} - let templateArgs: Array = [] - let detailsSeen = false - - for (const arg of args) { - if (arg instanceof Backtick) { - templateArgs.push(`\`${arg.val}\``) - } else if (arg instanceof Guard) { - templateArgs.push(arg.val) - } else if (arg instanceof Stringify) { - templateArgs.push(theme.white(jsonStringify(arg.val))) - } else if (arg instanceof StackTrace) { - assert(!detailsSeen, `Cannot use details() multiple times in the same errTemplate`) - detailsSeen = true - const { val } = arg - - messageDetails = chalk.magenta(formatMsgDetails(val)) - if (isErrorLike(val)) { - originalError = val +function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTemplateArg[], target: 'ansi' | 'markdown'): string { + let originalError: Error | undefined = undefined + const templateArgs = [] + + for (const arg of args) { + // We assume null/undefined values are skipped when rendering, for conditional templating + if (arg == null) { + templateArgs.push('') + } else if (arg instanceof Guard) { + // Guard prevents any formatting + templateArgs.push(arg.val) + } else if (arg instanceof Format) { + // Format = stringify & color ANSI, or make a markdown block + templateArgs.push(arg.formatVal(target)) + } else if (arg instanceof StackTrace) { + if (isErrorLike(arg.val)) { + assert(!originalError, `Cannot use fmt.stackTrace() multiple times in the same errTemplate`) + originalError = arg.val + } else { + if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { + throw new Error(`Cannot use arg.stackTrace with a non error-like value, saw ${JSON.stringify(arg.val)}`) } - templateArgs.push('') - } else if (isErrorLike(arg)) { - templateArgs.push(chalk.magenta(`${arg.name}: ${arg.message}`)) - } else { - templateArgs.push(formatVal(arg)) + const err = new Error() + + err.stack = typeof arg.val === 'string' ? arg.val : JSON.stringify(arg.val) + originalError = err } + } else if (arg instanceof PartialErr) { + // Partial error = prepMessage + interpolate + templateArgs.push(prepMessage(arg.strArr, arg.args, target)) + } else { + throw new Error(`Invalid value passed to prepMessage, saw ${arg}`) } - - return stripIndent(strings, ...templateArgs) } - const msg = trimMultipleNewLines(prepMessage()) - - return { - message: msg, - details: messageDetails, - originalError, - forBrowser () { - const msg = trimMultipleNewLines(prepMessage(false)) - - return { - message: stripAnsi(msg), - } - }, - } + return stripIndent(templateStrings, ...templateArgs) } diff --git a/packages/errors/src/errorTypes.ts b/packages/errors/src/errorTypes.ts index bfeac7c5578f..143db3027719 100644 --- a/packages/errors/src/errorTypes.ts +++ b/packages/errors/src/errorTypes.ts @@ -18,6 +18,7 @@ export interface ErrorLike { * if one exists, and an isCypressError for duck-type checking */ export interface CypressError extends ErrorLike { + messageMarkdown: string type: keyof typeof AllCypressErrors isCypressErr: boolean originalError?: CypressError | ErrorLike @@ -25,17 +26,13 @@ export interface CypressError extends ErrorLike { code?: string | number errno?: string | number stackWithoutMessage?: string - forBrowser: ErrTemplateResult['forBrowser'] } export interface ErrTemplateResult { message: string + messageMarkdown: string details?: string originalError?: Error - forBrowser(): { - message: string - details?: string - } } export interface ClonedError { diff --git a/packages/errors/src/errorUtils.ts b/packages/errors/src/errorUtils.ts index 28310bbc88ba..41f2fc06a2ad 100644 --- a/packages/errors/src/errorUtils.ts +++ b/packages/errors/src/errorUtils.ts @@ -38,8 +38,8 @@ type AllowedChalkColors = 'red' | 'blue' | 'green' | 'magenta' | 'yellow' export const logError = function (err: CypressError | ErrorLike, color: AllowedChalkColors = 'red') { console.log(chalk[color](err.message)) - if (err.details) { - console.log(`\n${err.details}`) + if (err.originalError) { + console.log(`\n${err.originalError.stack}`) } // bail if this error came from known diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index e9e3b47c60ed..aeff1e1d5896 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -5,9 +5,8 @@ import _ from 'lodash' import path from 'path' import stripAnsi from 'strip-ansi' import { humanTime, logError, pluralize } from './errorUtils' -import { backtick, errTemplate, fmt } from './errTemplate' +import { errPartial, errTemplate, fmt } from './errTemplate' import { stackWithoutMessage } from './stackUtils' -import { stripIndent } from './stripIndent' import type { ClonedError, CypressError, ErrorLike, ErrTemplateResult } from './errorTypes' @@ -20,7 +19,7 @@ const displayRetriesRemaining = function (tries: number) { const lastTryNewLine = tries === 1 ? '\n' : '' - return chalk.gray( + return fmt.meta( `We will try connecting to it ${tries} more ${times}...${lastTryNewLine}`, ) } @@ -28,16 +27,16 @@ const displayRetriesRemaining = function (tries: number) { const getUsedTestsMessage = (limit: number, usedTestsMessage: string) => { return _.isFinite(limit) ? fmt.off(`The limit is ${fmt.highlight(`${limit}`)} ${usedTestsMessage} results.`) - : '' + : fmt.off('') } export const warnIfExplicitCiBuildId = function (ciBuildId?: string | null) { if (!ciBuildId) { - return '' + return null } - return `\ -It also looks like you also passed in an explicit --ci-build-id flag. + return errPartial`\ +It also looks like you also passed in an explicit ${fmt.flag('--ci-build-id')} flag. This is only necessary if you are NOT running in one of our supported CI providers. @@ -57,7 +56,7 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${fmt.stackTrace(arg1)}` + ${fmt.highlightSecondary(arg1)}` }, CANNOT_REMOVE_OLD_BROWSER_PROFILES: (arg1: string) => { return errTemplate`\ @@ -65,7 +64,7 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${fmt.stackTrace(arg1)}` + ${fmt.highlightSecondary(arg1)}` }, VIDEO_RECORDING_FAILED: (arg1: string) => { return errTemplate`\ @@ -73,7 +72,7 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${fmt.stackTrace(arg1)}` + ${fmt.highlightSecondary(arg1)}` }, VIDEO_POST_PROCESSING_FAILED: (arg1: string) => { return errTemplate`\ @@ -81,20 +80,20 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${fmt.stackTrace(arg1)}` + ${fmt.highlightSecondary(arg1)}` }, CHROME_WEB_SECURITY_NOT_SUPPORTED: (browser: string) => { return errTemplate`\ - Your project has set the configuration option: ${`chromeWebSecurity`} to ${fmt.highlightTertiary(`false`)} + Your project has set the configuration option: ${fmt.highlight(`chromeWebSecurity`)} to ${fmt.highlightTertiary(`false`)} This option will not have an effect in ${fmt.off(_.capitalize(browser))}. Tests that rely on web security being disabled will not run as expected.` }, BROWSER_NOT_FOUND_BY_NAME: (browser: string, foundBrowsersStr: string[]) => { - let canarySuffix = '' + let canarySuffix = null if (browser === 'canary') { - canarySuffix += '\n\n' - canarySuffix += stripIndent`\ + canarySuffix = errPartial`\ + ${fmt.off('\n\n')} Note: In ${fmt.cypressVersion(`4.0.0`)}, Canary must be launched as ${fmt.highlightSecondary(`chrome:canary`)}, not ${fmt.highlightSecondary(`canary`)}. See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0.` @@ -103,7 +102,7 @@ export const AllCypressErrors = { return errTemplate`\ Can't run because you've entered an invalid browser name. - Browser: ${browser} was not found on your system or is not supported by Cypress. + Browser: ${fmt.highlight(browser)} was not found on your system or is not supported by Cypress. Cypress supports the following browsers: ${fmt.listItems(['electron', 'chrome', 'chromium', 'edge', 'firefox'])} @@ -111,7 +110,7 @@ export const AllCypressErrors = { You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: - ${fmt.listItems(foundBrowsersStr)}${fmt.off(canarySuffix)}` + ${fmt.listItems(foundBrowsersStr)}${canarySuffix}` }, BROWSER_NOT_FOUND_BY_PATH: (arg1: string, arg2: string) => { return errTemplate`\ @@ -119,13 +118,13 @@ export const AllCypressErrors = { The output from the command we ran was: - ${fmt.stackTrace(arg2)}` + ${fmt.highlightSecondary(arg2)}` }, NOT_LOGGED_IN: () => { return errTemplate`\ You're not logged in. - Run ${`cypress open`} to open the Desktop App and log in.` + Run ${fmt.highlight(`cypress open`)} to open the Desktop App and log in.` }, TESTS_DID_NOT_START_RETRYING: (arg1: string) => { return errTemplate`Timed out waiting for the browser to connect. ${fmt.off(arg1)}` @@ -147,7 +146,7 @@ export const AllCypressErrors = { The server's response was: - ${arg1.response}` + ${fmt.highlightSecondary(arg1.response)}` /* Because of fmt.listFlags() and fmt.listItems() */ /* eslint-disable indent */ }, @@ -164,7 +163,7 @@ export const AllCypressErrors = { The server's response was: - ${arg1.response}` + ${fmt.highlightSecondary(arg1.response)}` }, DASHBOARD_CANNOT_PROCEED_IN_SERIAL: (arg1: {flags: any, response: Error}) => { return errTemplate`\ @@ -177,7 +176,7 @@ export const AllCypressErrors = { The server's response was: - ${arg1.response}` + ${fmt.highlightSecondary(arg1.response)}` }, DASHBOARD_UNKNOWN_INVALID_REQUEST: (arg1: {flags: any, response: Error}) => { return errTemplate`\ @@ -194,14 +193,14 @@ export const AllCypressErrors = { The server's response was: - ${arg1.response}` + ${fmt.highlightSecondary(arg1.response)}` }, DASHBOARD_UNKNOWN_CREATE_RUN_WARNING: (arg1: {props: any, message: string}) => { return errTemplate`\ - Warning from Cypress Dashboard: ${arg1.message} + Warning from Cypress Dashboard: ${fmt.highlight(arg1.message)} Details: - ${fmt.stringify(arg1.props)}` + ${fmt.highlightSecondary(arg1.props)}` }, DASHBOARD_STALE_RUN: (arg1: {runUrl: string, [key: string]: any}) => { return errTemplate`\ @@ -290,7 +289,7 @@ export const AllCypressErrors = { This machine sent the following parameters: - ${fmt.stringify(arg1.parameters)} + ${fmt.highlightSecondary(arg1.parameters)} https://on.cypress.io/parallel-group-params-mismatch` }, @@ -314,7 +313,7 @@ export const AllCypressErrors = { }, DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS: () => { return errTemplate`\ - Deprecation Warning: The ${`before:browser:launch`} plugin event changed its signature in ${fmt.cypressVersion(`4.0.0`)} + Deprecation Warning: The ${fmt.highlight(`before:browser:launch`)} plugin event changed its signature in ${fmt.cypressVersion(`4.0.0`)} The event switched from yielding the second argument as an ${fmt.highlightSecondary(`array`)} of browser arguments to an options ${fmt.highlightSecondary(`object`)} with an ${fmt.highlightSecondary(`args`)} property. @@ -393,7 +392,7 @@ export const AllCypressErrors = { return errTemplate`\ You passed the ${fmt.flag(`--record`)} flag but this project has not been setup to record. - This project is missing the ${`projectId`} inside of: ${fmt.path(configFilePath)} + This project is missing the ${fmt.highlight(`projectId`)} inside of: ${fmt.path(configFilePath)} We cannot uniquely identify this project without this id. @@ -407,7 +406,7 @@ export const AllCypressErrors = { return errTemplate`\ This project has been configured to record runs on our Dashboard. - It currently has the projectId: ${arg1} + It currently has the projectId: ${fmt.highlight(arg1)} You also provided your Record Key, but you did not pass the ${fmt.flag(`--record`)} flag. @@ -427,21 +426,21 @@ export const AllCypressErrors = { return errTemplate`\ Recording this run failed because the request was invalid. - ${arg1.message} + ${fmt.highlight(arg1.message)} Errors: - ${fmt.stringify(arg1.errors)} + ${fmt.highlightSecondary(arg1.errors)} Request Sent: - ${fmt.stringify(arg1.object)}` + ${fmt.highlightTertiary(arg1.object)}` }, RECORDING_FROM_FORK_PR: () => { return errTemplate`\ Warning: It looks like you are trying to record this run from a forked PR. - The ${`Record Key`} is missing. Your CI provider is likely not passing private environment variables to builds from forks. + The ${fmt.highlight(`Record Key`)} is missing. Your CI provider is likely not passing private environment variables to builds from forks. These results will not be recorded. @@ -455,7 +454,7 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${apiErr}` + ${fmt.highlightSecondary(apiErr)}` }, DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE: (apiErr: Error) => { return errTemplate`\ @@ -465,11 +464,11 @@ export const AllCypressErrors = { This error will not alter the exit code. - ${apiErr}` + ${fmt.highlightSecondary(apiErr)}` }, DASHBOARD_RECORD_KEY_NOT_VALID: (recordKey: string, projectId: string) => { return errTemplate`\ - Your Record Key ${recordKey} is not valid with this projectId: ${fmt.highlightSecondary(projectId)} + Your Record Key ${fmt.highlight(recordKey)} is not valid with this projectId: ${fmt.highlightSecondary(projectId)} It may have been recently revoked by you or another user. @@ -479,7 +478,7 @@ export const AllCypressErrors = { }, DASHBOARD_PROJECT_NOT_FOUND: (projectId: string, configFileBaseName: string) => { return errTemplate`\ - We could not find a Dashboard project with the projectId: ${projectId} + We could not find a Dashboard project with the projectId: ${fmt.highlight(projectId)} This ${fmt.highlightSecondary(`projectId`)} came from your ${fmt.path(configFileBaseName)} file or an environment variable. @@ -492,7 +491,7 @@ export const AllCypressErrors = { https://on.cypress.io/dashboard` }, NO_PROJECT_ID: (configFilePath: string | false) => { - return errTemplate`Can't find ${`projectId`} in the config file: ${fmt.path(configFilePath || '')}` + return errTemplate`Can't find ${fmt.highlight(`projectId`)} in the config file: ${fmt.path(configFilePath || '')}` }, NO_PROJECT_FOUND_AT_PROJECT_ROOT: (projectRoot: string) => { return errTemplate`Can't find a project at the path: ${fmt.path(projectRoot)}` @@ -504,11 +503,11 @@ export const AllCypressErrors = { return errTemplate`Can't create project's secret key.` }, PORT_IN_USE_SHORT: (arg1: string | number) => { - return errTemplate`Port ${arg1} is already in use.` + return errTemplate`Port ${fmt.highlight(arg1)} is already in use.` }, PORT_IN_USE_LONG: (arg1: string | number) => { return errTemplate`\ - Can't run project because port is currently in use: ${arg1} + Can't run project because port is currently in use: ${fmt.highlight(arg1)} Assign a different port with the ${fmt.flag(`--port `)} argument or shut down the other running process.` }, @@ -535,7 +534,7 @@ export const AllCypressErrors = { ${fmt.listItem(folderPath)}` } - const globPath = path.join(fmt.path(folderPath), globPattern) + const globPath = path.join(chalk.blue(folderPath), globPattern) return errTemplate`\ Can't run because ${fmt.highlightSecondary(`no spec files`)} were found. @@ -569,7 +568,7 @@ export const AllCypressErrors = { }, SUPPORT_FILE_NOT_FOUND: (supportFilePath: string) => { return errTemplate`\ - Your ${`supportFile`} is missing or invalid: ${fmt.path(supportFilePath)} + Your ${fmt.highlight(`supportFile`)} is missing or invalid: ${fmt.path(supportFilePath)} The supportFile must be a .js, .ts, .coffee file or be supported by your preprocessor plugin (if configured). @@ -581,7 +580,7 @@ export const AllCypressErrors = { }, PLUGINS_FILE_ERROR: (pluginsFilePath: string, err: Error) => { return errTemplate`\ - Your ${`pluginsFile`} is invalid: ${fmt.path(pluginsFilePath)} + Your ${fmt.highlight(`pluginsFile`)} is invalid: ${fmt.path(pluginsFilePath)} It threw an error when required, check the stack trace below: @@ -590,7 +589,7 @@ export const AllCypressErrors = { }, PLUGINS_FILE_NOT_FOUND: (pluginsFilePath: string) => { return errTemplate`\ - Your ${`pluginsFile`} was not found at path: ${fmt.path(pluginsFilePath)} + Your ${fmt.highlight(`pluginsFile`)} was not found at path: ${fmt.path(pluginsFilePath)} Create this file, or set pluginsFile to ${fmt.highlightSecondary(`false`)} if a plugins file is not necessary for your project. @@ -598,27 +597,27 @@ export const AllCypressErrors = { ` }, PLUGINS_DIDNT_EXPORT_FUNCTION: (pluginsFilePath: string, exported: any) => { - const code = stripIndent` + const code = errPartial` module.exports = (on, config) => { - ${fmt.meta(`// configure plugins here`)} + ${fmt.comment(`// configure plugins here`)} }` return errTemplate`\ - The ${`pluginsFile`} must export a function with the following signature: + The ${fmt.highlight(`pluginsFile`)} must export a function with the following signature: - ${fmt.meta(`// ${pluginsFilePath}`)} + ${fmt.comment(`// ${pluginsFilePath}`)} ${fmt.code(code)} Instead it exported: - ${fmt.stringify(exported)} + ${fmt.highlightSecondary(exported)} Learn more: https://on.cypress.io/plugins-api ` }, PLUGINS_FUNCTION_ERROR: (pluginsFilePath: string, err: Error) => { return errTemplate`\ - The function exported by the ${`pluginsFile`} threw an error: ${fmt.path(pluginsFilePath)} + The function exported by the ${fmt.highlight(`pluginsFile`)} threw an error: ${fmt.path(pluginsFilePath)} ${fmt.stackTrace(err)} ` @@ -636,7 +635,7 @@ export const AllCypressErrors = { // TODO: test this PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` - Your ${`pluginsFile`} threw a validation error: ${fmt.path(arg1)} + Your ${fmt.highlight(`pluginsFile`)} threw a validation error: ${fmt.path(arg1)} ${fmt.stackTrace(arg2)} ` @@ -648,7 +647,7 @@ export const AllCypressErrors = { You must pass a valid event name when registering a plugin. - You passed: ${invalidEventName} + You passed: ${fmt.highlight(invalidEventName)} The following are valid events: @@ -668,7 +667,7 @@ export const AllCypressErrors = { The error was: - ${arg2} + ${fmt.highlight(arg2)} This occurred while Cypress was compiling and bundling your test code. This is usually caused by: @@ -684,7 +683,7 @@ export const AllCypressErrors = { return errTemplate`\ We found an invalid value in the file: ${fmt.path(configFileBaseName)} - ${errMessage}` + ${fmt.highlight(errMessage)}` // happens when there is an invalid config value is returned from the // project's plugins file like "cypress/plugins.index.js" }, @@ -693,7 +692,7 @@ export const AllCypressErrors = { return errTemplate`\ An invalid configuration value returned from the plugins file: ${fmt.path(relativePluginsPath)} - ${errMsg}` + ${fmt.highlight(errMsg)}` // general configuration error not-specific to configuration or plugins files }, // TODO: test this @@ -701,13 +700,13 @@ export const AllCypressErrors = { return errTemplate`\ We found an invalid configuration value: - ${errMsg}` + ${fmt.highlight(errMsg)}` }, RENAMED_CONFIG_OPTION: (arg1: {name: string, newName: string}) => { return errTemplate`\ - The ${arg1.name} configuration option you have supplied has been renamed. + The ${fmt.highlight(arg1.name)} configuration option you have supplied has been renamed. - Please rename ${arg1.name} to ${fmt.highlightSecondary(arg1.newName)}` + Please rename ${fmt.highlight(arg1.name)} to ${fmt.highlightSecondary(arg1.newName)}` }, CANNOT_CONNECT_BASE_URL: () => { return errTemplate`\ @@ -721,7 +720,7 @@ export const AllCypressErrors = { ${fmt.listItem(arg1)} - This server has been configured as your ${`baseUrl`}, and tests will likely fail if it is not running.` + This server has been configured as your ${fmt.highlight(`baseUrl`)}, and tests will likely fail if it is not running.` }, // TODO: test this CANNOT_CONNECT_BASE_URL_RETRYING: (arg1: {attempt: number, baseUrl: string, remaining: number, delay: number}) => { @@ -732,19 +731,19 @@ export const AllCypressErrors = { ${fmt.listItem(arg1.baseUrl)} - We are verifying this server because it has been configured as your ${`baseUrl`}. + We are verifying this server because it has been configured as your ${fmt.highlight(`baseUrl`)}. Cypress automatically waits until your server is accessible before running tests. ${displayRetriesRemaining(arg1.remaining)}` default: - return errTemplate`${fmt.off(displayRetriesRemaining(arg1.remaining))}` + return errTemplate`${displayRetriesRemaining(arg1.remaining)}` } }, // TODO: test this INVALID_REPORTER_NAME: (arg1: {name: string, paths: string[], error: string}) => { return errTemplate`\ - Error loading the reporter: ${arg1.name} + Error loading the reporter: ${fmt.highlight(arg1.name)} We searched for the reporter in these paths: @@ -752,7 +751,7 @@ export const AllCypressErrors = { The error was: - ${chalk.yellow(arg1.error)} + ${fmt.stackTrace(arg1.error)} Learn more at https://on.cypress.io/reporters` }, @@ -764,7 +763,7 @@ export const AllCypressErrors = { // TODO: verify these are configBaseName and not configPath CONFIG_FILES_LANGUAGE_CONFLICT: (projectRoot: string, configFileBaseName1: string, configFileBaseName2: string) => { return errTemplate` - There is both a ${configFileBaseName1} and a ${configFileBaseName2} at the location below: + There is both a ${fmt.highlight(configFileBaseName1)} and a ${fmt.highlight(configFileBaseName2)} at the location below: ${fmt.listItem(projectRoot)} @@ -775,7 +774,7 @@ export const AllCypressErrors = { return errTemplate`\ Could not find a Cypress configuration file. - We looked but did not find a ${configFileBaseName} file in this folder: ${fmt.path(projectRoot)}` + We looked but did not find a ${fmt.highlight(configFileBaseName)} file in this folder: ${fmt.path(projectRoot)}` }, INVOKED_BINARY_OUTSIDE_NPM_MODULE: () => { return errTemplate`\ @@ -783,7 +782,7 @@ export const AllCypressErrors = { This is not the recommended approach, and Cypress may not work correctly. - Please install the ${`cypress`} NPM package and follow the instructions here: + Please install the ${fmt.highlight(`cypress`)} NPM package and follow the instructions here: https://on.cypress.io/installing-cypress` }, @@ -823,7 +822,7 @@ export const AllCypressErrors = { return errTemplate`\ You've exceeded the limit of test results under your free plan this month. ${getUsedTestsMessage(arg1.limit, arg1.usedTestsMessage)} - Your plan is now in a grace period, which means you will have the full benefits of your current plan until ${arg1.gracePeriodMessage}. + Your plan is now in a grace period, which means you will have the full benefits of your current plan until ${fmt.highlight(arg1.gracePeriodMessage)}. Please visit your billing to upgrade your plan. @@ -831,7 +830,7 @@ export const AllCypressErrors = { }, PLAN_EXCEEDS_MONTHLY_TESTS: (arg1: {link: string, planType: string, usedTestsMessage: string, limit: number}) => { return errTemplate`\ - You've exceeded the limit of test results under your ${arg1.planType} billing plan this month. ${getUsedTestsMessage(arg1.limit, arg1.usedTestsMessage)} + You've exceeded the limit of test results under your ${fmt.highlight(arg1.planType)} billing plan this month. ${getUsedTestsMessage(arg1.limit, arg1.usedTestsMessage)} To continue getting the full benefits of your current plan, please visit your billing to upgrade. @@ -841,7 +840,7 @@ export const AllCypressErrors = { return errTemplate`\ ${fmt.highlightSecondary(`Parallelization`)} is not included under your free plan. - Your plan is now in a grace period, which means your tests will still run in parallel until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests in parallel in the future. + Your plan is now in a grace period, which means your tests will still run in parallel until ${fmt.highlight(arg1.gracePeriodMessage)}. Please upgrade your plan to continue running your tests in parallel in the future. ${fmt.off(arg1.link)}` }, @@ -857,7 +856,7 @@ export const AllCypressErrors = { return errTemplate`\ ${fmt.highlightSecondary(`Grouping`)} is not included under your free plan. - Your plan is now in a grace period, which means your tests will still run with groups until ${arg1.gracePeriodMessage}. Please upgrade your plan to continue running your tests with groups in the future. + Your plan is now in a grace period, which means your tests will still run with groups until ${fmt.highlight(arg1.gracePeriodMessage)}. Please upgrade your plan to continue running your tests with groups in the future. ${fmt.off(arg1.link)}` }, @@ -874,12 +873,12 @@ export const AllCypressErrors = { return errTemplate`\ A fixture file could not be found at any of the following paths: - > ${arg1} - > ${arg1}.[ext] + ${fmt.listItem(arg1)} + ${fmt.listItem(arg1)}.[ext] Cypress looked for these file extensions at the provided path: - > ${arg2.join(', ')} + ${fmt.listItem(arg2.join(', '))} Provide a path to an existing fixture file.` }, @@ -914,7 +913,7 @@ export const AllCypressErrors = { }, COULD_NOT_FIND_SYSTEM_NODE: (nodeVersion: string) => { return errTemplate`\ - ${`nodeVersion`} is set to ${fmt.highlightTertiary(`system`)} but Cypress could not find a usable Node executable on your ${fmt.highlightSecondary(`PATH`)}. + ${fmt.highlight(`nodeVersion`)} is set to ${fmt.highlightTertiary(`system`)} but Cypress could not find a usable Node executable on your ${fmt.highlightSecondary(`PATH`)}. Make sure that your Node executable exists and can be run by the current user. @@ -922,16 +921,16 @@ export const AllCypressErrors = { }, INVALID_CYPRESS_INTERNAL_ENV: (val: string) => { return errTemplate`\ - We have detected an unknown or unsupported ${fmt.highlightSecondary(`CYPRESS_INTERNAL_ENV`)} value: ${val} + We have detected an unknown or unsupported ${fmt.highlightSecondary(`CYPRESS_INTERNAL_ENV`)} value: ${fmt.highlight(val)} CYPRESS_INTERNAL_ENV is reserved for internal use and cannot be modified.` }, CDP_VERSION_TOO_OLD: (minimumVersion: string, currentVersion: {major: number, minor: string | number}) => { const phrase = currentVersion.major !== 0 ? fmt.highlight(`${currentVersion.major}.${currentVersion.minor}`) - : 'an older version' + : fmt.off('an older version') - return errTemplate`A minimum CDP version of ${minimumVersion} is required, but the current browser has ${fmt.off(phrase)}.` + return errTemplate`A minimum CDP version of ${fmt.highlight(minimumVersion)} is required, but the current browser has ${phrase}.` }, CDP_COULD_NOT_CONNECT: (browserName: string, port: number, err: Error) => { // we include a stack trace here because it may contain useful information @@ -942,7 +941,7 @@ export const AllCypressErrors = { This usually indicates there was a problem opening the ${fmt.off(_.capitalize(browserName))} browser. - The CDP port requested was ${`${port}`}. + The CDP port requested was ${fmt.highlight(port)}. ${fmt.stackTrace(err)}` }, @@ -968,7 +967,7 @@ export const AllCypressErrors = { }, UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES: (arg1: string[], arg2: string[]) => { return errTemplate`\ - The ${`launchOptions`} object returned by your plugin's ${fmt.highlightSecondary(`before:browser:launch`)} handler contained unexpected properties: + The ${fmt.highlight('launchOptions')} object returned by your plugin's ${fmt.highlightSecondary(`before:browser:launch`)} handler contained unexpected properties: ${fmt.listItems(arg1)} @@ -981,7 +980,7 @@ export const AllCypressErrors = { // TODO: test this COULD_NOT_PARSE_ARGUMENTS: (argName: string, argValue: string, errMsg: string) => { return errTemplate`\ - Cypress encountered an error while parsing the argument: ${`--${argName}`} + Cypress encountered an error while parsing the argument: ${fmt.flag(`--${argName}`)} You passed: ${fmt.highlightTertiary(argValue)} @@ -1009,7 +1008,7 @@ export const AllCypressErrors = { }, EXPERIMENTAL_SAMESITE_REMOVED: () => { return errTemplate`\ - The ${`experimentalGetCookiesSameSite`} configuration option was removed in ${fmt.cypressVersion(`5.0.0`)}. + The ${fmt.highlight(`experimentalGetCookiesSameSite`)} configuration option was removed in ${fmt.cypressVersion(`5.0.0`)}. Returning the ${fmt.highlightSecondary(`sameSite`)} property is now the default behavior of the ${fmt.highlightSecondary(`cy.cookie`)} commands. @@ -1018,7 +1017,7 @@ export const AllCypressErrors = { // TODO: verify configFile is absolute path EXPERIMENTAL_COMPONENT_TESTING_REMOVED: (arg1: {configFile: string}) => { return errTemplate`\ - The ${'experimentalComponentTesting'} configuration option was removed in ${fmt.cypressVersion(`7.0.0`)}. + The ${fmt.highlight('experimentalComponentTesting')} configuration option was removed in ${fmt.cypressVersion(`7.0.0`)}. Please remove this flag from: ${fmt.path(arg1.configFile)} @@ -1030,25 +1029,25 @@ export const AllCypressErrors = { }, EXPERIMENTAL_SHADOW_DOM_REMOVED: () => { return errTemplate`\ - The ${`experimentalShadowDomSupport`} configuration option was removed in ${fmt.cypressVersion(`5.2.0`)}. It is no longer necessary when utilizing the ${fmt.highlightSecondary(`includeShadowDom`)} option. + The ${fmt.highlight(`experimentalShadowDomSupport`)} configuration option was removed in ${fmt.cypressVersion(`5.2.0`)}. It is no longer necessary when utilizing the ${fmt.highlightSecondary(`includeShadowDom`)} option. You can safely remove this option from your config.` }, EXPERIMENTAL_NETWORK_STUBBING_REMOVED: () => { return errTemplate`\ - The ${`experimentalNetworkStubbing`} configuration option was removed in ${fmt.cypressVersion(`6.0.0`)}. + The ${fmt.highlight(`experimentalNetworkStubbing`)} configuration option was removed in ${fmt.cypressVersion(`6.0.0`)}. It is no longer necessary for using ${fmt.highlightSecondary(`cy.intercept()`)}. You can safely remove this option from your config.` }, EXPERIMENTAL_RUN_EVENTS_REMOVED: () => { return errTemplate`\ - The ${`experimentalRunEvents`} configuration option was removed in ${fmt.cypressVersion(`6.7.0`)}. It is no longer necessary when listening to run events in the plugins file. + The ${fmt.highlight(`experimentalRunEvents`)} configuration option was removed in ${fmt.cypressVersion(`6.7.0`)}. It is no longer necessary when listening to run events in the plugins file. You can safely remove this option from your config.` }, FIREFOX_GC_INTERVAL_REMOVED: () => { return errTemplate`\ - The ${`firefoxGcInterval`} configuration option was removed in ${fmt.cypressVersion(`8.0.0`)}. It was introduced to work around a bug in Firefox 79 and below. + The ${fmt.highlight(`firefoxGcInterval`)} configuration option was removed in ${fmt.cypressVersion(`8.0.0`)}. It was introduced to work around a bug in Firefox 79 and below. Since Cypress no longer supports Firefox 85 and below in Cypress ${fmt.cypressVersion(`8.0.0`)}, this option was removed. @@ -1056,7 +1055,7 @@ export const AllCypressErrors = { }, INCOMPATIBLE_PLUGIN_RETRIES: (arg1: string) => { return errTemplate`\ - We've detected that the incompatible plugin ${`cypress-plugin-retries`} is installed at: ${fmt.path(arg1)} + We've detected that the incompatible plugin ${fmt.highlight(`cypress-plugin-retries`)} is installed at: ${fmt.path(arg1)} Test retries is now natively supported in ${fmt.cypressVersion(`5.0.0`)}. @@ -1079,24 +1078,24 @@ export const AllCypressErrors = { }, PLUGINS_RUN_EVENT_ERROR: (arg1: string, arg2: Error) => { return errTemplate`\ - An error was thrown in your plugins file while executing the handler for the ${arg1} event. + An error was thrown in your plugins file while executing the handler for the ${fmt.highlight(arg1)} event. The error we received was: ${fmt.stackTrace(arg2)}` }, CT_NO_DEV_START_EVENT: (pluginsFilePath: string) => { - const code = stripIndent` + const code = errPartial` + ${fmt.comment(`// ${pluginsFilePath}`)} module.exports = (on, config) => { on('dev-server:start', () => startDevServer(...) }` return errTemplate`\ - To run component-testing, cypress needs the ${`dev-server:start`} event. + To run component-testing, cypress needs the ${fmt.highlight(`dev-server:start`)} event. Please implement it by adding this code to your ${fmt.highlightSecondary(`pluginsFile`)}. - ${fmt.meta(`// ${pluginsFilePath}`)} ${fmt.code(code)} See https://on.cypress.io/component-testing for help on setting up component testing.` @@ -1106,23 +1105,23 @@ export const AllCypressErrors = { }, NODE_VERSION_DEPRECATION_SYSTEM: (arg1: {name: string, value: any, configFile: string}) => { return errTemplate`\ - Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. + Deprecation Warning: ${fmt.highlight(arg1.name)} is currently set to ${fmt.highlightSecondary(arg1.value)} in the ${fmt.highlightTertiary(arg1.configFile)} configuration file. - As of ${fmt.cypressVersion(`9.0.0`)} the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. + As of ${fmt.cypressVersion(`9.0.0`)} the default behavior of ${fmt.highlight(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. - Please remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. + Please remove the ${fmt.highlight(arg1.name)} configuration option from ${fmt.highlightTertiary(arg1.configFile)}. ` }, // TODO: does this need to change since its a warning? NODE_VERSION_DEPRECATION_BUNDLED: (arg1: {name: string, value: any, configFile: string}) => { return errTemplate`\ - Deprecation Warning: ${backtick(arg1.name)} is currently set to ${backtick(arg1.value)} in the ${backtick(arg1.configFile)} configuration file. + Deprecation Warning: ${fmt.highlight(arg1.name)} is currently set to ${fmt.highlightSecondary(arg1.value)} in the ${fmt.highlightTertiary(arg1.configFile)} configuration file. - As of ${fmt.cypressVersion(`9.0.0`)} the default behavior of ${backtick(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. + As of ${fmt.cypressVersion(`9.0.0`)} the default behavior of ${fmt.highlight(arg1.name)} has changed to always use the version of Node used to start cypress via the cli. - When ${backtick(arg1.name)} is set to ${backtick(arg1.value)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. + When ${fmt.highlight(arg1.name)} is set to ${fmt.highlightSecondary(arg1.value)}, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. - As the ${backtick(arg1.name)} configuration option will be removed in a future release, it is recommended to remove the ${backtick(arg1.name)} configuration option from ${backtick(arg1.configFile)}. + As the ${fmt.highlight(arg1.name)} configuration option will be removed in a future release, it is recommended to remove the ${fmt.highlight(arg1.name)} configuration option from ${fmt.highlightTertiary(arg1.configFile)}. ` }, } as const @@ -1160,14 +1159,14 @@ export const getError = function (type: // @ts-expect-error const result = AllCypressErrors[type](...args) as ErrTemplateResult - const { message, details, originalError, forBrowser } = result + const { message, details, originalError, messageMarkdown } = result const err = new Error(message) as CypressError err.isCypressErr = true err.type = type err.details = details - err.forBrowser = forBrowser + err.messageMarkdown = messageMarkdown err.originalError = originalError if (originalError) { @@ -1215,20 +1214,16 @@ export const cloneError = function (err: CypressError | GenericError, options: { html: false, }) - const message = _.isFunction(err.forBrowser) ? err.forBrowser().message : err.message - // pull off these properties - const obj = _.pick(err, 'type', 'name', 'stack', 'fileName', 'lineNumber', 'columnNumber') as ClonedError + const obj = _.pick(err, 'message', 'messageMarkdown', 'type', 'name', 'stack', 'fileName', 'lineNumber', 'columnNumber') as ClonedError if (options.html) { - obj.message = ansi_up.ansi_to_html(message) + obj.message = ansi_up.ansi_to_html(err.message) // revert back the distorted characters // in case there is an error in a child_process // that contains quotes .replace(/\&\#x27;/g, '\'') .replace(/\"\;/g, '"') - } else { - obj.message = message } // and any own (custom) properties diff --git a/packages/errors/test/unit/errTemplate_spec.ts b/packages/errors/test/unit/errTemplate_spec.ts index 394ea4cbedc9..f535e2373434 100644 --- a/packages/errors/test/unit/errTemplate_spec.ts +++ b/packages/errors/test/unit/errTemplate_spec.ts @@ -8,21 +8,21 @@ describe('errTemplate', () => { const obj = errTemplate`Hello world` expect(obj).to.include({ message: 'Hello world' }) - expect(obj.forBrowser()).to.include({ message: 'Hello world' }) + expect(obj).to.include({ messageMarkdown: 'Hello world' }) }) it('colors yellow by default for the console, backticks passed arguments for the browser,', () => { - const obj = errTemplate`Hello world ${'special'}` + const obj = errTemplate`Hello world ${fmt.highlight('special')}` expect(obj).to.include({ message: `Hello world ${chalk.yellow('special')}` }) - expect(obj.forBrowser()).to.include({ message: 'Hello world `special`' }) + expect(obj).to.include({ messageMarkdown: 'Hello world `special`' }) }) it('uses fmt.off to guard passed values', () => { const obj = errTemplate`Hello world ${fmt.off('special')}` expect(obj).to.include({ message: `Hello world special` }) - expect(obj.forBrowser()).to.include({ message: `Hello world special` }) + expect(obj).to.include({ messageMarkdown: `Hello world special` }) }) it('provides as details for toErrorProps', () => { @@ -44,11 +44,11 @@ describe('errTemplate', () => { const obj = errTemplate` This was returned from the app: - ${someObj} + ${fmt.highlightTertiary(someObj)} ` - expect(obj.forBrowser()).to.include({ - message: stripIndent` + expect(obj).to.include({ + messageMarkdown: stripIndent` This was returned from the app: \`\`\` @@ -73,7 +73,7 @@ describe('errTemplate', () => { ${fmt.stackTrace(someObj)} ` - expect(obj.forBrowser()).to.include({ message: `This was returned from the app:` }) + expect(obj).to.include({ messageMarkdown: `This was returned from the app:` }) expect(obj).to.include({ message: `This was returned from the app:`, details: chalk.magenta(JSON.stringify(someObj, null, 2)), @@ -84,12 +84,12 @@ describe('errTemplate', () => { const specFile = 'specFile.js' const err = new Error() const obj = errTemplate` - This was an error in ${specFile} + This was an error in ${fmt.highlight(specFile)} ${fmt.stackTrace(err)} ` - expect(obj.forBrowser()).to.include({ message: `This was an error in \`specFile.js\`` }) + expect(obj).to.include({ messageMarkdown: `This was an error in \`specFile.js\`` }) expect(obj).to.include({ message: `This was an error in ${chalk.yellow(specFile)}`, details: chalk.magenta(err.stack ?? ''), diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index ec15ca0efc65..4bd6ee551d62 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -36,6 +36,8 @@ termToHtml.themes.dark.bg = '#111' const lineAndColNumsRe = /:\d+:\d+/ const snapshotHtmlFolder = path.join(__dirname, '..', '..', '__snapshot-html__') +const snapshotHtmlLocalFolder = path.join(__dirname, '..', '..', '__snapshot-html-local__') +const snapshotMarkdownFolder = path.join(__dirname, '..', '..', '__snapshot-md__') const snapshotImagesFolder = path.join(__dirname, '..', '..', '__snapshot-images__') const saveHtml = async (filename: string, html: string) => { @@ -50,7 +52,7 @@ const sanitize = (str: string) => { .split(cypressRootPath).join('cypress') } -const snapshotErrorConsoleLogs = function (errorFileName: string) { +const snapshotAndTestErrorConsole = async function (errorFileName: string) { const logs = _ .chain(consoleLog.args) .map((args) => { @@ -59,8 +61,6 @@ const snapshotErrorConsoleLogs = function (errorFileName: string) { .join('\n') .value() - expect(logs).not.to.contain('[object Object]') - // if the sanitized snapshot matches, let's save the ANSI colors converted into HTML const html = termToHtml .strings(logs, termToHtml.themes.dark.name) @@ -92,12 +92,26 @@ const snapshotErrorConsoleLogs = function (errorFileName: string) { `) // remove margin/padding and force text overflow like a terminal - return saveHtml(errorFileName, html) + try { + fse.accessSync(errorFileName) + } catch (e) { + await saveHtml(errorFileName, html) + } + + const contents = await fse.readFile(errorFileName, 'utf8') + + try { + expect(contents).to.eq(html) + } catch (e) { + await saveHtml(errorFileName.replace('__snapshot-html__', '__snapshot-html-local__'), html) + throw e + } } let consoleLog: SinonSpy beforeEach(() => { + sinon.restore() consoleLog = sinon.spy(console, 'log') }) @@ -122,11 +136,18 @@ const testVisualError = (errorGeneratorFn: () => Er const err = errors.get(errorType, ...arr) + if (!errors.isCypressErr(err)) { + throw new Error(`Expected Cypress Error`) + } + errors.log(err) const htmlFilename = path.join(snapshotHtmlFolder, `${filename}.html`) + const mdFilename = path.join(snapshotMarkdownFolder, `${filename}.md`) + + await snapshotAndTestErrorConsole(htmlFilename) - await snapshotErrorConsoleLogs(htmlFilename) + await fse.outputFile(mdFilename, err.messageMarkdown, 'utf8') debug(`Snapshotted ${htmlFilename}`) @@ -154,7 +175,8 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K // prune out all existing snapshot html in case // errors were removed and we have stale snapshots return Promise.all([ - fse.remove(snapshotHtmlFolder), + isCi ? fse.remove(snapshotHtmlFolder) : null, + fse.remove(snapshotHtmlLocalFolder), fse.remove(snapshotImagesFolder), ]) }) diff --git a/packages/server/lib/plugins/util.js b/packages/server/lib/plugins/util.js index eb78eaa57c7f..8d0e82384762 100644 --- a/packages/server/lib/plugins/util.js +++ b/packages/server/lib/plugins/util.js @@ -6,7 +6,7 @@ const Promise = require('bluebird') const UNDEFINED_SERIALIZED = '__cypress_undefined__' const serializeError = (err) => { - return _.pick(err, 'name', 'message', 'stack', 'code', 'annotated', 'type') + return _.pick(err, 'name', 'message', 'stack', 'code', 'annotated', 'type', 'isCypressErr', 'messageMarkdown') } module.exports = { From fa82f2df0c84494dbd7a5af99bf182f211623c25 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Mon, 7 Feb 2022 22:29:43 -0500 Subject: [PATCH 094/165] feat: fixing up errors, added simple error comparison tool --- .../BROWSER_NOT_FOUND_BY_PATH.html | 6 +- .../CANNOT_CONNECT_BASE_URL_RETRYING.html | 2 +- .../CANNOT_RECORD_NO_PROJECT_ID.html | 8 +- .../CANNOT_REMOVE_OLD_BROWSER_PROFILES.html | 8 +- .../CANNOT_TRASH_ASSETS.html | 8 +- .../CDP_COULD_NOT_CONNECT.html | 8 +- .../CDP_COULD_NOT_RECONNECT.html | 8 +- .../CDP_RETRYING_CONNECTION.html | 2 +- .../CHROME_WEB_SECURITY_NOT_SUPPORTED.html | 4 +- .../CONFIG_FILE_NOT_FOUND.html | 2 +- .../COULD_NOT_FIND_SYSTEM_NODE.html | 4 +- .../COULD_NOT_PARSE_ARGUMENTS.html | 4 +- .../CT_NO_DEV_START_EVENT.html | 12 +- .../DASHBOARD_ALREADY_COMPLETE.html | 4 +- ...PI_RESPONSE_FAILED_RETRYING - lastTry.html | 2 +- ...ASHBOARD_API_RESPONSE_FAILED_RETRYING.html | 2 +- ...SHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html | 2 +- .../DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html | 4 +- .../DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html | 2 +- .../DASHBOARD_CANNOT_UPLOAD_RESULTS.html | 2 +- .../DASHBOARD_INVALID_RUN_REQUEST.html | 18 +- .../DASHBOARD_PARALLEL_DISALLOWED.html | 6 +- ...HBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html | 24 +-- .../DASHBOARD_PARALLEL_REQUIRED.html | 6 +- .../DASHBOARD_PROJECT_NOT_FOUND.html | 4 +- .../DASHBOARD_RECORD_KEY_NOT_VALID.html | 4 +- .../DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html | 8 +- .../DASHBOARD_STALE_RUN.html | 6 +- .../DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html | 12 +- .../DASHBOARD_UNKNOWN_INVALID_REQUEST.html | 2 +- ...DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html | 6 +- .../__snapshot-html__/ERROR_READING_FILE.html | 10 +- .../__snapshot-html__/ERROR_WRITING_FILE.html | 10 +- ...XPERIMENTAL_COMPONENT_TESTING_REMOVED.html | 6 +- ...EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html | 4 +- .../EXPERIMENTAL_RUN_EVENTS_REMOVED.html | 4 +- .../EXPERIMENTAL_SAMESITE_REMOVED.html | 6 +- .../EXPERIMENTAL_SHADOW_DOM_REMOVED.html | 4 +- .../EXTENSION_NOT_LOADED.html | 4 +- .../FIREFOX_COULD_NOT_CONNECT.html | 8 +- .../FIREFOX_GC_INTERVAL_REMOVED.html | 6 +- .../FIREFOX_MARIONETTE_FAILURE.html | 10 +- .../__snapshot-html__/FIXTURE_NOT_FOUND.html | 8 +- .../FOLDER_NOT_WRITABLE.html | 4 +- ...PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html | 4 +- .../INCOMPATIBLE_PLUGIN_RETRIES.html | 6 +- .../INCORRECT_CI_BUILD_ID_USAGE.html | 4 +- .../INDETERMINATE_CI_BUILD_ID.html | 4 +- .../INVALID_CYPRESS_INTERNAL_ENV.html | 4 +- .../INVALID_REPORTER_NAME.html | 9 +- .../NODE_VERSION_DEPRECATION_BUNDLED.html | 10 +- .../NODE_VERSION_DEPRECATION_SYSTEM.html | 8 +- .../NO_DEFAULT_CONFIG_FILE_FOUND.html | 2 +- .../NO_PROJECT_FOUND_AT_PROJECT_ROOT.html | 2 +- .../__snapshot-html__/NO_PROJECT_ID.html | 2 +- .../NO_SPECS_FOUND - noPattern.html | 4 +- .../__snapshot-html__/NO_SPECS_FOUND.html | 4 +- ...ARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html | 4 +- ...RACE_PERIOD_RUN_GROUPING_FEATURE_USED.html | 4 +- .../PLUGINS_CONFIG_VALIDATION_ERROR.html | 4 +- ...LUGINS_DIDNT_EXPORT_FUNCTION - string.html | 12 +- .../PLUGINS_DIDNT_EXPORT_FUNCTION.html | 16 +- .../__snapshot-html__/PLUGINS_FILE_ERROR.html | 11 +- .../PLUGINS_FILE_NOT_FOUND.html | 6 +- .../PLUGINS_FUNCTION_ERROR.html | 11 +- .../PLUGINS_INVALID_EVENT_ERROR.html | 11 +- .../PLUGINS_RUN_EVENT_ERROR.html | 8 +- .../PLUGINS_UNEXPECTED_ERROR.html | 11 +- .../PLUGINS_VALIDATION_ERROR.html | 11 +- .../__snapshot-html__/PORT_IN_USE_LONG.html | 2 +- ..._ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html | 4 +- .../__snapshot-html__/RECORD_KEY_MISSING.html | 6 +- .../RECORD_PARAMS_WITHOUT_RECORDING.html | 4 +- .../RENAMED_CONFIG_OPTION.html | 2 +- ...ROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html | 4 +- .../SETTINGS_VALIDATION_ERROR.html | 4 +- .../SUPPORT_FILE_NOT_FOUND.html | 6 +- ...CTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html | 4 +- .../VIDEO_POST_PROCESSING_FAILED.html | 8 +- .../VIDEO_RECORDING_FAILED.html | 8 +- packages/errors/package.json | 1 + packages/errors/src/errTemplate.ts | 72 +++++-- packages/errors/src/errorUtils.ts | 6 +- packages/errors/src/errors.ts | 49 +++-- .../test/support/error-comparison-tool.ts | 191 ++++++++++++++++++ packages/errors/test/unit/errTemplate_spec.ts | 33 +-- .../test/unit/visualSnapshotErrors_spec.ts | 15 +- packages/server/lib/modes/run.js | 8 +- packages/server/lib/project-base.ts | 8 +- packages/ts/tsconfig.json | 1 + 90 files changed, 542 insertions(+), 340 deletions(-) create mode 100644 packages/errors/test/support/error-comparison-tool.ts diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html index f018567f8b70..7865034b9fcd 100644 --- a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_PATH.html @@ -34,9 +34,9 @@ -
We could not identify a known browser at the path you provided: /path/does/not/exist
+    
We could not identify a known browser at the path you provided: /path/does/not/exist
 
 The output from the command we ran was:
-
-fail whale
+
+fail whale
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html index f58e0d1408e0..ce25c081ad9a 100644 --- a/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html +++ b/packages/errors/__snapshot-html__/CANNOT_CONNECT_BASE_URL_RETRYING.html @@ -42,5 +42,5 @@ Cypress automatically waits until your server is accessible before running tests. -We will try connecting to it 60 more times...
+We will try connecting to it 60 more times...
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html b/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html index 29ebdf2bcdbc..c682601b3f0d 100644 --- a/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html +++ b/packages/errors/__snapshot-html__/CANNOT_RECORD_NO_PROJECT_ID.html @@ -34,15 +34,15 @@ -
You passed the --record flag but this project has not been setup to record.
+    
You passed the --record flag but this project has not been setup to record.
 
-This project is missing the projectId inside of: /path/to/cypress.json
+This project is missing the projectId inside of: /path/to/cypress.json
 
 We cannot uniquely identify this project without this id.
 
 You need to setup this project to record. This will generate a unique projectId.
 
-Alternatively if you omit the --record flag this project will run without recording.
+Alternatively if you omit the --record flag this project will run without recording.
 
-https://on.cypress.io/recording-project-runs
+https://on.cypress.io/recording-project-runs
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html index 44f3469253d9..5cc1dd6a3761 100644 --- a/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html +++ b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html @@ -37,8 +37,8 @@
Warning: We failed to remove old browser profiles from previous runs.
 
 This error will not alter the exit code.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CANNOT_REMOVE_OLD_BROWSER_PROFILES (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CANNOT_REMOVE_OLD_BROWSER_PROFILES (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html index 3ddd725d21ab..017007a3d782 100644 --- a/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html +++ b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html @@ -37,8 +37,8 @@
Warning: We failed to trash the existing run results.
 
 This error will not alter the exit code.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CANNOT_TRASH_ASSETS (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CANNOT_TRASH_ASSETS (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html index 2970c79ecdd2..85d9f10955d3 100644 --- a/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html @@ -39,8 +39,8 @@ This usually indicates there was a problem opening the Chrome browser. The CDP port requested was 2345. - -Error: fail whale - at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at CDP_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+ +Error: fail whale + at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at CDP_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html index 76c512ae60cf..6099e49b4c7b 100644 --- a/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html @@ -35,8 +35,8 @@
There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CDP_COULD_NOT_RECONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CDP_COULD_NOT_RECONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html b/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html index 2075198d652e..da6e47b6aaa9 100644 --- a/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html +++ b/packages/errors/__snapshot-html__/CDP_RETRYING_CONNECTION.html @@ -34,5 +34,5 @@ -
Still waiting to connect to Chrome, retrying in 1 second (attempt 1/62)
+    
Still waiting to connect to Chrome, retrying in 1 second (attempt 1/62)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html b/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html index 0939fc1992c9..78a59080d890 100644 --- a/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html +++ b/packages/errors/__snapshot-html__/CHROME_WEB_SECURITY_NOT_SUPPORTED.html @@ -34,7 +34,7 @@ -
Your project has set the configuration option: chromeWebSecurity to false
+    
Your project has set the configuration option: chromeWebSecurity to false
 
-This option will not have an effect in Firefox. Tests that rely on web security being disabled will not run as expected.
+This option will not have an effect in Firefox. Tests that rely on web security being disabled will not run as expected.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html index 42192a78108d..f2cf222723ba 100644 --- a/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/CONFIG_FILE_NOT_FOUND.html @@ -36,5 +36,5 @@
Could not find a Cypress configuration file.
 
-We looked but did not find a cypress.json file in this folder: /path/to/project/root
+We looked but did not find a cypress.json file in this folder: /path/to/project/root
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html b/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html index e4a3a86eef5b..74d37a11d83a 100644 --- a/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html +++ b/packages/errors/__snapshot-html__/COULD_NOT_FIND_SYSTEM_NODE.html @@ -34,9 +34,9 @@ -
nodeVersion is set to system but Cypress could not find a usable Node executable on your PATH.
+    
nodeVersion is set to system but Cypress could not find a usable Node executable on your PATH.
 
 Make sure that your Node executable exists and can be run by the current user.
 
-Cypress will use the built-in Node version 16.2.1 instead.
+Cypress will use the built-in Node version 16.2.1 instead.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html b/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html index 37326e3d23cd..38ab9bc38cf0 100644 --- a/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html +++ b/packages/errors/__snapshot-html__/COULD_NOT_PARSE_ARGUMENTS.html @@ -36,7 +36,7 @@
Cypress encountered an error while parsing the argument: --spec
 
-You passed: 1
+You passed: 1
 
-The error was: spec must be a string or comma-separated list
+The error was: spec must be a string or comma-separated list
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html b/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html index 4e5ba627f82e..245421bd26b4 100644 --- a/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html +++ b/packages/errors/__snapshot-html__/CT_NO_DEV_START_EVENT.html @@ -36,12 +36,12 @@
To run component-testing, cypress needs the dev-server:start event.
 
-Please implement it by adding this code to your pluginsFile.
+Please implement it by adding this code to your pluginsFile.
 
-// /path/to/plugins/file.js
-module.exports = (on, config) => {
-  on('dev-server:start', () => startDevServer(...)
-}
+// /path/to/plugins/file.js
+module.exports = (on, config) => {
+  on('dev-server:start', () => startDevServer(...)
+}
 
-See https://on.cypress.io/component-testing for help on setting up component testing.
+See https://on.cypress.io/component-testing for help on setting up component testing.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html b/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html index 68fbf4b453b1..b1f55d3cf73d 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_ALREADY_COMPLETE.html @@ -36,12 +36,12 @@
The run you are attempting to access is already complete and will not accept new groups.
 
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
 
 When a run finishes all of its groups, it waits for a configurable set of time before finally completing. You must add more groups during that time period.
 
 The --group flag you passed was: foo
 The --parallel flag you passed was: true
 
-https://on.cypress.io/already-complete
+https://on.cypress.io/already-complete
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING - lastTry.html b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING - lastTry.html index 5ab475c8471c..3851fc91ffdb 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING - lastTry.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING - lastTry.html @@ -40,5 +40,5 @@ The server's response was: -StatusCodeError: 500 - "Internal Server Error"
+StatusCodeError: 500 - "Internal Server Error"
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html index 5997a735a72f..8d56bdf9d80d 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_API_RESPONSE_FAILED_RETRYING.html @@ -40,5 +40,5 @@ The server's response was: -StatusCodeError: 500 - "Internal Server Error"
+StatusCodeError: 500 - "Internal Server Error"
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html index 8377b5e917b6..9f34f7c4ad88 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_CREATE_RUN_OR_INSTANCE.html @@ -40,5 +40,5 @@ This error will not alter the exit code. -StatusCodeError: 500 - "Internal Server Error" +StatusCodeError: 500 - "Internal Server Error"
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html index 19b8f5e4755a..61a5436cc1c3 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_PARALLEL.html @@ -36,12 +36,12 @@
We encountered an unexpected error talking to our servers.
 
-Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
+Because you passed the --parallel flag, this run cannot proceed because it requires a valid response from our servers.
 
 The --group flag you passed was: foo
 The --ciBuildId flag you passed was: invalid
 
 The server's response was:
 
-StatusCodeError: 500 - "Internal Server Error"
+StatusCodeError: 500 - "Internal Server Error"
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html index e2da0eaa4a32..ad85fcf206d3 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_PROCEED_IN_SERIAL.html @@ -41,5 +41,5 @@ The server's response was: -StatusCodeError: 500 - "Internal Server Error"
+StatusCodeError: 500 - "Internal Server Error"
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html index 8ab343c06edb..d179c0ff16ea 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_CANNOT_UPLOAD_RESULTS.html @@ -40,5 +40,5 @@ This error will not alter the exit code. -StatusCodeError: 500 - "Internal Server Error"
+StatusCodeError: 500 - "Internal Server Error"
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html b/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html index 03d21e3df134..d231b57b949f 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_INVALID_RUN_REQUEST.html @@ -40,16 +40,16 @@ Errors: -[ - "data.commit has additional properties", - "data.ci.buildNumber is required" -] +[ + "data.commit has additional properties", + "data.ci.buildNumber is required" +] Request Sent: -{ - "foo": "foo", - "bar": "bar", - "baz": "baz" -} +{ + "foo": "foo", + "bar": "bar", + "baz": "baz" +}
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html index 96dc50d29d2e..23869efd8942 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_DISALLOWED.html @@ -34,14 +34,14 @@ -
You passed the --parallel flag, but this run group was originally created without the --parallel flag.
+    
You passed the --parallel flag, but this run group was originally created without the --parallel flag.
 
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
 
 The --group flag you passed was: foo
 The --parallel flag you passed was: true
 
 You can not use the --parallel flag with this group.
 
-https://on.cypress.io/parallel-disallowed
+https://on.cypress.io/parallel-disallowed
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html index 992876f18b88..31c5524e3369 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_GROUP_PARAMS_MISMATCH.html @@ -34,11 +34,11 @@ -
You passed the --parallel flag, but we do not parallelize tests across different environments.
+    
You passed the --parallel flag, but we do not parallelize tests across different environments.
 
 This machine is sending different environment parameters than the first machine that started this parallel run.
 
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
 
 In order to run in parallel mode each machine must send identical environment parameters such as:
 
@@ -50,15 +50,15 @@
 
 This machine sent the following parameters:
 
-{
-  "osName": "darwin",
-  "osVersion": "v1",
-  "browserName": "Electron",
-  "browserVersion": "59.1.2.3",
-  "specs": [
-    "cypress/integration/app_spec.js"
-  ]
-}
+{
+  "osName": "darwin",
+  "osVersion": "v1",
+  "browserName": "Electron",
+  "browserVersion": "59.1.2.3",
+  "specs": [
+    "cypress/integration/app_spec.js"
+  ]
+}
 
-https://on.cypress.io/parallel-group-params-mismatch
+https://on.cypress.io/parallel-group-params-mismatch
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html index 127e48121ae8..06bd2f96e0bc 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_PARALLEL_REQUIRED.html @@ -34,14 +34,14 @@ -
You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag.
+    
You did not pass the --parallel flag, but this run's group was originally created with the --parallel flag.
 
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
 
 The --group flag you passed was: foo
 The --parallel flag you passed was: true
 
 You must use the --parallel flag with this group.
 
-https://on.cypress.io/parallel-required
+https://on.cypress.io/parallel-required
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html b/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html index 3f9612b72bdf..7f172da72117 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_PROJECT_NOT_FOUND.html @@ -36,7 +36,7 @@
We could not find a Dashboard project with the projectId: project-id-123
 
-This projectId came from your /path/to/cypress.json file or an environment variable.
+This projectId came from your /path/to/cypress.json file or an environment variable.
 
 Please log into the Dashboard and find your project.
 
@@ -44,5 +44,5 @@
 
 Alternatively, you can create a new project using the Desktop Application.
 
-https://on.cypress.io/dashboard
+https://on.cypress.io/dashboard
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html b/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html index c7d02879a890..59ef0c222877 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_RECORD_KEY_NOT_VALID.html @@ -34,11 +34,11 @@ -
Your Record Key record-key-123 is not valid with this projectId: project-id-123
+    
Your Record Key record-key-123 is not valid with this projectId: project-id-123
 
 It may have been recently revoked by you or another user.
 
 Please log into the Dashboard to see the valid record keys.
 
-https://on.cypress.io/dashboard/projects/project-id-123
+https://on.cypress.io/dashboard/projects/project-id-123
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html b/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html index 33b4180accf9..99b1af2c16a5 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_RUN_GROUP_NAME_NOT_UNIQUE.html @@ -34,14 +34,14 @@ -
You passed the --group flag, but this group name has already been used for this run.
+    
You passed the --group flag, but this group name has already been used for this run.
 
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
 
 The --group flag you passed was: foo
 The --parallel flag you passed was: true
 
-If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name.
+If you are trying to parallelize this run, then also pass the --parallel flag, else pass a different group name.
 
-https://on.cypress.io/run-group-name-not-unique
+https://on.cypress.io/run-group-name-not-unique
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html b/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html index 82411f629741..8eac4222419b 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_STALE_RUN.html @@ -34,14 +34,14 @@ -
You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago.
+    
You are attempting to pass the --parallel flag to a run that was completed over 24 hours ago.
 
-The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
+The existing run is: https://dashboard.cypress.io/project/abcd/runs/1
 
 You cannot parallelize a run that has been complete for that long.
 
 The --group flag you passed was: foo
 The --parallel flag you passed was: true
 
-https://on.cypress.io/stale-run
+https://on.cypress.io/stale-run
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html index 41bad8435799..d327093d0851 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html @@ -37,10 +37,10 @@
Warning from Cypress Dashboard: You are almost out of time
 
 Details:
-{
-  "code": "OUT_OF_TIME",
-  "name": "OutOfTime",
-  "hadTime": 1000,
-  "spentTime": 999
-}
+{
+  "code": "OUT_OF_TIME",
+  "name": "OutOfTime",
+  "hadTime": 1000,
+  "spentTime": 999
+}
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html index 6c948cd4023a..efd9ce64c704 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_INVALID_REQUEST.html @@ -43,5 +43,5 @@ The server's response was: -StatusCodeError: 500 - "Internal Server Error"
+StatusCodeError: 500 - "Internal Server Error"
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html b/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html index 401d55c00cb5..91c9b26e8662 100644 --- a/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html +++ b/packages/errors/__snapshot-html__/DEPRECATED_BEFORE_BROWSER_LAUNCH_ARGS.html @@ -34,11 +34,11 @@ -
Deprecation Warning: The before:browser:launch plugin event changed its signature in Cypress version 4.0.0
+    
Deprecation Warning: The before:browser:launch plugin event changed its signature in Cypress version 4.0.0
 
-The event switched from yielding the second argument as an array of browser arguments to an options object with an args property.
+The event switched from yielding the second argument as an array of browser arguments to an options object with an args property.
 
 We've detected that your code is still using the previous, deprecated interface signature.
 
-This code will not work in a future version of Cypress. Please see the upgrade guide: https://on.cypress.io/deprecated-before-browser-launch-args
+This code will not work in a future version of Cypress. Please see the upgrade guide: https://on.cypress.io/deprecated-before-browser-launch-args
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_READING_FILE.html b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html index d0498dca20fa..0415ef59922e 100644 --- a/packages/errors/__snapshot-html__/ERROR_READING_FILE.html +++ b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html @@ -34,9 +34,9 @@ -
Error reading from: /path/to/read/file.ts
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at ERROR_READING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    
Error reading from: /path/to/read/file.ts
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at ERROR_READING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html index 816e24b9cdf1..7b03d3b0be7f 100644 --- a/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html +++ b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html @@ -34,9 +34,9 @@ -
Error writing to: /path/to/write/file.ts
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at ERROR_WRITING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    
Error writing to: /path/to/write/file.ts
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at ERROR_WRITING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html index 645603204de6..610b39e2135b 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_COMPONENT_TESTING_REMOVED.html @@ -34,13 +34,13 @@ -
The experimentalComponentTesting configuration option was removed in Cypress version 7.0.0.
+    
The experimentalComponentTesting configuration option was removed in Cypress version 7.0.0.
 
-Please remove this flag from: /path/to/configFile.json
+Please remove this flag from: /path/to/configFile.json
 
 Component Testing is now a standalone command. You can now run your component tests with:
 
   $ cypress open-ct
 
-https://on.cypress.io/migration-guide
+https://on.cypress.io/migration-guide
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html index 20a5ebf473bf..c127cab16de2 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_NETWORK_STUBBING_REMOVED.html @@ -34,7 +34,7 @@ -
The experimentalNetworkStubbing configuration option was removed in Cypress version 6.0.0.
+    
The experimentalNetworkStubbing configuration option was removed in Cypress version 6.0.0.
 
-It is no longer necessary for using cy.intercept(). You can safely remove this option from your config.
+It is no longer necessary for using cy.intercept(). You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html index b2c96c6dc4e2..d16b743fd4c9 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_RUN_EVENTS_REMOVED.html @@ -34,7 +34,7 @@ -
The experimentalRunEvents configuration option was removed in Cypress version 6.7.0. It is no longer necessary when listening to run events in the plugins file.
+    
The experimentalRunEvents configuration option was removed in Cypress version 6.7.0. It is no longer necessary when listening to run events in the plugins file.
 
-You can safely remove this option from your config.
+You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html index fed97205421d..1aa0127b6c23 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_SAMESITE_REMOVED.html @@ -34,9 +34,9 @@ -
The experimentalGetCookiesSameSite configuration option was removed in Cypress version 5.0.0.
+    
The experimentalGetCookiesSameSite configuration option was removed in Cypress version 5.0.0.
 
-Returning the sameSite property is now the default behavior of the cy.cookie commands.
+Returning the sameSite property is now the default behavior of the cy.cookie commands.
 
-You can safely remove this option from your config.
+You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html b/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html index 759de1a5be3f..383ed62ad20b 100644 --- a/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html +++ b/packages/errors/__snapshot-html__/EXPERIMENTAL_SHADOW_DOM_REMOVED.html @@ -34,7 +34,7 @@ -
The experimentalShadowDomSupport configuration option was removed in Cypress version 5.2.0. It is no longer necessary when utilizing the includeShadowDom option.
+    
The experimentalShadowDomSupport configuration option was removed in Cypress version 5.2.0. It is no longer necessary when utilizing the includeShadowDom option.
 
-You can safely remove this option from your config.
+You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html b/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html index 923846012aca..2ab036e3f05b 100644 --- a/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html +++ b/packages/errors/__snapshot-html__/EXTENSION_NOT_LOADED.html @@ -34,7 +34,7 @@ -
Electron could not install the extension at path: /path/to/extension
+    
Electron could not install the extension at path: /path/to/extension
 
-Please verify that this is the path to a valid, unpacked WebExtension.
+Please verify that this is the path to a valid, unpacked WebExtension.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html index 90ac362a0644..6fdf20262dee 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html +++ b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html @@ -37,8 +37,8 @@
Cypress failed to make a connection to Firefox.
 
 This usually indicates there was a problem opening the Firefox browser.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at FIREFOX_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at FIREFOX_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html b/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html index 7ec17dcd33b5..19925cc65c91 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html +++ b/packages/errors/__snapshot-html__/FIREFOX_GC_INTERVAL_REMOVED.html @@ -34,9 +34,9 @@ -
The firefoxGcInterval configuration option was removed in Cypress version 8.0.0. It was introduced to work around a bug in Firefox 79 and below.
+    
The firefoxGcInterval configuration option was removed in Cypress version 8.0.0. It was introduced to work around a bug in Firefox 79 and below.
 
-Since Cypress no longer supports Firefox 85 and below in Cypress Cypress version 8.0.0, this option was removed.
+Since Cypress no longer supports Firefox 85 and below in Cypress Cypress version 8.0.0, this option was removed.
 
-You can safely remove this option from your config.
+You can safely remove this option from your config.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html index 101d37ab5484..ebe1251f6102 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html +++ b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html @@ -36,11 +36,11 @@
Cypress could not connect to Firefox.
 
-An unexpected error was received from Marionette: connection
+An unexpected error was received from Marionette: connection
 
 To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at FIREFOX_MARIONETTE_FAILURE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at FIREFOX_MARIONETTE_FAILURE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html b/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html index 6de2c6a39215..76c7820e7db0 100644 --- a/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/FIXTURE_NOT_FOUND.html @@ -36,12 +36,12 @@
A fixture file could not be found at any of the following paths:
 
-  > file
-  > file.[ext]
+    > file
+    > file.[ext]
 
 Cypress looked for these file extensions at the provided path:
 
-  > js, ts, json
+    > js, ts, json
 
-Provide a path to an existing fixture file.
+Provide a path to an existing fixture file.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html b/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html index c58265328b29..b76f5c1849c5 100644 --- a/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html +++ b/packages/errors/__snapshot-html__/FOLDER_NOT_WRITABLE.html @@ -34,11 +34,11 @@ -
This folder is not writable: /path/to/folder
+    
This folder is not writable: /path/to/folder
 
 Writing to this directory is required by Cypress in order to store screenshots and videos.
 
 Enable write permissions to this directory to ensure screenshots and videos are stored.
 
-If you don't require screenshots or videos to be stored you can safely ignore this warning.
+If you don't require screenshots or videos to be stored you can safely ignore this warning.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html index e22dbe1d25af..16acdd16cf39 100644 --- a/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html +++ b/packages/errors/__snapshot-html__/FREE_PLAN_IN_GRACE_PERIOD_PARALLEL_FEATURE.html @@ -34,9 +34,9 @@ -
Parallelization is not included under your free plan.
+    
Parallelization is not included under your free plan.
 
 Your plan is now in a grace period, which means your tests will still run in parallel until Feb 1, 2022. Please upgrade your plan to continue running your tests in parallel in the future.
 
-https://on.cypress.io/set-up-billing
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html b/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html index 685190a9bb51..6b1a26de534d 100644 --- a/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html +++ b/packages/errors/__snapshot-html__/INCOMPATIBLE_PLUGIN_RETRIES.html @@ -34,12 +34,12 @@ -
We've detected that the incompatible plugin cypress-plugin-retries is installed at: ./path/to/cypress-plugin-retries
+    
We've detected that the incompatible plugin cypress-plugin-retries is installed at: ./path/to/cypress-plugin-retries
 
-Test retries is now natively supported in Cypress version 5.0.0.
+Test retries is now natively supported in Cypress version 5.0.0.
 
 Remove the plugin from your dependencies to silence this warning.
 
 https://on.cypress.io/test-retries
-
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html b/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html index 8ab52b574863..1cf399ad14b2 100644 --- a/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html +++ b/packages/errors/__snapshot-html__/INCORRECT_CI_BUILD_ID_USAGE.html @@ -34,11 +34,11 @@ -
You passed the --ci-build-id flag but did not provide either a --group or --parallel flag.
+    
You passed the --ci-build-id flag but did not provide either a --group or --parallel flag.
 
 The --ci-build-id flag you passed was: ciBuildId123
 
 The --ci-build-id flag is used to either group or parallelize multiple runs together.
 
-https://on.cypress.io/incorrect-ci-build-id-usage
+https://on.cypress.io/incorrect-ci-build-id-usage
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html b/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html index ccdc5f366797..e5e81e635832 100644 --- a/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html +++ b/packages/errors/__snapshot-html__/INDETERMINATE_CI_BUILD_ID.html @@ -34,7 +34,7 @@ -
You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId.
+    
You passed the --group or --parallel flag but we could not automatically determine or generate a ciBuildId.
 
 The --group flag you passed was: foo
 The --parallel flag you passed was: false
@@ -69,5 +69,5 @@
 
 Because the ciBuildId could not be auto-detected you must pass the --ci-build-id flag manually.
 
-https://on.cypress.io/indeterminate-ci-build-id
+https://on.cypress.io/indeterminate-ci-build-id
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html b/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html index ff9126ba2c98..10693665d813 100644 --- a/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html +++ b/packages/errors/__snapshot-html__/INVALID_CYPRESS_INTERNAL_ENV.html @@ -34,7 +34,7 @@ -
We have detected an unknown or unsupported CYPRESS_INTERNAL_ENV value: foo
+    
We have detected an unknown or unsupported CYPRESS_INTERNAL_ENV value: foo
 
-CYPRESS_INTERNAL_ENV is reserved for internal use and cannot be modified.
+CYPRESS_INTERNAL_ENV is reserved for internal use and cannot be modified.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html b/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html index a526ac7d00a1..e51a50e7d70a 100644 --- a/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html +++ b/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html @@ -41,9 +41,10 @@ - /path/to/reporter - /path/reporter -The error was: +Learn more at https://on.cypress.io/reporters -stack-trace - -Learn more at https://on.cypress.io/reporters
+Error: fail whale + at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at INVALID_REPORTER_NAME (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) +
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html index b34c8449a136..2ae1e6cfc1ad 100644 --- a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html +++ b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_BUNDLED.html @@ -34,12 +34,12 @@ -
Deprecation Warning: `nodeVersion` is currently set to `bundled` in the `cypress.json` configuration file.
+    
Deprecation Warning: nodeVersion is currently set to bundled in the cypress.json configuration file.
 
-As of Cypress version 9.0.0 the default behavior of `nodeVersion` has changed to always use the version of Node used to start cypress via the cli.
+As of Cypress version 9.0.0 the default behavior of nodeVersion has changed to always use the version of Node used to start cypress via the cli.
 
-When `nodeVersion` is set to `bundled`, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations.
+When nodeVersion is set to bundled, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations.
 
-As the `nodeVersion` configuration option will be removed in a future release, it is recommended to remove the `nodeVersion` configuration option from `cypress.json`.
-
+As the nodeVersion configuration option will be removed in a future release, it is recommended to remove the nodeVersion configuration option from cypress.json.
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html index f1f0cbf674e8..528bc1d53bbf 100644 --- a/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html +++ b/packages/errors/__snapshot-html__/NODE_VERSION_DEPRECATION_SYSTEM.html @@ -34,10 +34,10 @@ -
Deprecation Warning: `nodeVersion` is currently set to `system` in the `cypress.json` configuration file.
+    
Deprecation Warning: nodeVersion is currently set to system in the cypress.json configuration file.
 
-As of Cypress version 9.0.0 the default behavior of `nodeVersion` has changed to always use the version of Node used to start cypress via the cli.
+As of Cypress version 9.0.0 the default behavior of nodeVersion has changed to always use the version of Node used to start cypress via the cli.
 
-Please remove the `nodeVersion` configuration option from `cypress.json`.
-
+Please remove the nodeVersion configuration option from cypress.json.
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html b/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html index 6c5020ef1710..227513bc9861 100644 --- a/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html +++ b/packages/errors/__snapshot-html__/NO_DEFAULT_CONFIG_FILE_FOUND.html @@ -34,5 +34,5 @@ -
Could not find a Cypress configuration file in this folder: /path/to/project/root
+    
Could not find a Cypress configuration file in this folder: /path/to/project/root
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html b/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html index ab286c77bb59..8b241e509dbf 100644 --- a/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html +++ b/packages/errors/__snapshot-html__/NO_PROJECT_FOUND_AT_PROJECT_ROOT.html @@ -34,5 +34,5 @@ -
Can't find a project at the path: /path/to/project
+    
Can't find a project at the path: /path/to/project
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_PROJECT_ID.html b/packages/errors/__snapshot-html__/NO_PROJECT_ID.html index a36b5c26ca3c..971dabfdcf6a 100644 --- a/packages/errors/__snapshot-html__/NO_PROJECT_ID.html +++ b/packages/errors/__snapshot-html__/NO_PROJECT_ID.html @@ -34,5 +34,5 @@ -
Can't find projectId in the config file: /path/to/project/cypress.json
+    
Can't find projectId in the config file: /path/to/project/cypress.json
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html index aaf59f28170c..fe4467703999 100644 --- a/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html +++ b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - noPattern.html @@ -34,9 +34,9 @@ -
Can't run because no spec files were found.
+    
Can't run because no spec files were found.
 
 We searched for specs inside of this folder:
 
-  > /path/to/project/root
+  > /path/to/project/root
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html b/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html index 862f9b0dc241..065b2725799d 100644 --- a/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html +++ b/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html @@ -34,9 +34,9 @@ -
Can't run because no spec files were found.
+    
Can't run because no spec files were found.
 
 We searched for specs matching this glob pattern:
 
-  > /path/to/project/root/**_spec.js
+  > /path/to/project/root/**_spec.js
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html b/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html index 57fbc5da8c99..1cb817fc1d87 100644 --- a/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html +++ b/packages/errors/__snapshot-html__/PARALLEL_FEATURE_NOT_AVAILABLE_IN_PLAN.html @@ -34,9 +34,9 @@ -
Parallelization is not included under your current billing plan.
+    
Parallelization is not included under your current billing plan.
 
 To run your tests in parallel, please visit your billing and upgrade to another plan with parallelization.
 
-https://on.cypress.io/set-up-billing
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html b/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html index 5abe869dabf2..9887d810545e 100644 --- a/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html +++ b/packages/errors/__snapshot-html__/PLAN_IN_GRACE_PERIOD_RUN_GROUPING_FEATURE_USED.html @@ -34,9 +34,9 @@ -
Grouping is not included under your free plan.
+    
Grouping is not included under your free plan.
 
 Your plan is now in a grace period, which means your tests will still run with groups until Feb 1, 2022. Please upgrade your plan to continue running your tests with groups in the future.
 
-https://on.cypress.io/set-up-billing
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html index 646361fbfd02..46908136c8bf 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html @@ -34,7 +34,7 @@ -
An invalid configuration value returned from the plugins file: /path/to/pluginsFile
+    
An invalid configuration value returned from the plugins file: /path/to/pluginsFile
 
-fail whale
+fail whale
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html index 8719a597fa14..c28b03cbbe4c 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html @@ -36,15 +36,15 @@
The pluginsFile must export a function with the following signature:
 
-// /path/to/pluginsFile
-module.exports = (on, config) => {
-  // configure plugins here
-}
+// /path/to/pluginsFile
+module.exports = (on, config) => {
+  // configure plugins here
+}
 
 Instead it exported:
 
-"some string"
+"some string"
 
 Learn more: https://on.cypress.io/plugins-api
-
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html index 11dd132b2b0a..06c10226ad56 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html @@ -36,17 +36,17 @@
The pluginsFile must export a function with the following signature:
 
-// /path/to/pluginsFile
-module.exports = (on, config) => {
-  // configure plugins here
-}
+// /path/to/pluginsFile
+module.exports = (on, config) => {
+  // configure plugins here
+}
 
 Instead it exported:
 
-{
-  "some": "object"
-}
+{
+  "some": "object"
+}
 
 Learn more: https://on.cypress.io/plugins-api
-
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html index 14e1f504a64b..50368a205812 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html @@ -34,11 +34,12 @@ -
Your pluginsFile is invalid: /path/to/pluginsFile
+    
Your pluginsFile is invalid: /path/to/pluginsFile
 
 It threw an error when required, check the stack trace below:
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_FILE_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_FILE_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html index 49946905dfdf..a43cc0343e60 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FILE_NOT_FOUND.html @@ -34,10 +34,10 @@ -
Your pluginsFile was not found at path: /path/to/pluginsFile
+    
Your pluginsFile was not found at path: /path/to/pluginsFile
 
-Create this file, or set pluginsFile to false if a plugins file is not necessary for your project.
+Create this file, or set pluginsFile to false if a plugins file is not necessary for your project.
 
 If you have just renamed the extension of your pluginsFile, restart Cypress.
-
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html index 33a7833ab9de..a9bc77073196 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html @@ -34,9 +34,10 @@ -
The function exported by the pluginsFile threw an error: /path/to/pluginsFile
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_FUNCTION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    
The function exported by the pluginsFile threw an error: /path/to/pluginsFile
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_FUNCTION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html index b052b4359f04..398c736021bf 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html @@ -34,7 +34,7 @@ -
Your pluginsFile threw a validation error: /path/to/pluginsFile
+    
Your pluginsFile threw a validation error: /path/to/pluginsFile
 
 You must pass a valid event name when registering a plugin.
 
@@ -45,8 +45,9 @@
  - foo
  - bar
  - baz
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_INVALID_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_INVALID_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html index be988155e26a..9471f4783271 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html @@ -37,8 +37,8 @@
An error was thrown in your plugins file while executing the handler for the before:spec event.
 
 The error we received was:
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_RUN_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_RUN_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html index 8a955fa32d86..e0a7e41eeb25 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html @@ -36,9 +36,10 @@
We stopped running your tests because a plugin crashed.
 
-The following error was thrown by your plugins file: /path/to/pluginsFile
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+The following error was thrown by your plugins file: /path/to/pluginsFile
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html index 99a1effcb3a3..f3ff5f5e226d 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html @@ -34,9 +34,10 @@ -
Your pluginsFile threw a validation error: /path/to/pluginsFile
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    
Your pluginsFile threw a validation error: /path/to/pluginsFile
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html b/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html index dca0726efd80..8a43571d3fc1 100644 --- a/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html +++ b/packages/errors/__snapshot-html__/PORT_IN_USE_LONG.html @@ -36,5 +36,5 @@
Can't run project because port is currently in use: 2020
 
-Assign a different port with the --port <port> argument or shut down the other running process.
+Assign a different port with the --port <port> argument or shut down the other running process.
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html b/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html index 24506afdcccc..a51bae399d44 100644 --- a/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html +++ b/packages/errors/__snapshot-html__/PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION.html @@ -38,7 +38,7 @@ It currently has the projectId: project-id-123 -You also provided your Record Key, but you did not pass the --record flag. +You also provided your Record Key, but you did not pass the --record flag. This run will not be recorded. @@ -50,5 +50,5 @@ $ cypress run --record false -https://on.cypress.io/recording-project-runs +https://on.cypress.io/recording-project-runs
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html b/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html index f312a70eea3c..b72f6fb5c6cc 100644 --- a/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html +++ b/packages/errors/__snapshot-html__/RECORD_KEY_MISSING.html @@ -34,13 +34,13 @@ -
You passed the --record flag but did not provide us your Record Key.
+    
You passed the --record flag but did not provide us your Record Key.
 
 You can pass us your Record Key like this:
 
   $ cypress run --record --key <record_key>
 
-You can also set the key as an environment variable with the name: CYPRESS_RECORD_KEY
+You can also set the key as an environment variable with the name: CYPRESS_RECORD_KEY
 
-https://on.cypress.io/how-do-i-record-runs
+https://on.cypress.io/how-do-i-record-runs
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html b/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html index 1511f56bea7b..a2e297e1e4dc 100644 --- a/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html +++ b/packages/errors/__snapshot-html__/RECORD_PARAMS_WITHOUT_RECORDING.html @@ -34,11 +34,11 @@ -
You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag.
+    
You passed the --ci-build-id, --group, --tag, or --parallel flag without also passing the --record flag.
 
 The --parallel flag you passed was: true
 
 These flags can only be used when recording to the Cypress Dashboard service.
 
-https://on.cypress.io/record-params-without-recording
+https://on.cypress.io/record-params-without-recording
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html b/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html index c5d54192c4d9..79d90cf30b52 100644 --- a/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html +++ b/packages/errors/__snapshot-html__/RENAMED_CONFIG_OPTION.html @@ -36,5 +36,5 @@
The oldName configuration option you have supplied has been renamed.
 
-Please rename oldName to newName
+Please rename oldName to newName
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html b/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html index d497a729e8a1..230f7a2cfa91 100644 --- a/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html +++ b/packages/errors/__snapshot-html__/RUN_GROUPING_FEATURE_NOT_AVAILABLE_IN_PLAN.html @@ -34,9 +34,9 @@ -
Grouping is not included under your current billing plan.
+    
Grouping is not included under your current billing plan.
 
 To run your tests with groups, please visit your billing and upgrade to another plan with grouping.
 
-https://on.cypress.io/set-up-billing
+https://on.cypress.io/set-up-billing
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html index a9c9e4eb89ab..dba440d0b223 100644 --- a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html @@ -34,7 +34,7 @@ -
We found an invalid value in the file: cypress.json
+    
We found an invalid value in the file: cypress.json
 
-fail whale
+fail whale
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html index 165b304fb95e..603486e8418f 100644 --- a/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html @@ -34,13 +34,13 @@ -
Your supportFile is missing or invalid: /path/to/supportFile
+    
Your supportFile is missing or invalid: /path/to/supportFile
 
 The supportFile must be a .js, .ts, .coffee file or be supported by your preprocessor plugin (if configured).
 
-Fix your support file, or set supportFile to false if a support file is not necessary for your project.
+Fix your support file, or set supportFile to false if a support file is not necessary for your project.
 
 If you have just renamed the extension of your supportFile, restart Cypress.
 
-Learn more at https://on.cypress.io/support-file-missing-or-invalid
+Learn more at https://on.cypress.io/support-file-missing-or-invalid
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html b/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html index ef45cb19ad88..9d870a82c6e7 100644 --- a/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html +++ b/packages/errors/__snapshot-html__/UNEXPECTED_BEFORE_BROWSER_LAUNCH_PROPERTIES.html @@ -34,7 +34,7 @@ -
The launchOptions object returned by your plugin's before:browser:launch handler contained unexpected properties:
+    
The launchOptions object returned by your plugin's before:browser:launch handler contained unexpected properties:
 
  - baz
 
@@ -44,5 +44,5 @@
  - extensions
  - args
 
-https://on.cypress.io/browser-launch-api
+https://on.cypress.io/browser-launch-api
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html index 74547800c5ff..1ddefce07f49 100644 --- a/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html +++ b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html @@ -37,8 +37,8 @@
Warning: We failed processing this video.
 
 This error will not alter the exit code.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at VIDEO_POST_PROCESSING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at VIDEO_POST_PROCESSING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html index 6620a45d8802..8c629e1a1988 100644 --- a/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html +++ b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html @@ -37,8 +37,8 @@
Warning: We failed to record the video.
 
 This error will not alter the exit code.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at VIDEO_RECORDING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at VIDEO_RECORDING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/package.json b/packages/errors/package.json index ab09cec89a20..e162033abbf0 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -6,6 +6,7 @@ "browser": "src/index.ts", "scripts": { "test": "yarn test-unit", + "comparison": "node -r @packages/ts/register test/support/error-comparison-tool.ts", "build": "../../scripts/run-if-ci.sh tsc || echo 'type errors'", "build-prod": "tsc", "check-ts": "tsc --noEmit", diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index 037bfecd60c1..a9ab1288eba3 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -5,13 +5,14 @@ import { stripIndent } from './stripIndent' import type { ErrTemplateResult, SerializedError } from './errorTypes' import assert from 'assert' +import stripAnsi from 'strip-ansi' interface ListOptions { prefix?: string - color?: Function + color?: keyof typeof theme } -const theme = { +export const theme = { blue: chalk.blueBright, gray: chalk.gray, white: chalk.white, @@ -46,13 +47,28 @@ class Format { private formatAnsi () { const val = this.prepVal('ansi') + const color = fmtHighlight[this.type] + + if (this.type === 'terminal') { + return `${theme.gray('$')} ${color(val)}` + } return fmtHighlight[this.type](val) } private formatMarkdown () { + if (this.type === 'comment') { + return this.val + } + + const val = this.prepVal('markdown') + + if (this.type === 'terminal') { + return `${'```'}\n$ ${val}${'```'}` + } + if (this.type === 'code') { - return this.prepVal('markdown') + return `${'```'}\n${val}${'```'}` } return mdFence(this.prepVal('markdown')) @@ -60,11 +76,11 @@ class Format { private prepVal (target: 'ansi' | 'markdown'): string { if (this.val instanceof PartialErr) { - return prepMessage(this.val.strArr, this.val.args, target) + return prepMessage(this.val.strArr, this.val.args, target, true) } if (isErrorLike(this.val)) { - return `\n${this.val.name}: ${this.val.message}` + return `${this.val.name}: ${this.val.message}` } if (this.val && (typeof this.val === 'object' || Array.isArray(this.val))) { @@ -103,6 +119,7 @@ const fmtHighlight = { highlight: theme.yellow, highlightSecondary: theme.magenta, highlightTertiary: theme.blue, + terminal: theme.blue, } as const export const fmt = { @@ -115,8 +132,8 @@ export const fmt = { highlight: makeFormat('highlight'), highlightSecondary: makeFormat('highlightSecondary'), highlightTertiary: makeFormat('highlightTertiary'), + terminal: makeFormat('terminal'), off: guard, - terminal, listItem, listItems, listFlags, @@ -124,10 +141,6 @@ export const fmt = { cypressVersion, } -function terminal (str: string) { - return guard(`${theme.gray('$')} ${theme.blue(str)}`) -} - function cypressVersion (version: string) { const parts = version.split('.') @@ -141,10 +154,10 @@ function cypressVersion (version: string) { function _item (item: string, options: ListOptions = {}) { const { prefix, color } = _.defaults(options, { prefix: '', - color: theme.blue, + color: 'blue', }) - return stripIndent`${theme.gray(prefix)}${color(item)}` + return stripIndent`${theme.gray(prefix)}${theme[color](item)}` } function listItem (item: string, options: ListOptions = {}) { @@ -224,6 +237,8 @@ export const errPartial = (templateStr: TemplateStringsArray, ...args: AllowedPa return new PartialErr(templateStr, args) } +let originalError: Error | undefined = undefined + /** * Creates a consistently formatted object to return from the error call. * @@ -236,19 +251,30 @@ export const errPartial = (templateStr: TemplateStringsArray, ...args: AllowedPa * - If details is an error, it gets provided as originalError */ export const errTemplate = (strings: TemplateStringsArray, ...args: AllowedTemplateArg[]): ErrTemplateResult => { - let originalError: Error | undefined = undefined - const msg = trimMultipleNewLines(prepMessage(strings, args, 'ansi')) return { message: msg, originalError, - messageMarkdown: trimMultipleNewLines(prepMessage(strings, args, 'markdown')), + messageMarkdown: trimMultipleNewLines(stripAnsi(prepMessage(strings, args, 'markdown'))), } } -function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTemplateArg[], target: 'ansi' | 'markdown'): string { - let originalError: Error | undefined = undefined +/** + * Takes an `errTemplate` / `errPartial` and converts it into a string, formatted conditionally + * depending on the target environment + * + * @param templateStrings + * @param args + * @param target + * @returns + */ +function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTemplateArg[], target: 'ansi' | 'markdown', isPartial: boolean = false): string { + // Reset the originalError to undefined on each new template string pass, we only need it to guard + if (!isPartial) { + originalError = undefined + } + const templateArgs = [] for (const arg of args) { @@ -262,8 +288,10 @@ function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTempla // Format = stringify & color ANSI, or make a markdown block templateArgs.push(arg.formatVal(target)) } else if (arg instanceof StackTrace) { + assert(!originalError, `Cannot use fmt.stackTrace() multiple times in the same errTemplate`) + assert(!isPartial, `Cannot use fmt.stackTrace() in errPartial template string`) + if (isErrorLike(arg.val)) { - assert(!originalError, `Cannot use fmt.stackTrace() multiple times in the same errTemplate`) originalError = arg.val } else { if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { @@ -275,9 +303,15 @@ function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTempla err.stack = typeof arg.val === 'string' ? arg.val : JSON.stringify(arg.val) originalError = err } + + if (target === 'ansi') { + templateArgs.push(chalk.magenta(originalError.stack ?? originalError.message)) + } else { + templateArgs.push('') + } } else if (arg instanceof PartialErr) { // Partial error = prepMessage + interpolate - templateArgs.push(prepMessage(arg.strArr, arg.args, target)) + templateArgs.push(prepMessage(arg.strArr, arg.args, target, true)) } else { throw new Error(`Invalid value passed to prepMessage, saw ${arg}`) } diff --git a/packages/errors/src/errorUtils.ts b/packages/errors/src/errorUtils.ts index 41f2fc06a2ad..7cfc32041bf8 100644 --- a/packages/errors/src/errorUtils.ts +++ b/packages/errors/src/errorUtils.ts @@ -38,9 +38,9 @@ type AllowedChalkColors = 'red' | 'blue' | 'green' | 'magenta' | 'yellow' export const logError = function (err: CypressError | ErrorLike, color: AllowedChalkColors = 'red') { console.log(chalk[color](err.message)) - if (err.originalError) { - console.log(`\n${err.originalError.stack}`) - } + // if (err.originalError) { + // console.log(`\n${err.originalError.stack}`) + // } // bail if this error came from known // list of Cypress errors diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index aeff1e1d5896..83ca8d324e52 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -5,7 +5,7 @@ import _ from 'lodash' import path from 'path' import stripAnsi from 'strip-ansi' import { humanTime, logError, pluralize } from './errorUtils' -import { errPartial, errTemplate, fmt } from './errTemplate' +import { errPartial, errTemplate, fmt, theme } from './errTemplate' import { stackWithoutMessage } from './stackUtils' import type { ClonedError, CypressError, ErrorLike, ErrTemplateResult } from './errorTypes' @@ -26,7 +26,7 @@ const displayRetriesRemaining = function (tries: number) { const getUsedTestsMessage = (limit: number, usedTestsMessage: string) => { return _.isFinite(limit) - ? fmt.off(`The limit is ${fmt.highlight(`${limit}`)} ${usedTestsMessage} results.`) + ? fmt.off(`The limit is ${chalk.yellow(`${limit}`)} ${usedTestsMessage} results.`) : fmt.off('') } @@ -50,37 +50,37 @@ This flag must be unique for each new run, but must also be identical for each m * The errors must return an "errTemplate", this is processed by the */ export const AllCypressErrors = { - CANNOT_TRASH_ASSETS: (arg1: string) => { + CANNOT_TRASH_ASSETS: (arg1: Error) => { return errTemplate`\ Warning: We failed to trash the existing run results. This error will not alter the exit code. - ${fmt.highlightSecondary(arg1)}` + ${fmt.stackTrace(arg1)}` }, - CANNOT_REMOVE_OLD_BROWSER_PROFILES: (arg1: string) => { + CANNOT_REMOVE_OLD_BROWSER_PROFILES: (arg1: Error) => { return errTemplate`\ Warning: We failed to remove old browser profiles from previous runs. This error will not alter the exit code. - ${fmt.highlightSecondary(arg1)}` + ${fmt.stackTrace(arg1)}` }, - VIDEO_RECORDING_FAILED: (arg1: string) => { + VIDEO_RECORDING_FAILED: (arg1: Error) => { return errTemplate`\ Warning: We failed to record the video. This error will not alter the exit code. - ${fmt.highlightSecondary(arg1)}` + ${fmt.stackTrace(arg1)}` }, - VIDEO_POST_PROCESSING_FAILED: (arg1: string) => { + VIDEO_POST_PROCESSING_FAILED: (arg1: Error) => { return errTemplate`\ Warning: We failed processing this video. This error will not alter the exit code. - ${fmt.highlightSecondary(arg1)}` + ${fmt.stackTrace(arg1)}` }, CHROME_WEB_SECURITY_NOT_SUPPORTED: (browser: string) => { return errTemplate`\ @@ -200,7 +200,7 @@ export const AllCypressErrors = { Warning from Cypress Dashboard: ${fmt.highlight(arg1.message)} Details: - ${fmt.highlightSecondary(arg1.props)}` + ${fmt.meta(arg1.props)}` }, DASHBOARD_STALE_RUN: (arg1: {runUrl: string, [key: string]: any}) => { return errTemplate`\ @@ -289,7 +289,7 @@ export const AllCypressErrors = { This machine sent the following parameters: - ${fmt.highlightSecondary(arg1.parameters)} + ${fmt.meta(arg1.parameters)} https://on.cypress.io/parallel-group-params-mismatch` }, @@ -325,7 +325,7 @@ export const AllCypressErrors = { return errTemplate`\ Warning: Multiple attempts to register the following task(s): - ${fmt.listItems(arg1, { color: fmt.highlight })} + ${fmt.listItems(arg1, { color: 'yellow' })} Only the last attempt will be registered.` }, @@ -430,11 +430,11 @@ export const AllCypressErrors = { Errors: - ${fmt.highlightSecondary(arg1.errors)} + ${fmt.meta(arg1.errors)} Request Sent: - ${fmt.highlightTertiary(arg1.object)}` + ${fmt.meta(arg1.object)}` }, RECORDING_FROM_FORK_PR: () => { return errTemplate`\ @@ -534,14 +534,14 @@ export const AllCypressErrors = { ${fmt.listItem(folderPath)}` } - const globPath = path.join(chalk.blue(folderPath), globPattern) + const globPath = path.join(theme.blue(folderPath), globPattern) return errTemplate`\ Can't run because ${fmt.highlightSecondary(`no spec files`)} were found. We searched for specs matching this glob pattern: - ${fmt.listItem(globPath, { color: fmt.highlight })}` + ${fmt.listItem(globPath, { color: 'yellow' })}` }, RENDERER_CRASHED: () => { return errTemplate`\ @@ -610,7 +610,7 @@ export const AllCypressErrors = { Instead it exported: - ${fmt.highlightSecondary(exported)} + ${fmt.meta(JSON.stringify(exported, null, 2))} Learn more: https://on.cypress.io/plugins-api ` @@ -741,7 +741,7 @@ export const AllCypressErrors = { } }, // TODO: test this - INVALID_REPORTER_NAME: (arg1: {name: string, paths: string[], error: string}) => { + INVALID_REPORTER_NAME: (arg1: {name: string, paths: string[], error: Error}) => { return errTemplate`\ Error loading the reporter: ${fmt.highlight(arg1.name)} @@ -749,11 +749,10 @@ export const AllCypressErrors = { ${fmt.listItems(arg1.paths)} - The error was: - + Learn more at https://on.cypress.io/reporters + ${fmt.stackTrace(arg1.error)} - - Learn more at https://on.cypress.io/reporters` + ` }, // TODO: test this out NO_DEFAULT_CONFIG_FILE_FOUND: (arg1: string) => { @@ -980,7 +979,7 @@ export const AllCypressErrors = { // TODO: test this COULD_NOT_PARSE_ARGUMENTS: (argName: string, argValue: string, errMsg: string) => { return errTemplate`\ - Cypress encountered an error while parsing the argument: ${fmt.flag(`--${argName}`)} + Cypress encountered an error while parsing the argument: ${fmt.highlight(`--${argName}`)} You passed: ${fmt.highlightTertiary(argValue)} @@ -1071,7 +1070,7 @@ export const AllCypressErrors = { return errTemplate`\ The following configuration ${fmt.off(phrase)} invalid: - ${fmt.listItems(arg1, { color: fmt.highlight })} + ${fmt.listItems(arg1, { color: 'yellow' })} https://on.cypress.io/configuration ` diff --git a/packages/errors/test/support/error-comparison-tool.ts b/packages/errors/test/support/error-comparison-tool.ts new file mode 100644 index 000000000000..898160b87486 --- /dev/null +++ b/packages/errors/test/support/error-comparison-tool.ts @@ -0,0 +1,191 @@ +/* eslint-disable no-console */ +import express from 'express' +import fs from 'fs-extra' +import path from 'path' +import Markdown from 'markdown-it' + +const ERRORS_DIR = path.join(__dirname, '..', '..') +const SNAPSHOT_HTML = path.join(ERRORS_DIR, '__snapshot-html__') +const SNAPSHOT_HTML_LOCAL = path.join(ERRORS_DIR, '__snapshot-html-local__') +const SNAPSHOT_MARKDOWN = path.join(ERRORS_DIR, '__snapshot-md__') + +const app = express() + +const LINKS = `
Ansi Compare | Ansi Base List | Markdown
` + +async function getRows (offset = 0, baseList: boolean = false) { + const toCompare = (await fs.readdir(baseList ? SNAPSHOT_HTML : SNAPSHOT_HTML_LOCAL)).filter((f) => f.endsWith('.html')).sort() + const rows = toCompare.slice(offset, offset + 10).map((f) => path.basename(f).split('.')[0]).map((name) => { + return ` + + ${name} + + ${baseList ? '' : ` + + + `} + ` + }) + + if (toCompare.length > offset + 10) { + rows.push(``) + } + + return rows.join('\n') +} + +app.get('/', async (req, res) => { + try { + const rows = await getRows() + + res.type('html').send(` + + + ${LINKS} + + + + + + ${rows} + +
TableOriginalNew
+ `) + } catch (e) { + res.json({ errStack: e.stack }) + } +}) + +app.get('/base-list', async (req, res) => { + try { + const rows = await getRows(0, true) + + res.type('html').send(` + + + ${LINKS} + + + + + + ${rows} + +
TableOriginal
+ `) + } catch (e) { + res.json({ errStack: e.stack }) + } +}) + +app.get<{offset: number}>('/load-more/:offset', async (req, res) => { + const rows = await getRows(req.params.offset) + + res.send(rows) +}) + +app.get<{offset: number}>('/load-more-base/:offset', async (req, res) => { + const rows = await getRows(req.params.offset, true) + + res.send(rows) +}) + +app.get('/looks-good/:name', async (req, res) => { + try { + await fs.move( + path.join(SNAPSHOT_HTML_LOCAL, `${req.params.name}.html`), + path.join(SNAPSHOT_HTML, `${req.params.name}.html`), + { overwrite: true }, + ) + + res.json({ ok: true }) + } catch (e) { + res.status(400).json({ stack: e.stack }) + } +}) + +app.get<{name: string, type: string}>('/html/:name/:type', async (req, res) => { + const pathToFile = path.join(ERRORS_DIR, req.params.type, `${req.params.name}.html`) + + try { + const contents = await fs.readFile(pathToFile, 'utf8') + + res.type('html').send(contents.replace(/\/g, '').replace('overflow: hidden;', '')) + } catch (e) { + res.json({ errStack: e }) + } +}) + +app.get('/md', async (req, res) => { + try { + const toRender = (await fs.readdir(SNAPSHOT_MARKDOWN)).filter((f) => f.endsWith('.md')).sort() + const markdownContents = await Promise.all(toRender.map((f) => fs.readFile(path.join(SNAPSHOT_MARKDOWN, f), 'utf8'))) + const md = new Markdown({ + html: true, + linkify: true, + }) + + res.type('html').send(` + ${LINKS} + +
+ ${toRender.map((r, i) => { + return `
+
${path.basename(r).split('.')[0]}
+
+
${md.render(markdownContents[i] ?? '')}
+
${markdownContents[i] ?? ''}
+
+
` + }).join('\n')} +
+ `) + } catch (e) { + res.json({ stack: e.stack }) + } +}) + +app.listen(5555, () => { + console.log(`Comparison server listening on 5555`) +}) diff --git a/packages/errors/test/unit/errTemplate_spec.ts b/packages/errors/test/unit/errTemplate_spec.ts index f535e2373434..2ba8975869c8 100644 --- a/packages/errors/test/unit/errTemplate_spec.ts +++ b/packages/errors/test/unit/errTemplate_spec.ts @@ -25,20 +25,6 @@ describe('errTemplate', () => { expect(obj).to.include({ messageMarkdown: `Hello world special` }) }) - it('provides as details for toErrorProps', () => { - const errStack = new Error().stack ?? '' - const obj = errTemplate` - This was an error - - ${fmt.stackTrace(errStack)} - ` - - expect(obj).to.include({ - message: `This was an error`, - details: chalk.magenta(errStack), - }) - }) - it('will stringify non scalar values', () => { const someObj = { a: 1, b: 2, c: 3 } const obj = errTemplate` @@ -51,9 +37,7 @@ describe('errTemplate', () => { messageMarkdown: stripIndent` This was returned from the app: - \`\`\` - ${JSON.stringify(someObj, null, 2)} - \`\`\``, + \`\`\`\n${JSON.stringify(someObj, null, 2)}\n\`\`\``, }) expect(obj).to.include({ @@ -65,21 +49,6 @@ describe('errTemplate', () => { }) }) - it('will stringify details values', () => { - const someObj = { a: 1, b: 2, c: 3 } - const obj = errTemplate` - This was returned from the app: - - ${fmt.stackTrace(someObj)} - ` - - expect(obj).to.include({ messageMarkdown: `This was returned from the app:` }) - expect(obj).to.include({ - message: `This was returned from the app:`, - details: chalk.magenta(JSON.stringify(someObj, null, 2)), - }) - }) - it('uses details to set originalError, for toErrorProps, highlight stack for console', () => { const specFile = 'specFile.js' const err = new Error() diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 4bd6ee551d62..7b0d24c4f6c0 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -92,6 +92,10 @@ const snapshotAndTestErrorConsole = async function (errorFileName: string) { `) // remove margin/padding and force text overflow like a terminal + if (isCi) { + expect(logs).not.to.contain('[object Object]') + } + try { fse.accessSync(errorFileName) } catch (e) { @@ -176,6 +180,7 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K // errors were removed and we have stale snapshots return Promise.all([ isCi ? fse.remove(snapshotHtmlFolder) : null, + fse.remove(snapshotMarkdownFolder), fse.remove(snapshotHtmlLocalFolder), fse.remove(snapshotImagesFolder), ]) @@ -253,28 +258,28 @@ describe('visual error templates', () => { const err = makeErr() return { - default: [err.stack], + default: [err], } }, CANNOT_REMOVE_OLD_BROWSER_PROFILES: () => { const err = makeErr() return { - default: [err.stack], + default: [err], } }, VIDEO_RECORDING_FAILED: () => { const err = makeErr() return { - default: [err.stack], + default: [err], } }, VIDEO_POST_PROCESSING_FAILED: () => { const err = makeErr() return { - default: [err.stack], + default: [err], } }, CHROME_WEB_SECURITY_NOT_SUPPORTED: () => { @@ -714,7 +719,7 @@ describe('visual error templates', () => { default: [{ name: 'missing-reporter-name', paths: ['/path/to/reporter', '/path/reporter'], - error: `stack-trace`, + error: makeErr(), }], } }, diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index 59d8b2fc87f7..affcbd620f5d 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -643,7 +643,7 @@ const removeOldProfiles = (browser) => { return browserUtils.removeOldProfiles(browser) .catch((err) => { // dont make removing old browsers profiles break the build - return errors.warning('CANNOT_REMOVE_OLD_BROWSER_PROFILES', err.stack) + return errors.warning('CANNOT_REMOVE_OLD_BROWSER_PROFILES', err) }) } @@ -659,7 +659,7 @@ const trashAssets = Promise.method((config = {}) => { ]) .catch((err) => { // dont make trashing assets fail the build - return errors.warning('CANNOT_TRASH_ASSETS', err.stack) + return errors.warning('CANNOT_TRASH_ASSETS', err) }) }) @@ -669,7 +669,7 @@ const createVideoRecording = function (videoName, options = {}) { const onError = _.once((err) => { // catch video recording failures and log them out // but don't let this affect the run at all - return errors.warning('VIDEO_RECORDING_FAILED', err.stack) + return errors.warning('VIDEO_RECORDING_FAILED', err) }) return fs @@ -726,7 +726,7 @@ const maybeStartVideoRecording = Promise.method(function (options = {}) { const warnVideoRecordingFailed = (err) => { // log that post processing was attempted // but failed and dont let this change the run exit code - errors.warning('VIDEO_POST_PROCESSING_FAILED', err.stack) + errors.warning('VIDEO_POST_PROCESSING_FAILED', err) } module.exports = { diff --git a/packages/server/lib/project-base.ts b/packages/server/lib/project-base.ts index 49323c862df4..c9eb60b855ef 100644 --- a/packages/server/lib/project-base.ts +++ b/packages/server/lib/project-base.ts @@ -563,16 +563,12 @@ export class ProjectBase extends EE { try { Reporter.loadReporter(reporter, projectRoot) - } catch (err: any) { + } catch (error: any) { const paths = Reporter.getSearchPathsForReporter(reporter, projectRoot) - // only include the message if this is the standard MODULE_NOT_FOUND - // else include the whole stack - const errorMsg = err.code === 'MODULE_NOT_FOUND' ? err.message : err.stack - errors.throw('INVALID_REPORTER_NAME', { paths, - error: errorMsg, + error, name: reporter, }) } diff --git a/packages/ts/tsconfig.json b/packages/ts/tsconfig.json index 5644ad1319f8..e362620f935a 100644 --- a/packages/ts/tsconfig.json +++ b/packages/ts/tsconfig.json @@ -17,6 +17,7 @@ // "importHelpers": true, /* Import emit helpers from 'tslib'. */ // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + "useUnknownInCatchVariables": false, /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ From 1a2dbefe9960b938207081cf5f4c4f48fc7fbf87 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 8 Feb 2022 09:21:44 -0500 Subject: [PATCH 095/165] wrapping up error formatting --- .../desktop-gui/src/project/error-message.jsx | 4 +-- .../CANNOT_REMOVE_OLD_BROWSER_PROFILES.html | 8 +++--- .../CANNOT_TRASH_ASSETS.html | 8 +++--- .../CDP_COULD_NOT_CONNECT.html | 8 +++--- .../CDP_COULD_NOT_RECONNECT.html | 8 +++--- .../__snapshot-html__/ERROR_READING_FILE.html | 8 +++--- .../__snapshot-html__/ERROR_WRITING_FILE.html | 8 +++--- .../FIREFOX_COULD_NOT_CONNECT.html | 8 +++--- .../FIREFOX_MARIONETTE_FAILURE.html | 8 +++--- .../INVALID_REPORTER_NAME.html | 9 +++---- ...PLUGINS_DIDNT_EXPORT_FUNCTION - array.html | 18 ++++++------- ...LUGINS_DIDNT_EXPORT_FUNCTION - string.html | 4 +-- .../PLUGINS_DIDNT_EXPORT_FUNCTION.html | 4 +-- .../__snapshot-html__/PLUGINS_FILE_ERROR.html | 9 +++---- .../PLUGINS_FUNCTION_ERROR.html | 9 +++---- .../PLUGINS_INVALID_EVENT_ERROR.html | 9 +++---- .../PLUGINS_RUN_EVENT_ERROR.html | 8 +++--- .../PLUGINS_UNEXPECTED_ERROR.html | 9 +++---- .../PLUGINS_VALIDATION_ERROR.html | 9 +++---- .../VIDEO_POST_PROCESSING_FAILED.html | 8 +++--- .../VIDEO_RECORDING_FAILED.html | 8 +++--- packages/errors/package.json | 4 ++- packages/errors/src/errTemplate.ts | 20 +++++++++----- packages/errors/src/errorUtils.ts | 6 ++--- packages/errors/src/errors.ts | 2 +- packages/errors/test/mocha.opts | 3 +++ .../test/support/error-comparison-tool.ts | 2 -- packages/errors/test/support/utils.ts | 7 ++--- packages/errors/test/unit/errTemplate_spec.ts | 21 +++++++-------- packages/errors/test/unit/errors_spec.ts | 16 +---------- .../test/unit/visualSnapshotErrors_spec.ts | 27 ++++++++++++++----- .../server/lib/plugins/child/run_plugins.js | 3 ++- 32 files changed, 144 insertions(+), 139 deletions(-) diff --git a/packages/desktop-gui/src/project/error-message.jsx b/packages/desktop-gui/src/project/error-message.jsx index 9e62ef2a410c..4bc15e26d516 100644 --- a/packages/desktop-gui/src/project/error-message.jsx +++ b/packages/desktop-gui/src/project/error-message.jsx @@ -9,7 +9,7 @@ import Markdown from 'markdown-it' const _copyErrorDetails = (err) => { let details = [ - `**Message:** ${err.message}`, + `**Message:** ${err.messageMarkdown}`, ] if (err.details) { @@ -85,7 +85,7 @@ class ErrorMessage extends Component {

this.errorMessageNode = node} dangerouslySetInnerHTML={{ - __html: md.render(err.message), + __html: md.render(err.messageMarkdown), }}>
{err.originalError && ( diff --git a/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html index 5cc1dd6a3761..2763657e162e 100644 --- a/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html +++ b/packages/errors/__snapshot-html__/CANNOT_REMOVE_OLD_BROWSER_PROFILES.html @@ -37,8 +37,8 @@
Warning: We failed to remove old browser profiles from previous runs.
 
 This error will not alter the exit code.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CANNOT_REMOVE_OLD_BROWSER_PROFILES (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CANNOT_REMOVE_OLD_BROWSER_PROFILES (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html index 017007a3d782..3ab26c3ef702 100644 --- a/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html +++ b/packages/errors/__snapshot-html__/CANNOT_TRASH_ASSETS.html @@ -37,8 +37,8 @@
Warning: We failed to trash the existing run results.
 
 This error will not alter the exit code.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CANNOT_TRASH_ASSETS (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CANNOT_TRASH_ASSETS (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html index 85d9f10955d3..8cf89fe130b0 100644 --- a/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_CONNECT.html @@ -39,8 +39,8 @@ This usually indicates there was a problem opening the Chrome browser. The CDP port requested was 2345. - -Error: fail whale - at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at CDP_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+ +Error: fail whale + at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at CDP_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html index 6099e49b4c7b..d43f466e1880 100644 --- a/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html +++ b/packages/errors/__snapshot-html__/CDP_COULD_NOT_RECONNECT.html @@ -35,8 +35,8 @@
There was an error reconnecting to the Chrome DevTools protocol. Please restart the browser.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at CDP_COULD_NOT_RECONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at CDP_COULD_NOT_RECONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_READING_FILE.html b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html index 0415ef59922e..e31d0f8c0832 100644 --- a/packages/errors/__snapshot-html__/ERROR_READING_FILE.html +++ b/packages/errors/__snapshot-html__/ERROR_READING_FILE.html @@ -35,8 +35,8 @@
Error reading from: /path/to/read/file.ts
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at ERROR_READING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at ERROR_READING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html index 7b03d3b0be7f..7d7a875f1cd3 100644 --- a/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html +++ b/packages/errors/__snapshot-html__/ERROR_WRITING_FILE.html @@ -35,8 +35,8 @@
Error writing to: /path/to/write/file.ts
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at ERROR_WRITING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at ERROR_WRITING_FILE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html index 6fdf20262dee..3eea972eca09 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html +++ b/packages/errors/__snapshot-html__/FIREFOX_COULD_NOT_CONNECT.html @@ -37,8 +37,8 @@
Cypress failed to make a connection to Firefox.
 
 This usually indicates there was a problem opening the Firefox browser.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at FIREFOX_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at FIREFOX_COULD_NOT_CONNECT (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html index ebe1251f6102..6bbfcca54418 100644 --- a/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html +++ b/packages/errors/__snapshot-html__/FIREFOX_MARIONETTE_FAILURE.html @@ -39,8 +39,8 @@ An unexpected error was received from Marionette: connection To avoid this error, ensure that there are no other instances of Firefox launched by Cypress running. - -Error: fail whale - at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at FIREFOX_MARIONETTE_FAILURE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+ +Error: fail whale + at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at FIREFOX_MARIONETTE_FAILURE (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html b/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html index e51a50e7d70a..bfc4e5903019 100644 --- a/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html +++ b/packages/errors/__snapshot-html__/INVALID_REPORTER_NAME.html @@ -42,9 +42,8 @@ - /path/reporter Learn more at https://on.cypress.io/reporters - -Error: fail whale - at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at INVALID_REPORTER_NAME (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) -
+ +Error: fail whale + at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at INVALID_REPORTER_NAME (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html index c527444f7343..42cd1ed02efb 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html @@ -36,18 +36,18 @@
The pluginsFile must export a function with the following signature:
 
-// /path/to/pluginsFile
-module.exports = (on, config) => {
-  // configure plugins here
-}
+// /path/to/pluginsFile
+module.exports = (on, config) => {
+  // configure plugins here
+}
 
 Instead it exported:
 
-[
-  "some",
-  "array"
-]
+[
+  "some",
+  "array"
+]
 
 Learn more: https://on.cypress.io/plugins-api
-
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html index c28b03cbbe4c..08253d3bf8a1 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html @@ -36,7 +36,7 @@
The pluginsFile must export a function with the following signature:
 
-// /path/to/pluginsFile
+// /path/to/pluginsFile
 module.exports = (on, config) => {
   // configure plugins here
 }
@@ -46,5 +46,5 @@
 "some string"
 
 Learn more: https://on.cypress.io/plugins-api
-
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html index 06c10226ad56..b87a25d72fa7 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html @@ -36,7 +36,7 @@
The pluginsFile must export a function with the following signature:
 
-// /path/to/pluginsFile
+// /path/to/pluginsFile
 module.exports = (on, config) => {
   // configure plugins here
 }
@@ -48,5 +48,5 @@
 }
 
 Learn more: https://on.cypress.io/plugins-api
-
+
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html index 50368a205812..0b5ead2d9c0c 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FILE_ERROR.html @@ -37,9 +37,8 @@
Your pluginsFile is invalid: /path/to/pluginsFile
 
 It threw an error when required, check the stack trace below:
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_FILE_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_FILE_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html index a9bc77073196..d26f805862f6 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html @@ -35,9 +35,8 @@
The function exported by the pluginsFile threw an error: /path/to/pluginsFile
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_FUNCTION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_FUNCTION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html index 398c736021bf..2fa9f7fddcc6 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html @@ -45,9 +45,8 @@ - foo - bar - baz - -Error: fail whale - at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) - at PLUGINS_INVALID_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) -
+ +Error: fail whale + at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts) + at PLUGINS_INVALID_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html index 9471f4783271..6d55ed6b054c 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_RUN_EVENT_ERROR.html @@ -37,8 +37,8 @@
An error was thrown in your plugins file while executing the handler for the before:spec event.
 
 The error we received was:
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_RUN_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_RUN_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html index e0a7e41eeb25..7595782deed3 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html @@ -37,9 +37,8 @@
We stopped running your tests because a plugin crashed.
 
 The following error was thrown by your plugins file: /path/to/pluginsFile
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html index f3ff5f5e226d..899416622d58 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html @@ -35,9 +35,8 @@
Your pluginsFile threw a validation error: /path/to/pluginsFile
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html index 1ddefce07f49..d825c4409dc5 100644 --- a/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html +++ b/packages/errors/__snapshot-html__/VIDEO_POST_PROCESSING_FAILED.html @@ -37,8 +37,8 @@
Warning: We failed processing this video.
 
 This error will not alter the exit code.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at VIDEO_POST_PROCESSING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at VIDEO_POST_PROCESSING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html index 8c629e1a1988..b227d04f3322 100644 --- a/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html +++ b/packages/errors/__snapshot-html__/VIDEO_RECORDING_FAILED.html @@ -37,8 +37,8 @@
Warning: We failed to record the video.
 
 This error will not alter the exit code.
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at VIDEO_RECORDING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at VIDEO_RECORDING_FAILED (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/package.json b/packages/errors/package.json index e162033abbf0..38d492e2ccce 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -12,7 +12,9 @@ "check-ts": "tsc --noEmit", "clean-deps": "rm -rf node_modules", "clean": "rm -f ./src/*.js ./src/**/*.js ./src/**/**/*.js ./test/**/*.js || echo 'cleaned'", - "test-unit": "yarn clean && xvfb-maybe electron --no-sandbox ./node_modules/.bin/_mocha --reporter mocha-multi-reporters --reporter-options configFile=../../mocha-reporter-config.json --exit" + "pretest-unit": "yarn clean", + "test-unit": "mocha", + "test-electron": "HTML_IMAGE_CONVERSION=1 xvfb-maybe electron --no-sandbox ./node_modules/.bin/_mocha" }, "dependencies": { "ansi_up": "5.0.0", diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index a9ab1288eba3..5348f6e9f821 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -68,7 +68,7 @@ class Format { } if (this.type === 'code') { - return `${'```'}\n${val}${'```'}` + return `${'```'}\n${val}\n${'```'}` } return mdFence(this.prepVal('markdown')) @@ -92,8 +92,13 @@ class Format { } function mdFence (val: string) { + // Don't double fence values + if (val.includes('`')) { + return val + } + if (isMultiLine(val)) { - return `\`\`\`${val}\`\`\`` + return `\`\`\`\n${val}\n\`\`\`` } return `\`${val}\`` @@ -238,6 +243,7 @@ export const errPartial = (templateStr: TemplateStringsArray, ...args: AllowedPa } let originalError: Error | undefined = undefined +let details: string | undefined /** * Creates a consistently formatted object to return from the error call. @@ -255,6 +261,7 @@ export const errTemplate = (strings: TemplateStringsArray, ...args: AllowedTempl return { message: msg, + details, originalError, messageMarkdown: trimMultipleNewLines(stripAnsi(prepMessage(strings, args, 'markdown'))), } @@ -273,6 +280,7 @@ function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTempla // Reset the originalError to undefined on each new template string pass, we only need it to guard if (!isPartial) { originalError = undefined + details = undefined } const templateArgs = [] @@ -293,6 +301,7 @@ function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTempla if (isErrorLike(arg.val)) { originalError = arg.val + details = originalError.stack } else { if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { throw new Error(`Cannot use arg.stackTrace with a non error-like value, saw ${JSON.stringify(arg.val)}`) @@ -302,13 +311,10 @@ function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTempla err.stack = typeof arg.val === 'string' ? arg.val : JSON.stringify(arg.val) originalError = err + details = err.stack } - if (target === 'ansi') { - templateArgs.push(chalk.magenta(originalError.stack ?? originalError.message)) - } else { - templateArgs.push('') - } + templateArgs.push('') } else if (arg instanceof PartialErr) { // Partial error = prepMessage + interpolate templateArgs.push(prepMessage(arg.strArr, arg.args, target, true)) diff --git a/packages/errors/src/errorUtils.ts b/packages/errors/src/errorUtils.ts index 7cfc32041bf8..1c30d5dd9ac2 100644 --- a/packages/errors/src/errorUtils.ts +++ b/packages/errors/src/errorUtils.ts @@ -38,9 +38,9 @@ type AllowedChalkColors = 'red' | 'blue' | 'green' | 'magenta' | 'yellow' export const logError = function (err: CypressError | ErrorLike, color: AllowedChalkColors = 'red') { console.log(chalk[color](err.message)) - // if (err.originalError) { - // console.log(`\n${err.originalError.stack}`) - // } + if (err.details) { + console.log(chalk.magenta(`\n${err.details}`)) + } // bail if this error came from known // list of Cypress errors diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 83ca8d324e52..c82f448b82ca 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -598,6 +598,7 @@ export const AllCypressErrors = { }, PLUGINS_DIDNT_EXPORT_FUNCTION: (pluginsFilePath: string, exported: any) => { const code = errPartial` + ${fmt.comment(`// ${pluginsFilePath}`)} module.exports = (on, config) => { ${fmt.comment(`// configure plugins here`)} }` @@ -605,7 +606,6 @@ export const AllCypressErrors = { return errTemplate`\ The ${fmt.highlight(`pluginsFile`)} must export a function with the following signature: - ${fmt.comment(`// ${pluginsFilePath}`)} ${fmt.code(code)} Instead it exported: diff --git a/packages/errors/test/mocha.opts b/packages/errors/test/mocha.opts index 13cc465f8dbc..69187492696b 100644 --- a/packages/errors/test/mocha.opts +++ b/packages/errors/test/mocha.opts @@ -2,3 +2,6 @@ test/unit -r @packages/ts/register --recursive --extension=js,ts +--reporter mocha-multi-reporters +--reporter-options configFile=../../mocha-reporter-config.json +--exit \ No newline at end of file diff --git a/packages/errors/test/support/error-comparison-tool.ts b/packages/errors/test/support/error-comparison-tool.ts index 898160b87486..720abd6fbdd2 100644 --- a/packages/errors/test/support/error-comparison-tool.ts +++ b/packages/errors/test/support/error-comparison-tool.ts @@ -49,7 +49,6 @@ app.get('/', async (req, res) => { $('#' + id).remove() }) .catch(e => { - debugger alert(e.stack) }) }) @@ -59,7 +58,6 @@ app.get('/', async (req, res) => { fetch('/load-more/' + offset) .then(res => res.text()) .then((val) => { - debugger $(val).appendTo('tbody') }) }) diff --git a/packages/errors/test/support/utils.ts b/packages/errors/test/support/utils.ts index 340583c2e027..557b6932535a 100644 --- a/packages/errors/test/support/utils.ts +++ b/packages/errors/test/support/utils.ts @@ -6,9 +6,10 @@ import { PNG } from 'pngjs' const isCi = require('is-ci') -app.on('window-all-closed', () => { - -}) +if (app) { + app.on('window-all-closed', () => { + }) +} const HEIGHT = 600 const WIDTH = 1200 diff --git a/packages/errors/test/unit/errTemplate_spec.ts b/packages/errors/test/unit/errTemplate_spec.ts index 2ba8975869c8..df39b358e4e9 100644 --- a/packages/errors/test/unit/errTemplate_spec.ts +++ b/packages/errors/test/unit/errTemplate_spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import chalk from 'chalk' -import { errTemplate, fmt } from '../../src/errTemplate' +import { errTemplate, fmt, theme } from '../../src/errTemplate' import { stripIndent } from '../../src/stripIndent' describe('errTemplate', () => { @@ -30,38 +30,37 @@ describe('errTemplate', () => { const obj = errTemplate` This was returned from the app: - ${fmt.highlightTertiary(someObj)} - ` + ${fmt.highlightTertiary(someObj)}` expect(obj).to.include({ messageMarkdown: stripIndent` This was returned from the app: - \`\`\`\n${JSON.stringify(someObj, null, 2)}\n\`\`\``, + \`\`\` + ${JSON.stringify(someObj, null, 2)} + \`\`\``, }) expect(obj).to.include({ message: stripIndent` This was returned from the app: - ${JSON.stringify(someObj, null, 2)} - `, + ${theme.blue(JSON.stringify(someObj, null, 2))}`, }) }) - it('uses details to set originalError, for toErrorProps, highlight stack for console', () => { + it('sets originalError, for toErrorProps, highlight stack for console', () => { const specFile = 'specFile.js' const err = new Error() const obj = errTemplate` This was an error in ${fmt.highlight(specFile)} - ${fmt.stackTrace(err)} - ` + ${fmt.stackTrace(err)}` expect(obj).to.include({ messageMarkdown: `This was an error in \`specFile.js\`` }) expect(obj).to.include({ message: `This was an error in ${chalk.yellow(specFile)}`, - details: chalk.magenta(err.stack ?? ''), + details: err.stack ?? '', }) }) @@ -74,6 +73,6 @@ describe('errTemplate', () => { ${fmt.stackTrace(new Error())} ` - }).to.throw(/Cannot use details\(\) multiple times in the same errTemplate/) + }).to.throw(/Cannot use fmt.stackTrace\(\) multiple times in the same errTemplate/) }) }) diff --git a/packages/errors/test/unit/errors_spec.ts b/packages/errors/test/unit/errors_spec.ts index ffa08aef8591..e69d221f10c7 100644 --- a/packages/errors/test/unit/errors_spec.ts +++ b/packages/errors/test/unit/errors_spec.ts @@ -54,21 +54,7 @@ describe('lib/errors', () => { expect(ret).to.be.undefined - expect(console.log).to.be.calledWithMatch('/path/to/project/cypress.json') - }) - - it('logs err.details', () => { - const userError = new Error('asdf') - - const err = errors.get('PLUGINS_FUNCTION_ERROR', 'foo/bar/baz', userError) - - const ret = errors.log(err) - - expect(ret).to.be.undefined - - expect(console.log).to.be.calledWithMatch('foo/bar/baz') - - expect(console.log).to.be.calledWithMatch(chalk.magenta(userError.stack ?? '')) + expect(console.log).to.be.calledWithMatch(chalk.red(err.message)) }) it('logs err.stack in development', () => { diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 7b0d24c4f6c0..ff8d94708adc 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import chai, { expect } from 'chai' import Debug from 'debug' import fse from 'fs-extra' @@ -21,6 +22,22 @@ const launcherBrowsers = require('@packages/launcher/lib/browsers') const debug = Debug(isCi ? '*' : 'visualSnapshotErrors') +let snapshotFailures = 0 + +after(() => { + if (snapshotFailures > 0) { + console.log(` + ================================= + + Snapshot failures see for visualSnapshotErrors. + + Run "yarn comparison" locally from the @packages/error package to resolve + + ================================= + `) + } +}) + interface ErrorGenerator { default: Parameters [key: string]: Parameters @@ -55,9 +72,7 @@ const sanitize = (str: string) => { const snapshotAndTestErrorConsole = async function (errorFileName: string) { const logs = _ .chain(consoleLog.args) - .map((args) => { - return args.map(sanitize).join(' ') - }) + .map((args) => args.map(sanitize).join(' ')) .join('\n') .value() @@ -107,6 +122,7 @@ const snapshotAndTestErrorConsole = async function (errorFileName: string) { try { expect(contents).to.eq(html) } catch (e) { + snapshotFailures++ await saveHtml(errorFileName.replace('__snapshot-html__', '__snapshot-html-local__'), html) throw e } @@ -155,8 +171,8 @@ const testVisualError = (errorGeneratorFn: () => Er debug(`Snapshotted ${htmlFilename}`) - // dont run html -> image conversion if we're in CI - if (!isCi && process.env.SKIP_HTML_IMAGE_CONVERSION !== '1') { + // dont run html -> image conversion if we're in CI / if not enabled + if (!isCi && process.env.HTML_IMAGE_CONVERSION) { debug(`Converting ${errorType} to image`) await convertHtmlToImage(htmlFilename, snapshotImagesFolder) @@ -199,7 +215,6 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K // await Promise.all(excessErrors.map((file) => { // const pathToHtml = path.join(baseImageFolder, file + EXT) - // return fse.remove(pathToHtml) // })) diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index 664e4cf16f8d..1d7e85e8f148 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -3,6 +3,7 @@ // and executing any tasks that the plugin registers const debug = require('debug')('cypress:server:plugins:child') const Promise = require('bluebird') +const path = require('path') const preprocessor = require('./preprocessor') const devServer = require('./dev-server') @@ -194,7 +195,7 @@ const runPlugins = (ipc, pluginsFile, projectRoot) => { if (typeof plugins !== 'function') { debug('not a function') - ipc.send('load:error', 'PLUGINS_DIDNT_EXPORT_FUNCTION', pluginsFile, plugins) + ipc.send('load:error', 'PLUGINS_DIDNT_EXPORT_FUNCTION', path.relative(projectRoot, pluginsFile), plugins) return } From e808c7e7a8dd1c2b45045fbd35d93cc99c0483cb Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 8 Feb 2022 11:17:24 -0500 Subject: [PATCH 096/165] Fixes for unit tests --- .../desktop-gui/src/project/error-message.jsx | 4 ++-- packages/errors/test/unit/errors_spec.ts | 16 ++++++++++++- .../server/__snapshots__/browsers_spec.js | 4 ---- .../server/lib/plugins/child/run_plugins.js | 3 +-- .../test/unit/browsers/browsers_spec.js | 4 ++-- .../server/test/unit/plugins/index_spec.js | 24 ++++++++++++------- packages/server/test/unit/project_spec.js | 4 +++- .../server/test/unit/project_utils_spec.ts | 3 ++- .../test/unit/util/chrome_policy_check.js | 8 ++++--- system-tests/__snapshots__/record_spec.js | 16 ++++++------- 10 files changed, 53 insertions(+), 33 deletions(-) diff --git a/packages/desktop-gui/src/project/error-message.jsx b/packages/desktop-gui/src/project/error-message.jsx index 4bc15e26d516..08f693d897ad 100644 --- a/packages/desktop-gui/src/project/error-message.jsx +++ b/packages/desktop-gui/src/project/error-message.jsx @@ -9,7 +9,7 @@ import Markdown from 'markdown-it' const _copyErrorDetails = (err) => { let details = [ - `**Message:** ${err.messageMarkdown}`, + `**Message:** ${err.messageMarkdown || err.message}`, ] if (err.details) { @@ -85,7 +85,7 @@ class ErrorMessage extends Component {

this.errorMessageNode = node} dangerouslySetInnerHTML={{ - __html: md.render(err.messageMarkdown), + __html: md.render(err.messageMarkdown || err.message), }}>
{err.originalError && ( diff --git a/packages/errors/test/unit/errors_spec.ts b/packages/errors/test/unit/errors_spec.ts index e69d221f10c7..ffa08aef8591 100644 --- a/packages/errors/test/unit/errors_spec.ts +++ b/packages/errors/test/unit/errors_spec.ts @@ -54,7 +54,21 @@ describe('lib/errors', () => { expect(ret).to.be.undefined - expect(console.log).to.be.calledWithMatch(chalk.red(err.message)) + expect(console.log).to.be.calledWithMatch('/path/to/project/cypress.json') + }) + + it('logs err.details', () => { + const userError = new Error('asdf') + + const err = errors.get('PLUGINS_FUNCTION_ERROR', 'foo/bar/baz', userError) + + const ret = errors.log(err) + + expect(ret).to.be.undefined + + expect(console.log).to.be.calledWithMatch('foo/bar/baz') + + expect(console.log).to.be.calledWithMatch(chalk.magenta(userError.stack ?? '')) }) it('logs err.stack in development', () => { diff --git a/packages/server/__snapshots__/browsers_spec.js b/packages/server/__snapshots__/browsers_spec.js index 3c7dbf19111a..7853ad496dc3 100644 --- a/packages/server/__snapshots__/browsers_spec.js +++ b/packages/server/__snapshots__/browsers_spec.js @@ -13,11 +13,7 @@ Cypress supports the following browsers: You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: -- chrome -- firefox -- electron - chrome - - chromium - firefox - electron ` diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index 1d7e85e8f148..664e4cf16f8d 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -3,7 +3,6 @@ // and executing any tasks that the plugin registers const debug = require('debug')('cypress:server:plugins:child') const Promise = require('bluebird') -const path = require('path') const preprocessor = require('./preprocessor') const devServer = require('./dev-server') @@ -195,7 +194,7 @@ const runPlugins = (ipc, pluginsFile, projectRoot) => { if (typeof plugins !== 'function') { debug('not a function') - ipc.send('load:error', 'PLUGINS_DIDNT_EXPORT_FUNCTION', path.relative(projectRoot, pluginsFile), plugins) + ipc.send('load:error', 'PLUGINS_DIDNT_EXPORT_FUNCTION', pluginsFile, plugins) return } diff --git a/packages/server/test/unit/browsers/browsers_spec.js b/packages/server/test/unit/browsers/browsers_spec.js index 7a8a16cf54fc..f3f673e1f4c9 100644 --- a/packages/server/test/unit/browsers/browsers_spec.js +++ b/packages/server/test/unit/browsers/browsers_spec.js @@ -11,7 +11,7 @@ const normalizeSnapshot = (str) => { } const normalizeBrowsers = (message) => { - return message.replace(/(found on your system are:)(?:\n- .*)*/, '$1\n- chrome\n- firefox\n- electron') + return message.replace(/(found on your system are:)(?:\n - .*)*/, '$1\n - chrome\n - firefox\n - electron') } // When we added component testing mode, we added the option for electron to be omitted @@ -70,7 +70,7 @@ describe('lib/browsers/index', () => { return expect(browsers.ensureAndGetByNameOrPath('browserNotGonnaBeFound')) .to.be.rejectedWith({ type: 'BROWSER_NOT_FOUND_BY_NAME' }) .then((err) => { - return normalizeSnapshot(normalizeBrowsers(err.message)) + return normalizeSnapshot(normalizeBrowsers(stripAnsi(err.message))) }) }) diff --git a/packages/server/test/unit/plugins/index_spec.js b/packages/server/test/unit/plugins/index_spec.js index 12c6e96ac7ea..7c423d928bbd 100644 --- a/packages/server/test/unit/plugins/index_spec.js +++ b/packages/server/test/unit/plugins/index_spec.js @@ -1,3 +1,5 @@ +const stripAnsi = require('strip-ansi') + require('../../spec_helper') const mockedEnv = require('mocked-env') @@ -186,7 +188,7 @@ describe('lib/plugins/index', () => { it('rejects plugins.init', () => { return plugins.init({ pluginsFile: 'cypress-plugin' }, getOptions()) .catch((err) => { - expect(err.message).to.contain('The plugins file is missing or invalid') + expect(stripAnsi(err.message)).to.contain('Your pluginsFile is invalid') expect(err.message).to.contain('path/to/pluginsFile.js') expect(err.details).to.contain('error message stack') @@ -196,13 +198,16 @@ describe('lib/plugins/index', () => { context('PLUGINS_FUNCTION_ERROR', () => { beforeEach(() => { - ipc.on.withArgs('load:error').yields('PLUGINS_FUNCTION_ERROR', 'path/to/pluginsFile.js', 'error message stack') + const e = new Error() + + e.stack = 'error message stack' + ipc.on.withArgs('load:error').yields('PLUGINS_FUNCTION_ERROR', 'path/to/pluginsFile.js', e) }) it('rejects plugins.init', () => { return plugins.init({ pluginsFile: 'cypress-plugin' }, getOptions()) .catch((err) => { - expect(err.message).to.contain('The function exported by the plugins file threw an error.') + expect(stripAnsi(err.message)).to.contain('The function exported by the pluginsFile threw an error') expect(err.message).to.contain('path/to/pluginsFile.js') expect(err.details).to.contain('error message stack') @@ -219,6 +224,7 @@ describe('lib/plugins/index', () => { err = { name: 'error name', message: 'error message', + stack: 'error stack', } onError = sinon.spy() @@ -243,18 +249,18 @@ describe('lib/plugins/index', () => { pluginsProcess.on.withArgs('error').yield(err) expect(onError).to.be.called expect(onError.lastCall.args[0].title).to.equal('Error running plugin') - expect(onError.lastCall.args[0].message).to.include('The following error was thrown by a plugin') + expect(onError.lastCall.args[0].message).to.include('The following error was thrown by your plugins file') - expect(onError.lastCall.args[0].details).to.include(err.message) + expect(onError.lastCall.args[0].details).to.include(err.stack) }) it('calls onError when ipc sends error', () => { ipc.on.withArgs('error').yield(err) expect(onError).to.be.called expect(onError.lastCall.args[0].title).to.equal('Error running plugin') - expect(onError.lastCall.args[0].message).to.include('The following error was thrown by a plugin') + expect(onError.lastCall.args[0].message).to.include('The following error was thrown by your plugins file') - expect(onError.lastCall.args[0].details).to.include(err.message) + expect(onError.lastCall.args[0].details).to.include(err.stack) }) }) @@ -278,7 +284,7 @@ describe('lib/plugins/index', () => { }) .catch((_err) => { expect(_err.title).to.equal('Error running plugin') - expect(_err.message).to.include('The following error was thrown by a plugin') + expect(_err.message).to.include('The following error was thrown by your plugins file') expect(_err.details).to.include(err.stack) }) }) @@ -290,7 +296,7 @@ describe('lib/plugins/index', () => { }) .catch((_err) => { expect(_err.title).to.equal('Error running plugin') - expect(_err.message).to.include('The following error was thrown by a plugin') + expect(_err.message).to.include('The following error was thrown by your plugins file') expect(_err.details).to.include(err.stack) }) }) diff --git a/packages/server/test/unit/project_spec.js b/packages/server/test/unit/project_spec.js index 2387cd11115e..7cd5321d41c1 100644 --- a/packages/server/test/unit/project_spec.js +++ b/packages/server/test/unit/project_spec.js @@ -1,3 +1,5 @@ +const { theme } = require('@packages/errors/src/errTemplate') + require('../spec_helper') const mockedEnv = require('mocked-env') @@ -246,7 +248,7 @@ describe('lib/project-base', () => { family: 'some-other-family', name: 'some-other-name', warning: `\ -Your project has set the configuration option: \`chromeWebSecurity: false\` +Your project has set the configuration option: ${theme.yellow('chromeWebSecurity')} to ${theme.blue('false')} This option will not have an effect in Some-other-name. Tests that rely on web security being disabled will not run as expected.\ `, diff --git a/packages/server/test/unit/project_utils_spec.ts b/packages/server/test/unit/project_utils_spec.ts index c8c1acedd5a7..f138b8ae9413 100644 --- a/packages/server/test/unit/project_utils_spec.ts +++ b/packages/server/test/unit/project_utils_spec.ts @@ -4,6 +4,7 @@ import sinon from 'sinon' import { fs } from '../../lib/util/fs' import { getSpecUrl, checkSupportFile, getDefaultConfigFilePath } from '../../lib/project_utils' import Fixtures from '@tooling/system-tests/lib/fixtures' +import stripAnsi from 'strip-ansi' const todosPath = Fixtures.projectPath('todos') @@ -115,7 +116,7 @@ describe('lib/project_utils', () => { supportFile: '/this/file/does/not/exist/foo/bar/cypress/support/index.js', }) } catch (e) { - expect(e.message).to.include('The support file is missing or invalid.') + expect(stripAnsi(e.message)).to.include('Your supportFile is missing or invalid') } }) }) diff --git a/packages/server/test/unit/util/chrome_policy_check.js b/packages/server/test/unit/util/chrome_policy_check.js index 449f39b681c5..3d6332579e0c 100644 --- a/packages/server/test/unit/util/chrome_policy_check.js +++ b/packages/server/test/unit/util/chrome_policy_check.js @@ -1,3 +1,5 @@ +const stripAnsi = require('strip-ansi') + require('../../spec_helper') const _ = require('lodash') @@ -31,13 +33,13 @@ describe('lib/util/chrome_policy_check', () => { expect(cb).to.be.calledOnce - expect(cb.getCall(0).args[0].message).to.eq(stripIndent(`\ + expect(stripAnsi(cb.getCall(0).args[0].message)).to.eq(stripIndent(`\ Cypress detected policy settings on your computer that may cause issues. The following policies were detected that may prevent Cypress from automating Chrome: - > HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome\\ProxyServer - > HKEY_CURRENT_USER\\Software\\Policies\\Google\\Chromium\\ExtensionSettings + - HKEY_LOCAL_MACHINE\\Software\\Policies\\Google\\Chrome\\ProxyServer + - HKEY_CURRENT_USER\\Software\\Policies\\Google\\Chromium\\ExtensionSettings For more information, see https://on.cypress.io/bad-browser-policy\ `)) diff --git a/system-tests/__snapshots__/record_spec.js b/system-tests/__snapshots__/record_spec.js index 51b609ac680d..08f35c5565fc 100644 --- a/system-tests/__snapshots__/record_spec.js +++ b/system-tests/__snapshots__/record_spec.js @@ -2646,18 +2646,18 @@ Can't run because you've entered an invalid browser name. Browser: browserDoesNotExist was not found on your system or is not supported by Cypress. Cypress supports the following browsers: -- chrome -- chromium -- edge -- electron -- firefox + - chrome + - chromium + - edge + - electron + - firefox You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: -- browser1 -- browser2 -- browser3 + - browser1 + - browser2 + - browser3 ` exports['e2e record metadata sends Studio usage metadata 1'] = ` From b08dfd8eb6f453dc0c19c333115c6d51dda7be1a Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 8 Feb 2022 12:36:16 -0500 Subject: [PATCH 097/165] fix PLUGINS_VALIDATION_ERROR --- guides/error-handling.md | 1 - packages/desktop-gui/src/project/error-message.jsx | 2 +- packages/errors/src/errTemplate.ts | 2 +- packages/errors/src/errors.ts | 1 - packages/server/lib/plugins/child/run_plugins.js | 6 +++++- packages/server/lib/plugins/child/validate_event.js | 10 +++++++++- 6 files changed, 16 insertions(+), 6 deletions(-) diff --git a/guides/error-handling.md b/guides/error-handling.md index 8bb2505b80c2..24838568e9bb 100644 --- a/guides/error-handling.md +++ b/guides/error-handling.md @@ -31,7 +31,6 @@ Return Value of `errTemplate` (`ErrTemplateResult`): messageMarkdown: string, details?: string, // Exists if there is `details()` call in the errTemplate originalError?: ErrorLike // Exists if an error was passed into the `details()` - } ``` diff --git a/packages/desktop-gui/src/project/error-message.jsx b/packages/desktop-gui/src/project/error-message.jsx index 08f693d897ad..f98fbcc58a48 100644 --- a/packages/desktop-gui/src/project/error-message.jsx +++ b/packages/desktop-gui/src/project/error-message.jsx @@ -40,7 +40,7 @@ const ErrorDetails = observer(({ err }) => { if (detailsBody) { return (
-        
+
{detailsTitle} {detailsBody}
diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index 5348f6e9f821..ac8e445f95b0 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -64,7 +64,7 @@ class Format { const val = this.prepVal('markdown') if (this.type === 'terminal') { - return `${'```'}\n$ ${val}${'```'}` + return `${'```'}\n$ ${val}\n${'```'}` } if (this.type === 'code') { diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index c82f448b82ca..d07849d8d4ec 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -632,7 +632,6 @@ export const AllCypressErrors = { ${fmt.stackTrace(arg2)} ` }, - // TODO: test this PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` Your ${fmt.highlight(`pluginsFile`)} threw a validation error: ${fmt.path(arg1)} diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index 664e4cf16f8d..73a40fbf8be4 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -49,7 +49,11 @@ const load = (ipc, config, pluginsFile) => { const { isValid, userEvents, error } = validateEvent(event, handler, config, register) if (!isValid) { - ipc.send('load:error', 'PLUGINS_INVALID_EVENT_ERROR', pluginsFile, event, userEvents, util.serializeError(error)) + if (userEvents) { + ipc.send('load:error', 'PLUGINS_INVALID_EVENT_ERROR', pluginsFile, event, userEvents, util.serializeError(error)) + } else { + ipc.send('load:error', 'PLUGINS_VALIDATION_ERROR', pluginsFile, util.serializeError(error)) + } return } diff --git a/packages/server/lib/plugins/child/validate_event.js b/packages/server/lib/plugins/child/validate_event.js index 728273561475..d49c66ea6fba 100644 --- a/packages/server/lib/plugins/child/validate_event.js +++ b/packages/server/lib/plugins/child/validate_event.js @@ -56,7 +56,15 @@ const validateEvent = (event, handler, config, errConstructorFn) => { } } - return validator(event, handler, config) + const result = validator(event, handler, config) + + if (!result.isValid) { + result.error.name = 'ValidationError' + + Error.captureStackTrace(result.error, errConstructorFn) + } + + return result } module.exports = validateEvent From 91641ea5009babf941475197eeeb537fd7ae495b Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 12:59:01 -0500 Subject: [PATCH 098/165] fix fs.readdir bug, show rows even if there's only markdown formatting [SKIP CI] --- .../errors/test/support/error-comparison-tool.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/errors/test/support/error-comparison-tool.ts b/packages/errors/test/support/error-comparison-tool.ts index 720abd6fbdd2..848600a3d913 100644 --- a/packages/errors/test/support/error-comparison-tool.ts +++ b/packages/errors/test/support/error-comparison-tool.ts @@ -1,8 +1,9 @@ /* eslint-disable no-console */ import express from 'express' import fs from 'fs-extra' -import path from 'path' +import globby from 'globby' import Markdown from 'markdown-it' +import path from 'path' const ERRORS_DIR = path.join(__dirname, '..', '..') const SNAPSHOT_HTML = path.join(ERRORS_DIR, '__snapshot-html__') @@ -13,8 +14,14 @@ const app = express() const LINKS = `
Ansi Compare | Ansi Base List | Markdown
` +const getFiles = async (baseDir: string) => { + return (await globby(`${baseDir}/**/*`)).filter((f) => f.endsWith('.html')).sort() +} + async function getRows (offset = 0, baseList: boolean = false) { - const toCompare = (await fs.readdir(baseList ? SNAPSHOT_HTML : SNAPSHOT_HTML_LOCAL)).filter((f) => f.endsWith('.html')).sort() + const pattern = baseList ? SNAPSHOT_HTML : SNAPSHOT_HTML_LOCAL + const toCompare = await getFiles(pattern) + const rows = toCompare.slice(offset, offset + 10).map((f) => path.basename(f).split('.')[0]).map((name) => { return ` @@ -185,5 +192,5 @@ app.get('/md', async (req, res) => { }) app.listen(5555, () => { - console.log(`Comparison server listening on 5555`) + console.log(`Comparison server listening on: http://localhost:5555`) }) From d82e8cb718c8cd10371ff34ce5174e02c513da21 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 13:06:24 -0500 Subject: [PATCH 099/165] when in base-list, show full width of errors --- packages/errors/test/support/error-comparison-tool.ts | 9 +++++++-- packages/errors/test/support/utils.ts | 6 ++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/errors/test/support/error-comparison-tool.ts b/packages/errors/test/support/error-comparison-tool.ts index 848600a3d913..baf3b329c4dd 100644 --- a/packages/errors/test/support/error-comparison-tool.ts +++ b/packages/errors/test/support/error-comparison-tool.ts @@ -4,6 +4,7 @@ import fs from 'fs-extra' import globby from 'globby' import Markdown from 'markdown-it' import path from 'path' +import { WIDTH } from './utils' const ERRORS_DIR = path.join(__dirname, '..', '..') const SNAPSHOT_HTML = path.join(ERRORS_DIR, '__snapshot-html__') @@ -23,12 +24,16 @@ async function getRows (offset = 0, baseList: boolean = false) { const toCompare = await getFiles(pattern) const rows = toCompare.slice(offset, offset + 10).map((f) => path.basename(f).split('.')[0]).map((name) => { + const width = baseList ? WIDTH : 550 + // const height = baseList ? HEIGHT : 600 + const height = 400 + return ` ${name} - + ${baseList ? '' : ` - + `} ` diff --git a/packages/errors/test/support/utils.ts b/packages/errors/test/support/utils.ts index 557b6932535a..e71b6272c8bb 100644 --- a/packages/errors/test/support/utils.ts +++ b/packages/errors/test/support/utils.ts @@ -11,8 +11,10 @@ if (app) { }) } -const HEIGHT = 600 -const WIDTH = 1200 +export const HEIGHT = 600 + +export const WIDTH = 1200 + const EXT = '.png' const debug = Debug(isCi ? '*' : 'visualSnapshotErrors') From 49d739eec20ae15fbcb9a0b09d8644913312ca49 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 8 Feb 2022 13:08:53 -0500 Subject: [PATCH 100/165] Fix type errors --- packages/errors/src/errTemplate.ts | 12 ++++++------ packages/errors/src/errors.ts | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index ac8e445f95b0..6659bf7e87ba 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -24,7 +24,7 @@ type AllowedPartialArg = Guard | Format | PartialErr | null type AllowedTemplateArg = StackTrace | AllowedPartialArg -class PartialErr { +export class PartialErr { constructor (readonly strArr: TemplateStringsArray, readonly args: AllowedTemplateArg[]) {} } @@ -37,9 +37,9 @@ type ToFormat = string | number | Error | object | null | Guard | AllowedTemplat class Format { constructor (readonly type: keyof typeof fmtHighlight, readonly val: ToFormat, readonly config?: FormatConfig) {} - formatVal (target: 'ansi' | 'markdown') { + formatVal (target: 'ansi' | 'markdown'): string { if (this.val instanceof Guard) { - return this.val.val + return `${this.val.val}` } return target === 'ansi' ? this.formatAnsi() : this.formatMarkdown() @@ -58,7 +58,7 @@ class Format { private formatMarkdown () { if (this.type === 'comment') { - return this.val + return `${this.val}` } const val = this.prepVal('markdown') @@ -283,7 +283,7 @@ function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTempla details = undefined } - const templateArgs = [] + const templateArgs: string[] = [] for (const arg of args) { // We assume null/undefined values are skipped when rendering, for conditional templating @@ -291,7 +291,7 @@ function prepMessage (templateStrings: TemplateStringsArray, args: AllowedTempla templateArgs.push('') } else if (arg instanceof Guard) { // Guard prevents any formatting - templateArgs.push(arg.val) + templateArgs.push(`${arg.val}`) } else if (arg instanceof Format) { // Format = stringify & color ANSI, or make a markdown block templateArgs.push(arg.formatVal(target)) diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index d07849d8d4ec..7d0e0dd5f948 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -5,7 +5,7 @@ import _ from 'lodash' import path from 'path' import stripAnsi from 'strip-ansi' import { humanTime, logError, pluralize } from './errorUtils' -import { errPartial, errTemplate, fmt, theme } from './errTemplate' +import { errPartial, errTemplate, fmt, PartialErr, theme } from './errTemplate' import { stackWithoutMessage } from './stackUtils' import type { ClonedError, CypressError, ErrorLike, ErrTemplateResult } from './errorTypes' @@ -89,7 +89,7 @@ export const AllCypressErrors = { This option will not have an effect in ${fmt.off(_.capitalize(browser))}. Tests that rely on web security being disabled will not run as expected.` }, BROWSER_NOT_FOUND_BY_NAME: (browser: string, foundBrowsersStr: string[]) => { - let canarySuffix = null + let canarySuffix: PartialErr | null = null if (browser === 'canary') { canarySuffix = errPartial`\ From bba96856e08da8da177e5a0ca924398cbf056a25 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 13:36:02 -0500 Subject: [PATCH 101/165] added searching and filtering for files based on error name --- packages/errors/package.json | 1 + .../test/support/error-comparison-tool.ts | 44 ++++++++++++++----- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/errors/package.json b/packages/errors/package.json index 38d492e2ccce..0375c897a2f9 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -7,6 +7,7 @@ "scripts": { "test": "yarn test-unit", "comparison": "node -r @packages/ts/register test/support/error-comparison-tool.ts", + "comparison-debug": "node -r @packages/ts/register test/support/error-comparison-tool.ts", "build": "../../scripts/run-if-ci.sh tsc || echo 'type errors'", "build-prod": "tsc", "check-ts": "tsc --noEmit", diff --git a/packages/errors/test/support/error-comparison-tool.ts b/packages/errors/test/support/error-comparison-tool.ts index baf3b329c4dd..14a8062ec03d 100644 --- a/packages/errors/test/support/error-comparison-tool.ts +++ b/packages/errors/test/support/error-comparison-tool.ts @@ -13,17 +13,33 @@ const SNAPSHOT_MARKDOWN = path.join(ERRORS_DIR, '__snapshot-md__') const app = express() -const LINKS = `
Ansi Compare | Ansi Base List | Markdown
` +const LINKS = `
Ansi Compare | Ansi Base List | Markdown |
` -const getFiles = async (baseDir: string) => { - return (await globby(`${baseDir}/**/*`)).filter((f) => f.endsWith('.html')).sort() +const SEARCH_HANDLER = ` + +` + +const getFiles = async (baseDir: string, spec?: string) => { + const pattern = spec ? `${spec}*` : '**/*' + + return (await globby(`${baseDir}/${pattern}`)) } -async function getRows (offset = 0, baseList: boolean = false) { - const pattern = baseList ? SNAPSHOT_HTML : SNAPSHOT_HTML_LOCAL - const toCompare = await getFiles(pattern) +async function getRows (offset = 0, baseList: boolean = false, spec?: string) { + const baseDir = baseList ? SNAPSHOT_HTML : SNAPSHOT_HTML_LOCAL + const toCompare = await getFiles(baseDir, spec) - const rows = toCompare.slice(offset, offset + 10).map((f) => path.basename(f).split('.')[0]).map((name) => { + const rows = toCompare.filter((f) => f.endsWith('.html')).sort().slice(offset, offset + 10).map((f) => path.basename(f).split('.')[0]).map((name) => { const width = baseList ? WIDTH : 550 // const height = baseList ? HEIGHT : 600 const height = 400 @@ -90,8 +106,10 @@ app.get('/', async (req, res) => { }) app.get('/base-list', async (req, res) => { + const spec = req.query.spec as string + try { - const rows = await getRows(0, true) + const rows = await getRows(0, true, spec) res.type('html').send(` @@ -102,12 +120,12 @@ app.get('/base-list', async (req, res) => { fetch('/load-more-base/' + offset) .then(res => res.text()) .then((val) => { - debugger $(val).appendTo('tbody') }) }) ${LINKS} + ${SEARCH_HANDLER} @@ -161,16 +179,20 @@ app.get<{name: string, type: string}>('/html/:name/:type', async (req, res) => { }) app.get('/md', async (req, res) => { + const spec = req.query.spec as string + try { - const toRender = (await fs.readdir(SNAPSHOT_MARKDOWN)).filter((f) => f.endsWith('.md')).sort() - const markdownContents = await Promise.all(toRender.map((f) => fs.readFile(path.join(SNAPSHOT_MARKDOWN, f), 'utf8'))) + const toRender = (await getFiles(SNAPSHOT_MARKDOWN, spec)).filter((f) => f.endsWith('.md')).sort() + const markdownContents = await Promise.all(toRender.map((f) => fs.readFile(f, 'utf8'))) const md = new Markdown({ html: true, linkify: true, }) res.type('html').send(` + ${LINKS} + ${SEARCH_HANDLER} + + +
Can't run because no spec files were found.
+
+We searched for specs matching these glob patterns:
+
+  > /path/to/project/root/**_spec.js
+  > /path/to/project/root/**/*.cy.*
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_SPECS_FOUND - pathCommonPattern.html b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - pathCommonPattern.html new file mode 100644 index 000000000000..b2d4c57aaf6e --- /dev/null +++ b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - pathCommonPattern.html @@ -0,0 +1,43 @@ + + + + + + + + + + + +
Can't run because no spec files were found.
+
+We searched for specs matching these glob patterns:
+
+  > /path/to/project/**_spec.js
+  > /path/to/project/**/*.cy.*
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_SPECS_FOUND - pathNoCommonPattern.html b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - pathNoCommonPattern.html new file mode 100644 index 000000000000..c582cafae9c1 --- /dev/null +++ b/packages/errors/__snapshot-html__/NO_SPECS_FOUND - pathNoCommonPattern.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Can't run because no spec files were found.
+
+We searched for specs matching this glob pattern:
+
+  > /foo/*_spec.js
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html b/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html index 065b2725799d..f3438520314a 100644 --- a/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html +++ b/packages/errors/__snapshot-html__/NO_SPECS_FOUND.html @@ -38,5 +38,5 @@ We searched for specs matching this glob pattern: - > /path/to/project/root/**_spec.js + > /path/to/project/root/**_spec.js \ No newline at end of file diff --git a/packages/errors/src/errorUtils.ts b/packages/errors/src/errorUtils.ts index 1c30d5dd9ac2..1cb7f4262769 100644 --- a/packages/errors/src/errorUtils.ts +++ b/packages/errors/src/errorUtils.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import chalk from 'chalk' import _ from 'lodash' +import path from 'path' const pluralize = require('pluralize') const humanTime = require('@packages/server/lib/util/human_time') @@ -12,6 +13,22 @@ export { humanTime, } +export const parseResolvedPattern = (baseFolder: string, globPattern: string) => { + // folderPath: /Users/bmann/Dev/playground/cypress/server/integration/nested.spec.js + // resolved: /Users/bmann/Dev/playground/cypress/integration/nested.spec.js + + // folderPath: /Users/bmann/Dev/cypress/packages/server + // pattern: ../path/to/spec.js + // resolvedPath: /Users/bmann/Dev/cypress/packages/path/to/spec.js + const resolvedPath = path.resolve(baseFolder, globPattern) + const resolvedPathParts = resolvedPath.split(path.sep) + const folderPathPaths = baseFolder.split(path.sep) + const commonPath = _.intersection(folderPathPaths, resolvedPathParts).join(path.sep) + const remainingPattern = !commonPath ? resolvedPath : resolvedPath.replace(commonPath.concat(path.sep), '') + + return [commonPath, remainingPattern] +} + export const isCypressErr = (err: ErrorLike): err is CypressError => { return Boolean(err.isCypressErr) } diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 7d0e0dd5f948..8d4fd6657a3c 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -4,8 +4,8 @@ import chalk from 'chalk' import _ from 'lodash' import path from 'path' import stripAnsi from 'strip-ansi' -import { humanTime, logError, pluralize } from './errorUtils' -import { errPartial, errTemplate, fmt, PartialErr, theme } from './errTemplate' +import { humanTime, logError, parseResolvedPattern, pluralize } from './errorUtils' +import { errPartial, errTemplate, fmt, theme, PartialErr } from './errTemplate' import { stackWithoutMessage } from './stackUtils' import type { ClonedError, CypressError, ErrorLike, ErrTemplateResult } from './errorTypes' @@ -523,7 +523,7 @@ export const AllCypressErrors = { ${fmt.stackTrace(err)}` }, - NO_SPECS_FOUND: (folderPath: string, globPattern?: string | null) => { + NO_SPECS_FOUND: (folderPath: string, globPattern?: string[] | string | null) => { // no glob provided, searched all specs if (!globPattern) { return errTemplate`\ @@ -534,14 +534,20 @@ export const AllCypressErrors = { ${fmt.listItem(folderPath)}` } - const globPath = path.join(theme.blue(folderPath), globPattern) + const globPaths = _.castArray(globPattern).map((pattern) => { + const [resolvedBasePath, resolvedPattern] = parseResolvedPattern(folderPath, pattern) + + return path.join(resolvedBasePath!, theme.yellow(resolvedPattern!)) + }) + + const phrase = globPaths.length > 1 ? 'these glob patterns' : 'this glob pattern' return errTemplate`\ Can't run because ${fmt.highlightSecondary(`no spec files`)} were found. - We searched for specs matching this glob pattern: + We searched for specs matching ${fmt.off(phrase)}: - ${fmt.listItem(globPath, { color: 'yellow' })}` + ${fmt.listItems(globPaths, { color: 'blue', prefix: ' > ' })}` }, RENDERER_CRASHED: () => { return errTemplate`\ @@ -749,7 +755,7 @@ export const AllCypressErrors = { ${fmt.listItems(arg1.paths)} Learn more at https://on.cypress.io/reporters - + ${fmt.stackTrace(arg1.error)} ` }, diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index ff8d94708adc..f816d2429dab 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -28,11 +28,11 @@ after(() => { if (snapshotFailures > 0) { console.log(` ================================= - + Snapshot failures see for visualSnapshotErrors. - + Run "yarn comparison" locally from the @packages/error package to resolve - + ================================= `) } @@ -600,6 +600,9 @@ describe('visual error templates', () => { NO_SPECS_FOUND: () => { return { default: ['/path/to/project/root', '**_spec.js'], + pathCommonPattern: ['/path/to/project/root', ['../**_spec.js', '../**/*.cy.*']], + pathNoCommonPattern: ['/path/to/project/root', ['/foo/*_spec.js']], + arrPattern: ['/path/to/project/root', ['**_spec.js', '**/*.cy.*']], noPattern: ['/path/to/project/root'], } }, diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index affcbd620f5d..43378c02efff 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -1559,7 +1559,7 @@ module.exports = { .spread((sys = {}, browser = {}, specs = []) => { // return only what is return to the specPattern if (specPattern) { - specPattern = specsUtil.default.getPatternRelativeToProjectRoot(specPattern, projectRoot) + specPattern = specsUtil.default.getPatternRelativeToCwd(specPattern, options.cwd) } specs = specs.filter((spec) => { @@ -1571,7 +1571,7 @@ module.exports = { if (!specs.length) { // did we use the spec pattern? if (specPattern) { - errors.throw('NO_SPECS_FOUND', projectRoot, specPattern) + errors.throw('NO_SPECS_FOUND', options.cwd, specPattern) } else { // else we looked in the integration folder errors.throw('NO_SPECS_FOUND', config.integrationFolder, specPattern) diff --git a/packages/server/lib/util/specs.ts b/packages/server/lib/util/specs.ts index 8332d072f963..65172a6ed4b3 100644 --- a/packages/server/lib/util/specs.ts +++ b/packages/server/lib/util/specs.ts @@ -22,9 +22,9 @@ const SPEC_TYPES = { COMPONENT: 'component', } as const -const getPatternRelativeToProjectRoot = (specPattern: string, projectRoot: string) => { +const getPatternRelativeToCwd = (specPattern: string, cwdPath: string) => { return _.map(specPattern, (p) => { - return path.relative(projectRoot, p) + return path.relative(cwdPath, p) }) } @@ -240,5 +240,5 @@ const findSpecs = (payload: FindSpecs, specPattern?: string) => { export default { findSpecs, findSpecsOfType, - getPatternRelativeToProjectRoot, + getPatternRelativeToCwd, } diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index c12f2925ef6a..4dd8ec58d662 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -733,26 +733,62 @@ describe('lib/cypress', () => { describe('no specs found', function () { it('logs error and exits when spec file was specified and does not exist', function () { - return cypress.start([`--run-project=${this.todosPath}`, '--spec=path/to/spec']) + const cwd = process.cwd() + + return cypress.start([ + `--cwd=${cwd}`, + `--run-project=${this.todosPath}`, + '--spec=path/to/spec', + ]) + .then(() => { + // includes the search spec + this.expectExitWithErr('NO_SPECS_FOUND', 'We searched for specs matching this glob pattern:') + // includes the project path + this.expectExitWithErr('NO_SPECS_FOUND', path.join(cwd, 'path/to/spec')) + }) + }) + + it('logs error and exits when spec file was specified and does not exist using ../ pattern', function () { + const cwd = process.cwd() + + return cypress.start([ + `--cwd=${cwd}`, + `--run-project=${this.todosPath}`, + '--spec=../path/to/spec', + ]) + .then(() => { + // includes the search spec + this.expectExitWithErr('NO_SPECS_FOUND', 'We searched for specs matching this glob pattern:') + // includes the project path + this.expectExitWithErr('NO_SPECS_FOUND', path.join(cwd, '../path/to/spec')) + }) + }) + + it('logs error and exits when spec file was specified with glob and does not exist using ../ pattern', function () { + const cwd = process.cwd() + + return cypress.start([ + `--cwd=${cwd}`, + `--run-project=${this.todosPath}`, + '--spec=../path/to/**/*', + ]) .then(() => { // includes the search spec - this.expectExitWithErr('NO_SPECS_FOUND', 'path/to/spec') this.expectExitWithErr('NO_SPECS_FOUND', 'We searched for specs matching this glob pattern:') // includes the project path - this.expectExitWithErr('NO_SPECS_FOUND', path.join(this.todosPath, 'path/to/spec')) + this.expectExitWithErr('NO_SPECS_FOUND', path.join(cwd, '../path/to/**/*')) }) }) it('logs error and exits when spec absolute file was specified and does not exist', function () { return cypress.start([ `--run-project=${this.todosPath}`, - `--spec=${this.todosPath}/tests/path/to/spec`, + `--spec=/path/to/spec`, ]) .then(() => { - // includes path to the spec - this.expectExitWithErr('NO_SPECS_FOUND', 'tests/path/to/spec') - // includes folder name - this.expectExitWithErr('NO_SPECS_FOUND', this.todosPath) + // includes folder name + path to the spec + this.expectExitWithErr('NO_SPECS_FOUND', `/path/to/spec`) + expect(errors.log).not.to.be.calledWithMatch(this.todospath) }) }) From 0360bc9f0fab5f9bab820be341f8477c91f53a27 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 20:09:48 -0500 Subject: [PATCH 104/165] fixes failing tests --- packages/errors/src/errors.ts | 2 +- packages/server/__snapshots__/cypress_spec.js | 6 ++-- .../server/test/integration/cypress_spec.js | 31 ++++++++++++------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 8d4fd6657a3c..6b7f83940752 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -105,7 +105,7 @@ export const AllCypressErrors = { Browser: ${fmt.highlight(browser)} was not found on your system or is not supported by Cypress. Cypress supports the following browsers: - ${fmt.listItems(['electron', 'chrome', 'chromium', 'edge', 'firefox'])} + ${fmt.listItems(['electron', 'chrome', 'chromium', 'chrome:canary', 'edge', 'firefox'])} You can also use a custom browser: https://on.cypress.io/customize-browsers diff --git a/packages/server/__snapshots__/cypress_spec.js b/packages/server/__snapshots__/cypress_spec.js index b26dcf8b407b..cbb8a2e13d51 100644 --- a/packages/server/__snapshots__/cypress_spec.js +++ b/packages/server/__snapshots__/cypress_spec.js @@ -302,8 +302,10 @@ The error was: Cannot read property 'split' of undefined ` exports['INVALID_CONFIG_OPTION'] = ` -\`test\` is not a valid configuration option -\`foo\` is not a valid configuration option +The following configuration options are invalid: + + - test + - foo https://on.cypress.io/configuration diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index 4dd8ec58d662..b2ae0187c439 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -258,7 +258,8 @@ describe('lib/cypress', () => { return cypress.start(['--config=test=false', '--cwd=/foo/bar']) .then(() => { expect(errors.warning).to.be.calledWith('INVALID_CONFIG_OPTION') - expect(console.log).to.be.calledWithMatch('`test` is not a valid configuration option') + expect(console.log).to.be.calledWithMatch('The following configuration option is invalid:') + expect(console.log).to.be.calledWithMatch(`test`) expect(console.log).to.be.calledWithMatch('https://on.cypress.io/configuration') }) }) @@ -267,8 +268,9 @@ describe('lib/cypress', () => { return cypress.start(['--config=test=false,foo=bar', '--cwd=/foo/bar']) .then(() => { expect(errors.warning).to.be.calledWith('INVALID_CONFIG_OPTION') - expect(console.log).to.be.calledWithMatch('`test` is not a valid configuration option') - expect(console.log).to.be.calledWithMatch('`foo` is not a valid configuration option') + expect(console.log).to.be.calledWithMatch('The following configuration options are invalid:') + expect(console.log).to.be.calledWithMatch('test') + expect(console.log).to.be.calledWithMatch('foo') expect(console.log).to.be.calledWithMatch('https://on.cypress.io/configuration') snapshotConsoleLogs('INVALID_CONFIG_OPTION') @@ -644,7 +646,7 @@ describe('lib/cypress', () => { return cypress.start([`--run-project=${this.todosPath}`, '--key=asdf']) .then(() => { expect(errors.warning).to.be.calledWith('PROJECT_ID_AND_KEY_BUT_MISSING_RECORD_OPTION', 'abc123') - expect(console.log).to.be.calledWithMatch('You also provided your Record Key, but you did not pass the --record flag.') + expect(console.log).to.be.calledWithMatch('You also provided your Record Key, but you did not pass the') expect(console.log).to.be.calledWithMatch('cypress run --record') expect(console.log).to.be.calledWithMatch('https://on.cypress.io/recording-project-runs') }) @@ -657,7 +659,7 @@ describe('lib/cypress', () => { return cypress.start([`--run-project=${this.todosPath}`]) .then(() => { - expect(errors.warning).to.be.calledWith('CANNOT_REMOVE_OLD_BROWSER_PROFILES', err.stack) + expect(errors.warning).to.be.calledWith('CANNOT_REMOVE_OLD_BROWSER_PROFILES', err) expect(console.log).to.be.calledWithMatch('Warning: We failed to remove old browser profiles from previous runs.') expect(console.log).to.be.calledWithMatch(err.message) }) @@ -701,7 +703,7 @@ describe('lib/cypress', () => { const found1 = _.find(argsSet, (args) => { return _.find(args, (arg) => { - return arg.message && arg.message.includes( + return arg.message && stripAnsi(arg.message).includes( `Browser: foo was not found on your system or is not supported by Cypress.`, ) }) @@ -711,7 +713,7 @@ describe('lib/cypress', () => { const found2 = _.find(argsSet, (args) => { return _.find(args, (arg) => { - return arg.message && arg.message.includes( + return arg.message && stripAnsi(arg.message).includes( 'Cypress supports the following browsers:', ) }) @@ -722,7 +724,7 @@ describe('lib/cypress', () => { const found3 = _.find(argsSet, (args) => { return _.find(args, (arg) => { return arg.message && stripAnsi(arg.message).includes( - 'Available browsers found on your system are:\n- chrome\n- chromium\n- chrome:canary\n- electron', + 'Available browsers found on your system are:\n - chrome\n - chromium\n - chrome:canary\n - electron', ) }) }) @@ -1838,18 +1840,21 @@ describe('lib/cypress', () => { }) it('reads config from a custom config file', function () { + const bus = new EE() + return fs.writeJson(path.join(this.pristinePath, this.filename), { env: { foo: 'bar' }, port: 2020, }).then(() => { - cypress.start([ + return cypress.start([ `--config-file=${this.filename}`, ]) .then(() => { const options = Events.start.firstCall.args[0] - return Events.handleEvent(options, {}, {}, 123, 'open:project', this.pristinePath) - }).then(() => { + return Events.handleEvent(options, bus, {}, 123, 'open:project', this.pristinePath) + }) + .then(() => { expect(this.open).to.be.called const cfg = this.open.getCall(0).args[0] @@ -1862,6 +1867,8 @@ describe('lib/cypress', () => { }) it('creates custom config file if it does not exist', function () { + const bus = new EE() + return cypress.start([ `--config-file=${this.filename}`, ]) @@ -1871,7 +1878,7 @@ describe('lib/cypress', () => { debug('first call arguments %o', Events.start.firstCall.args) - return Events.handleEvent(options, {}, {}, 123, 'open:project', this.pristinePath) + return Events.handleEvent(options, bus, {}, 123, 'open:project', this.pristinePath) }).then(() => { expect(this.open, 'open was called').to.be.called From ea6b2d5e33257042194187edb7fa97d30a27caa3 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 20:15:16 -0500 Subject: [PATCH 105/165] fix test --- packages/server/test/integration/plugins_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/test/integration/plugins_spec.js b/packages/server/test/integration/plugins_spec.js index f3d94dba45c8..60f12197dd89 100644 --- a/packages/server/test/integration/plugins_spec.js +++ b/packages/server/test/integration/plugins_spec.js @@ -38,7 +38,7 @@ describe('lib/plugins', () => { }) .then(() => { expect(onWarning).to.be.calledOnce - expect(onWarning.firstCall.args[0].message).to.include('Deprecation Warning: The `before:browser:launch` plugin event changed its signature in version `4.0.0`') + expect(onWarning.firstCall.args[0].message).to.include('Deprecation Warning: The before:browser:launch plugin event changed its signature in Cypress version 4.0.0') }) }) }) From 95f22138e50152f30b885c6ebef4dcb50ed99ebd Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 20:36:28 -0500 Subject: [PATCH 106/165] preserve original spec pattern, display relative to projectRoot for terminal banner --- packages/server/lib/modes/run.js | 17 ++++++++++++----- packages/server/lib/util/specs.ts | 6 +++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index 43378c02efff..7df7c59b6493 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -1557,9 +1557,12 @@ module.exports = { trashAssets(config), ]) .spread((sys = {}, browser = {}, specs = []) => { - // return only what is return to the specPattern + const originalSpecPattern = specPattern + if (specPattern) { - specPattern = specsUtil.default.getPatternRelativeToCwd(specPattern, options.cwd) + // remap the spec pattern for terminal display purposes + // relative to the projectRoot + specPattern = specsUtil.default.getPatternRelativeToPath(specPattern, projectRoot) } specs = specs.filter((spec) => { @@ -1569,12 +1572,16 @@ module.exports = { }) if (!specs.length) { + // for error purposes: display the specPattern relative to the + // current working directly, not the project root as done above + const relativeCwdSpecPattern = specsUtil.default.getPatternRelativeToPath(originalSpecPattern, options.cwd) + // did we use the spec pattern? if (specPattern) { - errors.throw('NO_SPECS_FOUND', options.cwd, specPattern) + errors.throw('NO_SPECS_FOUND', options.cwd, relativeCwdSpecPattern) } else { // else we looked in the integration folder - errors.throw('NO_SPECS_FOUND', config.integrationFolder, specPattern) + errors.throw('NO_SPECS_FOUND', config.integrationFolder, relativeCwdSpecPattern) } } @@ -1591,7 +1598,6 @@ module.exports = { beforeSpecRun, afterSpecRun, projectRoot, - specPattern, socketId, parallel, onError, @@ -1603,6 +1609,7 @@ module.exports = { specs, sys, tag, + specPattern, videosFolder: config.videosFolder, video: config.video, videoCompression: config.videoCompression, diff --git a/packages/server/lib/util/specs.ts b/packages/server/lib/util/specs.ts index 65172a6ed4b3..fa69b1a8be1b 100644 --- a/packages/server/lib/util/specs.ts +++ b/packages/server/lib/util/specs.ts @@ -22,9 +22,9 @@ const SPEC_TYPES = { COMPONENT: 'component', } as const -const getPatternRelativeToCwd = (specPattern: string, cwdPath: string) => { +const getPatternRelativeToPath = (specPattern: string, relativePath: string) => { return _.map(specPattern, (p) => { - return path.relative(cwdPath, p) + return path.relative(relativePath, p) }) } @@ -240,5 +240,5 @@ const findSpecs = (payload: FindSpecs, specPattern?: string) => { export default { findSpecs, findSpecsOfType, - getPatternRelativeToCwd, + getPatternRelativeToPath, } From e06817616e8424bbdf54dd040c43d392a1705329 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 20:36:45 -0500 Subject: [PATCH 107/165] make the nodeVersion path display in gray, not white --- packages/server/lib/modes/run.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index 7df7c59b6493..4324b1150d02 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -149,7 +149,7 @@ const formatNodeVersion = ({ resolvedNodeVersion, resolvedNodePath }, width) => debug('formatting Node version. %o', { version: resolvedNodeVersion, path: resolvedNodePath }) if (resolvedNodePath) { - return formatPath(`v${resolvedNodeVersion} (${resolvedNodePath})`, width) + return formatPath(`v${resolvedNodeVersion} ${gray(`(${resolvedNodePath})`)}`, width) } } From 45c1a730c38e1a41ded62fe4447ba9cf20f64469 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 20:57:33 -0500 Subject: [PATCH 108/165] fix tests, pass right variables --- packages/server/lib/modes/run.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/server/lib/modes/run.js b/packages/server/lib/modes/run.js index 4324b1150d02..f2f79dd9b44b 100644 --- a/packages/server/lib/modes/run.js +++ b/packages/server/lib/modes/run.js @@ -1572,16 +1572,16 @@ module.exports = { }) if (!specs.length) { - // for error purposes: display the specPattern relative to the - // current working directly, not the project root as done above - const relativeCwdSpecPattern = specsUtil.default.getPatternRelativeToPath(originalSpecPattern, options.cwd) - // did we use the spec pattern? if (specPattern) { + // for error purposes: display the specPattern relative to the + // current working directly, not the project root as done above + const relativeCwdSpecPattern = specsUtil.default.getPatternRelativeToPath(originalSpecPattern, options.cwd) + errors.throw('NO_SPECS_FOUND', options.cwd, relativeCwdSpecPattern) } else { // else we looked in the integration folder - errors.throw('NO_SPECS_FOUND', config.integrationFolder, relativeCwdSpecPattern) + errors.throw('NO_SPECS_FOUND', config.integrationFolder) } } From 10621063925cb2746a8e6102492e370c4a7ced40 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 20:59:50 -0500 Subject: [PATCH 109/165] fix chrome:canary snapshots --- packages/server/__snapshots__/browsers_spec.js | 2 ++ packages/server/__snapshots__/chrome_spec.js | 1 + packages/server/__snapshots__/cri-client_spec.ts.js | 1 + system-tests/__snapshots__/plugins_spec.js | 1 + system-tests/__snapshots__/record_spec.js | 1 + 5 files changed, 6 insertions(+) diff --git a/packages/server/__snapshots__/browsers_spec.js b/packages/server/__snapshots__/browsers_spec.js index 7853ad496dc3..4d9e8283e385 100644 --- a/packages/server/__snapshots__/browsers_spec.js +++ b/packages/server/__snapshots__/browsers_spec.js @@ -7,6 +7,7 @@ Cypress supports the following browsers: - electron - chrome - chromium + - chrome:canary - edge - firefox @@ -27,6 +28,7 @@ Cypress supports the following browsers: - electron - chrome - chromium + - chrome:canary - edge - firefox diff --git a/packages/server/__snapshots__/chrome_spec.js b/packages/server/__snapshots__/chrome_spec.js index 5968c8cd46fc..01f958c079a1 100644 --- a/packages/server/__snapshots__/chrome_spec.js +++ b/packages/server/__snapshots__/chrome_spec.js @@ -6,6 +6,7 @@ Browser: browserNotGonnaBeFound was not found on your system or is not supported Cypress supports the following browsers: - chrome - chromium +- chrome:canary - edge - electron - firefox diff --git a/packages/server/__snapshots__/cri-client_spec.ts.js b/packages/server/__snapshots__/cri-client_spec.ts.js index 05b47b1e33d1..ee31605ecbc5 100644 --- a/packages/server/__snapshots__/cri-client_spec.ts.js +++ b/packages/server/__snapshots__/cri-client_spec.ts.js @@ -6,6 +6,7 @@ Browser: browserNotGonnaBeFound was not found on your system or is not supported Cypress supports the following browsers: - chrome - chromium +- chrome:canary - edge - electron - firefox diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js index 4683e940776a..e59375c77b5b 100644 --- a/system-tests/__snapshots__/plugins_spec.js +++ b/system-tests/__snapshots__/plugins_spec.js @@ -141,6 +141,7 @@ Cypress supports the following browsers: - electron - chrome - chromium + - chrome:canary - edge - firefox diff --git a/system-tests/__snapshots__/record_spec.js b/system-tests/__snapshots__/record_spec.js index 08f35c5565fc..bab1219e3afb 100644 --- a/system-tests/__snapshots__/record_spec.js +++ b/system-tests/__snapshots__/record_spec.js @@ -2648,6 +2648,7 @@ Browser: browserDoesNotExist was not found on your system or is not supported by Cypress supports the following browsers: - chrome - chromium + - chrome:canary - edge - electron - firefox From 2470ec4937c9a3951dfaec8cdbd336bbdac917af Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 20:59:57 -0500 Subject: [PATCH 110/165] update snapshots --- system-tests/__snapshots__/non_root_read_only_fs_spec.ts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system-tests/__snapshots__/non_root_read_only_fs_spec.ts.js b/system-tests/__snapshots__/non_root_read_only_fs_spec.ts.js index 42c93856a454..dd8e0a6207de 100644 --- a/system-tests/__snapshots__/non_root_read_only_fs_spec.ts.js +++ b/system-tests/__snapshots__/non_root_read_only_fs_spec.ts.js @@ -1,5 +1,5 @@ exports['e2e readonly fs / warns when unable to write to disk'] = ` -Folder /foo/bar/.projects/read-only-project-root is not writable. +This folder is not writable: /foo/bar/.projects/read-only-project-root Writing to this directory is required by Cypress in order to store screenshots and videos. From 307c52269f881a86ad216d6d6f697f0279460940 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:13:11 -0500 Subject: [PATCH 111/165] update snapshot --- system-tests/__snapshots__/deprecated_spec.ts.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/system-tests/__snapshots__/deprecated_spec.ts.js b/system-tests/__snapshots__/deprecated_spec.ts.js index e54e17f41169..b426db927532 100644 --- a/system-tests/__snapshots__/deprecated_spec.ts.js +++ b/system-tests/__snapshots__/deprecated_spec.ts.js @@ -15,9 +15,9 @@ exports['deprecated before:browser:launch args / push and no return - warns user ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: app_spec.js (1 of 1) -Deprecation Warning: The \`before:browser:launch\` plugin event changed its signature in version \`4.0.0\` +Deprecation Warning: The before:browser:launch plugin event changed its signature in Cypress version 4.0.0 -The \`before:browser:launch\` plugin event switched from yielding the second argument as an \`array\` of browser arguments to an options \`object\` with an \`args\` property. +The event switched from yielding the second argument as an array of browser arguments to an options object with an args property. We've detected that your code is still using the previous, deprecated interface signature. @@ -181,9 +181,9 @@ exports['deprecated before:browser:launch args / concat return returns once per ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: app_spec.js (1 of 2) -Deprecation Warning: The \`before:browser:launch\` plugin event changed its signature in version \`4.0.0\` +Deprecation Warning: The before:browser:launch plugin event changed its signature in Cypress version 4.0.0 -The \`before:browser:launch\` plugin event switched from yielding the second argument as an \`array\` of browser arguments to an options \`object\` with an \`args\` property. +The event switched from yielding the second argument as an array of browser arguments to an options object with an args property. We've detected that your code is still using the previous, deprecated interface signature. @@ -213,9 +213,9 @@ This code will not work in a future version of Cypress. Please see the upgrade g ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: app_spec2.js (2 of 2) -Deprecation Warning: The \`before:browser:launch\` plugin event changed its signature in version \`4.0.0\` +Deprecation Warning: The before:browser:launch plugin event changed its signature in Cypress version 4.0.0 -The \`before:browser:launch\` plugin event switched from yielding the second argument as an \`array\` of browser arguments to an options \`object\` with an \`args\` property. +The event switched from yielding the second argument as an array of browser arguments to an options object with an args property. We've detected that your code is still using the previous, deprecated interface signature. From 6f0edc1bf248cfae34fe24fc9be08370d47a53af Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:13:37 -0500 Subject: [PATCH 112/165] strip newlines caused by "Still waiting to connect to {browser}..." --- system-tests/lib/system-tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system-tests/lib/system-tests.ts b/system-tests/lib/system-tests.ts index b1e1c160669b..16d8ab2161f3 100644 --- a/system-tests/lib/system-tests.ts +++ b/system-tests/lib/system-tests.ts @@ -443,7 +443,7 @@ const normalizeStdout = function (str, options: any = {}) { // Different browsers have different cross-origin error messages .replace(crossOriginErrorRe, '[Cross origin error message]') // Replaces connection warning since Chrome or Firefox sometimes take longer to connect - .replace(/Still waiting to connect to .+, retrying in 1 second \(attempt .+\/.+\)/g, '') + .replace(/Still waiting to connect to .+, retrying in 1 second \(attempt .+\/.+\)\n/g, '') if (options.sanitizeScreenshotDimensions) { // screenshot dimensions From de68a21debde5be08ef1bb9d85526f1d2b93ef67 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:21:29 -0500 Subject: [PATCH 113/165] don't remove the snapshotHtmlFolder in CI, add additional verification snapshots match to error keys symmetrically --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index f816d2429dab..0a67d6a1235e 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -192,10 +192,8 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K // otherwise test all the errors before(() => { - // prune out all existing snapshot html in case - // errors were removed and we have stale snapshots + // prune out all existing local staging folders return Promise.all([ - isCi ? fse.remove(snapshotHtmlFolder) : null, fse.remove(snapshotMarkdownFolder), fse.remove(snapshotHtmlLocalFolder), fse.remove(snapshotImagesFolder), @@ -218,7 +216,10 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K // return fse.remove(pathToHtml) // })) - expect(uniqErrors).to.have.all.members(_.keys(errors.AllCypressErrors)) + const errorKeys = _.keys(errors.AllCypressErrors) + + expect(uniqErrors).to.have.all.members(errorKeys) + expect(errorKeys).to.have.all.members(uniqErrors) } }) From 2ef825f648e4f183da1070e976201195918a0d37 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:22:35 -0500 Subject: [PATCH 114/165] update snapshot --- system-tests/__snapshots__/retries_spec.ts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system-tests/__snapshots__/retries_spec.ts.js b/system-tests/__snapshots__/retries_spec.ts.js index 4df487a56ca7..b2ed16f04e58 100644 --- a/system-tests/__snapshots__/retries_spec.ts.js +++ b/system-tests/__snapshots__/retries_spec.ts.js @@ -67,9 +67,9 @@ exports['retries / supports retries'] = ` ` exports['retries / warns about retries plugin'] = ` -We've detected that the incompatible plugin \`cypress-plugin-retries\` is installed at node_modules/cypress-plugin-retries. +We've detected that the incompatible plugin cypress-plugin-retries is installed at: node_modules/cypress-plugin-retries -Test retries is now supported in Cypress version \`5.0.0\`. +Test retries is now natively supported in Cypress version 5.0.0. Remove the plugin from your dependencies to silence this warning. From 9659bdb7a6c00de18b4d1d82a45ebf49e7d1c37c Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:24:34 -0500 Subject: [PATCH 115/165] update snapshot --- system-tests/__snapshots__/task_spec.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system-tests/__snapshots__/task_spec.js b/system-tests/__snapshots__/task_spec.js index 0a19ea9f9d94..aca60cc5925e 100644 --- a/system-tests/__snapshots__/task_spec.js +++ b/system-tests/__snapshots__/task_spec.js @@ -1,5 +1,9 @@ exports['e2e task merges task events on subsequent registrations and logs warning for conflicts 1'] = ` -Warning: Multiple attempts to register the following task(s): two. Only the last attempt will be registered. +Warning: Multiple attempts to register the following task(s): + + - two + +Only the last attempt will be registered. ==================================================================================================== From a343e66d9e09287c03ef003b1d487cab43d5b0d6 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:27:46 -0500 Subject: [PATCH 116/165] update snapshot --- system-tests/__snapshots__/typescript_spec_support_spec.ts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system-tests/__snapshots__/typescript_spec_support_spec.ts.js b/system-tests/__snapshots__/typescript_spec_support_spec.ts.js index c5043e0d11b1..96e9e15c0305 100644 --- a/system-tests/__snapshots__/typescript_spec_support_spec.ts.js +++ b/system-tests/__snapshots__/typescript_spec_support_spec.ts.js @@ -82,7 +82,7 @@ exports['e2e typescript in spec and support file spec fails with syntax error 1' Oops...we found an error preparing this test file: - cypress/integration/typescript_syntax_error_spec.ts + > cypress/integration/typescript_syntax_error_spec.ts The error was: From 2e8e08855276123db529e6c67c9cb6fd1fc4dc81 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:29:31 -0500 Subject: [PATCH 117/165] update snapshot --- system-tests/__snapshots__/system_node_spec.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/system-tests/__snapshots__/system_node_spec.js b/system-tests/__snapshots__/system_node_spec.js index 5b838a16a58c..5a374abf4760 100644 --- a/system-tests/__snapshots__/system_node_spec.js +++ b/system-tests/__snapshots__/system_node_spec.js @@ -1,9 +1,9 @@ exports['e2e system node uses system node when launching plugins file 1'] = ` -Deprecation Warning: \`nodeVersion\` is currently set to \`system\` in the \`cypress.json\` configuration file. +Deprecation Warning: nodeVersion is currently set to system in the cypress.json configuration file. -As of Cypress version \`9.0.0\` the default behavior of \`nodeVersion\` has changed to always use the version of Node used to start cypress via the cli. +As of Cypress version 9.0.0 the default behavior of nodeVersion has changed to always use the version of Node used to start cypress via the cli. -Please remove the \`nodeVersion\` configuration option from \`cypress.json\`. +Please remove the nodeVersion configuration option from cypress.json. ==================================================================================================== @@ -65,13 +65,13 @@ Please remove the \`nodeVersion\` configuration option from \`cypress.json\`. ` exports['e2e system node uses bundled node when launching plugins file 1'] = ` -Deprecation Warning: \`nodeVersion\` is currently set to \`bundled\` in the \`cypress.json\` configuration file. +Deprecation Warning: nodeVersion is currently set to bundled in the cypress.json configuration file. -As of Cypress version \`9.0.0\` the default behavior of \`nodeVersion\` has changed to always use the version of Node used to start cypress via the cli. +As of Cypress version 9.0.0 the default behavior of nodeVersion has changed to always use the version of Node used to start cypress via the cli. -When \`nodeVersion\` is set to \`bundled\`, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. +When nodeVersion is set to bundled, Cypress will use the version of Node bundled with electron. This can cause problems running certain plugins or integrations. -As the \`nodeVersion\` configuration option will be removed in a future release, it is recommended to remove the \`nodeVersion\` configuration option from \`cypress.json\`. +As the nodeVersion configuration option will be removed in a future release, it is recommended to remove the nodeVersion configuration option from cypress.json. ==================================================================================================== From 6f89d9d506382609ab30d737e059671f5b8efea7 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:31:55 -0500 Subject: [PATCH 118/165] update snapshot --- system-tests/__snapshots__/plugin_run_events_spec.ts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system-tests/__snapshots__/plugin_run_events_spec.ts.js b/system-tests/__snapshots__/plugin_run_events_spec.ts.js index 0e5d0f56a1e3..e015c325a119 100644 --- a/system-tests/__snapshots__/plugin_run_events_spec.ts.js +++ b/system-tests/__snapshots__/plugin_run_events_spec.ts.js @@ -160,7 +160,7 @@ exports['e2e plugin run events / fails run if event handler throws'] = ` ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: run_event_throws_spec.js (1 of 1) -An error was thrown in your plugins file while executing the handler for the 'before:spec' event. +An error was thrown in your plugins file while executing the handler for the before:spec event. The error we received was: From b527e748ef969bac47f5614a1e9b29932109dcbb Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:41:32 -0500 Subject: [PATCH 119/165] update snapshot --- system-tests/__snapshots__/network_error_handling_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system-tests/__snapshots__/network_error_handling_spec.js b/system-tests/__snapshots__/network_error_handling_spec.js index c9f61fd46d2f..9b4ab0ea1156 100644 --- a/system-tests/__snapshots__/network_error_handling_spec.js +++ b/system-tests/__snapshots__/network_error_handling_spec.js @@ -3,7 +3,7 @@ Cypress could not verify that this server is running: > http://never-gonna-exist.invalid -We are verifying this server because it has been configured as your \`baseUrl\`. +We are verifying this server because it has been configured as your baseUrl. Cypress automatically waits until your server is accessible before running tests. From f65972e7f6642ee41d3390621e25d33fbeab45d7 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Tue, 8 Feb 2022 21:42:23 -0500 Subject: [PATCH 120/165] update gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index d0c3a004ebc4..60b2822e4c4b 100644 --- a/.gitignore +++ b/.gitignore @@ -70,6 +70,7 @@ packages/socket/lib/*.js # from system-tests system-tests/.projects +system-tests/.http-mitm-proxy system-tests/fixtures/large-img # from npm/react From a3aadba290065063ab99d80832034647197f1265 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 01:53:12 -0500 Subject: [PATCH 121/165] fix snapshot --- system-tests/__snapshots__/record_spec.js | 38 ++++++++++------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/system-tests/__snapshots__/record_spec.js b/system-tests/__snapshots__/record_spec.js index bab1219e3afb..da6cc0874107 100644 --- a/system-tests/__snapshots__/record_spec.js +++ b/system-tests/__snapshots__/record_spec.js @@ -22,7 +22,7 @@ exports['e2e record passing passes 1'] = ` Oops...we found an error preparing this test file: - cypress/integration/record_error_spec.js + > cypress/integration/record_error_spec.js The error was: @@ -257,9 +257,9 @@ We dynamically generated a new test to display this failure. ` exports['e2e record api interaction errors project 404 errors and exits 1'] = ` -We could not find a project with the ID: pid123 +We could not find a Dashboard project with the projectId: pid123 -This projectId came from your 'cypress.json' file or an environment variable. +This projectId came from your cypress.json file or an environment variable. Please log into the Dashboard and find your project. @@ -358,9 +358,9 @@ You passed the --record flag but did not provide us your Record Key. You can pass us your Record Key like this: - cypress run --record --key + $ cypress run --record --key -You can also set the key as an environment variable with the name CYPRESS_RECORD_KEY. +You can also set the key as an environment variable with the name: CYPRESS_RECORD_KEY https://on.cypress.io/how-do-i-record-runs @@ -369,11 +369,11 @@ https://on.cypress.io/how-do-i-record-runs exports['e2e record projectId errors and exits without projectId 1'] = ` You passed the --record flag but this project has not been setup to record. -This project is missing the 'projectId' inside of 'cypress.json'. +This project is missing the projectId inside of: cypress.json We cannot uniquely identify this project without this id. -You need to setup this project to record. This will generate a unique 'projectId'. +You need to setup this project to record. This will generate a unique projectId. Alternatively if you omit the --record flag this project will run without recording. @@ -382,7 +382,7 @@ https://on.cypress.io/recording-project-runs ` exports['e2e record api interaction errors recordKey and projectId errors and exits on 401 1'] = ` -Your Record Key f858a...ee7e1 is not valid with this project: pid123 +Your Record Key f858a...ee7e1 is not valid with this projectId: pid123 It may have been recently revoked by you or another user. @@ -552,20 +552,16 @@ exports['e2e record api interaction errors uploading assets warns but proceeds 1 exports['e2e record misconfiguration errors and exits when no specs found 1'] = ` Can't run because no spec files were found. -We searched for any files matching this glob pattern: +We searched for specs matching this glob pattern: -cypress/integration/notfound/** - -Relative to the project root folder: - -/foo/bar/.projects/e2e + > /foo/bar/.projects/e2e/cypress/integration/notfound/** ` exports['e2e record recordKey warns but does not exit when is forked pr 1'] = ` Warning: It looks like you are trying to record this run from a forked PR. -The 'Record Key' is missing. Your CI provider is likely not passing private environment variables to builds from forks. +The Record Key is missing. Your CI provider is likely not passing private environment variables to builds from forks. These results will not be recorded. @@ -731,7 +727,7 @@ exports['e2e record parallelization passes in parallel with group 2'] = ` Oops...we found an error preparing this test file: - cypress/integration/record_error_spec.js + > cypress/integration/record_error_spec.js The error was: @@ -1171,7 +1167,7 @@ StatusCodeError: 500 - "Internal Server Error" exports['e2e record recordKey warns but does not exit when is forked pr and parallel 1'] = ` Warning: It looks like you are trying to record this run from a forked PR. -The 'Record Key' is missing. Your CI provider is likely not passing private environment variables to builds from forks. +The Record Key is missing. Your CI provider is likely not passing private environment variables to builds from forks. These results will not be recorded. @@ -2646,19 +2642,19 @@ Can't run because you've entered an invalid browser name. Browser: browserDoesNotExist was not found on your system or is not supported by Cypress. Cypress supports the following browsers: + - electron - chrome - chromium - chrome:canary - edge - - electron - firefox You can also use a custom browser: https://on.cypress.io/customize-browsers Available browsers found on your system are: - - browser1 - - browser2 - - browser3 +- browser1 +- browser2 +- browser3 ` exports['e2e record metadata sends Studio usage metadata 1'] = ` From 19698545435a9a8ae20ec2b9dd11d7f9a4a2ac78 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 02:09:27 -0500 Subject: [PATCH 122/165] update snapshot html --- .../__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html | 3 ++- .../errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html index cf2946ed304c..95f021d5471a 100644 --- a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME - canary.html @@ -42,6 +42,7 @@ - electron - chrome - chromium + - chrome:canary - edge - firefox @@ -62,5 +63,5 @@ Note: In Cypress version 4.0.0, Canary must be launched as chrome:canary, not canary. -See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0. +See https://on.cypress.io/migration-guide for more information on breaking changes in 4.0.0. \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html index 3cb6658c2617..d29ace7a0dc5 100644 --- a/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html +++ b/packages/errors/__snapshot-html__/BROWSER_NOT_FOUND_BY_NAME.html @@ -42,6 +42,7 @@ - electron - chrome - chromium + - chrome:canary - edge - firefox @@ -58,5 +59,5 @@ - edge - edge:canary - edge:beta - - edge:dev + - edge:dev \ No newline at end of file From 6636722ebf4c9ff8e3de024e8ede094fd840bd13 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 02:18:46 -0500 Subject: [PATCH 123/165] update logic for parsing the resolve pattern matching, add tests --- packages/errors/src/errorUtils.ts | 14 ++++---- packages/errors/test/unit/errors_spec.ts | 41 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/packages/errors/src/errorUtils.ts b/packages/errors/src/errorUtils.ts index 1cb7f4262769..501249803ee7 100644 --- a/packages/errors/src/errorUtils.ts +++ b/packages/errors/src/errorUtils.ts @@ -13,17 +13,17 @@ export { humanTime, } -export const parseResolvedPattern = (baseFolder: string, globPattern: string) => { - // folderPath: /Users/bmann/Dev/playground/cypress/server/integration/nested.spec.js - // resolved: /Users/bmann/Dev/playground/cypress/integration/nested.spec.js +const whileMatching = (othArr: string[]) => { + return (val: string, index: number) => { + return val === othArr[index] + } +} - // folderPath: /Users/bmann/Dev/cypress/packages/server - // pattern: ../path/to/spec.js - // resolvedPath: /Users/bmann/Dev/cypress/packages/path/to/spec.js +export const parseResolvedPattern = (baseFolder: string, globPattern: string) => { const resolvedPath = path.resolve(baseFolder, globPattern) const resolvedPathParts = resolvedPath.split(path.sep) const folderPathPaths = baseFolder.split(path.sep) - const commonPath = _.intersection(folderPathPaths, resolvedPathParts).join(path.sep) + const commonPath = _.takeWhile(folderPathPaths, whileMatching(resolvedPathParts)).join(path.sep) const remainingPattern = !commonPath ? resolvedPath : resolvedPath.replace(commonPath.concat(path.sep), '') return [commonPath, remainingPattern] diff --git a/packages/errors/test/unit/errors_spec.ts b/packages/errors/test/unit/errors_spec.ts index ffa08aef8591..f8878c4801ea 100644 --- a/packages/errors/test/unit/errors_spec.ts +++ b/packages/errors/test/unit/errors_spec.ts @@ -5,6 +5,7 @@ import chai, { expect } from 'chai' import chalk from 'chalk' import sinon from 'sinon' import * as errors from '../../src' +import { parseResolvedPattern } from '../../src/errorUtils' chai.use(require('@cypress/sinon-chai')) @@ -99,4 +100,44 @@ describe('lib/errors', () => { expect(obj.message).to.eq('foo\u001b[34mbar\u001b[39m\u001b[33mbaz\u001b[39m') }) }) + + describe('.parseResolvedPattern', () => { + const folderPath = '/dev/cypress/packages/server' + + it('splits common paths', () => { + const pattern = '/dev/cypress/packages/server/cypress/integration/**notfound**' + + const [resolvedBasePath, resolvedPattern] = parseResolvedPattern(folderPath, pattern) + + expect(resolvedBasePath).to.eq('/dev/cypress/packages/server') + expect(resolvedPattern).to.eq('cypress/integration/**notfound**') + }) + + it('splits common paths factoring in ../', () => { + const pattern = '/dev/cypress/packages/server/../../integration/**notfound**' + + const [resolvedBasePath, resolvedPattern] = parseResolvedPattern(folderPath, pattern) + + expect(resolvedBasePath).to.eq('/dev/cypress') + expect(resolvedPattern).to.eq('integration/**notfound**') + }) + + it('splits common paths until falsy instead of doing an intersection', () => { + const pattern = '/private/var/cypress/integration/cypress/integration/**notfound**' + + const [resolvedBasePath, resolvedPattern] = parseResolvedPattern(folderPath, pattern) + + expect(resolvedBasePath).to.eq('') + expect(resolvedPattern).to.eq('/private/var/cypress/integration/cypress/integration/**notfound**') + }) + + it('splits common paths up directories until root is reached', () => { + const pattern = '/../../../../../../../cypress/integration/**notfound**' + + const [resolvedBasePath, resolvedPattern] = parseResolvedPattern(folderPath, pattern) + + expect(resolvedBasePath).to.eq('') + expect(resolvedPattern).to.eq('/cypress/integration/**notfound**') + }) + }) }) From 19acbb43c20dc3ead03dc4c9cff50413f2f4235f Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 02:19:00 -0500 Subject: [PATCH 124/165] update snapshots --- system-tests/__snapshots__/specs_spec.js | 12 ++++-------- system-tests/test/specs_spec.js | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/system-tests/__snapshots__/specs_spec.js b/system-tests/__snapshots__/specs_spec.js index f7bcf37ac586..c368ffa826f5 100644 --- a/system-tests/__snapshots__/specs_spec.js +++ b/system-tests/__snapshots__/specs_spec.js @@ -1,21 +1,17 @@ exports['e2e specs failing when no specs found 1'] = ` Can't run because no spec files were found. -We searched for any files inside of this folder: +We searched for specs inside of this folder: -/foo/bar/.projects/e2e/cypress/specs + > /foo/bar/.projects/e2e/cypress/specs ` exports['e2e specs failing when no spec pattern found 1'] = ` Can't run because no spec files were found. -We searched for any files matching this glob pattern: +We searched for specs matching this glob pattern: -cypress/integration/cypress/integration/**notfound** - -Relative to the project root folder: - -/foo/bar/.projects/e2e + > /foo/bar/.projects/e2e/cypress/integration/does/not/exist/**notfound** ` diff --git a/system-tests/test/specs_spec.js b/system-tests/test/specs_spec.js index 24106b470e38..477e7e385e26 100644 --- a/system-tests/test/specs_spec.js +++ b/system-tests/test/specs_spec.js @@ -13,7 +13,7 @@ describe('e2e specs', () => { it('failing when no spec pattern found', function () { return systemTests.exec(this, { - spec: 'cypress/integration/**notfound**', + spec: 'does/not/exist/**notfound**', snapshot: true, expectedExitCode: 1, }) From 60731a6b242a045333d800b8ab748f7d209f68df Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 02:25:53 -0500 Subject: [PATCH 125/165] update snapshot --- system-tests/__snapshots__/stdout_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system-tests/__snapshots__/stdout_spec.js b/system-tests/__snapshots__/stdout_spec.js index 7e3829809ce3..32846b2fafc3 100644 --- a/system-tests/__snapshots__/stdout_spec.js +++ b/system-tests/__snapshots__/stdout_spec.js @@ -137,7 +137,7 @@ exports['e2e stdout displays errors from exiting early due to bundle errors 1'] Oops...we found an error preparing this test file: - cypress/integration/stdout_exit_early_failing_spec.js + > cypress/integration/stdout_exit_early_failing_spec.js The error was: From f1394d8a0e83a3896a40bba0a3abbf3f5f2a4273 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 02:27:46 -0500 Subject: [PATCH 126/165] update snapshot --- system-tests/__snapshots__/es_modules_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system-tests/__snapshots__/es_modules_spec.js b/system-tests/__snapshots__/es_modules_spec.js index 1fb8c0a8919e..ebcd538ce716 100644 --- a/system-tests/__snapshots__/es_modules_spec.js +++ b/system-tests/__snapshots__/es_modules_spec.js @@ -82,7 +82,7 @@ exports['e2e es modules fails 1'] = ` Oops...we found an error preparing this test file: - cypress/integration/es_module_import_failing_spec.js + > cypress/integration/es_module_import_failing_spec.js The error was: From 508980079603633811bde7bbb67ce7543299ecdd Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 02:34:39 -0500 Subject: [PATCH 127/165] update snapshot --- system-tests/__snapshots__/reporters_spec.js | 21 ++++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/system-tests/__snapshots__/reporters_spec.js b/system-tests/__snapshots__/reporters_spec.js index 52fa0109055a..ef4cc4e42853 100644 --- a/system-tests/__snapshots__/reporters_spec.js +++ b/system-tests/__snapshots__/reporters_spec.js @@ -1,14 +1,14 @@ exports['e2e reporters reports error if cannot load reporter 1'] = ` -Could not load reporter by name: module-does-not-exist +Error loading the reporter: module-does-not-exist We searched for the reporter in these paths: -- /foo/bar/.projects/e2e/module-does-not-exist -- /foo/bar/.projects/e2e/node_modules/module-does-not-exist + - /foo/bar/.projects/e2e/module-does-not-exist + - /foo/bar/.projects/e2e/node_modules/module-does-not-exist -The error we received was: +Learn more at https://on.cypress.io/reporters -Cannot find module '/foo/bar/.projects/e2e/node_modules/module-does-not-exist' +Error: Cannot find module '/foo/bar/.projects/e2e/node_modules/module-does-not-exist' Require stack: - lib/reporter.js - lib/project-base.ts @@ -16,8 +16,8 @@ Require stack: - lib/cypress.js - index.js - + [stack trace lines] -Learn more at https://on.cypress.io/reporters ` @@ -662,19 +662,18 @@ Because this error occurred during a \`after all\` hook we are skipping the rema ` exports['e2e reporters reports error when thrown from reporter 1'] = ` -Could not load reporter by name: reporters/throws.js +Error loading the reporter: reporters/throws.js We searched for the reporter in these paths: -- /foo/bar/.projects/e2e/reporters/throws.js -- /foo/bar/.projects/e2e/node_modules/reporters/throws.js + - /foo/bar/.projects/e2e/reporters/throws.js + - /foo/bar/.projects/e2e/node_modules/reporters/throws.js -The error we received was: +Learn more at https://on.cypress.io/reporters Error: this reporter threw an error [stack trace lines] -Learn more at https://on.cypress.io/reporters ` From 7179ccdb826a0ef791c02dab6b55cf891f4a5059 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 02:54:40 -0500 Subject: [PATCH 128/165] fix failing test --- .../__snapshots__/web_security_spec.js | 97 ++++--------------- system-tests/test/web_security_spec.js | 2 +- 2 files changed, 18 insertions(+), 81 deletions(-) diff --git a/system-tests/__snapshots__/web_security_spec.js b/system-tests/__snapshots__/web_security_spec.js index 8ab2521a70ed..7e2e5f5631a8 100644 --- a/system-tests/__snapshots__/web_security_spec.js +++ b/system-tests/__snapshots__/web_security_spec.js @@ -103,6 +103,13 @@ https://on.cypress.io/cross-origin-violation +Warning: We failed processing this video. + +This error will not alter the exit code. + +TimeoutError: operation timed out + [stack trace lines] + (Results) @@ -113,7 +120,7 @@ https://on.cypress.io/cross-origin-violation │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 4 │ - │ Video: true │ + │ Video: false │ │ Duration: X seconds │ │ Spec Ran: web_security_spec.js │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -131,12 +138,6 @@ https://on.cypress.io/cross-origin-violation doing a CORS request cross-origin (failed).png - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/web_security_spec.js.mp4 (X second) - - ==================================================================================================== (Run Finished) @@ -149,70 +150,6 @@ https://on.cypress.io/cross-origin-violation ✖ 1 of 1 failed (100%) XX:XX 4 - 4 - - -` - -exports['e2e web security / when disabled / passes'] = ` - -==================================================================================================== - - (Run Starting) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Cypress: 1.2.3 │ - │ Browser: FooBrowser 88 │ - │ Specs: 1 found (web_security_spec.js) │ - │ Searched: cypress/integration/web_security_spec.js │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - -──────────────────────────────────────────────────────────────────────────────────────────────────── - - Running: web_security_spec.js (1 of 1) - - - web security - ✓ fails when clicking to another origin - ✓ fails when submitted a form and being redirected to another origin - ✓ fails when using a javascript redirect to another origin - ✓ fails when doing a CORS request cross-origin - - - 4 passing - - - (Results) - - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ Tests: 4 │ - │ Passing: 4 │ - │ Failing: 0 │ - │ Pending: 0 │ - │ Skipped: 0 │ - │ Screenshots: 0 │ - │ Video: true │ - │ Duration: X seconds │ - │ Spec Ran: web_security_spec.js │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - - - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/web_security_spec.js.mp4 (X second) - - -==================================================================================================== - - (Run Finished) - - - Spec Tests Passing Failing Pending Skipped - ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ - │ ✔ web_security_spec.js XX:XX 4 4 - - - │ - └────────────────────────────────────────────────────────────────────────────────────────────────┘ - ✔ All specs passed! XX:XX 4 4 - - - - - ` exports['e2e web security / firefox / displays warning when firefox and chromeWebSecurity:false'] = ` @@ -232,8 +169,7 @@ exports['e2e web security / firefox / displays warning when firefox and chromeWe ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: simple_passing_spec.js (1 of 1) - -Your project has set the configuration option: \`chromeWebSecurity: false\` +Your project has set the configuration option: chromeWebSecurity to false This option will not have an effect in Firefox. Tests that rely on web security being disabled will not run as expected. @@ -244,6 +180,13 @@ This option will not have an effect in Firefox. Tests that rely on web security 1 passing +Warning: We failed processing this video. + +This error will not alter the exit code. + +TimeoutError: operation timed out + [stack trace lines] + (Results) @@ -254,18 +197,12 @@ This option will not have an effect in Firefox. Tests that rely on web security │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: true │ + │ Video: false │ │ Duration: X seconds │ │ Spec Ran: simple_passing_spec.js │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ - (Video) - - - Started processing: Compressing to 32 CRF - - Finished processing: /XXX/XXX/XXX/cypress/videos/simple_passing_spec.js.mp4 (X second) - - ==================================================================================================== (Run Finished) diff --git a/system-tests/test/web_security_spec.js b/system-tests/test/web_security_spec.js index c87dcb0d3058..0e940576418a 100644 --- a/system-tests/test/web_security_spec.js +++ b/system-tests/test/web_security_spec.js @@ -95,7 +95,7 @@ describe('e2e web security', () => { chromeWebSecurity: false, }, onStdout (stdout) { - expect(stdout).include('Your project has set the configuration option: `chromeWebSecurity: false`\n\nThis option will not have an effect in Firefox.') + expect(stdout).include('Your project has set the configuration option: chromeWebSecurity to false\n\nThis option will not have an effect in Firefox.') }, }) }) From 23181e722e1f0695b2839e65d34de4c6b222f3a7 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 9 Feb 2022 08:47:21 -0500 Subject: [PATCH 129/165] fix: error_message_spec --- .../cypress/integration/error_message_spec.js | 38 +++++++++++++++++-- .../desktop-gui/src/project/error-message.jsx | 2 +- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/packages/desktop-gui/cypress/integration/error_message_spec.js b/packages/desktop-gui/cypress/integration/error_message_spec.js index f0a21e48030d..1ac0e28dcf5e 100644 --- a/packages/desktop-gui/cypress/integration/error_message_spec.js +++ b/packages/desktop-gui/cypress/integration/error_message_spec.js @@ -124,7 +124,13 @@ describe('Error Message', function () { .should('contain', 'this is markdown') }) - it('shows error details if provided', function () { + it('shows error details collapsed if cypress originalError provided', function () { + this.detailsErr.originalError = { + ...this.detailsErr, + stack: this.detailsErr.details, + isCypressErr: true, + } + cy.stub(this.ipc, 'onProjectError').yields(null, this.detailsErr) this.start() @@ -134,6 +140,20 @@ describe('Error Message', function () { cy.get('details.details-body > summary').should('contain', 'ReferenceError') }) + it('shows error details open if non cypress originalError provided', function () { + this.detailsErr.originalError = { + ...this.detailsErr, + stack: this.detailsErr.details, + } + + cy.stub(this.ipc, 'onProjectError').yields(null, this.detailsErr) + this.start() + + cy.get('.error').contains('ReferenceError: alsdkjf is not defined') + cy.get('details.details-body').should('have.attr', 'open') + cy.get('details.details-body > summary').should('contain', 'ReferenceError') + }) + it('doesn\'t show error details if not provided', function () { cy.stub(this.ipc, 'onProjectError').yields(null, this.err) this.start() @@ -173,7 +193,7 @@ describe('Error Message', function () { this.detailsErr = { name: 'Error', message: messageText, - stack: '[object Object]↵', + stack: 'ReferenceError: alsdkjf is not defined', details: 'ReferenceError: alsdkjf is not defined', } @@ -219,7 +239,12 @@ describe('Error Message', function () { }) it('does not overlay the nav/footer when long details are expanded (issue #4959)', function () { - this.detailsErr.details = `${this.detailsErr.details}${this.detailsErr.details}` // make details longer + this.detailsErr.originalError = { + ...this.detailsErr, + stack: `${this.detailsErr.details}${this.detailsErr.details}`, // make details longer + isCypressErr: true, + } + this.ipc.openProject.rejects(this.detailsErr) this.start() @@ -229,7 +254,12 @@ describe('Error Message', function () { }) it('it scrolls the error details when details are expanded (issue #4959)', function () { - this.detailsErr.details = `${this.detailsErr.details}${this.detailsErr.details}` // make details longer + this.detailsErr.originalError = { + ...this.detailsErr, + stack: `${this.detailsErr.details}${this.detailsErr.details}`, // make details longer + isCypressErr: true, + } + this.ipc.openProject.rejects(this.detailsErr) this.start() diff --git a/packages/desktop-gui/src/project/error-message.jsx b/packages/desktop-gui/src/project/error-message.jsx index f98fbcc58a48..1e4396db0afb 100644 --- a/packages/desktop-gui/src/project/error-message.jsx +++ b/packages/desktop-gui/src/project/error-message.jsx @@ -40,7 +40,7 @@ const ErrorDetails = observer(({ err }) => { if (detailsBody) { return (
-        
+
{detailsTitle} {detailsBody}
From fd3599926ff1744e6b8f409065bec2ba29e20674 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 9 Feb 2022 08:58:06 -0500 Subject: [PATCH 130/165] fix snapshot --- .../__snapshots__/web_security_spec.js | 95 +++++++++++++++---- 1 file changed, 79 insertions(+), 16 deletions(-) diff --git a/system-tests/__snapshots__/web_security_spec.js b/system-tests/__snapshots__/web_security_spec.js index 7e2e5f5631a8..2efb9d2d79d4 100644 --- a/system-tests/__snapshots__/web_security_spec.js +++ b/system-tests/__snapshots__/web_security_spec.js @@ -103,13 +103,6 @@ https://on.cypress.io/cross-origin-violation -Warning: We failed processing this video. - -This error will not alter the exit code. - -TimeoutError: operation timed out - [stack trace lines] - (Results) @@ -120,7 +113,7 @@ TimeoutError: operation timed out │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 4 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: web_security_spec.js │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ @@ -138,6 +131,12 @@ TimeoutError: operation timed out doing a CORS request cross-origin (failed).png + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/web_security_spec.js.mp4 (X second) + + ==================================================================================================== (Run Finished) @@ -150,6 +149,70 @@ TimeoutError: operation timed out ✖ 1 of 1 failed (100%) XX:XX 4 - 4 - - +` + +exports['e2e web security / when disabled / passes'] = ` + +==================================================================================================== + + (Run Starting) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Cypress: 1.2.3 │ + │ Browser: FooBrowser 88 │ + │ Specs: 1 found (web_security_spec.js) │ + │ Searched: cypress/integration/web_security_spec.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + +──────────────────────────────────────────────────────────────────────────────────────────────────── + + Running: web_security_spec.js (1 of 1) + + + web security + ✓ fails when clicking
to another origin + ✓ fails when submitted a form and being redirected to another origin + ✓ fails when using a javascript redirect to another origin + ✓ fails when doing a CORS request cross-origin + + + 4 passing + + + (Results) + + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ Tests: 4 │ + │ Passing: 4 │ + │ Failing: 0 │ + │ Pending: 0 │ + │ Skipped: 0 │ + │ Screenshots: 0 │ + │ Video: true │ + │ Duration: X seconds │ + │ Spec Ran: web_security_spec.js │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + + + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/web_security_spec.js.mp4 (X second) + + +==================================================================================================== + + (Run Finished) + + + Spec Tests Passing Failing Pending Skipped + ┌────────────────────────────────────────────────────────────────────────────────────────────────┐ + │ ✔ web_security_spec.js XX:XX 4 4 - - - │ + └────────────────────────────────────────────────────────────────────────────────────────────────┘ + ✔ All specs passed! XX:XX 4 4 - - - + + ` exports['e2e web security / firefox / displays warning when firefox and chromeWebSecurity:false'] = ` @@ -169,6 +232,7 @@ exports['e2e web security / firefox / displays warning when firefox and chromeWe ──────────────────────────────────────────────────────────────────────────────────────────────────── Running: simple_passing_spec.js (1 of 1) + Your project has set the configuration option: chromeWebSecurity to false This option will not have an effect in Firefox. Tests that rely on web security being disabled will not run as expected. @@ -180,13 +244,6 @@ This option will not have an effect in Firefox. Tests that rely on web security 1 passing -Warning: We failed processing this video. - -This error will not alter the exit code. - -TimeoutError: operation timed out - [stack trace lines] - (Results) @@ -197,12 +254,18 @@ TimeoutError: operation timed out │ Pending: 0 │ │ Skipped: 0 │ │ Screenshots: 0 │ - │ Video: false │ + │ Video: true │ │ Duration: X seconds │ │ Spec Ran: simple_passing_spec.js │ └────────────────────────────────────────────────────────────────────────────────────────────────┘ + (Video) + + - Started processing: Compressing to 32 CRF + - Finished processing: /XXX/XXX/XXX/cypress/videos/simple_passing_spec.js.mp4 (X second) + + ==================================================================================================== (Run Finished) From 6a0e00bcf20d4166cca79f4cf3223dcdb524a1eb Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 14:21:34 -0500 Subject: [PATCH 131/165] run each variant through an it(...) so multiple failures are received --- .../errors/test/unit/visualSnapshotErrors_spec.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 0a67d6a1235e..d5247069e9d7 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -140,14 +140,14 @@ afterEach(() => { }) const testVisualError = (errorGeneratorFn: () => ErrorGenerator, errorType: K) => { - it(errorType, async () => { - const variants = errorGeneratorFn() + const variants = errorGeneratorFn() - expect(variants).to.be.an('object') + expect(variants).to.be.an('object') - for (const [key, arr] of Object.entries(variants)) { - const filename = key === 'default' ? errorType : `${errorType} - ${key}` + for (const [key, arr] of Object.entries(variants)) { + const filename = key === 'default' ? errorType : `${errorType} - ${key}` + it(`${errorType} - ${key}`, async () => { debug(`Converting ${filename}`) terminalBanner(filename) @@ -179,8 +179,8 @@ const testVisualError = (errorGeneratorFn: () => Er debug(`Conversion complete for ${errorType}`) } - } - }).timeout(5000) + }).timeout(5000) + } } const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K in CypressErrorType]: () => ErrorGenerator}) => { From cd76bcf8aa62836aa06bd149194c62d22e619553 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 15:21:05 -0500 Subject: [PATCH 132/165] add newlines to multiline formatters, add fmt.stringify, allow format overrides --- packages/errors/src/errTemplate.ts | 39 +++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index 6659bf7e87ba..b9a17935db30 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -1,11 +1,11 @@ +import assert from 'assert' import chalk from 'chalk' import _ from 'lodash' +import stripAnsi from 'strip-ansi' import { trimMultipleNewLines } from './errorUtils' import { stripIndent } from './stripIndent' import type { ErrTemplateResult, SerializedError } from './errorTypes' -import assert from 'assert' -import stripAnsi from 'strip-ansi' interface ListOptions { prefix?: string @@ -29,31 +29,43 @@ export class PartialErr { } interface FormatConfig { - block: true + block?: true + color?: typeof theme[keyof typeof theme] + stringify?: boolean } type ToFormat = string | number | Error | object | null | Guard | AllowedTemplateArg class Format { - constructor (readonly type: keyof typeof fmtHighlight, readonly val: ToFormat, readonly config?: FormatConfig) {} + constructor ( + readonly type: keyof typeof fmtHighlight, + readonly val: ToFormat, + readonly config?: FormatConfig, + ) { + this.color = config.color || fmtHighlight[this.type] + } + + private color: typeof theme[keyof typeof theme] formatVal (target: 'ansi' | 'markdown'): string { if (this.val instanceof Guard) { return `${this.val.val}` } - return target === 'ansi' ? this.formatAnsi() : this.formatMarkdown() + const str = target === 'ansi' ? this.formatAnsi() : this.formatMarkdown() + + // add a newline to ensure this is on its own line + return isMultiLine(str) ? `\n\n${str}` : str } private formatAnsi () { const val = this.prepVal('ansi') - const color = fmtHighlight[this.type] if (this.type === 'terminal') { - return `${theme.gray('$')} ${color(val)}` + return `${theme.gray('$')} ${this.color(val)}` } - return fmtHighlight[this.type](val) + return this.color(val) } private formatMarkdown () { @@ -83,7 +95,7 @@ class Format { return `${this.val.name}: ${this.val.message}` } - if (this.val && (typeof this.val === 'object' || Array.isArray(this.val))) { + if (this.val && (this.config?.stringify || typeof this.val === 'object' || Array.isArray(this.val))) { return JSON.stringify(this.val, null, 2) } @@ -109,8 +121,11 @@ function isMultiLine (val: string) { } function makeFormat (type: keyof typeof fmtHighlight, config?: FormatConfig) { - return (val: ToFormat) => { - return new Format(type, val, config) + return (val: ToFormat, overrides?: FormatConfig) => { + return new Format(type, val, { + ...config, + ...overrides, + }) } } @@ -121,6 +136,7 @@ const fmtHighlight = { code: theme.blue, url: theme.blue, flag: theme.magenta, + stringify: theme.magenta, highlight: theme.yellow, highlightSecondary: theme.magenta, highlightTertiary: theme.blue, @@ -134,6 +150,7 @@ export const fmt = { code: makeFormat('code', { block: true }), url: makeFormat('url'), flag: makeFormat('flag'), + stringify: makeFormat('stringify', { stringify: true }), highlight: makeFormat('highlight'), highlightSecondary: makeFormat('highlightSecondary'), highlightTertiary: makeFormat('highlightTertiary'), From b32e91fac27ccfb614649f2a40cb7d5658db35d0 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 15:22:16 -0500 Subject: [PATCH 133/165] stringify invalid return values from plugins --- packages/errors/src/errors.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 6b7f83940752..f4044d3dcc71 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -582,7 +582,7 @@ export const AllCypressErrors = { If you have just renamed the extension of your supportFile, restart Cypress. - Learn more at https://on.cypress.io/support-file-missing-or-invalid` + https://on.cypress.io/support-file-missing-or-invalid` }, PLUGINS_FILE_ERROR: (pluginsFilePath: string, err: Error) => { return errTemplate`\ @@ -616,9 +616,9 @@ export const AllCypressErrors = { Instead it exported: - ${fmt.meta(JSON.stringify(exported, null, 2))} + ${fmt.stringify(exported)} - Learn more: https://on.cypress.io/plugins-api + https://on.cypress.io/plugins-api ` }, PLUGINS_FUNCTION_ERROR: (pluginsFilePath: string, err: Error) => { @@ -628,7 +628,6 @@ export const AllCypressErrors = { ${fmt.stackTrace(err)} ` }, - // TODO: use this for whimsical example PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` We stopped running your tests because a plugin crashed. @@ -661,7 +660,6 @@ export const AllCypressErrors = { ${fmt.stackTrace(err)} ` }, - // TODO: update the error message in the runner too BUNDLE_ERROR: (filePath: string, arg2: string) => { // IF YOU MODIFY THIS MAKE SURE TO UPDATE // THE ERROR MESSAGE IN THE RUNNER TOO From af1baf6c460c8dd42424ac5adbf570a2d0266e62 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 15:22:53 -0500 Subject: [PATCH 134/165] move config validation errors into packages/errors, properly highlight and stringify values --- packages/config/lib/validation.js | 13 ++--- .../DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html | 3 +- ...PLUGINS_DIDNT_EXPORT_FUNCTION - array.html | 10 ++-- ...LUGINS_DIDNT_EXPORT_FUNCTION - string.html | 4 +- .../PLUGINS_DIDNT_EXPORT_FUNCTION.html | 8 ++-- ...TINGS_VALIDATION_ERROR - invalidArray.html | 48 +++++++++++++++++++ ...INGS_VALIDATION_ERROR - invalidObject.html | 46 ++++++++++++++++++ ...INGS_VALIDATION_ERROR - invalidString.html | 42 ++++++++++++++++ .../SETTINGS_VALIDATION_ERROR.html | 4 +- .../SUPPORT_FILE_NOT_FOUND.html | 2 +- packages/errors/src/errorTypes.ts | 9 ++++ packages/errors/src/errors.ts | 12 +++-- .../test/unit/visualSnapshotErrors_spec.ts | 23 +++++++-- packages/server/lib/config.ts | 6 ++- 14 files changed, 197 insertions(+), 33 deletions(-) create mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidArray.html create mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidObject.html create mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidString.html diff --git a/packages/config/lib/validation.js b/packages/config/lib/validation.js index 37fb3f7f878d..20ebc424d0e9 100644 --- a/packages/config/lib/validation.js +++ b/packages/config/lib/validation.js @@ -11,17 +11,12 @@ const path = require('path') const str = JSON.stringify const { isArray, isString, isFinite: isNumber } = _ -/** - * Forms good Markdown-like string message. - * @param {string} key - The key that caused the error - * @param {string} type - The expected type name - * @param {any} value - The actual value - * @returns {string} Formatted error message -*/ const errMsg = (key, value, type) => { - return `Expected \`${key}\` to be ${type}. Instead the value was: \`${str( + return { + key, value, - )}\`` + type, + } } const isFullyQualifiedUrl = (value) => { diff --git a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html index d327093d0851..26cb0940387e 100644 --- a/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html +++ b/packages/errors/__snapshot-html__/DASHBOARD_UNKNOWN_CREATE_RUN_WARNING.html @@ -37,10 +37,11 @@
Warning from Cypress Dashboard: You are almost out of time
 
 Details:
+
 {
   "code": "OUT_OF_TIME",
   "name": "OutOfTime",
   "hadTime": 1000,
   "spentTime": 999
-}
+}
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html index 42cd1ed02efb..974547d95bc6 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html @@ -43,11 +43,11 @@ Instead it exported: -[ - "some", - "array" -] +[ + "some", + "array" +] -Learn more: https://on.cypress.io/plugins-api +https://on.cypress.io/plugins-api
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html index 08253d3bf8a1..60230d365267 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html @@ -43,8 +43,8 @@ Instead it exported: -"some string" +"some string" -Learn more: https://on.cypress.io/plugins-api +https://on.cypress.io/plugins-api \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html index b87a25d72fa7..a0965bd87962 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html @@ -43,10 +43,10 @@ Instead it exported: -{ - "some": "object" -} +{ + "some": "object" +} -Learn more: https://on.cypress.io/plugins-api +https://on.cypress.io/plugins-api \ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidArray.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidArray.html new file mode 100644 index 000000000000..10fb8a9d5683 --- /dev/null +++ b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidArray.html @@ -0,0 +1,48 @@ + + + + + + + + + + + +
We found an invalid value in the file: cypress.json
+
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: 
+
+[
+  1,
+  2,
+  3
+]
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidObject.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidObject.html new file mode 100644 index 000000000000..55315bfb76d9 --- /dev/null +++ b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidObject.html @@ -0,0 +1,46 @@ + + + + + + + + + + + +
We found an invalid value in the file: cypress.json
+
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: 
+
+{
+  "foo": "bar"
+}
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidString.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidString.html new file mode 100644 index 000000000000..b1052f175ddf --- /dev/null +++ b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidString.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
We found an invalid value in the file: cypress.json
+
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: "1234"
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html index dba440d0b223..f7926f2756eb 100644 --- a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html @@ -36,5 +36,7 @@
We found an invalid value in the file: cypress.json
 
-fail whale
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: false
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html index 603486e8418f..02c22756067e 100644 --- a/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html +++ b/packages/errors/__snapshot-html__/SUPPORT_FILE_NOT_FOUND.html @@ -42,5 +42,5 @@ If you have just renamed the extension of your supportFile, restart Cypress. -Learn more at https://on.cypress.io/support-file-missing-or-invalid +https://on.cypress.io/support-file-missing-or-invalid \ No newline at end of file diff --git a/packages/errors/src/errorTypes.ts b/packages/errors/src/errorTypes.ts index 143db3027719..524409271efa 100644 --- a/packages/errors/src/errorTypes.ts +++ b/packages/errors/src/errorTypes.ts @@ -1,5 +1,14 @@ import type { AllCypressErrors } from './errors' +/** + * A config validation result +*/ +export interface ConfigValidationError { + key: string + type: string + value: any +} + /** * An "error like" is an object which may or may-not be an error, * but contains at least a name & message, and probably a stack diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index f4044d3dcc71..e33e63abb56d 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -8,7 +8,7 @@ import { humanTime, logError, parseResolvedPattern, pluralize } from './errorUti import { errPartial, errTemplate, fmt, theme, PartialErr } from './errTemplate' import { stackWithoutMessage } from './stackUtils' -import type { ClonedError, CypressError, ErrorLike, ErrTemplateResult } from './errorTypes' +import type { ClonedError, ConfigValidationError, CypressError, ErrorLike, ErrTemplateResult } from './errorTypes' const ansi_up = new AU() @@ -680,13 +680,15 @@ export const AllCypressErrors = { Fix the error in your code and re-run your tests.` // happens when there is an error in configuration file like "cypress.json" }, - // TODO: should this be basename or absolute path? - // TODO: what should details be here? isn't it an error? - SETTINGS_VALIDATION_ERROR: (configFileBaseName: string, errMessage: string) => { + SETTINGS_VALIDATION_ERROR: (configFileBaseName: string, validationResult: ConfigValidationError) => { + const { key, type, value } = validationResult + return errTemplate`\ We found an invalid value in the file: ${fmt.path(configFileBaseName)} - ${fmt.highlight(errMessage)}` + Expected ${fmt.highlight(key)} to be ${fmt.off(type)}. + + Instead the value was: ${fmt.stringify(value)}` // happens when there is an invalid config value is returned from the // project's plugins file like "cypress/plugins.index.js" }, diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index d5247069e9d7..94255add6fa8 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -682,10 +682,27 @@ describe('visual error templates', () => { } }, SETTINGS_VALIDATION_ERROR: () => { - const err = makeErr() - return { - default: ['cypress.json', err.message], + default: ['cypress.json', { + key: 'defaultCommandTimeout', + type: 'a number', + value: false, + }], + invalidString: ['cypress.json', { + key: 'defaultCommandTimeout', + type: 'a number', + value: '1234', + }], + invalidObject: ['cypress.json', { + key: 'defaultCommandTimeout', + type: 'a number', + value: { foo: 'bar' }, + }], + invalidArray: ['cypress.json', { + key: 'defaultCommandTimeout', + type: 'a number', + value: [1, 2, 3], + }], } }, PLUGINS_CONFIG_VALIDATION_ERROR: () => { diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index c7a3b2304001..020ef0a3705b 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -13,6 +13,8 @@ import origin from './util/origin' import pathHelpers from './util/path_helpers' import * as settings from './util/settings' +import type { ConfigValidationError } from '@packages/errors' + const debug = Debug('cypress:server:config') export const RESOLVED_FROM = ['plugin', 'env', 'default', 'runtime', 'config'] as const @@ -45,8 +47,8 @@ const convertRelativeToAbsolutePaths = (projectRoot, obj) => { const validateFile = (file) => { return (settings) => { - return configUtils.validate(settings, (errMsg) => { - return errors.throw('SETTINGS_VALIDATION_ERROR', file, errMsg) + return configUtils.validate(settings, (validationResult: ConfigValidationError) => { + return errors.throw('SETTINGS_VALIDATION_ERROR', file, validationResult) }) } } From 16e19f36edc4876baa4840c38b2d2320875daeb3 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 15:23:07 -0500 Subject: [PATCH 135/165] add component testing yarn commands --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 78c78a3ec1cb..59a098165573 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,12 @@ "clean-untracked-files": "git clean -d -f", "precypress:open": "yarn ensure-deps", "cypress:open": "cypress open --dev --global", + "cypress:open:ct": "cypress open-ct --dev --global", "precypress:open:debug": "yarn ensure-deps", "cypress:open:debug": "node ./scripts/debug.js cypress:open", "precypress:run": "yarn ensure-deps", "cypress:run": "cypress run --dev", + "cypress:run:ct": "cypress run-ct --dev", "precypress:run:debug": "yarn ensure-deps", "cypress:run:debug": "node ./scripts/debug.js cypress:run", "cypress:verify": "cypress verify --dev", From 0bb1ea917c0648b9ff3c20752752e68a04e983b6 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 15:49:05 -0500 Subject: [PATCH 136/165] fix the arrow not showing on details --- packages/desktop-gui/src/project/project.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/desktop-gui/src/project/project.scss b/packages/desktop-gui/src/project/project.scss index 33b145d7e526..2f81c2fa2fb3 100644 --- a/packages/desktop-gui/src/project/project.scss +++ b/packages/desktop-gui/src/project/project.scss @@ -6,3 +6,7 @@ width: 100%; overflow: auto; } + +details.stacktrace summary { + display: list-item; +} From 60913531d265472dba11e53aad506832ff19274d Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 17:12:51 -0500 Subject: [PATCH 137/165] fix typescript error --- packages/errors/src/errTemplate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/errors/src/errTemplate.ts b/packages/errors/src/errTemplate.ts index b9a17935db30..30931a6bb8c1 100644 --- a/packages/errors/src/errTemplate.ts +++ b/packages/errors/src/errTemplate.ts @@ -40,7 +40,7 @@ class Format { constructor ( readonly type: keyof typeof fmtHighlight, readonly val: ToFormat, - readonly config?: FormatConfig, + readonly config: FormatConfig, ) { this.color = config.color || fmtHighlight[this.type] } From 05a39178ba855d2a9b2fa5f943121442e5f57006 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 18:44:15 -0500 Subject: [PATCH 138/165] fixed lots of poorly written tests that weren't actually testing anything. created settings validation error when given a string validation result. --- .../SETTINGS_VALIDATION_MSG_ERROR.html | 40 ++++++ packages/errors/src/errors.ts | 10 +- .../test/unit/visualSnapshotErrors_spec.ts | 5 + packages/server/lib/config.ts | 6 +- packages/server/test/unit/config_spec.js | 119 +++++++++++++----- 5 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_MSG_ERROR.html diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_MSG_ERROR.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_MSG_ERROR.html new file mode 100644 index 000000000000..25637ad28f1d --- /dev/null +++ b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_MSG_ERROR.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
We found an invalid value in the file: cypress.json
+
+`something` was not right
+
\ No newline at end of file diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index e33e63abb56d..5b4fa9e6dcde 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -680,6 +680,12 @@ export const AllCypressErrors = { Fix the error in your code and re-run your tests.` // happens when there is an error in configuration file like "cypress.json" }, + SETTINGS_VALIDATION_MSG_ERROR: (configFileBaseName: string, validationMsg: string) => { + return errTemplate`\ + We found an invalid value in the file: ${fmt.path(configFileBaseName)} + + ${fmt.highlight(validationMsg)}` + }, SETTINGS_VALIDATION_ERROR: (configFileBaseName: string, validationResult: ConfigValidationError) => { const { key, type, value } = validationResult @@ -689,9 +695,9 @@ export const AllCypressErrors = { Expected ${fmt.highlight(key)} to be ${fmt.off(type)}. Instead the value was: ${fmt.stringify(value)}` - // happens when there is an invalid config value is returned from the - // project's plugins file like "cypress/plugins.index.js" }, + // happens when there is an invalid config value is returned from the + // project's plugins file like "cypress/plugins.index.js" // TODO: should this be relative or absolute? PLUGINS_CONFIG_VALIDATION_ERROR: (relativePluginsPath: string, errMsg: string) => { return errTemplate`\ diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 94255add6fa8..de72c006aae8 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -705,6 +705,11 @@ describe('visual error templates', () => { }], } }, + SETTINGS_VALIDATION_MSG_ERROR: () => { + return { + default: ['cypress.json', '`something` was not right'], + } + }, PLUGINS_CONFIG_VALIDATION_ERROR: () => { const err = makeErr() diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts index 020ef0a3705b..823fbd625e95 100644 --- a/packages/server/lib/config.ts +++ b/packages/server/lib/config.ts @@ -47,7 +47,11 @@ const convertRelativeToAbsolutePaths = (projectRoot, obj) => { const validateFile = (file) => { return (settings) => { - return configUtils.validate(settings, (validationResult: ConfigValidationError) => { + return configUtils.validate(settings, (validationResult: ConfigValidationError | string) => { + if (_.isString(validationResult)) { + return errors.throw('SETTINGS_VALIDATION_MSG_ERROR', file, validationResult) + } + return errors.throw('SETTINGS_VALIDATION_ERROR', file, validationResult) }) } diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index a4e6f4f32028..a2c79f1fadaf 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -3,6 +3,7 @@ require('../spec_helper') const _ = require('lodash') const debug = require('debug')('test') const stripAnsi = require('strip-ansi') +const { stripIndent } = require('common-tags') const Fixtures = require('@tooling/system-tests/lib/fixtures') const config = require(`${root}lib/config`) @@ -133,7 +134,7 @@ describe('lib/config', () => { .then(() => { throw new Error('should throw validation error') }).catch((err) => { - expect(err.message).to.include(errorMessage) + expect(stripAnsi(err.message)).to.include(stripIndent`${errorMessage}`) }) } }) @@ -171,9 +172,17 @@ describe('lib/config', () => { it('fails if not a number', function () { this.setup({ animationDistanceThreshold: { foo: 'bar' } }) - this.expectValidationFails('be a number') - return this.expectValidationFails('the value was: \`{"foo":"bar"}\`') + return this.expectValidationFails('be a number') + .then(() => { + return this.expectValidationFails(` + the value was: \ + + + { + "foo": "bar" + }`) + }) }) }) @@ -212,9 +221,11 @@ describe('lib/config', () => { it('fails if not a boolean', function () { this.setup({ chromeWebSecurity: 42 }) - this.expectValidationFails('be a boolean') - return this.expectValidationFails('the value was: `42`') + return this.expectValidationFails('be a boolean') + .then(() => { + return this.expectValidationFails('the value was: 42') + }) }) }) @@ -227,9 +238,11 @@ describe('lib/config', () => { it('fails if not a boolean', function () { this.setup({ modifyObstructiveCode: 42 }) - this.expectValidationFails('be a boolean') - return this.expectValidationFails('the value was: `42`') + return this.expectValidationFails('be a boolean') + .then(() => { + return this.expectValidationFails('the value was: 42') + }) }) }) @@ -247,16 +260,20 @@ describe('lib/config', () => { it('fails if not a plain object', function () { this.setup({ component: false }) - this.expectValidationFails('to be a plain object') - return this.expectValidationFails('the value was: `false`') + return this.expectValidationFails('to be a plain object') + .then(() => { + return this.expectValidationFails('the value was: false') + }) }) it('fails if nested property is incorrect', function () { this.setup({ component: { baseUrl: false } }) - this.expectValidationFails('Expected `component.baseUrl` to be a fully qualified URL (starting with `http://` or `https://`).') - return this.expectValidationFails('the value was: `false`') + return this.expectValidationFails('Expected component.baseUrl to be a fully qualified URL (starting with `http://` or `https://`).') + .then(() => { + return this.expectValidationFails('the value was: false') + }) }) }) @@ -272,23 +289,29 @@ describe('lib/config', () => { it('fails if not a plain object', function () { this.setup({ e2e: false }) - this.expectValidationFails('to be a plain object') - return this.expectValidationFails('the value was: `false`') + return this.expectValidationFails('to be a plain object') + .then(() => { + return this.expectValidationFails('the value was: false') + }) }) it('fails if nested property is incorrect', function () { this.setup({ e2e: { animationDistanceThreshold: 'this is definitely not a number' } }) - this.expectValidationFails('Expected `e2e.animationDistanceThreshold` to be a number') - return this.expectValidationFails('the value was: `"this is definitely not a number"`') + return this.expectValidationFails('Expected e2e.animationDistanceThreshold to be a number') + .then(() => { + return this.expectValidationFails('the value was: "this is definitely not a number"') + }) }) it('fails if nested property is incorrect', function () { this.setup({ component: { baseUrl: false } }) - this.expectValidationFails('Expected `component.baseUrl` to be a fully qualified URL (starting with `http://` or `https://`).') - return this.expectValidationFails('the value was: `false`') + return this.expectValidationFails('Expected component.baseUrl to be a fully qualified URL (starting with `http://` or `https://`).') + .then(() => { + return this.expectValidationFails('the value was: false') + }) }) }) @@ -301,9 +324,11 @@ describe('lib/config', () => { it('fails if not a number', function () { this.setup({ defaultCommandTimeout: 'foo' }) - this.expectValidationFails('be a number') - return this.expectValidationFails('the value was: `"foo"`') + return this.expectValidationFails('be a number') + .then(() => { + return this.expectValidationFails('the value was: "foo"') + }) }) }) @@ -358,9 +383,11 @@ describe('lib/config', () => { it('fails if not a string', function () { this.setup({ fileServerFolder: true }) - this.expectValidationFails('be a string') - return this.expectValidationFails('the value was: `true`') + return this.expectValidationFails('be a string') + .then(() => { + return this.expectValidationFails('the value was: true') + }) }) }) @@ -405,9 +432,17 @@ describe('lib/config', () => { it('fails if not an array of strings', function () { this.setup({ ignoreTestFiles: [5] }) - this.expectValidationFails('be a string or an array of strings') - return this.expectValidationFails('the value was: `[5]`') + return this.expectValidationFails('be a string or an array of strings') + .then(() => { + return this.expectValidationFails(` + the value was: \ + + + [ + 5 + ]`) + }) }) }) @@ -578,9 +613,17 @@ describe('lib/config', () => { it('fails if not an array of strings', function () { this.setup({ testFiles: [5] }) - this.expectValidationFails('be a string or an array of strings') - return this.expectValidationFails('the value was: `[5]`') + return this.expectValidationFails('be a string or an array of strings') + .then(() => { + return this.expectValidationFails(` + the value was: \ + + + [ + 5 + ]`) + }) }) }) @@ -821,9 +864,17 @@ describe('lib/config', () => { it('fails if not an array of strings', function () { this.setup({ blockHosts: [5] }) - this.expectValidationFails('be a string or an array of strings') - return this.expectValidationFails('the value was: `[5]`') + return this.expectValidationFails('be a string or an array of strings') + .then(() => { + return this.expectValidationFails(` + the value was: \ + + + [ + 5 + ]`) + }) }) }) @@ -909,7 +960,7 @@ describe('lib/config', () => { cfg.clientCertificates[0].url = null this.setup(cfg) - return this.expectValidationFails('`clientCertificates[0].url` to be a URL matcher') + return this.expectValidationFails('clientCertificates[0].url to be a URL matcher.\n\nInstead the value was: null') }) it('detects invalid config with no certs', function () { @@ -918,7 +969,7 @@ describe('lib/config', () => { cfg.clientCertificates[0].certs = null this.setup(cfg) - return this.expectValidationFails('`clientCertificates[0].certs` to be an array of certs') + return this.expectValidationFails('clientCertificates[0].certs to be an array of certs.\n\nInstead the value was: null') }) it('detects invalid config with no cert', function () { @@ -945,7 +996,7 @@ describe('lib/config', () => { cfg.clientCertificates[0].certs[0].key = null this.setup(cfg) - return this.expectValidationFails('`clientCertificates[0].certs[0].key` to be a key filepath') + return this.expectValidationFails('clientCertificates[0].certs[0].key to be a key filepath.\n\nInstead the value was: null') }) it('detects PEM cert absolute path', function () { @@ -954,7 +1005,7 @@ describe('lib/config', () => { cfg.clientCertificates[0].certs[0].cert = '/home/files/a_file' this.setup(cfg) - return this.expectValidationFails('`clientCertificates[0].certs[0].cert` to be a relative filepath') + return this.expectValidationFails('clientCertificates[0].certs[0].cert to be a relative filepath.\n\nInstead the value was: "/home/files/a_file"') }) it('detects PEM key absolute path', function () { @@ -963,7 +1014,7 @@ describe('lib/config', () => { cfg.clientCertificates[0].certs[0].key = '/home/files/a_file' this.setup(cfg) - return this.expectValidationFails('`clientCertificates[0].certs[0].key` to be a relative filepath') + return this.expectValidationFails('clientCertificates[0].certs[0].key to be a relative filepath.\n\nInstead the value was: "/home/files/a_file"') }) it('detects PFX absolute path', function () { @@ -973,7 +1024,7 @@ describe('lib/config', () => { cfg.clientCertificates[0].certs[0].pfx = '/home/files/a_file' this.setup(cfg) - return this.expectValidationFails('`clientCertificates[0].certs[0].pfx` to be a relative filepath') + return this.expectValidationFails('clientCertificates[0].certs[0].pfx to be a relative filepath.\n\nInstead the value was: "/home/files/a_file"') }) it('detects CA absolute path', function () { @@ -982,7 +1033,7 @@ describe('lib/config', () => { cfg.clientCertificates[0].ca[0] = '/home/files/a_file' this.setup(cfg) - return this.expectValidationFails('`clientCertificates[0].ca[0]` to be a relative filepath') + return this.expectValidationFails('clientCertificates[0].ca[0] to be a relative filepath.\n\nInstead the value was: "/home/files/a_file"') }) }) }) From 3ecde0962280f445fa02b9a062dd486d5ce202b9 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 18:58:25 -0500 Subject: [PATCH 139/165] fixes tests --- packages/config/__snapshots__/index_spec.js | 2 +- packages/config/test/unit/index_spec.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/config/__snapshots__/index_spec.js b/packages/config/__snapshots__/index_spec.js index 0e8d783fced8..574dc54d5e11 100644 --- a/packages/config/__snapshots__/index_spec.js +++ b/packages/config/__snapshots__/index_spec.js @@ -34,7 +34,6 @@ exports['src/index .getDefaultValues returns list of public config keys 1'] = { "ignoreTestFiles": "*.hot-update.js", "includeShadowDom": false, "integrationFolder": "cypress/integration", - "isInteractive": true, "keystrokeDelay": 0, "modifyObstructiveCode": true, "numTestsKeptInMemory": 50, @@ -77,6 +76,7 @@ exports['src/index .getDefaultValues returns list of public config keys 1'] = { "configFile": "cypress.json", "devServerPublicPathRoute": "/__cypress/src", "hosts": null, + "isInteractive": true, "isTextTerminal": false, "morgan": true, "namespace": "__cypress", diff --git a/packages/config/test/unit/index_spec.js b/packages/config/test/unit/index_spec.js index 6fbd8b573b02..b255109a777c 100644 --- a/packages/config/test/unit/index_spec.js +++ b/packages/config/test/unit/index_spec.js @@ -104,7 +104,8 @@ describe('src/index', () => { 'baseUrl': ' ', }, errorFn) - expect(errorFn).to.have.been.calledWithMatch(/Expected `baseUrl`/) + expect(errorFn).to.have.been.calledWithMatch({ key: 'baseUrl' }) + expect(errorFn).to.have.been.calledWithMatch({ type: 'a fully qualified URL (starting with `http://` or `https://`)' }) }) }) From 11f47b2fd98de434a64aa416676352179ad124ff Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 20:40:02 -0500 Subject: [PATCH 140/165] fixes tests, adds new error template for validating within an array list (for browser validation) --- .../config/__snapshots__/validation_spec.js | 217 ++++++++++++------ packages/config/lib/validation.js | 8 +- .../SETTINGS_VALIDATION_ERROR - list.html | 50 ++++ packages/errors/src/errorTypes.ts | 1 + packages/errors/src/errors.ts | 15 +- .../test/unit/visualSnapshotErrors_spec.ts | 6 + 6 files changed, 218 insertions(+), 79 deletions(-) create mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - list.html diff --git a/packages/config/__snapshots__/validation_spec.js b/packages/config/__snapshots__/validation_spec.js index 1d3991be77d5..0f0171ed3ca9 100644 --- a/packages/config/__snapshots__/validation_spec.js +++ b/packages/config/__snapshots__/validation_spec.js @@ -6,9 +6,12 @@ exports['empty list of browsers'] = ` Expected at least one browser ` -exports['browsers list with a string'] = ` -Found an error while validating the \`browsers\` list. Expected \`name\` to be a non-empty string. Instead the value was: \`"foo"\` -` +exports['browsers list with a string'] = { + "key": "name", + "value": "foo", + "type": "a non-empty string", + "list": "browsers" +} exports['src/validation .isValidBrowser passes valid browsers and forms error messages for invalid ones isValidBrowser 1'] = { "name": "isValidBrowser", @@ -51,7 +54,14 @@ exports['src/validation .isValidBrowser passes valid browsers and forms error me "name": "No display name", "family": "chromium" }, - "expect": "Expected `displayName` to be a non-empty string. Instead the value was: `{\"name\":\"No display name\",\"family\":\"chromium\"}`" + "expect": { + "key": "displayName", + "value": { + "name": "No display name", + "family": "chromium" + }, + "type": "a non-empty string" + } }, { "given": { @@ -59,99 +69,158 @@ exports['src/validation .isValidBrowser passes valid browsers and forms error me "displayName": "Bad family browser", "family": "unknown family" }, - "expect": "Expected `family` to be either chromium or firefox. Instead the value was: `{\"name\":\"bad family\",\"displayName\":\"Bad family browser\",\"family\":\"unknown family\"}`" + "expect": { + "key": "family", + "value": { + "name": "bad family", + "displayName": "Bad family browser", + "family": "unknown family" + }, + "type": "either chromium or firefox" + } } ] } -exports['not one of the strings error message'] = ` -Expected \`test\` to be one of these values: "foo", "bar". Instead the value was: \`"nope"\` -` +exports['not one of the strings error message'] = { + "key": "test", + "value": "nope", + "type": "one of these values: \"foo\", \"bar\"" +} -exports['number instead of string'] = ` -Expected \`test\` to be one of these values: "foo", "bar". Instead the value was: \`42\` -` +exports['number instead of string'] = { + "key": "test", + "value": 42, + "type": "one of these values: \"foo\", \"bar\"" +} -exports['null instead of string'] = ` -Expected \`test\` to be one of these values: "foo", "bar". Instead the value was: \`null\` -` +exports['null instead of string'] = { + "key": "test", + "value": null, + "type": "one of these values: \"foo\", \"bar\"" +} -exports['not one of the numbers error message'] = ` -Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`4\` -` +exports['not one of the numbers error message'] = { + "key": "test", + "value": 4, + "type": "one of these values: 1, 2, 3" +} -exports['string instead of a number'] = ` -Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`"foo"\` -` +exports['string instead of a number'] = { + "key": "test", + "value": "foo", + "type": "one of these values: 1, 2, 3" +} -exports['null instead of a number'] = ` -Expected \`test\` to be one of these values: 1, 2, 3. Instead the value was: \`null\` -` +exports['null instead of a number'] = { + "key": "test", + "value": null, + "type": "one of these values: 1, 2, 3" +} -exports['src/validation .isStringOrFalse returns error message when value is neither string nor false 1'] = ` -Expected \`mockConfigKey\` to be a string or false. Instead the value was: \`null\` -` +exports['src/validation .isStringOrFalse returns error message when value is neither string nor false 1'] = { + "key": "mockConfigKey", + "value": null, + "type": "a string or false" +} -exports['src/validation .isBoolean returns error message when value is a not a string 1'] = ` -Expected \`mockConfigKey\` to be a string. Instead the value was: \`1\` -` +exports['src/validation .isBoolean returns error message when value is a not a string 1'] = { + "key": "mockConfigKey", + "value": 1, + "type": "a string" +} -exports['src/validation .isString returns error message when value is a not a string 1'] = ` -Expected \`mockConfigKey\` to be a string. Instead the value was: \`1\` -` +exports['src/validation .isString returns error message when value is a not a string 1'] = { + "key": "mockConfigKey", + "value": 1, + "type": "a string" +} -exports['src/validation .isArray returns error message when value is a non-array 1'] = ` -Expected \`mockConfigKey\` to be an array. Instead the value was: \`1\` -` +exports['src/validation .isArray returns error message when value is a non-array 1'] = { + "key": "mockConfigKey", + "value": 1, + "type": "an array" +} -exports['not string or array'] = ` -Expected \`mockConfigKey\` to be a string or an array of strings. Instead the value was: \`null\` -` +exports['not string or array'] = { + "key": "mockConfigKey", + "value": null, + "type": "a string or an array of strings" +} -exports['array of non-strings'] = ` -Expected \`mockConfigKey\` to be a string or an array of strings. Instead the value was: \`[1,2,3]\` -` +exports['array of non-strings'] = { + "key": "mockConfigKey", + "value": [ + 1, + 2, + 3 + ], + "type": "a string or an array of strings" +} -exports['src/validation .isNumberOrFalse returns error message when value is a not number or false 1'] = ` -Expected \`mockConfigKey\` to be a number or false. Instead the value was: \`null\` -` +exports['src/validation .isNumberOrFalse returns error message when value is a not number or false 1'] = { + "key": "mockConfigKey", + "value": null, + "type": "a number or false" +} -exports['src/validation .isPlainObject returns error message when value is a not an object 1'] = ` -Expected \`mockConfigKey\` to be a plain object. Instead the value was: \`1\` -` +exports['src/validation .isPlainObject returns error message when value is a not an object 1'] = { + "key": "mockConfigKey", + "value": 1, + "type": "a plain object" +} -exports['src/validation .isNumber returns error message when value is a not a number 1'] = ` -Expected \`mockConfigKey\` to be a number. Instead the value was: \`"string"\` -` +exports['src/validation .isNumber returns error message when value is a not a number 1'] = { + "key": "mockConfigKey", + "value": "string", + "type": "a number" +} -exports['invalid retry value'] = ` -Expected \`mockConfigKey\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` -` +exports['invalid retry value'] = { + "key": "mockConfigKey", + "value": "1", + "type": "a positive number or null or an object with keys \"openMode\" and \"runMode\" with values of numbers or nulls" +} -exports['invalid retry object'] = ` -Expected \`mockConfigKey\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`{"fakeMode":1}\` -` +exports['invalid retry object'] = { + "key": "mockConfigKey", + "value": { + "fakeMode": 1 + }, + "type": "a positive number or null or an object with keys \"openMode\" and \"runMode\" with values of numbers or nulls" +} -exports['src/validation .isValidClientCertificatesSet returns error message for certs not passed as an array array 1'] = ` -Expected \`mockConfigKey\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` -` +exports['src/validation .isValidClientCertificatesSet returns error message for certs not passed as an array array 1'] = { + "key": "mockConfigKey", + "value": "1", + "type": "a positive number or null or an object with keys \"openMode\" and \"runMode\" with values of numbers or nulls" +} -exports['src/validation .isValidClientCertificatesSet returns error message for certs object without url 1'] = ` -Expected \`clientCertificates[0].url\` to be a URL matcher. Instead the value was: \`undefined\` -` +exports['src/validation .isValidClientCertificatesSet returns error message for certs object without url 1'] = { + "key": "clientCertificates[0].url", + "type": "a URL matcher" +} -exports['missing https protocol'] = ` -Expected \`clientCertificates[0].url\` to be an https protocol. Instead the value was: \`"http://url.com"\` -` +exports['missing https protocol'] = { + "key": "clientCertificates[0].url", + "value": "http://url.com", + "type": "an https protocol" +} -exports['invalid url'] = ` -Expected \`clientCertificates[0].url\` to be a valid URL. Instead the value was: \`"not *"\` -` +exports['invalid url'] = { + "key": "clientCertificates[0].url", + "value": "not *", + "type": "a valid URL" +} -exports['not qualified url'] = ` -Expected \`mockConfigKey\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"url.com"\` -` +exports['not qualified url'] = { + "key": "mockConfigKey", + "value": "url.com", + "type": "a fully qualified URL (starting with `http://` or `https://`)" +} -exports['empty string'] = ` -Expected \`mockConfigKey\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\` -` \ No newline at end of file +exports['empty string'] = { + "key": "mockConfigKey", + "value": "", + "type": "a fully qualified URL (starting with `http://` or `https://`)" +} diff --git a/packages/config/lib/validation.js b/packages/config/lib/validation.js index 20ebc424d0e9..75c3ca6ed301 100644 --- a/packages/config/lib/validation.js +++ b/packages/config/lib/validation.js @@ -86,10 +86,12 @@ const isValidBrowserList = (key, browsers) => { } for (let k = 0; k < browsers.length; k += 1) { - const err = isValidBrowser(browsers[k]) + const validationResult = isValidBrowser(browsers[k]) - if (err !== true) { - return `Found an error while validating the \`browsers\` list. ${err}` + if (validationResult !== true) { + validationResult.list = 'browsers' + + return validationResult } } diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - list.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - list.html new file mode 100644 index 000000000000..c43fa56ed3fa --- /dev/null +++ b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - list.html @@ -0,0 +1,50 @@ + + + + + + + + + + + +
We found an invalid value in the file: cypress.json
+
+Found an error while validating the browsers list.
+
+Expected displayName to be a non-empty string.
+
+Instead the value was: 
+
+{
+  "name": "chrome",
+  "version": "1.2.3",
+  "displayName": null
+}
+
\ No newline at end of file diff --git a/packages/errors/src/errorTypes.ts b/packages/errors/src/errorTypes.ts index 524409271efa..5f88a5906f73 100644 --- a/packages/errors/src/errorTypes.ts +++ b/packages/errors/src/errorTypes.ts @@ -7,6 +7,7 @@ export interface ConfigValidationError { key: string type: string value: any + list?: string } /** diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 5b4fa9e6dcde..f15d3258ebb1 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -687,14 +687,25 @@ export const AllCypressErrors = { ${fmt.highlight(validationMsg)}` }, SETTINGS_VALIDATION_ERROR: (configFileBaseName: string, validationResult: ConfigValidationError) => { - const { key, type, value } = validationResult + const { key, type, value, list } = validationResult - return errTemplate`\ + if (list) { + return errTemplate`\ We found an invalid value in the file: ${fmt.path(configFileBaseName)} + Found an error while validating the ${fmt.highlightSecondary(list)} list. + Expected ${fmt.highlight(key)} to be ${fmt.off(type)}. Instead the value was: ${fmt.stringify(value)}` + } + + return errTemplate`\ + We found an invalid value in the file: ${fmt.path(configFileBaseName)} + + Expected ${fmt.highlight(key)} to be ${fmt.off(type)}. + + Instead the value was: ${fmt.stringify(value)}` }, // happens when there is an invalid config value is returned from the // project's plugins file like "cypress/plugins.index.js" diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index de72c006aae8..39cac4790afe 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -688,6 +688,12 @@ describe('visual error templates', () => { type: 'a number', value: false, }], + list: ['cypress.json', { + key: 'displayName', + type: 'a non-empty string', + value: { name: 'chrome', version: '1.2.3', displayName: null }, + list: 'browsers', + }], invalidString: ['cypress.json', { key: 'defaultCommandTimeout', type: 'a number', From 114dd029ebf3ea712e039f982be1e14766906343 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 20:48:07 -0500 Subject: [PATCH 141/165] remove dupe line --- packages/errors/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/errors/package.json b/packages/errors/package.json index 0375c897a2f9..38d492e2ccce 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -7,7 +7,6 @@ "scripts": { "test": "yarn test-unit", "comparison": "node -r @packages/ts/register test/support/error-comparison-tool.ts", - "comparison-debug": "node -r @packages/ts/register test/support/error-comparison-tool.ts", "build": "../../scripts/run-if-ci.sh tsc || echo 'type errors'", "build-prod": "tsc", "check-ts": "tsc --noEmit", From 3268eabad1617924fec3c078d3210a7cab07f79e Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Wed, 9 Feb 2022 20:58:25 -0500 Subject: [PATCH 142/165] fix copy for consistency, update snapshots --- .../PLUGINS_CONFIG_VALIDATION_ERROR.html | 2 +- .../PLUGINS_DIDNT_EXPORT_FUNCTION - array.html | 2 +- .../PLUGINS_DIDNT_EXPORT_FUNCTION - string.html | 2 +- .../PLUGINS_DIDNT_EXPORT_FUNCTION.html | 2 +- .../__snapshot-html__/PLUGINS_FUNCTION_ERROR.html | 2 +- packages/errors/src/errors.ts | 6 +++--- packages/server/test/unit/plugins/index_spec.js | 2 +- system-tests/__snapshots__/plugins_spec.js | 12 ++++++------ 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html index 46908136c8bf..f4cc4a1ee9f7 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html @@ -34,7 +34,7 @@ -
An invalid configuration value returned from the plugins file: /path/to/pluginsFile
+    
An invalid configuration value was returned from the plugins file: /path/to/pluginsFile
 
 fail whale
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html index 974547d95bc6..01d50e8c799f 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html @@ -34,7 +34,7 @@ -
The pluginsFile must export a function with the following signature:
+    
Your pluginsFile must export a function with the following signature:
 
 // /path/to/pluginsFile
 module.exports = (on, config) => {
diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html
index 60230d365267..21e9a44c12bc 100644
--- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html	
+++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html	
@@ -34,7 +34,7 @@
     
   
     
-    
The pluginsFile must export a function with the following signature:
+    
Your pluginsFile must export a function with the following signature:
 
 // /path/to/pluginsFile
 module.exports = (on, config) => {
diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html
index a0965bd87962..39f2f8a91b25 100644
--- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html
+++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html
@@ -34,7 +34,7 @@
     
   
     
-    
The pluginsFile must export a function with the following signature:
+    
Your pluginsFile must export a function with the following signature:
 
 // /path/to/pluginsFile
 module.exports = (on, config) => {
diff --git a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html
index d26f805862f6..64e125157456 100644
--- a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html
+++ b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html
@@ -34,7 +34,7 @@
     
   
     
-    
The function exported by the pluginsFile threw an error: /path/to/pluginsFile
+    
The function exported by your pluginsFile threw an error: /path/to/pluginsFile
 
 Error: fail whale
     at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts
index f15d3258ebb1..025576f8f757 100644
--- a/packages/errors/src/errors.ts
+++ b/packages/errors/src/errors.ts
@@ -610,7 +610,7 @@ export const AllCypressErrors = {
       }`
 
     return errTemplate`\
-      The ${fmt.highlight(`pluginsFile`)} must export a function with the following signature:
+      Your ${fmt.highlight(`pluginsFile`)} must export a function with the following signature:
 
       ${fmt.code(code)}
 
@@ -623,7 +623,7 @@ export const AllCypressErrors = {
   },
   PLUGINS_FUNCTION_ERROR: (pluginsFilePath: string, err: Error) => {
     return errTemplate`\
-      The function exported by the ${fmt.highlight(`pluginsFile`)} threw an error: ${fmt.path(pluginsFilePath)}
+      The function exported by your ${fmt.highlight(`pluginsFile`)} threw an error: ${fmt.path(pluginsFilePath)}
 
       ${fmt.stackTrace(err)}
     `
@@ -712,7 +712,7 @@ export const AllCypressErrors = {
   // TODO: should this be relative or absolute?
   PLUGINS_CONFIG_VALIDATION_ERROR: (relativePluginsPath: string, errMsg: string) => {
     return errTemplate`\
-        An invalid configuration value returned from the plugins file: ${fmt.path(relativePluginsPath)}
+        An invalid configuration value was returned from the plugins file: ${fmt.path(relativePluginsPath)}
 
         ${fmt.highlight(errMsg)}`
     // general configuration error not-specific to configuration or plugins files
diff --git a/packages/server/test/unit/plugins/index_spec.js b/packages/server/test/unit/plugins/index_spec.js
index 7c423d928bbd..d7817634666e 100644
--- a/packages/server/test/unit/plugins/index_spec.js
+++ b/packages/server/test/unit/plugins/index_spec.js
@@ -207,7 +207,7 @@ describe('lib/plugins/index', () => {
         it('rejects plugins.init', () => {
           return plugins.init({ pluginsFile: 'cypress-plugin' }, getOptions())
           .catch((err) => {
-            expect(stripAnsi(err.message)).to.contain('The function exported by the pluginsFile threw an error')
+            expect(stripAnsi(err.message)).to.contain('The function exported by your pluginsFile threw an error')
             expect(err.message).to.contain('path/to/pluginsFile.js')
 
             expect(err.details).to.contain('error message stack')
diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js
index e59375c77b5b..90c493364d77 100644
--- a/system-tests/__snapshots__/plugins_spec.js
+++ b/system-tests/__snapshots__/plugins_spec.js
@@ -119,7 +119,7 @@ exports['e2e plugins can modify config from plugins 1'] = `
 `
 
 exports['e2e plugins catches invalid browsers list returned from plugins 1'] = `
-An invalid configuration value returned from the plugins file: cypress/plugins/index.js
+An invalid configuration value was returned from the plugins file: cypress/plugins/index.js
 
 Expected at least one browser
 
@@ -408,7 +408,7 @@ Error: Async error from plugins file
 `
 
 exports['e2e plugins fails when there is no function exported 1'] = `
-The pluginsFile must export a function with the following signature:
+Your pluginsFile must export a function with the following signature:
 
 // /foo/bar/.projects/plugin-no-function-return/cypress/plugins/index.js
 module.exports = (on, config) => {
@@ -422,7 +422,7 @@ Instead it exported:
   "bar": "bar"
 }
 
-Learn more: https://on.cypress.io/plugins-api
+https://on.cypress.io/plugins-api
 
 
 `
@@ -518,7 +518,7 @@ exports['e2e plugins does not report more screenshots than exist if user overwri
 `
 
 exports['e2e plugins fails when there is nothing exported 1'] = `
-The pluginsFile must export a function with the following signature:
+Your pluginsFile must export a function with the following signature:
 
 // /foo/bar/.projects/plugin-empty/cypress/plugins/index.js
 module.exports = (on, config) => {
@@ -529,7 +529,7 @@ Instead it exported:
 
 {}
 
-Learn more: https://on.cypress.io/plugins-api
+https://on.cypress.io/plugins-api
 
 
 `
@@ -554,7 +554,7 @@ RootSyncError: Root sync error from plugins file
 `
 
 exports['e2e plugins fails when function throws synchronously 1'] = `
-The function exported by the pluginsFile threw an error: /foo/bar/.projects/plugins-function-sync-error/cypress/plugins/index.js
+The function exported by your pluginsFile threw an error: /foo/bar/.projects/plugins-function-sync-error/cypress/plugins/index.js
 
 FunctionSyncError: Function sync error from plugins file
       [stack trace lines]

From 7d4292b48abf6f3de05bf1989e89e1a8bb3adce4 Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Thu, 10 Feb 2022 03:53:16 -0500
Subject: [PATCH 143/165] remove redundant errors, standardize formatting and
 phrasing

---
 packages/errors/src/errors.ts                 | 41 ++++++++-----------
 packages/server/lib/config.ts                 | 24 +++++++----
 .../server/lib/plugins/child/run_plugins.js   |  2 +-
 3 files changed, 32 insertions(+), 35 deletions(-)

diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts
index 025576f8f757..0dbdfc36c79a 100644
--- a/packages/errors/src/errors.ts
+++ b/packages/errors/src/errors.ts
@@ -637,7 +637,8 @@ export const AllCypressErrors = {
       ${fmt.stackTrace(arg2)}
     `
   },
-  PLUGINS_VALIDATION_ERROR: (arg1: string, arg2: string | Error) => {
+  // TODO: STRING | ERROR?
+  PLUGINS_EVENT_ERROR: (arg1: string, arg2: string | Error) => {
     return errTemplate`
       Your ${fmt.highlight(`pluginsFile`)} threw a validation error: ${fmt.path(arg1)}
 
@@ -680,20 +681,27 @@ export const AllCypressErrors = {
       Fix the error in your code and re-run your tests.`
     // happens when there is an error in configuration file like "cypress.json"
   },
-  SETTINGS_VALIDATION_MSG_ERROR: (configFileBaseName: string, validationMsg: string) => {
-    return errTemplate`\
-      We found an invalid value in the file: ${fmt.path(configFileBaseName)}
+  CONFIG_VALIDATION_MSG_ERROR: (fileType: 'configFile' | 'pluginsFile' | null, fileName: string | null, validationMsg: string) => {
+    if (fileType) {
+      return errTemplate`
+        Your ${fmt.highlight(fileType)} set an invalid value: ${fmt.path(fileName)}
+
+        ${fmt.highlight(validationMsg)}`
+    }
+
+    return errTemplate`
+      An invalid configuration value was set:
 
       ${fmt.highlight(validationMsg)}`
   },
-  SETTINGS_VALIDATION_ERROR: (configFileBaseName: string, validationResult: ConfigValidationError) => {
+  CONFIG_VALIDATION_ERROR: (fileType: 'configFile' | 'pluginsFile' | null, filePath: string | null, validationResult: ConfigValidationError) => {
     const { key, type, value, list } = validationResult
 
     if (list) {
       return errTemplate`\
-        We found an invalid value in the file: ${fmt.path(configFileBaseName)}
+        Your ${fmt.highlight(fileType)} set an invalid value: ${fmt.path(filePath)}
 
-        Found an error while validating the ${fmt.highlightSecondary(list)} list.
+        The error occurred while validating the ${fmt.highlightSecondary(list)} list.
 
         Expected ${fmt.highlight(key)} to be ${fmt.off(type)}.
 
@@ -701,29 +709,12 @@ export const AllCypressErrors = {
     }
 
     return errTemplate`\
-      We found an invalid value in the file: ${fmt.path(configFileBaseName)}
+      Your ${fmt.highlight(fileType)} set an invalid value: ${fmt.path(filePath)}
 
       Expected ${fmt.highlight(key)} to be ${fmt.off(type)}.
 
       Instead the value was: ${fmt.stringify(value)}`
   },
-  // happens when there is an invalid config value is returned from the
-  // project's plugins file like "cypress/plugins.index.js"
-  // TODO: should this be relative or absolute?
-  PLUGINS_CONFIG_VALIDATION_ERROR: (relativePluginsPath: string, errMsg: string) => {
-    return errTemplate`\
-        An invalid configuration value was returned from the plugins file: ${fmt.path(relativePluginsPath)}
-
-        ${fmt.highlight(errMsg)}`
-    // general configuration error not-specific to configuration or plugins files
-  },
-  // TODO: test this
-  CONFIG_VALIDATION_ERROR: (errMsg: string) => {
-    return errTemplate`\
-        We found an invalid configuration value:
-
-        ${fmt.highlight(errMsg)}`
-  },
   RENAMED_CONFIG_OPTION: (arg1: {name: string, newName: string}) => {
     return errTemplate`\
         The ${fmt.highlight(arg1.name)} configuration option you have supplied has been renamed.
diff --git a/packages/server/lib/config.ts b/packages/server/lib/config.ts
index 823fbd625e95..3b68d5975f5f 100644
--- a/packages/server/lib/config.ts
+++ b/packages/server/lib/config.ts
@@ -49,10 +49,10 @@ const validateFile = (file) => {
   return (settings) => {
     return configUtils.validate(settings, (validationResult: ConfigValidationError | string) => {
       if (_.isString(validationResult)) {
-        return errors.throw('SETTINGS_VALIDATION_MSG_ERROR', file, validationResult)
+        return errors.throw('CONFIG_VALIDATION_MSG_ERROR', 'configFile', file, validationResult)
       }
 
-      return errors.throw('SETTINGS_VALIDATION_ERROR', file, validationResult)
+      return errors.throw('CONFIG_VALIDATION_ERROR', 'configFile', file, validationResult)
     })
   }
 }
@@ -237,8 +237,13 @@ export function mergeDefaults (config: Record = {}, options: Record
 
   // validate config again here so that we catch configuration errors coming
   // from the CLI overrides or env var overrides
-  configUtils.validate(_.omit(config, 'browsers'), (errMsg) => {
-    return errors.throw('CONFIG_VALIDATION_ERROR', errMsg)
+  configUtils.validate(_.omit(config, 'browsers'), (validationResult: ConfigValidationError | string) => {
+    // return errors.throw('CONFIG_VALIDATION_ERROR', errMsg)
+    if (_.isString(validationResult)) {
+      return errors.throw('CONFIG_VALIDATION_MSG_ERROR', null, null, validationResult)
+    }
+
+    return errors.throw('CONFIG_VALIDATION_ERROR', null, null, validationResult)
   })
 
   configUtils.validateNoBreakingConfig(config, errors.warning, errors.throw)
@@ -286,14 +291,15 @@ export function updateWithPluginValues (cfg, overrides) {
 
   // make sure every option returned from the plugins file
   // passes our validation functions
-  configUtils.validate(overrides, (errMsg) => {
-    if (cfg.pluginsFile && cfg.projectRoot) {
-      const relativePluginsPath = path.relative(cfg.projectRoot, cfg.pluginsFile)
+  configUtils.validate(overrides, (validationResult: ConfigValidationError | string) => {
+    const relativePluginsPath = path.relative(cfg.projectRoot, cfg.pluginsFile)
 
-      return errors.throw('PLUGINS_CONFIG_VALIDATION_ERROR', relativePluginsPath, errMsg)
+    if (_.isString(validationResult)) {
+      return errors.throw('CONFIG_VALIDATION_MSG_ERROR', 'pluginsFile', relativePluginsPath, validationResult)
     }
 
-    return errors.throw('CONFIG_VALIDATION_ERROR', errMsg)
+    return errors.throw('CONFIG_VALIDATION_ERROR', 'pluginsFile', relativePluginsPath, validationResult)
+    // return errors.throw('CONFIG_VALIDATION_ERROR', 'pluginsFile', relativePluginsPath, errMsg)
   })
 
   let originalResolvedBrowsers = cfg && cfg.resolved && cfg.resolved.browsers && _.cloneDeep(cfg.resolved.browsers)
diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js
index 73a40fbf8be4..24bdc6c7801e 100644
--- a/packages/server/lib/plugins/child/run_plugins.js
+++ b/packages/server/lib/plugins/child/run_plugins.js
@@ -52,7 +52,7 @@ const load = (ipc, config, pluginsFile) => {
       if (userEvents) {
         ipc.send('load:error', 'PLUGINS_INVALID_EVENT_ERROR', pluginsFile, event, userEvents, util.serializeError(error))
       } else {
-        ipc.send('load:error', 'PLUGINS_VALIDATION_ERROR', pluginsFile, util.serializeError(error))
+        ipc.send('load:error', 'PLUGINS_EVENT_ERROR', pluginsFile, util.serializeError(error))
       }
 
       return

From b748c1f1b2fdd27a1411125ca0e7407584275ad1 Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Thu, 10 Feb 2022 04:05:35 -0500
Subject: [PATCH 144/165] more formatting

---
 ...ONFIG_VALIDATION_ERROR - invalidArray.html | 48 ++++++++++++++++++
 ...NFIG_VALIDATION_ERROR - invalidObject.html | 46 +++++++++++++++++
 ...NFIG_VALIDATION_ERROR - invalidString.html | 42 ++++++++++++++++
 .../CONFIG_VALIDATION_ERROR - list.html       | 50 +++++++++++++++++++
 ...CONFIG_VALIDATION_ERROR - pluginsFile.html | 42 ++++++++++++++++
 .../CONFIG_VALIDATION_ERROR.html              |  6 ++-
 .../CONFIG_VALIDATION_MSG_ERROR.html          | 40 +++++++++++++++
 .../PLUGINS_EVENT_ERROR.html                  | 42 ++++++++++++++++
 packages/errors/src/errors.ts                 |  8 +--
 .../test/unit/visualSnapshotErrors_spec.ts    | 37 ++++++--------
 10 files changed, 332 insertions(+), 29 deletions(-)
 create mode 100644 packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidArray.html
 create mode 100644 packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidObject.html
 create mode 100644 packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidString.html
 create mode 100644 packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - list.html
 create mode 100644 packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - pluginsFile.html
 create mode 100644 packages/errors/__snapshot-html__/CONFIG_VALIDATION_MSG_ERROR.html
 create mode 100644 packages/errors/__snapshot-html__/PLUGINS_EVENT_ERROR.html

diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidArray.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidArray.html
new file mode 100644
index 000000000000..169f36f643e6
--- /dev/null
+++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidArray.html	
@@ -0,0 +1,48 @@
+
+    
+    
+      
+      
+    
+    
+    
+    
+  
+    
+    
Your configFile set an invalid value from: cypress.json
+
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: 
+
+[
+  1,
+  2,
+  3
+]
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidObject.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidObject.html new file mode 100644 index 000000000000..33c5f7b1090b --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidObject.html @@ -0,0 +1,46 @@ + + + + + + + + + + + +
Your configFile set an invalid value from: cypress.json
+
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: 
+
+{
+  "foo": "bar"
+}
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidString.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidString.html new file mode 100644 index 000000000000..95e4944872bb --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - invalidString.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Your configFile set an invalid value from: cypress.json
+
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: "1234"
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - list.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - list.html new file mode 100644 index 000000000000..ceac95500d82 --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - list.html @@ -0,0 +1,50 @@ + + + + + + + + + + + +
Your configFile set an invalid value from: cypress.json
+
+The error occurred while validating the browsers list.
+
+Expected displayName to be a non-empty string.
+
+Instead the value was: 
+
+{
+  "name": "chrome",
+  "version": "1.2.3",
+  "displayName": null
+}
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - pluginsFile.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - pluginsFile.html new file mode 100644 index 000000000000..887f4f86edda --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - pluginsFile.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Your pluginsFile set an invalid value from: cypress/plugins/index.js
+
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: false
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html index 1d210f776571..f56a76deabc2 100644 --- a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR.html @@ -34,7 +34,9 @@ -
We found an invalid configuration value:
+    
Your configFile set an invalid value from: cypress.json
 
-fail whale
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: false
 
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_MSG_ERROR.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_MSG_ERROR.html new file mode 100644 index 000000000000..fd8bbffb06ff --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_MSG_ERROR.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
Your configFile set an invalid value from: cypress.json
+
+`something` was not right
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_EVENT_ERROR.html new file mode 100644 index 000000000000..7b27ab03eb76 --- /dev/null +++ b/packages/errors/__snapshot-html__/PLUGINS_EVENT_ERROR.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
Your pluginsFile threw a validation error from: /path/to/pluginsFile
+
+Error: fail whale
+    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+
\ No newline at end of file diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 0dbdfc36c79a..fd7ae20c680a 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -640,7 +640,7 @@ export const AllCypressErrors = { // TODO: STRING | ERROR? PLUGINS_EVENT_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` - Your ${fmt.highlight(`pluginsFile`)} threw a validation error: ${fmt.path(arg1)} + Your ${fmt.highlight(`pluginsFile`)} threw a validation error from: ${fmt.path(arg1)} ${fmt.stackTrace(arg2)} ` @@ -684,7 +684,7 @@ export const AllCypressErrors = { CONFIG_VALIDATION_MSG_ERROR: (fileType: 'configFile' | 'pluginsFile' | null, fileName: string | null, validationMsg: string) => { if (fileType) { return errTemplate` - Your ${fmt.highlight(fileType)} set an invalid value: ${fmt.path(fileName)} + Your ${fmt.highlight(fileType)} set an invalid value from: ${fmt.path(fileName)} ${fmt.highlight(validationMsg)}` } @@ -699,7 +699,7 @@ export const AllCypressErrors = { if (list) { return errTemplate`\ - Your ${fmt.highlight(fileType)} set an invalid value: ${fmt.path(filePath)} + Your ${fmt.highlight(fileType)} set an invalid value from: ${fmt.path(filePath)} The error occurred while validating the ${fmt.highlightSecondary(list)} list. @@ -709,7 +709,7 @@ export const AllCypressErrors = { } return errTemplate`\ - Your ${fmt.highlight(fileType)} set an invalid value: ${fmt.path(filePath)} + Your ${fmt.highlight(fileType)} set an invalid value from: ${fmt.path(filePath)} Expected ${fmt.highlight(key)} to be ${fmt.off(type)}. diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 39cac4790afe..0b3c39caaa21 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -655,7 +655,7 @@ describe('visual error templates', () => { default: ['/path/to/pluginsFile', err], } }, - PLUGINS_VALIDATION_ERROR: () => { + PLUGINS_EVENT_ERROR: () => { const err = makeErr() return { @@ -681,53 +681,44 @@ describe('visual error templates', () => { default: ['/path/to/file', err.message], } }, - SETTINGS_VALIDATION_ERROR: () => { + CONFIG_VALIDATION_ERROR: () => { return { - default: ['cypress.json', { + default: ['configFile', 'cypress.json', { key: 'defaultCommandTimeout', type: 'a number', value: false, }], - list: ['cypress.json', { + list: ['configFile', 'cypress.json', { key: 'displayName', type: 'a non-empty string', value: { name: 'chrome', version: '1.2.3', displayName: null }, list: 'browsers', }], - invalidString: ['cypress.json', { + invalidString: ['configFile', 'cypress.json', { key: 'defaultCommandTimeout', type: 'a number', value: '1234', }], - invalidObject: ['cypress.json', { + invalidObject: ['configFile', 'cypress.json', { key: 'defaultCommandTimeout', type: 'a number', value: { foo: 'bar' }, }], - invalidArray: ['cypress.json', { + invalidArray: ['configFile', 'cypress.json', { key: 'defaultCommandTimeout', type: 'a number', value: [1, 2, 3], }], + pluginsFile: ['pluginsFile', 'cypress/plugins/index.js', { + key: 'defaultCommandTimeout', + type: 'a number', + value: false, + }], } }, - SETTINGS_VALIDATION_MSG_ERROR: () => { - return { - default: ['cypress.json', '`something` was not right'], - } - }, - PLUGINS_CONFIG_VALIDATION_ERROR: () => { - const err = makeErr() - - return { - default: ['/path/to/pluginsFile', err.message], - } - }, - CONFIG_VALIDATION_ERROR: () => { - const err = makeErr() - + CONFIG_VALIDATION_MSG_ERROR: () => { return { - default: [err.message], + default: ['configFile', 'cypress.json', '`something` was not right'], } }, RENAMED_CONFIG_OPTION: () => { From 04a93895bafc84ca61e343a9ae964c3c2ba919ac Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 04:15:11 -0500 Subject: [PATCH 145/165] remove excess snapshots --- .../PLUGINS_CONFIG_VALIDATION_ERROR.html | 40 --------------- .../PLUGINS_VALIDATION_ERROR.html | 42 ---------------- ...TINGS_VALIDATION_ERROR - invalidArray.html | 48 ------------------ ...INGS_VALIDATION_ERROR - invalidObject.html | 46 ----------------- ...INGS_VALIDATION_ERROR - invalidString.html | 42 ---------------- .../SETTINGS_VALIDATION_ERROR - list.html | 50 ------------------- .../SETTINGS_VALIDATION_ERROR.html | 42 ---------------- .../SETTINGS_VALIDATION_MSG_ERROR.html | 40 --------------- 8 files changed, 350 deletions(-) delete mode 100644 packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html delete mode 100644 packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html delete mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidArray.html delete mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidObject.html delete mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidString.html delete mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - list.html delete mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html delete mode 100644 packages/errors/__snapshot-html__/SETTINGS_VALIDATION_MSG_ERROR.html diff --git a/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html deleted file mode 100644 index f4cc4a1ee9f7..000000000000 --- a/packages/errors/__snapshot-html__/PLUGINS_CONFIG_VALIDATION_ERROR.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - -
An invalid configuration value was returned from the plugins file: /path/to/pluginsFile
-
-fail whale
-
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html deleted file mode 100644 index 899416622d58..000000000000 --- a/packages/errors/__snapshot-html__/PLUGINS_VALIDATION_ERROR.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - -
Your pluginsFile threw a validation error: /path/to/pluginsFile
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_VALIDATION_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidArray.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidArray.html deleted file mode 100644 index 10fb8a9d5683..000000000000 --- a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidArray.html +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - -
We found an invalid value in the file: cypress.json
-
-Expected defaultCommandTimeout to be a number.
-
-Instead the value was: 
-
-[
-  1,
-  2,
-  3
-]
-
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidObject.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidObject.html deleted file mode 100644 index 55315bfb76d9..000000000000 --- a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidObject.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - -
We found an invalid value in the file: cypress.json
-
-Expected defaultCommandTimeout to be a number.
-
-Instead the value was: 
-
-{
-  "foo": "bar"
-}
-
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidString.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidString.html deleted file mode 100644 index b1052f175ddf..000000000000 --- a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - invalidString.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - -
We found an invalid value in the file: cypress.json
-
-Expected defaultCommandTimeout to be a number.
-
-Instead the value was: "1234"
-
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - list.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - list.html deleted file mode 100644 index c43fa56ed3fa..000000000000 --- a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR - list.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - -
We found an invalid value in the file: cypress.json
-
-Found an error while validating the browsers list.
-
-Expected displayName to be a non-empty string.
-
-Instead the value was: 
-
-{
-  "name": "chrome",
-  "version": "1.2.3",
-  "displayName": null
-}
-
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html deleted file mode 100644 index f7926f2756eb..000000000000 --- a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_ERROR.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - -
We found an invalid value in the file: cypress.json
-
-Expected defaultCommandTimeout to be a number.
-
-Instead the value was: false
-
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_MSG_ERROR.html b/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_MSG_ERROR.html deleted file mode 100644 index 25637ad28f1d..000000000000 --- a/packages/errors/__snapshot-html__/SETTINGS_VALIDATION_MSG_ERROR.html +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - -
We found an invalid value in the file: cypress.json
-
-`something` was not right
-
\ No newline at end of file From 05f910319afa6f1e52379c05597dbfbe635cf676 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 04:24:21 -0500 Subject: [PATCH 146/165] prune out excessive error snapshot html files when not in CI --- .../test/unit/visualSnapshotErrors_spec.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 0b3c39caaa21..c1db78dc9e9e 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -216,10 +216,31 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K // return fse.remove(pathToHtml) // })) - const errorKeys = _.keys(errors.AllCypressErrors) - - expect(uniqErrors).to.have.all.members(errorKeys) - expect(errorKeys).to.have.all.members(uniqErrors) + expect(uniqErrors.sort()).to.deep.eq(errorKeys) + expect(errorKeys.sort()).to.deep.eq(uniqErrors) + } else { + const errorFiles = files.map((file) => { + return { + errorType: path.basename(file, '.html').split(' ')[0], + filePath: file, + } + }) + const excessErrors = _ + .chain(errorFiles) + .map('errorType') + .uniq() + .difference(errorKeys) + .value() + + return Promise.all( + errorFiles + .filter((obj) => { + return _.includes(excessErrors, obj.errorType) + }) + .map((obj) => { + return fse.remove(obj.filePath) + }), + ) } }) From 3f47bd68f9d7d2c2145f0cd375c0e302f07a0942 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 04:25:12 -0500 Subject: [PATCH 147/165] add missing tests, add case for when config validation fails without a fileType --- .../CONFIG_VALIDATION_ERROR - noFileType.html | 42 +++++++++++++++++++ ...FIG_VALIDATION_MSG_ERROR - noFileType.html | 40 ++++++++++++++++++ packages/errors/src/errors.ts | 15 +++++-- .../test/unit/visualSnapshotErrors_spec.ts | 10 ++++- 4 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - noFileType.html create mode 100644 packages/errors/__snapshot-html__/CONFIG_VALIDATION_MSG_ERROR - noFileType.html diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - noFileType.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - noFileType.html new file mode 100644 index 000000000000..0e901dd54791 --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - noFileType.html @@ -0,0 +1,42 @@ + + + + + + + + + + + +
An invalid configuration value was set:
+
+Expected defaultCommandTimeout to be a number.
+
+Instead the value was: false
+
\ No newline at end of file diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_MSG_ERROR - noFileType.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_MSG_ERROR - noFileType.html new file mode 100644 index 000000000000..686eace28b3f --- /dev/null +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_MSG_ERROR - noFileType.html @@ -0,0 +1,40 @@ + + + + + + + + + + + +
An invalid configuration value was set:
+
+`something` was not right
+
\ No newline at end of file diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index fd7ae20c680a..05fc35a12fa7 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -682,21 +682,30 @@ export const AllCypressErrors = { // happens when there is an error in configuration file like "cypress.json" }, CONFIG_VALIDATION_MSG_ERROR: (fileType: 'configFile' | 'pluginsFile' | null, fileName: string | null, validationMsg: string) => { - if (fileType) { + if (!fileType) { return errTemplate` - Your ${fmt.highlight(fileType)} set an invalid value from: ${fmt.path(fileName)} + An invalid configuration value was set: ${fmt.highlight(validationMsg)}` } return errTemplate` - An invalid configuration value was set: + Your ${fmt.highlight(fileType)} set an invalid value from: ${fmt.path(fileName)} ${fmt.highlight(validationMsg)}` }, CONFIG_VALIDATION_ERROR: (fileType: 'configFile' | 'pluginsFile' | null, filePath: string | null, validationResult: ConfigValidationError) => { const { key, type, value, list } = validationResult + if (!fileType) { + return errTemplate`\ + An invalid configuration value was set: + + Expected ${fmt.highlight(key)} to be ${fmt.off(type)}. + + Instead the value was: ${fmt.stringify(value)}` + } + if (list) { return errTemplate`\ Your ${fmt.highlight(fileType)} set an invalid value from: ${fmt.path(filePath)} diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index c1db78dc9e9e..f5e2635bec48 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -203,8 +203,10 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K after(async () => { // if we're in CI, make sure there's all the files // we expect there to be in __snapshot-html__ + const files = await globby(`${snapshotHtmlFolder}/*`) + const errorKeys = _.keys(errors.AllCypressErrors) + if (isCi) { - const files = await globby(`${snapshotHtmlFolder}/*`) const errorNames = files.map((file) => { return path.basename(file, '.html').split(' ')[0] }) @@ -735,11 +737,17 @@ describe('visual error templates', () => { type: 'a number', value: false, }], + noFileType: [null, null, { + key: 'defaultCommandTimeout', + type: 'a number', + value: false, + }], } }, CONFIG_VALIDATION_MSG_ERROR: () => { return { default: ['configFile', 'cypress.json', '`something` was not right'], + noFileType: [null, null, '`something` was not right'], } }, RENAMED_CONFIG_OPTION: () => { From 2307e44088da49a587393fac090dad8048f4620c Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 13:10:14 -0500 Subject: [PATCH 148/165] fixes test --- packages/server/test/unit/config_spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/test/unit/config_spec.js b/packages/server/test/unit/config_spec.js index a2c79f1fadaf..85a61ecb6709 100644 --- a/packages/server/test/unit/config_spec.js +++ b/packages/server/test/unit/config_spec.js @@ -1866,6 +1866,8 @@ describe('lib/config', () => { } const cfg = { + projectRoot: '/foo/bar', + pluginsFile: '/foo/bar/cypress/plugins/index.js', browsers: [browser], resolved: { browsers: { @@ -1882,7 +1884,7 @@ describe('lib/config', () => { sinon.stub(errors, 'throw') config.updateWithPluginValues(cfg, overrides) - expect(errors.throw).to.have.been.calledWith('CONFIG_VALIDATION_ERROR') + expect(errors.throw).to.have.been.calledWith('CONFIG_VALIDATION_MSG_ERROR') }) it('allows user to filter browsers', () => { From 23241f51e102769bf9cc24eddcf1b1dc3a6dbe15 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 13:10:21 -0500 Subject: [PATCH 149/165] update snapshot --- system-tests/__snapshots__/record_spec.js | 1 + 1 file changed, 1 insertion(+) diff --git a/system-tests/__snapshots__/record_spec.js b/system-tests/__snapshots__/record_spec.js index da6cc0874107..3338bf3fc5a4 100644 --- a/system-tests/__snapshots__/record_spec.js +++ b/system-tests/__snapshots__/record_spec.js @@ -1450,6 +1450,7 @@ exports['e2e record api interaction warnings create run warnings unknown warning Warning from Cypress Dashboard: You are almost out of time Details: + { "code": "OUT_OF_TIME", "name": "OutOfTime", From ed274fad20a70ba38345f46b77ad30395f1b86fc Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 13:11:24 -0500 Subject: [PATCH 150/165] update snapshot --- system-tests/__snapshots__/config_spec.js | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/system-tests/__snapshots__/config_spec.js b/system-tests/__snapshots__/config_spec.js index 88e121477cb8..edc5f4ae6711 100644 --- a/system-tests/__snapshots__/config_spec.js +++ b/system-tests/__snapshots__/config_spec.js @@ -140,16 +140,31 @@ exports['e2e config applies defaultCommandTimeout globally 1'] = ` ` exports['e2e config throws error when invalid viewportWidth in the configuration file 1'] = ` -We found an invalid value in the file: cypress.json +Your configFile set an invalid value from: cypress.json -Expected \`viewportWidth\` to be a number. Instead the value was: \`"foo"\` +Expected viewportWidth to be a number. + +Instead the value was: "foo" ` exports['e2e config throws error when invalid browser in the configuration file 1'] = ` -We found an invalid value in the file: cypress.json +Your configFile set an invalid value from: cypress.json + +The error occurred while validating the browsers list. + +Expected family to be either chromium or firefox. + +Instead the value was: -Found an error while validating the \`browsers\` list. Expected \`family\` to be either chromium or firefox. Instead the value was: \`{"name":"bad browser","family":"unknown family","displayName":"Bad browser","version":"no version","path":"/path/to","majorVersion":123}\` +{ + "name": "bad browser", + "family": "unknown family", + "displayName": "Bad browser", + "version": "no version", + "path": "/path/to", + "majorVersion": 123 +} ` From 95d6856b6b50aca4e5ab5c2ffbcbf440f01c1709 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 13:22:11 -0500 Subject: [PATCH 151/165] update snapshot --- system-tests/__snapshots__/plugins_spec.js | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js index 90c493364d77..7c6ea004791f 100644 --- a/system-tests/__snapshots__/plugins_spec.js +++ b/system-tests/__snapshots__/plugins_spec.js @@ -119,16 +119,25 @@ exports['e2e plugins can modify config from plugins 1'] = ` ` exports['e2e plugins catches invalid browsers list returned from plugins 1'] = ` -An invalid configuration value was returned from the plugins file: cypress/plugins/index.js +Your pluginsFile set an invalid value from: cypress/plugins/index.js Expected at least one browser ` exports['e2e plugins catches invalid browser returned from plugins 1'] = ` -An invalid configuration value returned from the plugins file: cypress/plugins/index.js +Your pluginsFile set an invalid value from: cypress/plugins/index.js -Found an error while validating the \`browsers\` list. Expected \`displayName\` to be a non-empty string. Instead the value was: \`{"name":"browser name","family":"chromium"}\` +The error occurred while validating the browsers list. + +Expected displayName to be a non-empty string. + +Instead the value was: + +{ + "name": "browser name", + "family": "chromium" +} ` @@ -347,9 +356,11 @@ exports['e2e plugins calls after:screenshot for cy.screenshot() and failure scre ` exports['e2e plugins catches invalid viewportWidth returned from plugins 1'] = ` -An invalid configuration value returned from the plugins file: cypress/plugins/index.js +Your pluginsFile set an invalid value from: cypress/plugins/index.js + +Expected viewportWidth to be a number. -Expected \`viewportWidth\` to be a number. Instead the value was: \`"foo"\` +Instead the value was: "foo" ` From 3fed188b29df22e61040c7287c2007672dd4375c Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 13:25:39 -0500 Subject: [PATCH 152/165] sort uniqErrors + errorKeys prior to assertion - assert one by one --- packages/errors/test/unit/visualSnapshotErrors_spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index f5e2635bec48..79b8154bac30 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -218,8 +218,12 @@ const testVisualErrors = (whichError: CypressErrorType | '*', errorsToTest: {[K // return fse.remove(pathToHtml) // })) - expect(uniqErrors.sort()).to.deep.eq(errorKeys) - expect(errorKeys.sort()).to.deep.eq(uniqErrors) + const sortedUniqErrors = uniqErrors.sort() + const sortedErrorKeys = errorKeys.sort() + + _.each(sortedUniqErrors, (val, index) => { + expect(val).to.eq(sortedErrorKeys[index]) + }) } else { const errorFiles = files.map((file) => { return { From 703d9766373aea6574d099dd47407460719f1202 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 13:35:09 -0500 Subject: [PATCH 153/165] add system test for binding to an event with the wrong handler --- system-tests/__snapshots__/plugins_spec.js | 7 +++++++ .../plugin-invalid-event-handler-error/cypress.json | 1 + .../cypress/integration/app_spec.js | 3 +++ .../cypress/plugins/index.js | 3 +++ system-tests/test/plugins_spec.js | 10 ++++++++++ 5 files changed, 24 insertions(+) create mode 100644 system-tests/projects/plugin-invalid-event-handler-error/cypress.json create mode 100644 system-tests/projects/plugin-invalid-event-handler-error/cypress/integration/app_spec.js create mode 100644 system-tests/projects/plugin-invalid-event-handler-error/cypress/plugins/index.js diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js index 7c6ea004791f..9bc0e7c09aa2 100644 --- a/system-tests/__snapshots__/plugins_spec.js +++ b/system-tests/__snapshots__/plugins_spec.js @@ -570,3 +570,10 @@ The function exported by your pluginsFile threw an error: /foo/bar/.projects/plu FunctionSyncError: Function sync error from plugins file [stack trace lines] ` + +exports['e2e plugins fails when invalid event handler is registered 1'] = ` +Your pluginsFile threw a validation error from: /foo/bar/.projects/plugin-invalid-event-handler-error/cypress/plugins/index.js + +ValidationError: The handler for the event \`task\` must be an object + [stack trace lines] +` diff --git a/system-tests/projects/plugin-invalid-event-handler-error/cypress.json b/system-tests/projects/plugin-invalid-event-handler-error/cypress.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/system-tests/projects/plugin-invalid-event-handler-error/cypress.json @@ -0,0 +1 @@ +{} diff --git a/system-tests/projects/plugin-invalid-event-handler-error/cypress/integration/app_spec.js b/system-tests/projects/plugin-invalid-event-handler-error/cypress/integration/app_spec.js new file mode 100644 index 000000000000..99a13400edf9 --- /dev/null +++ b/system-tests/projects/plugin-invalid-event-handler-error/cypress/integration/app_spec.js @@ -0,0 +1,3 @@ +it('passes', () => { + expect(true).to.be.true +}) diff --git a/system-tests/projects/plugin-invalid-event-handler-error/cypress/plugins/index.js b/system-tests/projects/plugin-invalid-event-handler-error/cypress/plugins/index.js new file mode 100644 index 000000000000..1a6d25b5109e --- /dev/null +++ b/system-tests/projects/plugin-invalid-event-handler-error/cypress/plugins/index.js @@ -0,0 +1,3 @@ +module.exports = (on) => { + on('task', () => {}) +} diff --git a/system-tests/test/plugins_spec.js b/system-tests/test/plugins_spec.js index ada355074816..7e16f3121e92 100644 --- a/system-tests/test/plugins_spec.js +++ b/system-tests/test/plugins_spec.js @@ -154,6 +154,16 @@ describe('e2e plugins', function () { }) }) + it('fails when invalid event handler is registered', function () { + return systemTests.exec(this, { + spec: 'app_spec.js', + project: 'plugin-invalid-event-handler-error', + sanitizeScreenshotDimensions: true, + snapshot: true, + expectedExitCode: 1, + }) + }) + it('fails when there is nothing exported', function () { return systemTests.exec(this, { spec: 'app_spec.js', From 6e484a514e6181aab717dd1dad0000665cc007a2 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 13:38:13 -0500 Subject: [PATCH 154/165] fixes tests --- packages/server/test/integration/cypress_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index b2ae0187c439..c0d89ffd4f49 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -829,7 +829,7 @@ describe('lib/cypress', () => { .then(() => { return cypress.start([`--run-project=${this.todosPath}`]) }).then(() => { - this.expectExitWithErr('SETTINGS_VALIDATION_ERROR', 'cypress.json') + this.expectExitWithErr('CONFIG_VALIDATION_ERROR', 'cypress.json') }) }) @@ -840,7 +840,7 @@ describe('lib/cypress', () => { ]) .then(() => { this.expectExitWithErr('CONFIG_VALIDATION_ERROR', 'localhost:9999') - this.expectExitWithErr('CONFIG_VALIDATION_ERROR', 'We found an invalid configuration value') + this.expectExitWithErr('CONFIG_VALIDATION_ERROR', 'An invalid configuration value was set.') }) }) @@ -850,7 +850,7 @@ describe('lib/cypress', () => { return cypress.start([`--run-project=${this.todosPath}`]) .then(() => { this.expectExitWithErr('CONFIG_VALIDATION_ERROR', 'localhost:9999') - this.expectExitWithErr('CONFIG_VALIDATION_ERROR', 'We found an invalid configuration value') + this.expectExitWithErr('CONFIG_VALIDATION_ERROR', 'An invalid configuration value was set.') }) }) From 42316ae569f8fead90be3ad5ea12c3e27173070d Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 16:17:41 -0500 Subject: [PATCH 155/165] set more descriptive errors when setting invalid plugin events, or during plugin event validation --- packages/driver/src/cypress.ts | 28 +++++++++---------- .../CONFIG_VALIDATION_ERROR - noFileType.html | 2 +- ... => PLUGINS_INVALID_EVENT_NAME_ERROR.html} | 2 +- packages/errors/src/errors.ts | 4 +-- .../test/unit/visualSnapshotErrors_spec.ts | 2 +- .../server/lib/plugins/child/run_plugins.js | 2 +- .../lib/plugins/child/validate_event.js | 6 ++-- .../unit/plugins/child/validate_event_spec.js | 8 ++++-- system-tests/__snapshots__/plugins_spec.js | 4 +-- 9 files changed, 31 insertions(+), 27 deletions(-) rename packages/errors/__snapshot-html__/{PLUGINS_INVALID_EVENT_ERROR.html => PLUGINS_INVALID_EVENT_NAME_ERROR.html} (87%) diff --git a/packages/driver/src/cypress.ts b/packages/driver/src/cypress.ts index 5bd14576d7ba..15a6fa43db1e 100644 --- a/packages/driver/src/cypress.ts +++ b/packages/driver/src/cypress.ts @@ -146,23 +146,23 @@ class $Cypress { this.config = $SetterGetter.create(config, (config) => { if (!window.top.__cySkipValidateConfig) { validateNoReadOnlyConfig(config, (errProperty) => { - let errMessage - - if (this.state('runnable')) { - errMessage = $errUtils.errByPath('config.invalid_cypress_config_override', { - errProperty, - }) - } else { - errMessage = $errUtils.errByPath('config.invalid_test_config_override', { - errProperty, - }) - } - - throw new this.state('specWindow').Error(errMessage) + const errType = this.state('runnable') + ? 'config.invalid_cypress_config_override' + : 'invalid_test_config_override' + + const errMsg = $errUtils.errByPath(errType, { + errProperty, + }) + + throw new this.state('specWindow').Error(errMsg) }) } - validate(config, (errMsg) => { + validate(config, (errResult) => { + const errMsg = _.isString(errResult) + ? errResult + : `Expected \`${errResult.key}\` to be ${errResult.type}.\n\nInstead the value was: \`${JSON.stringify(errResult.value)}\`` + throw new this.state('specWindow').Error(errMsg) }) }) diff --git a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - noFileType.html b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - noFileType.html index 0e901dd54791..534dff554fa1 100644 --- a/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - noFileType.html +++ b/packages/errors/__snapshot-html__/CONFIG_VALIDATION_ERROR - noFileType.html @@ -34,7 +34,7 @@ -
An invalid configuration value was set:
+    
An invalid configuration value was set.
 
 Expected defaultCommandTimeout to be a number.
 
diff --git a/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_NAME_ERROR.html
similarity index 87%
rename from packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html
rename to packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_NAME_ERROR.html
index 2fa9f7fddcc6..ea59808e0dc9 100644
--- a/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_ERROR.html
+++ b/packages/errors/__snapshot-html__/PLUGINS_INVALID_EVENT_NAME_ERROR.html
@@ -48,5 +48,5 @@
 
 Error: fail whale
     at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_INVALID_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_INVALID_EVENT_NAME_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index 05fc35a12fa7..cde1f7fbacf5 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -646,7 +646,7 @@ export const AllCypressErrors = { ` }, // NOTE: mention this update in the PR, use in whimsical - PLUGINS_INVALID_EVENT_ERROR: (pluginsFilePath: string, invalidEventName: string, validEventNames: string[], err: Error) => { + PLUGINS_INVALID_EVENT_NAME_ERROR: (pluginsFilePath: string, invalidEventName: string, validEventNames: string[], err: Error) => { return errTemplate` Your ${fmt.highlightSecondary(`pluginsFile`)} threw a validation error: ${fmt.path(pluginsFilePath)} @@ -699,7 +699,7 @@ export const AllCypressErrors = { if (!fileType) { return errTemplate`\ - An invalid configuration value was set: + An invalid configuration value was set. Expected ${fmt.highlight(key)} to be ${fmt.off(type)}. diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index 79b8154bac30..fbcb59775bef 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -689,7 +689,7 @@ describe('visual error templates', () => { default: ['/path/to/pluginsFile', err], } }, - PLUGINS_INVALID_EVENT_ERROR: () => { + PLUGINS_INVALID_EVENT_NAME_ERROR: () => { const err = makeErr() return { diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index 24bdc6c7801e..f09863262133 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -50,7 +50,7 @@ const load = (ipc, config, pluginsFile) => { if (!isValid) { if (userEvents) { - ipc.send('load:error', 'PLUGINS_INVALID_EVENT_ERROR', pluginsFile, event, userEvents, util.serializeError(error)) + ipc.send('load:error', 'PLUGINS_INVALID_EVENT_NAME_ERROR', pluginsFile, event, userEvents, util.serializeError(error)) } else { ipc.send('load:error', 'PLUGINS_EVENT_ERROR', pluginsFile, util.serializeError(error)) } diff --git a/packages/server/lib/plugins/child/validate_event.js b/packages/server/lib/plugins/child/validate_event.js index d49c66ea6fba..561377e95895 100644 --- a/packages/server/lib/plugins/child/validate_event.js +++ b/packages/server/lib/plugins/child/validate_event.js @@ -43,9 +43,9 @@ const validateEvent = (event, handler, config, errConstructorFn) => { if (!validator) { const userEvents = _.reject(_.keys(eventValidators), (event) => event.startsWith('_')) - const error = new Error('invalid event name') + const error = new Error(`invalid event name registered: ${event}`) - error.name = 'ValidationError' + error.name = 'InvalidEventNameError' Error.captureStackTrace(error, errConstructorFn) @@ -59,7 +59,7 @@ const validateEvent = (event, handler, config, errConstructorFn) => { const result = validator(event, handler, config) if (!result.isValid) { - result.error.name = 'ValidationError' + result.error.name = 'InvalidEventHandlerError' Error.captureStackTrace(result.error, errConstructorFn) } diff --git a/packages/server/test/unit/plugins/child/validate_event_spec.js b/packages/server/test/unit/plugins/child/validate_event_spec.js index 4fb5cbfbf4b5..dee6e85a0727 100644 --- a/packages/server/test/unit/plugins/child/validate_event_spec.js +++ b/packages/server/test/unit/plugins/child/validate_event_spec.js @@ -20,13 +20,15 @@ describe('lib/plugins/child/validate_event', () => { const { isValid, error } = validateEvent() expect(isValid).to.be.false - expect(error.message).to.equal(`invalid event name`) + expect(error.name).to.equal('InvalidEventNameError') + expect(error.message).to.equal(`invalid event name registered: undefined`) }) it('returns error when called with no event handler', () => { const { isValid, error } = validateEvent('file:preprocessor') expect(isValid).to.be.false + expect(error.name).to.equal('InvalidEventHandlerError') expect(error.message).to.equal('The handler for the event `file:preprocessor` must be a function') }) @@ -34,7 +36,8 @@ describe('lib/plugins/child/validate_event', () => { const { isValid, error } = validateEvent('invalid:event:name', {}) expect(isValid).to.be.false - expect(error.message).to.equal(`invalid event name`) + expect(error.name).to.equal('InvalidEventNameError') + expect(error.message).to.equal(`invalid event name registered: invalid:event:name`) }) _.each(events, ([event, type]) => { @@ -42,6 +45,7 @@ describe('lib/plugins/child/validate_event', () => { const { isValid, error } = validateEvent(event, 'invalid type') expect(isValid).to.be.false + expect(error.name).to.equal('InvalidEventHandlerError') expect(error.message).to.equal(`The handler for the event \`${event}\` must be ${type}`) }) }) diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js index 9bc0e7c09aa2..f3f79b7b0e41 100644 --- a/system-tests/__snapshots__/plugins_spec.js +++ b/system-tests/__snapshots__/plugins_spec.js @@ -457,7 +457,7 @@ The following are valid events: - file:preprocessor - task -ValidationError: invalid event name +InvalidEventNameError: invalid event name registered: invalid:event [stack trace lines] ` @@ -574,6 +574,6 @@ FunctionSyncError: Function sync error from plugins file exports['e2e plugins fails when invalid event handler is registered 1'] = ` Your pluginsFile threw a validation error from: /foo/bar/.projects/plugin-invalid-event-handler-error/cypress/plugins/index.js -ValidationError: The handler for the event \`task\` must be an object +InvalidEventHandlerError: The handler for the event \`task\` must be an object [stack trace lines] ` From 84cb7e2a612cc7f72c671cd30892c83a0c3bcca2 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 17:24:42 -0500 Subject: [PATCH 156/165] remove duplicate PLUGINS_EVENT_ERROR, collapse into existing error --- .../PLUGINS_EVENT_ERROR.html | 42 ------------------- packages/errors/src/errors.ts | 9 +--- .../test/unit/visualSnapshotErrors_spec.ts | 7 ---- .../server/lib/plugins/child/run_plugins.js | 2 +- system-tests/__snapshots__/plugins_spec.js | 2 +- 5 files changed, 3 insertions(+), 59 deletions(-) delete mode 100644 packages/errors/__snapshot-html__/PLUGINS_EVENT_ERROR.html diff --git a/packages/errors/__snapshot-html__/PLUGINS_EVENT_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_EVENT_ERROR.html deleted file mode 100644 index 7b27ab03eb76..000000000000 --- a/packages/errors/__snapshot-html__/PLUGINS_EVENT_ERROR.html +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - -
Your pluginsFile threw a validation error from: /path/to/pluginsFile
-
-Error: fail whale
-    at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_EVENT_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-
\ No newline at end of file diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index cde1f7fbacf5..ff700c416502 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -636,14 +636,7 @@ export const AllCypressErrors = { ${fmt.stackTrace(arg2)} ` - }, - // TODO: STRING | ERROR? - PLUGINS_EVENT_ERROR: (arg1: string, arg2: string | Error) => { - return errTemplate` - Your ${fmt.highlight(`pluginsFile`)} threw a validation error from: ${fmt.path(arg1)} - - ${fmt.stackTrace(arg2)} - ` + // Your ${fmt.highlight(`pluginsFile`)} threw an error from: ${fmt.path(pluginsFilePath)} }, // NOTE: mention this update in the PR, use in whimsical PLUGINS_INVALID_EVENT_NAME_ERROR: (pluginsFilePath: string, invalidEventName: string, validEventNames: string[], err: Error) => { diff --git a/packages/errors/test/unit/visualSnapshotErrors_spec.ts b/packages/errors/test/unit/visualSnapshotErrors_spec.ts index fbcb59775bef..9beadb63f3b4 100644 --- a/packages/errors/test/unit/visualSnapshotErrors_spec.ts +++ b/packages/errors/test/unit/visualSnapshotErrors_spec.ts @@ -682,13 +682,6 @@ describe('visual error templates', () => { default: ['/path/to/pluginsFile', err], } }, - PLUGINS_EVENT_ERROR: () => { - const err = makeErr() - - return { - default: ['/path/to/pluginsFile', err], - } - }, PLUGINS_INVALID_EVENT_NAME_ERROR: () => { const err = makeErr() diff --git a/packages/server/lib/plugins/child/run_plugins.js b/packages/server/lib/plugins/child/run_plugins.js index f09863262133..63ba4fd62562 100644 --- a/packages/server/lib/plugins/child/run_plugins.js +++ b/packages/server/lib/plugins/child/run_plugins.js @@ -52,7 +52,7 @@ const load = (ipc, config, pluginsFile) => { if (userEvents) { ipc.send('load:error', 'PLUGINS_INVALID_EVENT_NAME_ERROR', pluginsFile, event, userEvents, util.serializeError(error)) } else { - ipc.send('load:error', 'PLUGINS_EVENT_ERROR', pluginsFile, util.serializeError(error)) + ipc.send('load:error', 'PLUGINS_FUNCTION_ERROR', pluginsFile, util.serializeError(error)) } return diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js index f3f79b7b0e41..d5af08476380 100644 --- a/system-tests/__snapshots__/plugins_spec.js +++ b/system-tests/__snapshots__/plugins_spec.js @@ -572,7 +572,7 @@ FunctionSyncError: Function sync error from plugins file ` exports['e2e plugins fails when invalid event handler is registered 1'] = ` -Your pluginsFile threw a validation error from: /foo/bar/.projects/plugin-invalid-event-handler-error/cypress/plugins/index.js +The function exported by your pluginsFile threw an error: /foo/bar/.projects/plugin-invalid-event-handler-error/cypress/plugins/index.js InvalidEventHandlerError: The handler for the event \`task\` must be an object [stack trace lines] From 01f3d5e08c61048ab8e1883b3c0d7db13c673698 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 17:27:39 -0500 Subject: [PATCH 157/165] use the same multiline formatting as @packages/errors --- packages/driver/src/cypress.ts | 9 +- .../testConfigOverrides_spec.ts.js | 136 +++++++++++++----- 2 files changed, 110 insertions(+), 35 deletions(-) diff --git a/packages/driver/src/cypress.ts b/packages/driver/src/cypress.ts index 15a6fa43db1e..b3ce07e45ae0 100644 --- a/packages/driver/src/cypress.ts +++ b/packages/driver/src/cypress.ts @@ -159,9 +159,16 @@ class $Cypress { } validate(config, (errResult) => { + const stringify = (str) => format(JSON.stringify(str)) + + const format = (str) => `\`${str}\`` + + // TODO: this does not use the @packages/error rewriting rules + // for stdout vs markdown - it always inserts backticks for markdown + // and those leak out into the stdout formatting. const errMsg = _.isString(errResult) ? errResult - : `Expected \`${errResult.key}\` to be ${errResult.type}.\n\nInstead the value was: \`${JSON.stringify(errResult.value)}\`` + : `Expected ${format(errResult.key)} to be ${errResult.type}.\n\nInstead the value was: ${stringify(errResult.value)}\`` throw new this.state('specWindow').Error(errMsg) }) diff --git a/system-tests/__snapshots__/testConfigOverrides_spec.ts.js b/system-tests/__snapshots__/testConfigOverrides_spec.ts.js index 8f960a0c4c12..358540fb1614 100644 --- a/system-tests/__snapshots__/testConfigOverrides_spec.ts.js +++ b/system-tests/__snapshots__/testConfigOverrides_spec.ts.js @@ -180,18 +180,24 @@ exports['testConfigOverrides / fails when passing invalid config values - [chrom 8 failing 1) inline test config override throws error: - Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\` + Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`""\`\` [stack trace lines] 2) inline test config override throws error when executed within cy cmd: - Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"null"\` + Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`"null"\`\` [stack trace lines] 3) context config overrides throws error runs: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config Error @@ -201,7 +207,9 @@ https://on.cypress.io/config 1st test fails on overrides: CypressError: The config override passed to your test has the following validation error: -Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\` +Expected \`defaultCommandTimeout\` to be a number. + +Instead the value was: \`"500"\`\` https://on.cypress.io/config Error @@ -211,7 +219,9 @@ https://on.cypress.io/config 2nd test fails on overrides: CypressError: The config override passed to your test has the following validation error: -Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\` +Expected \`defaultCommandTimeout\` to be a number. + +Instead the value was: \`"500"\`\` https://on.cypress.io/config Error @@ -222,7 +232,9 @@ https://on.cypress.io/config throws error at the correct line number: CypressError: The config override passed to your test has the following validation error: -Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"not_an_http_url"\` +Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`"not_an_http_url"\`\` https://on.cypress.io/config Error @@ -232,7 +244,9 @@ https://on.cypress.io/config "before all" hook for "test config override throws error": CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config @@ -244,7 +258,9 @@ Because this error occurred during a \`before all\` hook we are skipping the rem test config override throws error: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config Error @@ -332,12 +348,16 @@ exports['testConfigOverrides / fails when passing invalid config values with bef 1) runs all tests inline test config override throws error: - Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\` + Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`""\`\` [stack trace lines] 2) runs all tests inline test config override throws error when executed within cy cmd: - Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"null"\` + Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`"null"\`\` [stack trace lines] 3) runs all tests @@ -345,7 +365,9 @@ exports['testConfigOverrides / fails when passing invalid config values with bef runs: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config Error @@ -356,7 +378,9 @@ https://on.cypress.io/config 1st test fails on overrides: CypressError: The config override passed to your test has the following validation error: -Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\` +Expected \`defaultCommandTimeout\` to be a number. + +Instead the value was: \`"500"\`\` https://on.cypress.io/config Error @@ -367,7 +391,9 @@ https://on.cypress.io/config 2nd test fails on overrides: CypressError: The config override passed to your test has the following validation error: -Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\` +Expected \`defaultCommandTimeout\` to be a number. + +Instead the value was: \`"500"\`\` https://on.cypress.io/config Error @@ -379,7 +405,9 @@ https://on.cypress.io/config throws error at the correct line number: CypressError: The config override passed to your test has the following validation error: -Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"not_an_http_url"\` +Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`"not_an_http_url"\`\` https://on.cypress.io/config Error @@ -390,7 +418,9 @@ https://on.cypress.io/config "before all" hook for "test config override throws error": CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config @@ -403,7 +433,9 @@ Because this error occurred during a \`before all\` hook we are skipping the rem test config override throws error: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config Error @@ -483,7 +515,9 @@ exports['testConfigOverrides / correctly fails when invalid config values for it throws error at the correct line number: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config Error @@ -565,18 +599,24 @@ exports['testConfigOverrides / fails when passing invalid config values - [firef 8 failing 1) inline test config override throws error: - Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\` + Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`""\`\` [stack trace lines] 2) inline test config override throws error when executed within cy cmd: - Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"null"\` + Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`"null"\`\` [stack trace lines] 3) context config overrides throws error runs: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config [stack trace lines] @@ -585,7 +625,9 @@ https://on.cypress.io/config 1st test fails on overrides: CypressError: The config override passed to your test has the following validation error: -Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\` +Expected \`defaultCommandTimeout\` to be a number. + +Instead the value was: \`"500"\`\` https://on.cypress.io/config [stack trace lines] @@ -594,7 +636,9 @@ https://on.cypress.io/config 2nd test fails on overrides: CypressError: The config override passed to your test has the following validation error: -Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\` +Expected \`defaultCommandTimeout\` to be a number. + +Instead the value was: \`"500"\`\` https://on.cypress.io/config [stack trace lines] @@ -604,7 +648,9 @@ https://on.cypress.io/config throws error at the correct line number: CypressError: The config override passed to your test has the following validation error: -Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"not_an_http_url"\` +Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`"not_an_http_url"\`\` https://on.cypress.io/config [stack trace lines] @@ -613,7 +659,9 @@ https://on.cypress.io/config "before all" hook for "test config override throws error": CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config @@ -624,7 +672,9 @@ Because this error occurred during a \`before all\` hook we are skipping the rem test config override throws error: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config [stack trace lines] @@ -711,12 +761,16 @@ exports['testConfigOverrides / fails when passing invalid config values with bef 1) runs all tests inline test config override throws error: - Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`""\` + Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`""\`\` [stack trace lines] 2) runs all tests inline test config override throws error when executed within cy cmd: - Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"null"\` + Error: Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`"null"\`\` [stack trace lines] 3) runs all tests @@ -724,7 +778,9 @@ exports['testConfigOverrides / fails when passing invalid config values with bef runs: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config [stack trace lines] @@ -734,7 +790,9 @@ https://on.cypress.io/config 1st test fails on overrides: CypressError: The config override passed to your test has the following validation error: -Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\` +Expected \`defaultCommandTimeout\` to be a number. + +Instead the value was: \`"500"\`\` https://on.cypress.io/config [stack trace lines] @@ -744,7 +802,9 @@ https://on.cypress.io/config 2nd test fails on overrides: CypressError: The config override passed to your test has the following validation error: -Expected \`defaultCommandTimeout\` to be a number. Instead the value was: \`"500"\` +Expected \`defaultCommandTimeout\` to be a number. + +Instead the value was: \`"500"\`\` https://on.cypress.io/config [stack trace lines] @@ -755,7 +815,9 @@ https://on.cypress.io/config throws error at the correct line number: CypressError: The config override passed to your test has the following validation error: -Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). Instead the value was: \`"not_an_http_url"\` +Expected \`baseUrl\` to be a fully qualified URL (starting with \`http://\` or \`https://\`). + +Instead the value was: \`"not_an_http_url"\`\` https://on.cypress.io/config [stack trace lines] @@ -765,7 +827,9 @@ https://on.cypress.io/config "before all" hook for "test config override throws error": CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config @@ -777,7 +841,9 @@ Because this error occurred during a \`before all\` hook we are skipping the rem test config override throws error: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config [stack trace lines] @@ -856,7 +922,9 @@ exports['testConfigOverrides / correctly fails when invalid config values for it throws error at the correct line number: CypressError: The config override passed to your test has the following validation error: -Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. Instead the value was: \`"1"\` +Expected \`retries\` to be a positive number or null or an object with keys "openMode" and "runMode" with values of numbers or nulls. + +Instead the value was: \`"1"\`\` https://on.cypress.io/config [stack trace lines] From c10421e717596a05701045ce486f6db7f7c19cd5 Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 17:44:51 -0500 Subject: [PATCH 158/165] standardize verbiage and highlighting for consistency --- .../PLUGINS_DIDNT_EXPORT_FUNCTION - array.html | 5 +++-- .../PLUGINS_DIDNT_EXPORT_FUNCTION - string.html | 5 +++-- .../PLUGINS_DIDNT_EXPORT_FUNCTION.html | 5 +++-- .../PLUGINS_FUNCTION_ERROR.html | 2 +- .../PLUGINS_UNEXPECTED_ERROR.html | 4 ++-- packages/errors/src/errors.ts | 15 +++++++-------- system-tests/__snapshots__/plugins_spec.js | 16 +++++++++------- system-tests/test/plugins_spec.js | 2 +- 8 files changed, 29 insertions(+), 25 deletions(-) diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html index 01d50e8c799f..a209881ee353 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html +++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - array.html @@ -34,9 +34,10 @@ -
Your pluginsFile must export a function with the following signature:
+    
Your pluginsFile did not export a valid function from: /path/to/pluginsFile
+
+It must export a function with the following signature:
 
-// /path/to/pluginsFile
 module.exports = (on, config) => {
   // configure plugins here
 }
diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html
index 21e9a44c12bc..a0c007e69f12 100644
--- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html	
+++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION - string.html	
@@ -34,9 +34,10 @@
     
   
     
-    
Your pluginsFile must export a function with the following signature:
+    
Your pluginsFile did not export a valid function from: /path/to/pluginsFile
+
+It must export a function with the following signature:
 
-// /path/to/pluginsFile
 module.exports = (on, config) => {
   // configure plugins here
 }
diff --git a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html
index 39f2f8a91b25..c86a0cbfef1f 100644
--- a/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html
+++ b/packages/errors/__snapshot-html__/PLUGINS_DIDNT_EXPORT_FUNCTION.html
@@ -34,9 +34,10 @@
     
   
     
-    
Your pluginsFile must export a function with the following signature:
+    
Your pluginsFile did not export a valid function from: /path/to/pluginsFile
+
+It must export a function with the following signature:
 
-// /path/to/pluginsFile
 module.exports = (on, config) => {
   // configure plugins here
 }
diff --git a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html
index 64e125157456..082b329ec7aa 100644
--- a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html
+++ b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html
@@ -34,7 +34,7 @@
     
   
     
-    
The function exported by your pluginsFile threw an error: /path/to/pluginsFile
+    
Your pluginsFile function threw an error from: /path/to/pluginsFile
 
 Error: fail whale
     at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
diff --git a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html
index 7595782deed3..c4adc141e561 100644
--- a/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html
+++ b/packages/errors/__snapshot-html__/PLUGINS_UNEXPECTED_ERROR.html
@@ -36,9 +36,9 @@
     
     
We stopped running your tests because a plugin crashed.
 
-The following error was thrown by your plugins file: /path/to/pluginsFile
+Your pluginsFile threw an error from: /path/to/pluginsFile
 
 Error: fail whale
     at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
-    at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
+    at PLUGINS_UNEXPECTED_ERROR (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
 
\ No newline at end of file diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts index ff700c416502..8576a90a114b 100644 --- a/packages/errors/src/errors.ts +++ b/packages/errors/src/errors.ts @@ -604,13 +604,14 @@ export const AllCypressErrors = { }, PLUGINS_DIDNT_EXPORT_FUNCTION: (pluginsFilePath: string, exported: any) => { const code = errPartial` - ${fmt.comment(`// ${pluginsFilePath}`)} module.exports = (on, config) => { ${fmt.comment(`// configure plugins here`)} }` return errTemplate`\ - Your ${fmt.highlight(`pluginsFile`)} must export a function with the following signature: + Your ${fmt.highlight(`pluginsFile`)} did not export a valid function from: ${fmt.path(pluginsFilePath)} + + It must export a function with the following signature: ${fmt.code(code)} @@ -623,20 +624,18 @@ export const AllCypressErrors = { }, PLUGINS_FUNCTION_ERROR: (pluginsFilePath: string, err: Error) => { return errTemplate`\ - The function exported by your ${fmt.highlight(`pluginsFile`)} threw an error: ${fmt.path(pluginsFilePath)} + Your ${fmt.highlight(`pluginsFile`)} function threw an error from: ${fmt.path(pluginsFilePath)} - ${fmt.stackTrace(err)} - ` + ${fmt.stackTrace(err)}` }, PLUGINS_UNEXPECTED_ERROR: (arg1: string, arg2: string | Error) => { return errTemplate` We stopped running your tests because a plugin crashed. - The following error was thrown by your plugins file: ${fmt.path(arg1)} + Your ${fmt.highlight(`pluginsFile`)} threw an error from: ${fmt.path(arg1)} ${fmt.stackTrace(arg2)} ` - // Your ${fmt.highlight(`pluginsFile`)} threw an error from: ${fmt.path(pluginsFilePath)} }, // NOTE: mention this update in the PR, use in whimsical PLUGINS_INVALID_EVENT_NAME_ERROR: (pluginsFilePath: string, invalidEventName: string, validEventNames: string[], err: Error) => { @@ -672,8 +671,8 @@ export const AllCypressErrors = { - A syntax error in the file or one of its dependencies Fix the error in your code and re-run your tests.` - // happens when there is an error in configuration file like "cypress.json" }, + // happens when there is an error in configuration file like "cypress.json" CONFIG_VALIDATION_MSG_ERROR: (fileType: 'configFile' | 'pluginsFile' | null, fileName: string | null, validationMsg: string) => { if (!fileType) { return errTemplate` diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js index d5af08476380..82bc9162bb58 100644 --- a/system-tests/__snapshots__/plugins_spec.js +++ b/system-tests/__snapshots__/plugins_spec.js @@ -384,7 +384,7 @@ exports['e2e plugins fails when there is an async error inside an event handler We stopped running your tests because a plugin crashed. -The following error was thrown by your plugins file: /foo/bar/.projects/plugins-async-error/cypress/plugins/index.js +Your pluginsFile threw an error from: /foo/bar/.projects/plugins-async-error/cypress/plugins/index.js Error: Async error from plugins file [stack trace lines] @@ -419,9 +419,10 @@ Error: Async error from plugins file ` exports['e2e plugins fails when there is no function exported 1'] = ` -Your pluginsFile must export a function with the following signature: +Your pluginsFile did not export a valid function from: /foo/bar/.projects/plugin-no-function-return/cypress/plugins/index.js + +It must export a function with the following signature: -// /foo/bar/.projects/plugin-no-function-return/cypress/plugins/index.js module.exports = (on, config) => { // configure plugins here } @@ -529,9 +530,10 @@ exports['e2e plugins does not report more screenshots than exist if user overwri ` exports['e2e plugins fails when there is nothing exported 1'] = ` -Your pluginsFile must export a function with the following signature: +Your pluginsFile did not export a valid function from: /foo/bar/.projects/plugin-empty/cypress/plugins/index.js + +It must export a function with the following signature: -// /foo/bar/.projects/plugin-empty/cypress/plugins/index.js module.exports = (on, config) => { // configure plugins here } @@ -565,14 +567,14 @@ RootSyncError: Root sync error from plugins file ` exports['e2e plugins fails when function throws synchronously 1'] = ` -The function exported by your pluginsFile threw an error: /foo/bar/.projects/plugins-function-sync-error/cypress/plugins/index.js +Your pluginsFile function threw an error from: /foo/bar/.projects/plugins-function-sync-error/cypress/plugins/index.js FunctionSyncError: Function sync error from plugins file [stack trace lines] ` exports['e2e plugins fails when invalid event handler is registered 1'] = ` -The function exported by your pluginsFile threw an error: /foo/bar/.projects/plugin-invalid-event-handler-error/cypress/plugins/index.js +Your pluginsFile function threw an error from: /foo/bar/.projects/plugin-invalid-event-handler-error/cypress/plugins/index.js InvalidEventHandlerError: The handler for the event \`task\` must be an object [stack trace lines] diff --git a/system-tests/test/plugins_spec.js b/system-tests/test/plugins_spec.js index 7e16f3121e92..9135e42398fc 100644 --- a/system-tests/test/plugins_spec.js +++ b/system-tests/test/plugins_spec.js @@ -20,7 +20,7 @@ describe('e2e plugins', function () { onRun (exec) { return exec().then(({ stdout }) => { expect(stdout).to.include('We stopped running your tests because a plugin crashed.') - expect(stdout).to.include('The following error was thrown by your plugins file:') + expect(stdout).to.include('Your pluginsFile threw an error from:') expect(stdout).to.include('Error: Root async error from plugins file') }) }, From ef463e09583e1d805b6acfe055f527a85025b5ee Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 17:47:27 -0500 Subject: [PATCH 159/165] fix incorrect error path --- packages/driver/src/cypress.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/driver/src/cypress.ts b/packages/driver/src/cypress.ts index b3ce07e45ae0..03a5eb432909 100644 --- a/packages/driver/src/cypress.ts +++ b/packages/driver/src/cypress.ts @@ -146,11 +146,11 @@ class $Cypress { this.config = $SetterGetter.create(config, (config) => { if (!window.top.__cySkipValidateConfig) { validateNoReadOnlyConfig(config, (errProperty) => { - const errType = this.state('runnable') + const errPath = this.state('runnable') ? 'config.invalid_cypress_config_override' - : 'invalid_test_config_override' + : 'config.invalid_test_config_override' - const errMsg = $errUtils.errByPath(errType, { + const errMsg = $errUtils.errByPath(errPath, { errProperty, }) From 293afe556d2e1b8a1e5e361d220cd6774b02a03f Mon Sep 17 00:00:00 2001 From: Brian Mann Date: Thu, 10 Feb 2022 20:18:25 -0500 Subject: [PATCH 160/165] fixes tests, standardized and condensed more language --- .../__snapshot-html__/PLUGINS_FUNCTION_ERROR.html | 2 +- packages/errors/src/errors.ts | 2 +- packages/server/test/unit/plugins/index_spec.js | 10 +++++----- system-tests/__snapshots__/plugins_spec.js | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html index 082b329ec7aa..01f33c5fdf5e 100644 --- a/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html +++ b/packages/errors/__snapshot-html__/PLUGINS_FUNCTION_ERROR.html @@ -34,7 +34,7 @@ -
Your pluginsFile function threw an error from: /path/to/pluginsFile
+    
Your pluginsFile threw an error from: /path/to/pluginsFile
 
 Error: fail whale
     at makeErr (cypress/packages/errors/test/unit/visualSnapshotErrors_spec.ts)
diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts
index 8576a90a114b..c0fc7e4cb7c2 100644
--- a/packages/errors/src/errors.ts
+++ b/packages/errors/src/errors.ts
@@ -624,7 +624,7 @@ export const AllCypressErrors = {
   },
   PLUGINS_FUNCTION_ERROR: (pluginsFilePath: string, err: Error) => {
     return errTemplate`\
-      Your ${fmt.highlight(`pluginsFile`)} function threw an error from: ${fmt.path(pluginsFilePath)}
+      Your ${fmt.highlight(`pluginsFile`)} threw an error from: ${fmt.path(pluginsFilePath)}
 
       ${fmt.stackTrace(err)}`
   },
diff --git a/packages/server/test/unit/plugins/index_spec.js b/packages/server/test/unit/plugins/index_spec.js
index d7817634666e..46469f4c2432 100644
--- a/packages/server/test/unit/plugins/index_spec.js
+++ b/packages/server/test/unit/plugins/index_spec.js
@@ -207,7 +207,7 @@ describe('lib/plugins/index', () => {
         it('rejects plugins.init', () => {
           return plugins.init({ pluginsFile: 'cypress-plugin' }, getOptions())
           .catch((err) => {
-            expect(stripAnsi(err.message)).to.contain('The function exported by your pluginsFile threw an error')
+            expect(stripAnsi(err.message)).to.contain('Your pluginsFile threw an error from:')
             expect(err.message).to.contain('path/to/pluginsFile.js')
 
             expect(err.details).to.contain('error message stack')
@@ -249,7 +249,7 @@ describe('lib/plugins/index', () => {
         pluginsProcess.on.withArgs('error').yield(err)
         expect(onError).to.be.called
         expect(onError.lastCall.args[0].title).to.equal('Error running plugin')
-        expect(onError.lastCall.args[0].message).to.include('The following error was thrown by your plugins file')
+        expect(stripAnsi(onError.lastCall.args[0].message)).to.include('Your pluginsFile threw an error from:')
 
         expect(onError.lastCall.args[0].details).to.include(err.stack)
       })
@@ -258,7 +258,7 @@ describe('lib/plugins/index', () => {
         ipc.on.withArgs('error').yield(err)
         expect(onError).to.be.called
         expect(onError.lastCall.args[0].title).to.equal('Error running plugin')
-        expect(onError.lastCall.args[0].message).to.include('The following error was thrown by your plugins file')
+        expect(stripAnsi(onError.lastCall.args[0].message)).to.include('Your pluginsFile threw an error from:')
 
         expect(onError.lastCall.args[0].details).to.include(err.stack)
       })
@@ -284,7 +284,7 @@ describe('lib/plugins/index', () => {
         })
         .catch((_err) => {
           expect(_err.title).to.equal('Error running plugin')
-          expect(_err.message).to.include('The following error was thrown by your plugins file')
+          expect(stripAnsi(_err.message)).to.include('Your pluginsFile threw an error from:')
           expect(_err.details).to.include(err.stack)
         })
       })
@@ -296,7 +296,7 @@ describe('lib/plugins/index', () => {
         })
         .catch((_err) => {
           expect(_err.title).to.equal('Error running plugin')
-          expect(_err.message).to.include('The following error was thrown by your plugins file')
+          expect(stripAnsi(_err.message)).to.include('Your pluginsFile threw an error from:')
           expect(_err.details).to.include(err.stack)
         })
       })
diff --git a/system-tests/__snapshots__/plugins_spec.js b/system-tests/__snapshots__/plugins_spec.js
index 82bc9162bb58..252219641876 100644
--- a/system-tests/__snapshots__/plugins_spec.js
+++ b/system-tests/__snapshots__/plugins_spec.js
@@ -567,14 +567,14 @@ RootSyncError: Root sync error from plugins file
 `
 
 exports['e2e plugins fails when function throws synchronously 1'] = `
-Your pluginsFile function threw an error from: /foo/bar/.projects/plugins-function-sync-error/cypress/plugins/index.js
+Your pluginsFile threw an error from: /foo/bar/.projects/plugins-function-sync-error/cypress/plugins/index.js
 
 FunctionSyncError: Function sync error from plugins file
       [stack trace lines]
 `
 
 exports['e2e plugins fails when invalid event handler is registered 1'] = `
-Your pluginsFile function threw an error from: /foo/bar/.projects/plugin-invalid-event-handler-error/cypress/plugins/index.js
+Your pluginsFile threw an error from: /foo/bar/.projects/plugin-invalid-event-handler-error/cypress/plugins/index.js
 
 InvalidEventHandlerError: The handler for the event \`task\` must be an object
       [stack trace lines]

From ac14cce8b813623c2b0d73e72e32decf5c923412 Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Thu, 10 Feb 2022 23:44:31 -0500
Subject: [PATCH 161/165] Update packages/errors/src/errors.ts

Co-authored-by: Ryan Manuel 
---
 packages/errors/src/errors.ts | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts
index c0fc7e4cb7c2..b2dbdcb4e80f 100644
--- a/packages/errors/src/errors.ts
+++ b/packages/errors/src/errors.ts
@@ -1241,11 +1241,9 @@ export const cloneError = function (err: CypressError | GenericError, options: {
 
   // and any own (custom) properties
   // of the err object
-  for (let prop of Object.keys(err || {})) {
-    const val = err[prop]
-
+  Object.entries(err || {}).forEach(([prop, val]) => {
     obj[prop] = val
-  }
+  })
 
   if (err.stackWithoutMessage) {
     obj.stack = err.stackWithoutMessage

From 45d2682a99523fc469d7a83696cefe4892bf32d7 Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Thu, 10 Feb 2022 23:44:39 -0500
Subject: [PATCH 162/165] Update guides/error-handling.md

Co-authored-by: Ryan Manuel 
---
 guides/error-handling.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/guides/error-handling.md b/guides/error-handling.md
index 24838568e9bb..c2047cf72e5f 100644
--- a/guides/error-handling.md
+++ b/guides/error-handling.md
@@ -75,7 +75,7 @@ Any time we know about an edge case that is an error, we should define an error
 The `CypressError` is an `Error` containing the message returned from the `errTemplate`. The `stack` is set to that of the `originalError` if it exists (error object passed into `details`), otherwise it's the `stack` from where the `getError` / `throwError` is called.
 
 
-The `CypressError` has a `isCypressErr` prop which we use as a duck-type guard against exiting the process when logging exceptions. It also maintains a reference to the `originalError` if it exists.
+The `CypressError` has an `isCypressErr` prop which we use as a duck-type guard against exiting the process when logging exceptions. It also maintains a reference to the `originalError` if it exists.
 
 ### Child-Process Errors
 

From 65071334b061103f691992eeb9b9acdd0d7771de Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Thu, 10 Feb 2022 23:44:42 -0500
Subject: [PATCH 163/165] Update guides/error-handling.md

Co-authored-by: Ryan Manuel 
---
 guides/error-handling.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/guides/error-handling.md b/guides/error-handling.md
index c2047cf72e5f..486073673a6a 100644
--- a/guides/error-handling.md
+++ b/guides/error-handling.md
@@ -72,7 +72,7 @@ PLUGINS_FILE_ERROR: (arg1: string, arg2: Error) => {
 
 Any time we know about an edge case that is an error, we should define an error in `errors.ts`. This error should be retrieved by `getError`, which converts it to a `CypressError`. 
 
-The `CypressError` is an `Error` containing the message returned from the `errTemplate`. The `stack` is set to that of the `originalError` if it exists (error object passed into `details`), otherwise it's the `stack` from where the `getError` / `throwError` is called.
+The `CypressError` is an `Error` containing the message returned from the `errTemplate`. The `stack` is set to that of the `originalError` if it exists (i.e. the error object passed into `details`), otherwise it's the `stack` from where the `getError` / `throwError` is called.
 
 
 The `CypressError` has an `isCypressErr` prop which we use as a duck-type guard against exiting the process when logging exceptions. It also maintains a reference to the `originalError` if it exists.

From 58a74a3da7a13b10cbab6fcec4ae99ca2cb8f2ad Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Thu, 10 Feb 2022 23:48:14 -0500
Subject: [PATCH 164/165] added some final todo's

---
 packages/errors/src/errors.ts | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/packages/errors/src/errors.ts b/packages/errors/src/errors.ts
index c0fc7e4cb7c2..494a3f5b0466 100644
--- a/packages/errors/src/errors.ts
+++ b/packages/errors/src/errors.ts
@@ -388,6 +388,7 @@ export const AllCypressErrors = {
 
         https://on.cypress.io/how-do-i-record-runs`
   },
+  // TODO: make this relative path, not absolute
   CANNOT_RECORD_NO_PROJECT_ID: (configFilePath: string) => {
     return errTemplate`\
         You passed the ${fmt.flag(`--record`)} flag but this project has not been setup to record.
@@ -490,6 +491,7 @@ export const AllCypressErrors = {
 
         https://on.cypress.io/dashboard`
   },
+  // TODO: make this relative path, not absolute
   NO_PROJECT_ID: (configFilePath: string | false) => {
     return errTemplate`Can't find ${fmt.highlight(`projectId`)} in the config file: ${fmt.path(configFilePath || '')}`
   },
@@ -572,6 +574,7 @@ export const AllCypressErrors = {
   AUTOMATION_SERVER_DISCONNECTED: () => {
     return errTemplate`The automation client disconnected. Cannot continue running tests.`
   },
+  // TODO: make this relative path, not absolute
   SUPPORT_FILE_NOT_FOUND: (supportFilePath: string) => {
     return errTemplate`\
         Your ${fmt.highlight(`supportFile`)} is missing or invalid: ${fmt.path(supportFilePath)}
@@ -584,6 +587,7 @@ export const AllCypressErrors = {
 
         https://on.cypress.io/support-file-missing-or-invalid`
   },
+  // TODO: make this relative path, not absolute
   PLUGINS_FILE_ERROR: (pluginsFilePath: string, err: Error) => {
     return errTemplate`\
         Your ${fmt.highlight(`pluginsFile`)} is invalid: ${fmt.path(pluginsFilePath)}
@@ -593,6 +597,7 @@ export const AllCypressErrors = {
         ${fmt.stackTrace(err)}
       `
   },
+  // TODO: make this relative path, not absolute
   PLUGINS_FILE_NOT_FOUND: (pluginsFilePath: string) => {
     return errTemplate`\
         Your ${fmt.highlight(`pluginsFile`)} was not found at path: ${fmt.path(pluginsFilePath)}
@@ -602,6 +607,7 @@ export const AllCypressErrors = {
         If you have just renamed the extension of your pluginsFile, restart Cypress.
       `
   },
+  // TODO: make this relative path, not absolute
   PLUGINS_DIDNT_EXPORT_FUNCTION: (pluginsFilePath: string, exported: any) => {
     const code = errPartial`
       module.exports = (on, config) => {
@@ -622,6 +628,7 @@ export const AllCypressErrors = {
       https://on.cypress.io/plugins-api
     `
   },
+  // TODO: make this relative path, not absolute
   PLUGINS_FUNCTION_ERROR: (pluginsFilePath: string, err: Error) => {
     return errTemplate`\
       Your ${fmt.highlight(`pluginsFile`)} threw an error from: ${fmt.path(pluginsFilePath)}
@@ -637,7 +644,7 @@ export const AllCypressErrors = {
       ${fmt.stackTrace(arg2)}
     `
   },
-  // NOTE: mention this update in the PR, use in whimsical
+  // TODO: make this relative path, not absolute
   PLUGINS_INVALID_EVENT_NAME_ERROR: (pluginsFilePath: string, invalidEventName: string, validEventNames: string[], err: Error) => {
     return errTemplate`
       Your ${fmt.highlightSecondary(`pluginsFile`)} threw a validation error: ${fmt.path(pluginsFilePath)}
@@ -673,6 +680,7 @@ export const AllCypressErrors = {
       Fix the error in your code and re-run your tests.`
   },
   // happens when there is an error in configuration file like "cypress.json"
+  // TODO: make this relative path, not absolute
   CONFIG_VALIDATION_MSG_ERROR: (fileType: 'configFile' | 'pluginsFile' | null, fileName: string | null, validationMsg: string) => {
     if (!fileType) {
       return errTemplate`
@@ -686,6 +694,7 @@ export const AllCypressErrors = {
 
       ${fmt.highlight(validationMsg)}`
   },
+  // TODO: make this relative path, not absolute
   CONFIG_VALIDATION_ERROR: (fileType: 'configFile' | 'pluginsFile' | null, filePath: string | null, validationResult: ConfigValidationError) => {
     const { key, type, value, list } = validationResult
 
@@ -768,7 +777,7 @@ export const AllCypressErrors = {
         ${fmt.stackTrace(arg1.error)}
         `
   },
-  // TODO: test this out
+  // TODO: manually test this
   NO_DEFAULT_CONFIG_FILE_FOUND: (arg1: string) => {
     return errTemplate`\
         Could not find a Cypress configuration file in this folder: ${fmt.path(arg1)}`
@@ -1028,6 +1037,7 @@ export const AllCypressErrors = {
         You can safely remove this option from your config.`
   },
   // TODO: verify configFile is absolute path
+  // TODO: make this relative path, not absolute
   EXPERIMENTAL_COMPONENT_TESTING_REMOVED: (arg1: {configFile: string}) => {
     return errTemplate`\
         The ${fmt.highlight('experimentalComponentTesting')} configuration option was removed in ${fmt.cypressVersion(`7.0.0`)}.
@@ -1066,6 +1076,7 @@ export const AllCypressErrors = {
 
         You can safely remove this option from your config.`
   },
+  // TODO: make this relative path, not absolute
   INCOMPATIBLE_PLUGIN_RETRIES: (arg1: string) => {
     return errTemplate`\
       We've detected that the incompatible plugin ${fmt.highlight(`cypress-plugin-retries`)} is installed at: ${fmt.path(arg1)}

From 879d30c8dae070fb0d69fa3d6399e871bb796c88 Mon Sep 17 00:00:00 2001
From: Brian Mann 
Date: Fri, 11 Feb 2022 00:51:01 -0500
Subject: [PATCH 165/165] fix types

---
 packages/driver/src/cypress/commands.ts    |  8 +++-----
 packages/driver/src/cypress/error_utils.ts |  9 ++++-----
 yarn.lock                                  | 11 +----------
 3 files changed, 8 insertions(+), 20 deletions(-)

diff --git a/packages/driver/src/cypress/commands.ts b/packages/driver/src/cypress/commands.ts
index 0384c887d45c..a14cc7a0b4f5 100644
--- a/packages/driver/src/cypress/commands.ts
+++ b/packages/driver/src/cypress/commands.ts
@@ -1,10 +1,8 @@
 import _ from 'lodash'
-
-import $errUtils from './error_utils'
-import $stackUtils from './stack_utils'
-
 import { allCommands } from '../cy/commands'
 import { addCommand } from '../cy/net-stubbing'
+import $errUtils from './error_utils'
+import $stackUtils from './stack_utils'
 
 const builtInCommands = [
   // `default` is necessary if a file uses `export default` syntax.
@@ -140,7 +138,7 @@ export default {
             errProps: {
               appendToStack: {
                 title: 'From Cypress Internals',
-                content: $stackUtils.stackWithoutMessage((new Error('add command internal stack')).stack),
+                content: $stackUtils.stackWithoutMessage((new Error('add command internal stack')).stack || ''),
               } },
           })
         }
diff --git a/packages/driver/src/cypress/error_utils.ts b/packages/driver/src/cypress/error_utils.ts
index 7d7cf105efd2..9c58d6950aa8 100644
--- a/packages/driver/src/cypress/error_utils.ts
+++ b/packages/driver/src/cypress/error_utils.ts
@@ -1,12 +1,11 @@
 // See: ./errorScenarios.md for details about error messages and stack traces
 
-import _ from 'lodash'
 import chai from 'chai'
-
+import _ from 'lodash'
 import $dom from '../dom'
-import $utils from './utils'
-import $stackUtils, { StackAndCodeFrameIndex } from './stack_utils'
 import $errorMessages from './error_messages'
+import $stackUtils, { StackAndCodeFrameIndex } from './stack_utils'
+import $utils from './utils'
 
 const ERROR_PROPS = 'message type name stack sourceMappedStack parsedStack fileName lineNumber columnNumber host uncaught actual expected showDiff isPending docsUrl codeFrame'.split(' ')
 const ERR_PREPARED_FOR_SERIALIZATION = Symbol('ERR_PREPARED_FOR_SERIALIZATION')
@@ -405,7 +404,7 @@ const stackAndCodeFrameIndex = (err, userInvocationStack): StackAndCodeFrameInde
     return $stackUtils.stackWithUserInvocationStackSpliced(err, userInvocationStack)
   }
 
-  return { stack: $stackUtils.replacedStack(err, userInvocationStack) }
+  return { stack: $stackUtils.replacedStack(err, userInvocationStack) || '' }
 }
 
 const preferredStackAndCodeFrameIndex = (err, userInvocationStack) => {
diff --git a/yarn.lock b/yarn.lock
index 968aa31cf361..3ee0c7ff014a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -35655,7 +35655,7 @@ shell-quote@1.7.2, shell-quote@^1.4.2, shell-quote@^1.6.1:
   resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.2.tgz#67a7d02c76c9da24f99d20808fcaded0e0e04be2"
   integrity sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==
 
-shelljs@0.8.5:
+shelljs@0.8.5, shelljs@^0.8.4:
   version "0.8.5"
   resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c"
   integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==
@@ -35664,15 +35664,6 @@ shelljs@0.8.5:
     interpret "^1.0.0"
     rechoir "^0.6.2"
 
-shelljs@^0.8.4:
-  version "0.8.4"
-  resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.4.tgz#de7684feeb767f8716b326078a8a00875890e3c2"
-  integrity sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==
-  dependencies:
-    glob "^7.0.0"
-    interpret "^1.0.0"
-    rechoir "^0.6.2"
-
 shellwords@^0.1.1:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
TableOriginal