From 36866da64d86dc7e162489e357d7fc814ff025bb Mon Sep 17 00:00:00 2001
From: Chris Breiding
Date: Tue, 17 Mar 2020 00:33:56 -0400
Subject: [PATCH] Error Improvements (#6724)
* redesign and improve reporter error display
- add markdown support
- collapse stacktrace
- separate docs url and add link in reporter
Co-authored-by: Jennifer Shehane
* $utils -> $errUtils
* derp
* serializeError -> wrapErr
* cloneErr -> makeErrFromObj
* yarn.lock
* fix unit tests
* move err-model
* fix styles
* fix/improve error logging
* fix non-converted bits
* transfer missed changes
* fix issues
* remove obselete spec
* make type test more reliable
* use should, get retries
* update snapshots
* update e2e network error test
* update more snapshots
* update error whitespace
* update snapshot
* try something out
* nevermind
* fix tooltip
* add some logging
* remove whitespace
* remove spying on window
* update snapshot
* fix test
* update snapshot
* fix merge: snapshot stacktraces
* fix noStackTrace and update snapshot
* update snapshot
* fix yarn.lock
* don't show diff if retrying an existence error
* url -> URL
* don't add newline after docs url and update a few snapshots
* keep opening stack trace from collapsing test
* remove unnecessary global cy reference
* fix tests
* put e2e timeout increase back in the right spot for exit: false
* don't show diff when assertion contains an element
also, keep mocha from messing up extracting error name when it includes a colon
* use backticks for hook error title
* fix appending error message when original message is falsy
* don't show diff on existence failures
* update snapshots
* fix finish/done being called twice due to not returning
* prevent error print button click from propagating
* use correct error methods and remove need for workaround
* create better abstraction around creating cypress error from path, refactor
* fix throwErr and tests
Co-authored-by: Jennifer Shehane
Co-authored-by: Brian Mann
Co-authored-by: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Co-authored-by: Jennifer Shehane
Co-authored-by: Brian Mann
Co-authored-by: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
---
packages/driver/src/cy/actionability.js | 2 +-
packages/driver/src/cy/chai.js | 26 +-
.../src/cy/commands/actions/check.coffee | 2 +-
.../src/cy/commands/actions/scroll.coffee | 2 +-
.../driver/src/cy/commands/angular.coffee | 2 +-
.../driver/src/cy/commands/asserting.coffee | 4 +-
.../driver/src/cy/commands/commands.coffee | 4 +-
.../driver/src/cy/commands/connectors.coffee | 38 +-
packages/driver/src/cy/commands/cookies.js | 2 +-
packages/driver/src/cy/commands/files.coffee | 11 +-
packages/driver/src/cy/commands/querying.js | 4 +-
.../driver/src/cy/commands/screenshot.coffee | 2 +-
.../driver/src/cy/commands/traversals.coffee | 4 +-
packages/driver/src/cy/commands/window.coffee | 2 +-
packages/driver/src/cy/commands/xhr.coffee | 2 +-
packages/driver/src/cy/ensures.coffee | 6 +-
packages/driver/src/cy/errors.coffee | 29 +-
packages/driver/src/cy/retries.coffee | 21 +-
packages/driver/src/cypress.js | 11 +-
packages/driver/src/cypress/cy.js | 17 +-
.../driver/src/cypress/error_messages.coffee | 1778 +++++++++++------
packages/driver/src/cypress/error_utils.js | 306 +--
packages/driver/src/cypress/log.js | 2 +-
packages/driver/src/dom/visibility.js | 36 +-
.../driver/test/cypress/fixtures/dom.html | 17 +-
.../commands/actions/check_spec.coffee | 33 +-
.../commands/actions/clear_spec.js | 32 +-
.../commands/actions/click_spec.js | 67 +-
.../commands/actions/focus_spec.coffee | 34 +-
.../commands/actions/hover_spec.coffee | 4 +-
.../commands/actions/scroll_spec.coffee | 50 +-
.../commands/actions/select_spec.coffee | 29 +-
.../commands/actions/submit_spec.coffee | 8 +-
.../commands/actions/trigger_spec.coffee | 21 +-
.../commands/actions/type_errors_spec.js | 99 +-
.../integration/commands/agents_spec.coffee | 14 +-
.../integration/commands/aliasing_spec.coffee | 21 +-
.../integration/commands/angular_spec.coffee | 6 +-
.../integration/commands/assertions_spec.js | 31 +-
.../integration/commands/clock_spec.coffee | 18 +-
.../integration/commands/commands_spec.coffee | 4 +-
.../commands/connectors_spec.coffee | 220 +-
.../integration/commands/cookies_spec.coffee | 45 +-
.../integration/commands/exec_spec.coffee | 26 +-
.../integration/commands/files_spec.coffee | 44 +-
.../integration/commands/fixtures_spec.coffee | 6 +-
.../commands/local_storage_spec.coffee | 3 +-
.../integration/commands/location_spec.coffee | 14 +
.../integration/commands/misc_spec.coffee | 4 +-
.../commands/navigation_spec.coffee | 104 +-
.../integration/commands/querying_spec.js | 42 +-
.../integration/commands/request_spec.coffee | 70 +-
.../commands/screenshot_spec.coffee | 81 +-
.../integration/commands/task_spec.coffee | 22 +-
.../commands/traversals_spec.coffee | 8 +-
.../integration/commands/waiting_spec.coffee | 134 +-
.../integration/commands/window_spec.coffee | 22 +-
.../integration/commands/xhr_spec.coffee | 101 +-
.../cypress/integration/cy/timeouts_spec.js | 45 +
.../integration/cypress/browser_spec.coffee | 2 +-
.../integration/cypress/cy_spec.coffee | 26 +-
.../integration/cypress/cypress_spec.coffee | 8 +-
.../cypress/error_utils_spec.coffee | 52 +-
.../cypress/screenshot_spec.coffee | 143 +-
.../cypress/selector_playground_spec.coffee | 33 +-
.../integration/dom/visibility_spec.ts | 71 +-
.../integration/e2e/promises_spec.coffee | 13 +-
.../integration/e2e/return_value_spec.coffee | 9 +-
.../e2e/uncaught_errors_spec.coffee | 17 +-
.../cypress/integration/errors_spec.js | 46 -
.../cypress/integration/test_errors_spec.js | 173 ++
packages/reporter/package.json | 2 +-
.../src/collapsible/collapsible.spec.tsx | 6 +-
.../reporter/src/collapsible/collapsible.tsx | 19 +-
.../reporter/src/commands/command-model.ts | 2 +-
packages/reporter/src/commands/commands.scss | 21 +-
.../src/{lib => errors}/err-model.spec.ts | 8 +-
packages/reporter/src/errors/err-model.ts | 46 +
packages/reporter/src/errors/errors.scss | 129 ++
.../reporter/src/errors/test-error.spec.tsx | 48 -
packages/reporter/src/errors/test-error.tsx | 73 +-
.../reporter/src/hooks/hook-model.spec.ts | 20 +-
packages/reporter/src/hooks/hook-model.ts | 4 +-
packages/reporter/src/lib/base.scss | 12 +
packages/reporter/src/lib/err-model.ts | 35 -
packages/reporter/src/lib/events.spec.ts | 31 +-
packages/reporter/src/lib/events.ts | 14 +-
packages/reporter/src/lib/variables.scss | 8 +
.../reporter/src/runnables/runnables.scss | 29 +-
packages/reporter/src/test/test-model.spec.ts | 12 +-
packages/reporter/src/test/test-model.ts | 2 +-
packages/reporter/src/test/test.tsx | 7 +-
packages/runner/src/lib/event-manager.js | 23 +-
.../1_async_timeouts_spec.coffee.js | 4 +-
...caught_uncaught_hook_errors_spec.coffee.js | 20 +-
.../1_commands_outside_of_test_spec.coffee.js | 10 +-
.../__snapshots__/3_config_spec.coffee.js | 2 +-
.../__snapshots__/3_issue_1669_spec.coffee.js | 2 +-
.../__snapshots__/3_issue_173_spec.coffee.js | 2 +-
.../__snapshots__/3_issue_674_spec.coffee.js | 8 +-
.../3_js_error_handling_spec.coffee.js | 10 +-
.../4_form_submissions_spec.coffee.js | 7 +-
.../__snapshots__/4_request_spec.coffee.js | 20 +-
.../4_return_value_spec.coffee.js | 10 +-
.../5_screenshots_spec.coffee.js | 6 +-
.../5_spec_isolation_spec.coffee.js | 14 +-
.../__snapshots__/5_stdout_spec.coffee.js | 114 +-
.../5_task_not_registered_spec.coffee.js | 2 +-
.../__snapshots__/6_task_spec.coffee.js | 4 +-
.../6_uncaught_spec_errors_spec.coffee.js | 2 +-
.../__snapshots__/6_visit_spec.coffee.js | 48 +-
.../6_web_security_spec.coffee.js | 9 +-
.../__snapshots__/7_record_spec.coffee.js | 4 +-
.../__snapshots__/8_reporters_spec.coffee.js | 18 +-
packages/server/lib/reporter.coffee | 20 +
packages/server/test/e2e/5_stdout_spec.coffee | 6 +
.../e2e/8_network_error_handling_spec.coffee | 2 +-
.../stdout_assertion_errors_spec.js | 19 +
.../server/expected_stdout_failures.txt | 2 +-
packages/server/test/support/helpers/e2e.js | 4 +-
yarn.lock | 91 +-
121 files changed, 3340 insertions(+), 2056 deletions(-)
create mode 100644 packages/driver/test/cypress/integration/cy/timeouts_spec.js
delete mode 100644 packages/reporter/cypress/integration/errors_spec.js
create mode 100644 packages/reporter/cypress/integration/test_errors_spec.js
rename packages/reporter/src/{lib => errors}/err-model.spec.ts (85%)
create mode 100644 packages/reporter/src/errors/err-model.ts
delete mode 100644 packages/reporter/src/errors/test-error.spec.tsx
delete mode 100644 packages/reporter/src/lib/err-model.ts
create mode 100644 packages/server/test/support/fixtures/projects/e2e/cypress/integration/stdout_assertion_errors_spec.js
diff --git a/packages/driver/src/cy/actionability.js b/packages/driver/src/cy/actionability.js
index 6aa9dc3096dc..3689cc1e7525 100644
--- a/packages/driver/src/cy/actionability.js
+++ b/packages/driver/src/cy/actionability.js
@@ -251,7 +251,7 @@ const ensureNotAnimating = function (cy, $el, coordsHistory, animationDistanceTh
// if we dont have at least 2 points
// then automatically retry
if (coordsHistory.length < 2) {
- throw $errUtils.cypressErr('coordsHistory must be at least 2 sets of coords')
+ $errUtils.throwErrByPath('dom.animation_coords_history_invalid')
}
// verify that our element is not currently animating
diff --git a/packages/driver/src/cy/chai.js b/packages/driver/src/cy/chai.js
index cbed329059c4..9857e7078351 100644
--- a/packages/driver/src/cy/chai.js
+++ b/packages/driver/src/cy/chai.js
@@ -49,16 +49,12 @@ chai.use((chai, u) => {
$chaiJquery(chai, chaiUtils, {
onInvalid (method, obj) {
- const err = $errUtils.cypressErr(
- $errUtils.errMsgByPath(
- 'chai.invalid_jquery_obj', {
- assertion: method,
- subject: $utils.stringifyActual(obj),
- },
- ),
- )
-
- throw err
+ $errUtils.throwErrByPath('chai.invalid_jquery_obj', {
+ args: {
+ assertion: method,
+ subject: $utils.stringifyActual(obj),
+ },
+ })
},
onError (err, method, obj, negated) {
@@ -253,7 +249,7 @@ chai.use((chai, u) => {
return _super.apply(this, arguments)
}
- const err = $errUtils.cypressErr($errUtils.errMsgByPath('chai.match_invalid_argument', { regExp }))
+ const err = $errUtils.cypressErrByPath('chai.match_invalid_argument', { args: { regExp } })
err.retry = false
throw err
@@ -340,11 +336,11 @@ chai.use((chai, u) => {
return `Not enough elements found. Found '${len1}', expected '${len2}'.`
}
- e1.displayMessage = getLongLengthMessage(obj.length, length)
+ e1.message = getLongLengthMessage(obj.length, length)
throw e1
}
- const e2 = $errUtils.cypressErr($errUtils.errMsgByPath('chai.length_invalid_argument', { length }))
+ const e2 = $errUtils.cypressErrByPath('chai.length_invalid_argument', { args: { length } })
e2.retry = false
throw e2
@@ -397,10 +393,10 @@ chai.use((chai, u) => {
return `Expected ${node} not to exist in the DOM, but it was continuously found.`
}
- return `Expected to find element: '${obj.selector}', but never found it.`
+ return `Expected to find element: \`${obj.selector}\`, but never found it.`
}
- e1.displayMessage = getLongExistsMessage(obj)
+ e1.message = getLongExistsMessage(obj)
throw e1
}
}
diff --git a/packages/driver/src/cy/commands/actions/check.coffee b/packages/driver/src/cy/commands/actions/check.coffee
index d28856463cdb..6509e804a654 100644
--- a/packages/driver/src/cy/commands/actions/check.coffee
+++ b/packages/driver/src/cy/commands/actions/check.coffee
@@ -58,7 +58,7 @@ checkOrUncheck = (type, subject, values = [], options = {}) ->
if not isAcceptableElement($el)
node = $dom.stringify($el)
word = $utils.plural(options.$el, "contains", "is")
- phrase = if type is "check" then " and :radio" else ""
+ phrase = if type is "check" then " and `:radio`" else ""
$errUtils.throwErrByPath("check_uncheck.invalid_element", {
onFail: options._log
args: { node, word, phrase, cmd: type }
diff --git a/packages/driver/src/cy/commands/actions/scroll.coffee b/packages/driver/src/cy/commands/actions/scroll.coffee
index 49895948ba0c..915b2e178b96 100644
--- a/packages/driver/src/cy/commands/actions/scroll.coffee
+++ b/packages/driver/src/cy/commands/actions/scroll.coffee
@@ -32,7 +32,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->
## ensure the subject is not window itself
## cause how are you gonna scroll the window into view...
if subject is state("window")
- $utils.throwErrByPath("scrollIntoView.subject_is_window")
+ $errUtils.throwErrByPath("scrollIntoView.subject_is_window")
## throw if we're trying to scroll to multiple elements
if subject.length > 1
diff --git a/packages/driver/src/cy/commands/angular.coffee b/packages/driver/src/cy/commands/angular.coffee
index c5a1e0dc7337..27d2723eaf8f 100644
--- a/packages/driver/src/cy/commands/angular.coffee
+++ b/packages/driver/src/cy/commands/angular.coffee
@@ -35,7 +35,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->
cy.verifyUpcomingAssertions(getEl($elements), options, {
onRetry: resolveElements
onFail: (err) ->
- err.displayMessage = "Could not find element for binding: '#{binding}'."
+ err.message = "Could not find element for binding: '#{binding}'."
})
findByNgAttr = (name, attr, el, options) ->
diff --git a/packages/driver/src/cy/commands/asserting.coffee b/packages/driver/src/cy/commands/asserting.coffee
index 35ea2110a285..d55aac333f75 100644
--- a/packages/driver/src/cy/commands/asserting.coffee
+++ b/packages/driver/src/cy/commands/asserting.coffee
@@ -84,7 +84,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->
options = {}
if reEventually.test(chainers)
- err = $errUtils.cypressErr("The 'eventually' assertion chainer has been deprecated. This is now the default behavior so you can safely remove this word and everything should work as before.")
+ err = $errUtils.cypressErrByPath('should.eventually_deprecated')
err.retry = false
throwAndLogErr(err)
@@ -123,7 +123,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->
newExp = _.reduce chainers, (memo, value) =>
if value not of memo
- err = $errUtils.cypressErr("The chainer: '#{value}' was not found. Could not build assertion.")
+ err = $errUtils.cypressErrByPath('should.chainer_not_found', { args: { chainer: value } })
err.retry = false
throwAndLogErr(err)
diff --git a/packages/driver/src/cy/commands/commands.coffee b/packages/driver/src/cy/commands/commands.coffee
index 80233288ed44..e0ed6dd2bad1 100644
--- a/packages/driver/src/cy/commands/commands.coffee
+++ b/packages/driver/src/cy/commands/commands.coffee
@@ -5,12 +5,12 @@ $errUtils = require("../../cypress/error_utils")
command = (ctx, name, args...) ->
if not ctx[name]
- cmds = _.keys($Chainer.prototype).join(", ")
+ cmds = "\`#{_.keys($Chainer.prototype).join("`, `")}\`"
$errUtils.throwErrByPath("miscellaneous.invalid_command", {
args: { name, cmds }
})
- ctx[name].apply(window, args)
+ ctx[name].apply(ctx, args)
module.exports = (Commands, Cypress, cy, state, config) ->
Commands.addChainer({
diff --git a/packages/driver/src/cy/commands/connectors.coffee b/packages/driver/src/cy/commands/connectors.coffee
index 82e5e87e7f9d..e431f379d4e0 100644
--- a/packages/driver/src/cy/commands/connectors.coffee
+++ b/packages/driver/src/cy/commands/connectors.coffee
@@ -211,47 +211,45 @@ module.exports = (Commands, Cypress, cy, state, config) ->
args: { cmd: name }
})
- ## TODO: use the new error utils that are part of
- ## the error message enhancements PR
propertyNotOnSubjectErr = (prop) ->
- $errUtils.cypressErr(
- $errUtils.errMsgByPath("invoke_its.nonexistent_prop", {
+ $errUtils.cypressErrByPath("invoke_its.nonexistent_prop", {
+ args: {
prop,
cmd: name
- })
- )
+ }
+ })
propertyValueNullOrUndefinedErr = (prop, value) ->
errMessagePath = if isCmdIts then "its" else "invoke"
- $errUtils.cypressErr(
- $errUtils.errMsgByPath("#{errMessagePath}.null_or_undefined_prop_value", {
+ $errUtils.cypressErrByPath("#{errMessagePath}.null_or_undefined_prop_value", {
+ args: {
prop,
value,
- cmd: name
- })
- )
+ }
+ cmd: name
+ })
subjectNullOrUndefinedErr = (prop, value) ->
errMessagePath = if isCmdIts then "its" else "invoke"
- $errUtils.cypressErr(
- $errUtils.errMsgByPath("#{errMessagePath}.subject_null_or_undefined", {
+ $errUtils.cypressErrByPath("#{errMessagePath}.subject_null_or_undefined", {
+ args: {
prop,
- value,
cmd: name
- })
- )
+ value,
+ }
+ })
propertyNotOnPreviousNullOrUndefinedValueErr = (prop, value, previousProp) ->
- $errUtils.cypressErr(
- $errUtils.errMsgByPath("invoke_its.previous_prop_null_or_undefined", {
+ $errUtils.cypressErrByPath("invoke_its.previous_prop_null_or_undefined", {
+ args: {
prop,
value,
previousProp,
cmd: name
- })
- )
+ }
+ })
traverseObjectAtPath = (acc, pathsArray, index = 0) ->
## traverse at this depth
diff --git a/packages/driver/src/cy/commands/cookies.js b/packages/driver/src/cy/commands/cookies.js
index 79330c44758d..4d6bee8eb130 100644
--- a/packages/driver/src/cy/commands/cookies.js
+++ b/packages/driver/src/cy/commands/cookies.js
@@ -92,7 +92,7 @@ module.exports = function (Commands, Cypress, cy, state, config) {
$errUtils.throwErrByPath('cookies.backend_error', {
args: {
action,
- command,
+ cmd: command,
browserDisplayName: Cypress.browser.displayName,
errMessage: err.message,
errStack: err.stack,
diff --git a/packages/driver/src/cy/commands/files.coffee b/packages/driver/src/cy/commands/files.coffee
index bb325526849e..5f1a8bb648ff 100644
--- a/packages/driver/src/cy/commands/files.coffee
+++ b/packages/driver/src/cy/commands/files.coffee
@@ -2,6 +2,7 @@ _ = require("lodash")
Promise = require("bluebird")
$errUtils = require("../../cypress/error_utils")
+$errMessages = require("../../cypress/error_messages")
module.exports = (Commands, Cypress, cy, state, config) ->
Commands.addAll({
@@ -50,16 +51,20 @@ module.exports = (Commands, Cypress, cy, state, config) ->
onFail: (err) ->
return unless err.type is "existence"
- if contents?
+ { message, docsUrl } = if contents?
## file exists but it shouldn't
- err.displayMessage = $errUtils.errMsgByPath("files.existent", {
+ $errUtils.errObjByPath($errMessages, "files.existent", {
file, filePath
})
else
## file doesn't exist but it should
- err.displayMessage = $errUtils.errMsgByPath("files.nonexistent", {
+ $errUtils.errObjByPath($errMessages, "files.nonexistent", {
file, filePath
})
+
+ err.message = message
+ err.docsUrl = docsUrl
+
onRetry: verifyAssertions
})
diff --git a/packages/driver/src/cy/commands/querying.js b/packages/driver/src/cy/commands/querying.js
index 18124f7013c7..50f9977b86fb 100644
--- a/packages/driver/src/cy/commands/querying.js
+++ b/packages/driver/src/cy/commands/querying.js
@@ -68,7 +68,7 @@ module.exports = (Commands, Cypress, cy) => {
get (selector, options = {}) {
const ctx = this
- if ((options === null) || Array.isArray(options) || (typeof options !== 'object')) {
+ if ((options === null) || _.isArray(options) || !_.isPlainObject(options)) {
return $errUtils.throwErrByPath('get.invalid_options', {
args: { options },
})
@@ -497,7 +497,7 @@ module.exports = (Commands, Cypress, cy) => {
break
case 'existence':
- return err.displayMessage = getErr(err)
+ return err.message = getErr(err)
default:
break
}
diff --git a/packages/driver/src/cy/commands/screenshot.coffee b/packages/driver/src/cy/commands/screenshot.coffee
index 73ca3a12665d..ea9e54ce11ef 100644
--- a/packages/driver/src/cy/commands/screenshot.coffee
+++ b/packages/driver/src/cy/commands/screenshot.coffee
@@ -352,7 +352,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->
isWin = $dom.isWindow(subject)
screenshotConfig = _.pick(options, "capture", "scale", "disableTimersAndAnimations", "blackout", "waitForCommandSynchronization", "padding", "clip", "onBeforeScreenshot", "onAfterScreenshot")
- screenshotConfig = $Screenshot.validate(screenshotConfig, "cy.screenshot", options._log)
+ screenshotConfig = $Screenshot.validate(screenshotConfig, "screenshot", options._log)
screenshotConfig = _.extend($Screenshot.getConfig(), screenshotConfig)
## set this regardless of options.log b/c its used by the
diff --git a/packages/driver/src/cy/commands/traversals.coffee b/packages/driver/src/cy/commands/traversals.coffee
index f5a9a81df5a5..e1226797ec84 100644
--- a/packages/driver/src/cy/commands/traversals.coffee
+++ b/packages/driver/src/cy/commands/traversals.coffee
@@ -59,5 +59,5 @@ module.exports = (Commands, Cypress, cy, state, config) ->
onFail: (err) ->
if err.type is "existence"
node = $dom.stringify(subject, "short")
- err.displayMessage += " Queried from element: #{node}"
- })
+ err.message += " Queried from element: #{node}"
+ })
diff --git a/packages/driver/src/cy/commands/window.coffee b/packages/driver/src/cy/commands/window.coffee
index 4f3a7415491d..d5a62a70bf7c 100644
--- a/packages/driver/src/cy/commands/window.coffee
+++ b/packages/driver/src/cy/commands/window.coffee
@@ -178,7 +178,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->
orientationIsValidAndLandscape = (orientation) =>
if orientation not in validOrientations
- all = validOrientations.join("' or '")
+ all = validOrientations.join("` or `")
$errUtils.throwErrByPath "viewport.invalid_orientation", {
onFail: options._log
args: { all, orientation }
diff --git a/packages/driver/src/cy/commands/xhr.coffee b/packages/driver/src/cy/commands/xhr.coffee
index 37b1eb451d2c..f7b1a5a4c032 100644
--- a/packages/driver/src/cy/commands/xhr.coffee
+++ b/packages/driver/src/cy/commands/xhr.coffee
@@ -168,7 +168,7 @@ startXhrServer = (cy, state, config) ->
log.snapshot("response").end()
onNetworkError: (xhr) ->
- err = $errUtils.cypressErr($errUtils.errMsgByPath("xhr.network_error"))
+ err = $errUtils.cypressErrByPath("xhr.network_error")
if log = logs[xhr.id]
log.snapshot("failed").error(err)
diff --git a/packages/driver/src/cy/ensures.coffee b/packages/driver/src/cy/ensures.coffee
index 091eff0ea1dc..53850c58a37a 100644
--- a/packages/driver/src/cy/ensures.coffee
+++ b/packages/driver/src/cy/ensures.coffee
@@ -71,7 +71,9 @@ create = (state, expect) ->
if types.length > 1
## append a nice error message telling the user this
- err = $errUtils.appendErrMsg(err, "All #{types.length} subject validations failed on this subject.")
+ errProps = $errUtils.appendErrMsg(err, "All #{types.length} subject validations failed on this subject.")
+
+ $errUtils.mergeErrProps(err, errProps)
throw err
@@ -223,7 +225,7 @@ create = (state, expect) ->
## TODO: REFACTOR THIS TO CALL THE CHAI-OVERRIDES DIRECTLY
## OR GO THROUGH I18N
- cy.ensureExistence($el)
+ ensureExistence($el)
ensureElDoesNotHaveCSS = ($el, cssProperty, cssValue, onFail) ->
cmd = state("current").get("name")
diff --git a/packages/driver/src/cy/errors.coffee b/packages/driver/src/cy/errors.coffee
index 812ae4f95854..cf80ad288e9b 100644
--- a/packages/driver/src/cy/errors.coffee
+++ b/packages/driver/src/cy/errors.coffee
@@ -1,5 +1,7 @@
+_ = require("lodash")
$dom = require("../dom")
$errUtils = require("../cypress/error_utils")
+$errorMessages = require('../cypress/error_messages')
crossOriginScriptRe = /^script error/i
@@ -37,28 +39,37 @@ create = (state, config, log) ->
msg = $errUtils.errMsgByPath("uncaught.cross_origin_script")
createErrFromMsg = ->
- new Error $errUtils.errMsgByPath("uncaught.error", { msg, source, lineno })
+ new Error($errUtils.errMsgByPath("uncaught.error", {
+ msg, source, lineno
+ }))
## if we have the 5th argument it means we're in a super
## modern browser making this super simple to work with.
err ?= createErrFromMsg()
- err.name = "Uncaught " + err.name
-
- suffixMsg = switch type
+ uncaughtErrLookup = switch type
when "app" then "uncaught.fromApp"
when "spec" then "uncaught.fromSpec"
- err = $errUtils.appendErrMsg(err, $errUtils.errMsgByPath(suffixMsg))
+ uncaughtErrObj = $errUtils.errObjByPath($errorMessages, uncaughtErrLookup)
+
+ err.name = "Uncaught " + err.name
+
+ uncaughtErrProps = $errUtils.modifyErrMsg(err, uncaughtErrObj.message, (msg1, msg2) ->
+ return "#{msg1}\n\n#{msg2}"
+ )
+ _.defaults(uncaughtErrProps, uncaughtErrObj)
+
+ uncaughtErr = $errUtils.mergeErrProps(err, uncaughtErrProps)
- err.onFail = ->
+ uncaughtErr.onFail = ->
if l = current and current.getLastLog()
- l.error(err)
+ l.error(uncaughtErr)
## normalize error message for firefox
- $errUtils.normalizeErrorStack(err)
+ $errUtils.normalizeErrorStack(uncaughtErr)
- return err
+ return uncaughtErr
commandRunningFailed = (err) ->
## allow for our own custom onFail function
diff --git a/packages/driver/src/cy/retries.coffee b/packages/driver/src/cy/retries.coffee
index 0f672586dda6..8e13f595a81e 100644
--- a/packages/driver/src/cy/retries.coffee
+++ b/packages/driver/src/cy/retries.coffee
@@ -1,6 +1,7 @@
_ = require("lodash")
Promise = require("bluebird")
debug = require('debug')('cypress:driver:retries')
+
$utils = require("../cypress/utils")
$errUtils = require("../cypress/error_utils")
@@ -58,15 +59,19 @@ create = (Cypress, state, timeout, clearTimeout, whenStable, finishAssertions) -
if assertions = options.assertions
finishAssertions(assertions)
- getErrMessage = (err) ->
- _.get(err, 'displayMessage') or
- _.get(err, 'message') or
- err
+ { error, onFail } = options
+
+ prependMsg = $errUtils.errMsgByPath("miscellaneous.retry_timed_out")
+
+ retryErrProps = $errUtils.modifyErrMsg(error, prependMsg, (msg1, msg2) ->
+ return "#{msg2}#{msg1}"
+ )
+
+ retryErr = $errUtils.mergeErrProps(error, retryErrProps)
- $errUtils.throwErrByPath "miscellaneous.retry_timed_out", {
- onFail: (options.onFail or log)
- args: { error: getErrMessage(options.error) }
- }
+ $errUtils.throwErr(retryErr, {
+ onFail: onFail or log
+ })
runnableHasChanged = ->
## if we've changed runnables don't retry!
diff --git a/packages/driver/src/cypress.js b/packages/driver/src/cypress.js
index 0e00357f9d07..ad0c2161f026 100644
--- a/packages/driver/src/cypress.js
+++ b/packages/driver/src/cypress.js
@@ -54,7 +54,7 @@ $Log.command = () => {
return $errUtils.throwErrByPath('miscellaneous.command_log_renamed')
}
-const throwDeprecatedCommandInterface = (key, method) => {
+const throwDeprecatedCommandInterface = (key = 'commandName', method) => {
let signature = ''
switch (method) {
@@ -326,9 +326,12 @@ class $Cypress {
case 'runner:fail': {
// mocha runner calculated a failure
-
const err = args[0].err
+ if (err.type === 'existence' || $dom.isDom(err.actual) || $dom.isDom(err.expected)) {
+ err.showDiff = false
+ }
+
if (err.actual) {
err.actual = chai.util.inspect(err.actual)
}
@@ -504,7 +507,7 @@ class $Cypress {
// attaching long stace traces
// which otherwise make this err
// unusably long
- const err = $errUtils.cloneErr(e)
+ const err = $errUtils.makeErrFromObj(e)
err.__stackCleaned__ = true
err.backend = true
@@ -526,7 +529,7 @@ class $Cypress {
const e = reply.error
if (e) {
- const err = $errUtils.cloneErr(e)
+ const err = $errUtils.makeErrFromObj(e)
err.automation = true
diff --git a/packages/driver/src/cypress/cy.js b/packages/driver/src/cypress/cy.js
index 840c64e57146..453f62b9fb3b 100644
--- a/packages/driver/src/cypress/cy.js
+++ b/packages/driver/src/cypress/cy.js
@@ -105,9 +105,9 @@ const create = function (specWindow, Cypress, Cookies, state, config, log) {
const warnMixingPromisesAndCommands = function () {
const title = state('runnable').fullTitle()
- const msg = $errUtils.errMsgByPath('miscellaneous.mixing_promises_and_commands', title)
-
- return $utils.warning(msg)
+ $errUtils.warnByPath('miscellaneous.mixing_promises_and_commands', {
+ args: { title },
+ })
}
const $$ = function (selector, context) {
@@ -678,7 +678,9 @@ const create = function (specWindow, Cypress, Cookies, state, config, log) {
stopped = true
- $errUtils.normalizeErrorStack(err)
+ err = $errUtils.normalizeErrorStack(err)
+
+ err = $errUtils.processErr(err, config)
// store the error on state now
state('error', err)
@@ -718,12 +720,13 @@ const create = function (specWindow, Cypress, Cookies, state, config, log) {
rets = Cypress.action('cy:fail', err, state('runnable'))
} catch (err2) {
$errUtils.normalizeErrorStack(err2)
+
// and if any of these throw synchronously immediately error
- finish(err2)
+ return finish(err2)
}
// bail if we had callbacks attached
- if (rets.length) {
+ if (rets && rets.length) {
return
}
@@ -1269,7 +1272,7 @@ const create = function (specWindow, Cypress, Cookies, state, config, log) {
$utils.stringify(ret)
$errUtils.throwErrByPath('miscellaneous.returned_value_and_commands', {
- args: ret,
+ args: { returned: ret },
})
}
diff --git a/packages/driver/src/cypress/error_messages.coffee b/packages/driver/src/cypress/error_messages.coffee
index ede78696fff0..9663e68c460c 100644
--- a/packages/driver/src/cypress/error_messages.coffee
+++ b/packages/driver/src/cypress/error_messages.coffee
@@ -14,9 +14,9 @@ format = (data) ->
formatConfigFile = (configFile) ->
if configFile == false
- return "'cypress.json' (currently disabled by --config-file=false)"
+ return "`cypress.json` (currently disabled by --config-file=false)"
- return "'#{format(configFile)}'"
+ return "`#{format(configFile)}`"
formatRedirect = (redirect) -> " - #{redirect}"
@@ -31,7 +31,12 @@ formatProp = (memo, field) ->
memo
cmd = (command, args = "") ->
- "cy.#{command}(#{args})"
+ prefix = if command.startsWith("Cypress") then "" else "cy."
+
+ "`#{prefix}#{command}(#{args})`"
+
+getScreenshotDocsPath = (cmd) ->
+ if cmd is "Cypress.Screenshot.defaults" then "screenshot-api" else "screenshot"
getRedirects = (obj, phrase) ->
redirects = obj.redirects ? []
@@ -57,34 +62,58 @@ getHttpProps = (fields = []) ->
module.exports = {
add:
- type_missing: "Cypress.add(key, fn, type) must include a type!"
+ type_missing: "`Cypress.add(key, fn, type)` must include a type!"
agents:
- deprecated_warning: "cy.agents() is deprecated. Use cy.stub() and cy.spy() instead."
+ deprecated_warning: "#{cmd('agents')} is deprecated. Use #{cmd('stub')} and #{cmd('spy')} instead."
alias:
- invalid: "Invalid alias: '{{name}}'.\nYou forgot the '@'. It should be written as: '@{{displayName}}'."
- not_registered_with_available: "#{cmd('{{cmd}}')} could not find a registered alias for: '@{{displayName}}'.\nAvailable aliases are: '{{availableAliases}}'."
- not_registered_without_available: "#{cmd('{{cmd}}')} could not find a registered alias for: '@{{displayName}}'.\nYou have not aliased anything yet."
+ invalid: "Invalid alias: `{{name}}`.\nYou forgot the `@`. It should be written as: `@{{displayName}}`."
+ not_registered_with_available: "#{cmd('{{cmd}}')} could not find a registered alias for: `@{{displayName}}`.\nAvailable aliases are: `{{availableAliases}}`."
+ not_registered_without_available: "#{cmd('{{cmd}}')} could not find a registered alias for: `@{{displayName}}`.\nYou have not aliased anything yet."
as:
- empty_string: "#{cmd('as')} cannot be passed an empty string."
- invalid_type: "#{cmd('as')} can only accept a string."
- invalid_first_token: "'{{alias}}' cannot be named starting with the '@' symbol. Try renaming the alias to '{{suggestedName}}', or something else that does not start with the '@' symbol."
- reserved_word: "#{cmd('as')} cannot be aliased as: '{{alias}}'. This word is reserved."
+ empty_string: {
+ message: "#{cmd('as')} cannot be passed an empty string."
+ docsUrl: "https://on.cypress.io/as"
+ }
+ invalid_type: {
+ message: "#{cmd('as')} can only accept a string."
+ docsUrl: "https://on.cypress.io/as"
+ }
+ invalid_first_token: {
+ message: "`{{alias}}` cannot be named starting with the `@` symbol. Try renaming the alias to `{{suggestedName}}`, or something else that does not start with the `@` symbol."
+ docsUrl: "https://on.cypress.io/as"
+ }
+ reserved_word: {
+ message: "#{cmd('as')} cannot be aliased as: `{{alias}}`. This word is reserved."
+ docsUrl: "https://on.cypress.io/as"
+ }
blur:
- multiple_elements: "#{cmd('blur')} can only be called on a single element. Your subject contained {{num}} elements."
- no_focused_element: "#{cmd('blur')} can only be called when there is a currently focused element."
- timed_out: "#{cmd('blur')} timed out because your browser did not receive any blur events. This is a known bug in Chrome when it is not the currently focused window."
- wrong_focused_element: "#{cmd('blur')} can only be called on the focused element. Currently the focused element is a: {{node}}"
+ multiple_elements: {
+ message: "#{cmd('blur')} can only be called on a single element. Your subject contained {{num}} elements."
+ docsUrl: "https://on.cypress.io/blur"
+ }
+ no_focused_element: {
+ message: "#{cmd('blur')} can only be called when there is a currently focused element."
+ docsUrl: "https://on.cypress.io/blur"
+ }
+ timed_out: {
+ message: "#{cmd('blur')} timed out because your browser did not receive any `blur` events. This is a known bug in Chrome when it is not the currently focused window."
+ docsUrl: "https://on.cypress.io/blur"
+ }
+ wrong_focused_element: {
+ message: "#{cmd('blur')} can only be called on the focused element. Currently the focused element is a: `{{node}}`"
+ docsUrl: "https://on.cypress.io/blur"
+ }
browser:
- invalid_arg: "Cypress.{{method}}() must be passed the name of a browser or an object to filter with. You passed: {{obj}}"
+ invalid_arg: "`Cypress.{{method}}()` must be passed the name of a browser or an object to filter with. You passed: `{{obj}}`"
chai:
- length_invalid_argument: "You must provide a valid number to a length assertion. You passed: '{{length}}'"
- match_invalid_argument: "'match' requires its argument be a 'RegExp'. You passed: '{{regExp}}'"
+ length_invalid_argument: "You must provide a valid number to a `length` assertion. You passed: `{{length}}`"
+ match_invalid_argument: "`match` requires its argument be a `RegExp`. You passed: `{{regExp}}`"
invalid_jquery_obj: (obj) ->
"""
You attempted to make a chai-jQuery assertion on an object that is neither a DOM object or a jQuery object.
@@ -102,366 +131,510 @@ module.exports = {
This can sometimes happen if a previous assertion changed the subject.
"""
- chain:
- removed: """
- #{cmd('chain')} was an undocumented command that has now been removed.
-
- You can safely remove this from your code and it should work without it.
- """
-
check_uncheck:
- invalid_element: "#{cmd('{{cmd}}')} can only be called on :checkbox{{phrase}}. Your subject {{word}} a: {{node}}"
+ invalid_element: {
+ message: "#{cmd('{{cmd}}')} can only be called on `:checkbox`{{phrase}}. Your subject {{word}} a: `{{node}}`"
+ docsUrl: "https://on.cypress.io/{{cmd}}"
+ }
clear:
- invalid_element: """
- #{cmd('clear')} failed because it requires a valid clearable element.
-
- The element cleared was:
-
- > {{node}}
-
- A clearable element matches one of the following selectors:
- 'a[href]'
- 'area[href]'
- 'input'
- 'select'
- 'textarea'
- 'button'
- 'iframe'
- '[tabindex]'
- '[contenteditable]'
- """
+ invalid_element: {
+ message: """
+ #{cmd('clear')} failed because it requires a valid clearable element.
+
+ The element cleared was:
+
+ > `{{node}}`
+
+ A clearable element matches one of the following selectors:
+ 'a[href]'
+ 'area[href]'
+ 'input'
+ 'select'
+ 'textarea'
+ 'button'
+ 'iframe'
+ '[tabindex]'
+ '[contenteditable]'
+ """
+ docsUrl: "https://on.cypress.io/clear"
+ }
clearCookie:
- invalid_argument: "#{cmd('clearCookie')} must be passed a string argument for name."
+ invalid_argument: {
+ message: "#{cmd('clearCookie')} must be passed a string argument for name."
+ docsUrl: "https://on.cypress.io/clearcookie"
+ }
clearLocalStorage:
- invalid_argument: "#{cmd('clearLocalStorage')} must be called with either a string or regular expression."
+ invalid_argument: {
+ message: "#{cmd('clearLocalStorage')} must be called with either a string or regular expression."
+ docsUrl: "https://on.cypress.io/clearlocalstorage"
+ }
click:
- multiple_elements: "#{cmd('{{cmd}}')} can only be called on a single element. Your subject contained {{num}} elements. Pass { multiple: true } if you want to serially click each element."
- on_select_element: "#{cmd('{{cmd}}')} cannot be called on a
diff --git a/packages/driver/test/cypress/integration/commands/actions/check_spec.coffee b/packages/driver/test/cypress/integration/commands/actions/check_spec.coffee
index e0f5ed522588..291a69f41072 100644
--- a/packages/driver/test/cypress/integration/commands/actions/check_spec.coffee
+++ b/packages/driver/test/cypress/integration/commands/actions/check_spec.coffee
@@ -75,7 +75,7 @@ describe "src/cy/commands/actions/check", ->
done("should not fire change event")
cy.get(checkbox).check()
-
+
## readonly should only be limited to inputs, not checkboxes
it "can check readonly checkboxes", ->
cy.get('#readonly-checkbox').check().then ($checkbox) ->
@@ -234,14 +234,15 @@ describe "src/cy/commands/actions/check", ->
cy.on "fail", (err) ->
expect(checked).to.eq 1
- expect(err.message).to.include "cy.check() failed because this element"
+ expect(err.message).to.include "`cy.check()` failed because this element"
done()
cy.get(":checkbox:first").check().check()
it "throws when subject isnt a checkbox or radio", (done) ->
cy.on "fail", (err) ->
- expect(err.message).to.include "cy.check() can only be called on :checkbox and :radio. Your subject contains a: "
+ expect(err.message).to.include "`cy.check()` can only be called on `:checkbox` and `:radio`. Your subject contains a: ``"
+ expect(err.docsUrl).to.include("https://on.cypress.io/check")
done()
## this will find multiple forms
@@ -249,7 +250,8 @@ describe "src/cy/commands/actions/check", ->
it "throws when any member of the subject isnt a checkbox or radio", (done) ->
cy.on "fail", (err) ->
- expect(err.message).to.include "cy.check() can only be called on :checkbox and :radio. Your subject contains a: "
+ expect(err.message).to.include "`cy.check()` can only be called on `:checkbox` and `:radio`. Your subject contains a: ``"
+ expect(err.docsUrl).to.include("https://on.cypress.io/check")
done()
## find a textare which should blow up
@@ -264,7 +266,7 @@ describe "src/cy/commands/actions/check", ->
expect(@logs.length).to.eq(chk.length + 1)
expect(lastLog.get("error")).to.eq(err)
- expect(err.message).to.include "cy.check() failed because this element is not visible"
+ expect(err.message).to.include "`cy.check()` failed because this element is not visible"
done()
cy.get(":checkbox:first").check()
@@ -275,7 +277,7 @@ describe "src/cy/commands/actions/check", ->
cy.on "fail", (err) =>
## get + type logs
expect(@logs.length).eq(2)
- expect(err.message).to.include("cy.check() failed because this element is disabled:\n")
+ expect(err.message).to.include("`cy.check()` failed because this element is `disabled`:\n")
done()
cy.get(":checkbox:first").check()
@@ -289,7 +291,7 @@ describe "src/cy/commands/actions/check", ->
expect(@logs.length).to.eq(chk.length + 1)
expect(lastLog.get("error")).to.eq(err)
- expect(err.message).to.include "cy.check() failed because this element is not visible"
+ expect(err.message).to.include "`cy.check()` failed because this element is not visible"
done()
cy.get(":checkbox").check()
@@ -302,7 +304,7 @@ describe "src/cy/commands/actions/check", ->
expect(lastLog.get("error")).to.eq(err)
done()
- cy.check()
+ `cy.check()`
it "throws when input cannot be clicked", (done) ->
checkbox = $("").attr("id", "checkbox-covered-in-span").prependTo($("body"))
@@ -310,7 +312,7 @@ describe "src/cy/commands/actions/check", ->
cy.on "fail", (err) =>
expect(@logs.length).to.eq(2)
- expect(err.message).to.include "cy.check() failed because this element"
+ expect(err.message).to.include "`cy.check()` failed because this element"
expect(err.message).to.include "is being covered by another element"
done()
@@ -652,7 +654,8 @@ describe "src/cy/commands/actions/check", ->
cy.get(":radio").uncheck()
cy.on "fail", (err) ->
- expect(err.message).to.include "cy.uncheck() can only be called on :checkbox."
+ expect(err.message).to.include "`cy.uncheck()` can only be called on `:checkbox`."
+ expect(err.docsUrl).to.include("https://on.cypress.io/uncheck")
done()
it "throws if not a checkbox", (done) ->
@@ -670,7 +673,7 @@ describe "src/cy/commands/actions/check", ->
len = (chk.length * 2) + 6
expect(@logs.length).to.eq(len)
expect(lastLog.get("error")).to.eq(err)
- expect(err.message).to.include "cy.uncheck() failed because this element is not visible"
+ expect(err.message).to.include "`cy.uncheck()` failed because this element is not visible"
done()
cy
@@ -685,7 +688,7 @@ describe "src/cy/commands/actions/check", ->
expect(lastLog.get("error")).to.eq(err)
done()
- cy.uncheck()
+ `cy.uncheck()`
it "throws when subject is not in the document", (done) ->
unchecked = 0
@@ -698,7 +701,7 @@ describe "src/cy/commands/actions/check", ->
cy.on "fail", (err) ->
expect(unchecked).to.eq 1
- expect(err.message).to.include "cy.uncheck() failed because this element"
+ expect(err.message).to.include "`cy.uncheck()` failed because this element"
done()
cy.get(":checkbox:first").uncheck().uncheck()
@@ -709,7 +712,7 @@ describe "src/cy/commands/actions/check", ->
cy.on "fail", (err) =>
expect(@logs.length).to.eq(2)
- expect(err.message).to.include "cy.uncheck() failed because this element"
+ expect(err.message).to.include "`cy.uncheck()` failed because this element"
expect(err.message).to.include "is being covered by another element"
done()
@@ -721,7 +724,7 @@ describe "src/cy/commands/actions/check", ->
cy.on "fail", (err) =>
## get + type logs
expect(@logs.length).eq(2)
- expect(err.message).to.include("cy.uncheck() failed because this element is disabled:\n")
+ expect(err.message).to.include("`cy.uncheck()` failed because this element is `disabled`:\n")
done()
cy.get(":checkbox:first").uncheck()
diff --git a/packages/driver/test/cypress/integration/commands/actions/clear_spec.js b/packages/driver/test/cypress/integration/commands/actions/clear_spec.js
index 74141880c528..684d6aceefa0 100644
--- a/packages/driver/test/cypress/integration/commands/actions/clear_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/clear_spec.js
@@ -156,8 +156,6 @@ describe('src/cy/commands/actions/type - #clear', () => {
this.lastLog = log
}
})
-
- null
})
it('eventually passes the assertion', () => {
@@ -201,8 +199,6 @@ describe('src/cy/commands/actions/type - #clear', () => {
this.logs.push(log)
})
-
- null
})
it('throws when not a dom subject', (done) => {
@@ -224,7 +220,7 @@ describe('src/cy/commands/actions/type - #clear', () => {
cy.on('fail', (err) => {
expect(cleared).to.be.calledOnce
- expect(err.message).to.include('cy.clear() failed because this element')
+ expect(err.message).to.include('`cy.clear()` failed because this element')
done()
})
@@ -238,10 +234,11 @@ describe('src/cy/commands/actions/type - #clear', () => {
expect(this.logs.length).to.eq(3)
expect(lastLog.get('error')).to.eq(err)
- expect(err.message).to.include('cy.clear() failed because it requires a valid clearable element.')
+ expect(err.message).to.include('`cy.clear()` failed because it requires a valid clearable element.')
expect(err.message).to.include('The element cleared was:')
- expect(err.message).to.include('')
+ expect(err.message).to.include('``')
expect(err.message).to.include(`A clearable element matches one of the following selectors:`)
+ expect(err.docsUrl).to.equal('https://on.cypress.io/clear')
done()
})
@@ -251,10 +248,11 @@ describe('src/cy/commands/actions/type - #clear', () => {
it('throws if any subject isnt a :text', (done) => {
cy.on('fail', (err) => {
- expect(err.message).to.include('cy.clear() failed because it requires a valid clearable element.')
+ expect(err.message).to.include('`cy.clear()` failed because it requires a valid clearable element.')
expect(err.message).to.include('The element cleared was:')
- expect(err.message).to.include('
...
')
+ expect(err.message).to.include('`
...
`')
expect(err.message).to.include(`A clearable element matches one of the following selectors:`)
+ expect(err.docsUrl).to.equal('https://on.cypress.io/clear')
done()
})
@@ -264,10 +262,11 @@ describe('src/cy/commands/actions/type - #clear', () => {
it('throws on an input radio', (done) => {
cy.on('fail', (err) => {
- expect(err.message).to.include('cy.clear() failed because it requires a valid clearable element.')
+ expect(err.message).to.include('`cy.clear()` failed because it requires a valid clearable element.')
expect(err.message).to.include('The element cleared was:')
- expect(err.message).to.include('')
+ expect(err.message).to.include('``')
expect(err.message).to.include(`A clearable element matches one of the following selectors:`)
+ expect(err.docsUrl).to.equal('https://on.cypress.io/clear')
done()
})
@@ -276,10 +275,11 @@ describe('src/cy/commands/actions/type - #clear', () => {
it('throws on an input checkbox', (done) => {
cy.on('fail', (err) => {
- expect(err.message).to.include('cy.clear() failed because it requires a valid clearable element.')
+ expect(err.message).to.include('`cy.clear()` failed because it requires a valid clearable element.')
expect(err.message).to.include('The element cleared was:')
- expect(err.message).to.include('')
+ expect(err.message).to.include('``')
expect(err.message).to.include(`A clearable element matches one of the following selectors:`)
+ expect(err.docsUrl).to.equal('https://on.cypress.io/clear')
done()
})
@@ -291,7 +291,7 @@ describe('src/cy/commands/actions/type - #clear', () => {
cy.$$('input:text:first').show().hide()
cy.on('fail', (err) => {
- expect(err.message).to.include('cy.clear() failed because this element is not visible')
+ expect(err.message).to.include('`cy.clear()` failed because this element is not visible')
done()
})
@@ -305,7 +305,7 @@ describe('src/cy/commands/actions/type - #clear', () => {
cy.on('fail', (err) => {
// get + type logs
expect(this.logs.length).eq(2)
- expect(err.message).to.include('cy.clear() failed because this element is disabled:\n')
+ expect(err.message).to.include('`cy.clear()` failed because this element is `disabled`:\n')
done()
})
@@ -345,7 +345,7 @@ describe('src/cy/commands/actions/type - #clear', () => {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(2)
- expect(err.message).to.include('cy.clear() failed because this element')
+ expect(err.message).to.include('`cy.clear()` failed because this element')
expect(err.message).to.include('is being covered by another element')
done()
diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.js b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
index bd90398306ed..4bf49661ada4 100644
--- a/packages/driver/test/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
@@ -873,7 +873,7 @@ describe('src/cy/commands/actions/click', () => {
const onError = cy.stub().callsFake((err) => {
const { lastLog } = this
- expect(err.message).to.contain(`has CSS 'pointer-events: none'`)
+ expect(err.message).to.contain('has CSS `pointer-events: none`')
expect(err.message).to.not.contain('inherited from')
const consoleProps = lastLog.invoke('consoleProps')
@@ -899,7 +899,7 @@ describe('src/cy/commands/actions/click', () => {
const onError = cy.stub().callsFake((err) => {
const { lastLog } = this
- expect(err.message).to.contain(`has CSS 'pointer-events: none', inherited from this element:`)
+ expect(err.message).to.contain('has CSS `pointer-events: none`, inherited from this element:')
expect(err.message).to.contain('
{
it('throws when attempting to click multiple elements', (done) => {
cy.on('fail', (err) => {
- expect(err.message).to.eq('cy.click() can only be called on a single element. Your subject contained 4 elements. Pass { multiple: true } if you want to serially click each element.')
-
+ expect(err.message).to.eq(`\`cy.click()\` can only be called on a single element. Your subject contained 4 elements. Pass \`{ multiple: true }\` if you want to serially click each element.`)
+ expect(err.docsUrl).to.eq('https://on.cypress.io/click')
done()
})
@@ -1904,7 +1904,7 @@ describe('src/cy/commands/actions/click', () => {
cy.on('fail', (err) => {
expect(clicked).to.eq(1)
- expect(err.message).to.include('cy.click() failed because this element is detached from the DOM')
+ expect(err.message).to.include('`cy.click()` failed because this element is detached from the DOM')
done()
})
@@ -1914,7 +1914,7 @@ describe('src/cy/commands/actions/click', () => {
it('throws when subject is detached during actionability', (done) => {
cy.on('fail', (err) => {
- expect(err.message).to.include('cy.click() failed because this element is detached from the DOM')
+ expect(err.message).to.include('`cy.click()` failed because this element is detached from the DOM')
done()
})
@@ -1958,7 +1958,7 @@ describe('src/cy/commands/actions/click', () => {
expect(logsArr).to.have.length(4)
expect(lastLog.get('error')).to.eq(err)
- expect(err.message).to.include('cy.click() failed because this element is not visible')
+ expect(err.message).to.include('`cy.click()` failed because this element is not visible')
done()
})
@@ -1972,7 +1972,7 @@ describe('src/cy/commands/actions/click', () => {
cy.on('fail', (err) => {
// get + click logs
expect(this.logs.length).eq(2)
- expect(err.message).to.include('cy.click() failed because this element is disabled:\n')
+ expect(err.message).to.include('`cy.click()` failed because this element is `disabled`:\n')
done()
})
@@ -1997,8 +1997,9 @@ describe('src/cy/commands/actions/click', () => {
expect(lastLog.get('snapshots')[0].name).to.eq('before')
expect(lastLog.get('snapshots')[1]).to.be.an('object')
expect(lastLog.get('snapshots')[1].name).to.eq('after')
- expect(err.message).to.include('cy.click() failed because this element')
+ expect(err.message).to.include('`cy.click()` failed because this element')
expect(err.message).to.include('is being covered by another element')
+ expect(err.docsUrl).to.eq('https://on.cypress.io/element-cannot-be-interacted-with')
const clickLog = this.logs[1]
@@ -2032,8 +2033,9 @@ describe('src/cy/commands/actions/click', () => {
expect(lastLog.get('snapshots')[0].name).to.eq('before')
expect(lastLog.get('snapshots')[1]).to.be.an('object')
expect(lastLog.get('snapshots')[1].name).to.eq('after')
- expect(err.message).to.include('cy.click() failed because this element')
+ expect(err.message).to.include('`cy.click()` failed because this element')
expect(err.message).to.include('is being covered by another element')
+ expect(err.docsUrl).to.eq('https://on.cypress.io/element-cannot-be-interacted-with')
const console = lastLog.invoke('consoleProps')
@@ -2069,10 +2071,11 @@ describe('src/cy/commands/actions/click', () => {
expect(lastLog.get('snapshots')[0].name).to.eq('before')
expect(lastLog.get('snapshots')[1]).to.be.an('object')
expect(lastLog.get('snapshots')[1].name).to.eq('after')
- expect(err.message).to.include('cy.click() failed because this element is not visible:')
+ expect(err.message).to.include('`cy.click()` failed because this element is not visible:')
expect(err.message).to.include('>button ...')
- expect(err.message).to.include(`'