Skip to content
26 changes: 14 additions & 12 deletions packages/driver/cypress/integration/cypress/error_utils_spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// @ts-nocheck

import { allowTsModuleStubbing } from '../../support/helpers'

allowTsModuleStubbing()

import $stackUtils from '@packages/driver/src/cypress/stack_utils'
import $errUtils from '@packages/driver/src/cypress/error_utils'
import $errUtils, { CypressError } from '@packages/driver/src/cypress/error_utils'
import $errorMessages from '@packages/driver/src/cypress/error_messages'

describe('driver/src/cypress/error_utils', () => {
Expand Down Expand Up @@ -76,6 +74,7 @@ describe('driver/src/cypress/error_utils', () => {
it('throws error when it is an error', () => {
const err = new Error('Something unexpected')

// @ts-ignore
err.extraProp = 'extra prop'
const fn = () => {
$errUtils.throwErr(err)
Expand Down Expand Up @@ -117,6 +116,7 @@ describe('driver/src/cypress/error_utils', () => {

context('.errByPath', () => {
beforeEach(() => {
// @ts-ignore
$errorMessages.__test_errors = {
obj: {
message: 'This is a simple error message',
Expand Down Expand Up @@ -186,7 +186,7 @@ describe('driver/src/cypress/error_utils', () => {

describe('when message value is an object', () => {
it('has correct name, message, and docs url when path exists', () => {
const err = $errUtils.errByPath('__test_errors.obj')
const err = $errUtils.errByPath('__test_errors.obj') as CypressError

expect(err.name).to.eq('CypressError')
expect(err.message).to.include('This is a simple error message')
Expand All @@ -196,7 +196,7 @@ describe('driver/src/cypress/error_utils', () => {
it('uses args provided for the error', () => {
const err = $errUtils.errByPath('__test_errors.obj_with_args', {
foo: 'foo', bar: ['bar', 'qux'],
})
}) as CypressError

expect(err.message).to.include('This has args like \'foo\' and bar,qux')
expect(err.docsUrl).to.include('https://on.link.io')
Expand All @@ -205,7 +205,7 @@ describe('driver/src/cypress/error_utils', () => {
it('handles args being used multiple times in message', () => {
const err = $errUtils.errByPath('__test_errors.obj_with_multi_args', {
foo: 'foo', bar: ['bar', 'qux'],
})
}) as CypressError

expect(err.message).to.include('This has args like \'foo\' and bar,qux, and \'foo\' is used twice')
expect(err.docsUrl).to.include('https://on.link.io')
Expand All @@ -214,7 +214,7 @@ describe('driver/src/cypress/error_utils', () => {
it('formats markdown in the error message', () => {
const err = $errUtils.errByPath('__test_errors.obj_with_markdown', {
foo: 'foo', bar: ['bar', 'qux'],
})
}) as CypressError

expect(err.message).to.include('This has markdown like `foo`, *bar,qux*, **foo**, and _bar,qux_')
expect(err.docsUrl).to.include('https://on.link.io')
Expand All @@ -223,7 +223,7 @@ describe('driver/src/cypress/error_utils', () => {

describe('when message value is a string', () => {
it('has correct name, message, and docs url', () => {
const err = $errUtils.errByPath('__test_errors.str')
const err = $errUtils.errByPath('__test_errors.str') as CypressError

expect(err.name).to.eq('CypressError')
expect(err.message).to.include('This is a simple error message')
Expand Down Expand Up @@ -299,7 +299,7 @@ describe('driver/src/cypress/error_utils', () => {
})

it('has the right message and docs url', () => {
const err = $errUtils.errByPath('__test_errors.fn_returns_obj')
const err = $errUtils.errByPath('__test_errors.fn_returns_obj') as CypressError

expect(err.message).to.include('This is a simple error message')
expect(err.docsUrl).to.include('https://on.link.io')
Expand All @@ -310,7 +310,7 @@ describe('driver/src/cypress/error_utils', () => {
it('uses them in the error message', () => {
const err = $errUtils.errByPath('__test_errors.fn_returns_obj_with_args', {
foo: 'foo', bar: ['bar', 'qux'],
})
}) as CypressError

expect(err.message).to.include('This has args like \'foo\' and bar,qux')
expect(err.docsUrl).to.include('https://on.link.io')
Expand All @@ -321,7 +321,7 @@ describe('driver/src/cypress/error_utils', () => {
it('uses them in the error message', () => {
const err = $errUtils.errByPath('__test_errors.fn_returns_obj_with_multi_args', {
foo: 'foo', bar: ['bar', 'qux'],
})
}) as CypressError

expect(err.message).to.include('This has args like \'foo\' and bar,qux, and \'foo\' is used twice')
expect(err.docsUrl).to.include('https://on.link.io')
Expand All @@ -334,6 +334,7 @@ describe('driver/src/cypress/error_utils', () => {
let fn

beforeEach(() => {
// @ts-ignore
$errorMessages.__test_errors = {
test: 'Simple error {{message}}',
}
Expand Down Expand Up @@ -370,6 +371,7 @@ describe('driver/src/cypress/error_utils', () => {

context('.throwErrByPath', () => {
it('looks up error and throws it', () => {
// @ts-ignore
$errorMessages.__test_error = 'simple error message'

const fn = () => $errUtils.throwErrByPath('__test_error')
Expand Down Expand Up @@ -598,7 +600,7 @@ describe('driver/src/cypress/error_utils', () => {
context('Error.captureStackTrace', () => {
it('works - even where not natively support', () => {
function removeMe2 () {
const err = {}
const err: Record<string, any> = {}

Error.captureStackTrace(err, removeMeAndAbove)

Expand Down
146 changes: 100 additions & 46 deletions packages/driver/src/cypress.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
// @ts-nocheck

import { validate, validateNoReadOnlyConfig } from '@packages/config'
import _ from 'lodash'
import $ from 'jquery'
Expand Down Expand Up @@ -42,6 +40,15 @@ import * as resolvers from './cypress/resolvers'

const debug = debugFn('cypress:driver:cypress')

declare global {
interface Window {
__cySkipValidateConfig: boolean
Cypress: Cypress.Cypress
Runner: any
cy: Cypress.cy
}
}

const jqueryProxyFn = function (...args) {
if (!this.cy) {
$errUtils.throwErrByPath('miscellaneous.no_cy')
Expand All @@ -56,7 +63,83 @@ const throwPrivateCommandInterface = (method) => {
})
}

interface BackendError extends Error {
__stackCleaned__: boolean
backend: boolean
}

interface AutomationError extends Error {
automation: boolean
}

class $Cypress {
cy: any
chai: any
mocha: any
runner: any
downloads: any
Commands: any
$autIframe: any
onSpecReady: any
events: any
$: any
arch: any
spec: any
version: any
browser: any
platform: any
testingType: any
state: any
originalConfig: any
config: any
env: any
getTestRetries: any
Cookies: any
ProxyLogging: any
_onInitialize: any
isCy: any
log: any
isBrowser: any
emit: any
emitThen: any
emitMap: any

// attach to $Cypress to access
// all of the constructors
// to enable users to monkeypatch
$Cypress = $Cypress
Cy = $Cy
Chainer = $Chainer
Command = $Command
dom = $dom
errorMessages = $errorMessages
Keyboard = $Keyboard
Location = $Location
Log = $Log
LocalStorage = $LocalStorage
Mocha = $Mocha
resolveWindowReference = resolvers.resolveWindowReference
resolveLocationReference = resolvers.resolveLocationReference
Mouse = {
create: createMouse,
}

Runner = $Runner
Server = $Server
Screenshot = $Screenshot
SelectorPlayground = $SelectorPlayground
utils = $utils
_ = _
Blob = blobUtil
Buffer = Buffer
Promise = Promise
minimatch = minimatch
sinon = sinon
lolex = fakeTimers

static $: any
static utils: any

constructor (config = {}) {
this.cy = null
this.chai = null
Expand All @@ -75,7 +158,7 @@ class $Cypress {
this.setConfig(config)
}

setConfig (config = {}) {
setConfig (config: Record<string, any> = {}) {
// config.remote
// {
// origin: "http://localhost:2020"
Expand Down Expand Up @@ -144,7 +227,7 @@ class $Cypress {
this.state = $SetterGetter.create({})
this.originalConfig = _.cloneDeep(config)
this.config = $SetterGetter.create(config, (config) => {
if (!window.top.__cySkipValidateConfig) {
if (!window.top!.__cySkipValidateConfig) {
validateNoReadOnlyConfig(config, (errProperty) => {
const errPath = this.state('runnable')
? 'config.invalid_cypress_config_override'
Expand Down Expand Up @@ -191,6 +274,8 @@ class $Cypress {

this.Cookies = $Cookies.create(config.namespace, d)

// TODO: Remove this after $Events functions are added to $Cypress.
// @ts-ignore
this.ProxyLogging = new ProxyLogging(this)

return this.action('cypress:config', config)
Expand All @@ -216,15 +301,15 @@ class $Cypress {
// Method to manually re-execute Runner (usually within $autIframe)
// used mainly by Component Testing
restartRunner () {
if (!window.top.Cypress) {
if (!window.top!.Cypress) {
throw Error('Cannot re-run spec without Cypress')
}

// MobX state is only available on the Runner instance
// which is attached to the top level `window`
// We avoid infinite restart loop by checking if not in a loading state.
if (!window.top.Runner.state.isLoading) {
window.top.Runner.emit('restart')
if (!window.top!.Runner.state.isLoading) {
window.top!.Runner.emit('restart')
}
}

Expand All @@ -249,6 +334,8 @@ class $Cypress {
this.events.proxyTo(this.cy)

$scriptUtils.runScripts(specWindow, scripts)
// TODO: remove this after making the type of `runScripts` more specific.
// @ts-ignore
.catch((error) => {
this.runner.onSpecError('error')({ error })
})
Expand All @@ -275,6 +362,8 @@ class $Cypress {
return this.backend('firefox:window:focus')
}
}

return
})
.then(() => {
this.cy.initialize(this.$autIframe)
Expand Down Expand Up @@ -589,7 +678,7 @@ class $Cypress {
// attaching long stace traces
// which otherwise make this err
// unusably long
const err = $errUtils.makeErrFromObj(e)
const err = $errUtils.makeErrFromObj(e) as BackendError

err.__stackCleaned__ = true
err.backend = true
Expand All @@ -611,7 +700,7 @@ class $Cypress {
const e = reply.error

if (e) {
const err = $errUtils.makeErrFromObj(e)
const err = $errUtils.makeErrFromObj(e) as AutomationError

err.automation = true

Expand Down Expand Up @@ -670,43 +759,8 @@ class $Cypress {
}
}

// // attach to $Cypress to access
// // all of the constructors
// // to enable users to monkeypatch
$Cypress.prototype.$Cypress = $Cypress
$Cypress.prototype.Cy = $Cy
$Cypress.prototype.Chainer = $Chainer
$Cypress.prototype.Cookies = $Cookies
$Cypress.prototype.Command = $Command
$Cypress.prototype.Commands = $Commands
$Cypress.prototype.dom = $dom
$Cypress.prototype.errorMessages = $errorMessages
$Cypress.prototype.Keyboard = $Keyboard
$Cypress.prototype.Location = $Location
$Cypress.prototype.Log = $Log
$Cypress.prototype.LocalStorage = $LocalStorage
$Cypress.prototype.Mocha = $Mocha
$Cypress.prototype.resolveWindowReference = resolvers.resolveWindowReference
$Cypress.prototype.resolveLocationReference = resolvers.resolveLocationReference
$Cypress.prototype.Mouse = {
create: createMouse,
}

$Cypress.prototype.Runner = $Runner
$Cypress.prototype.Server = $Server
$Cypress.prototype.Screenshot = $Screenshot
$Cypress.prototype.SelectorPlayground = $SelectorPlayground
$Cypress.prototype.utils = $utils
$Cypress.prototype._ = _
$Cypress.prototype.Blob = blobUtil
$Cypress.prototype.Buffer = Buffer
$Cypress.prototype.Promise = Promise
$Cypress.prototype.minimatch = minimatch
$Cypress.prototype.sinon = sinon
$Cypress.prototype.lolex = fakeTimers

// // attaching these so they are accessible
// // via the runner + integration spec helper
// attaching these so they are accessible
// via the runner + integration spec helper
$Cypress.$ = $
$Cypress.utils = $utils
export default $Cypress
6 changes: 5 additions & 1 deletion packages/driver/src/cypress/error_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,11 @@ const preferredStackAndCodeFrameIndex = (err, userInvocationStack) => {
return { stack, index }
}

const enhanceStack = ({ err, userInvocationStack, projectRoot }) => {
const enhanceStack = ({ err, userInvocationStack, projectRoot }: {
err: any
userInvocationStack?: any
projectRoot?: any
}) => {
const { stack, index } = preferredStackAndCodeFrameIndex(err, userInvocationStack)
const { sourceMapped, parsed } = $stackUtils.getSourceStack(stack, projectRoot)

Expand Down
Loading