Skip to content

Commit

Permalink
capture AUT uncaught errors and properly route them to uncaught… (#5249)
Browse files Browse the repository at this point in the history
* set global onerror same as autWindow.onerror

* cleanup spec

* add link to issue

* Update uncaught_errors_spec.coffee

* fix spec: use throw instead of done cb

* fix spec for global onerror

* temp 10/30/19 [skip ci]

* cleanup

* use object.defineproperty to overwrite setter, only set top.onerror once

* update snapshot

* refactor xhr_spec

* fix xhr_spec

* refactor address feedback

* fix oops

* fix rerun mode


Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
  • Loading branch information
2 people authored and brian-mann committed Oct 30, 2019
1 parent 249db45 commit 232c004
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 4 deletions.
6 changes: 4 additions & 2 deletions packages/driver/src/cy/commands/xhr.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,10 @@ startXhrServer = (cy, state, config) ->
if log = logs[xhr.id]
log.snapshot("error").error(err)

if r = state("reject")
r(err)
## re-throw the error since this came from AUT code, and needs to
## cause an 'uncaught:exception' event. This error will be caught in
## top.onerror with stack as 5th argument.
throw err

onXhrAbort: (xhr, stack) =>
setResponse(state, xhr)
Expand Down
28 changes: 28 additions & 0 deletions packages/driver/src/cypress/cy.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,32 @@ setWindowDocumentProps = (contentWindow, state) ->
setRemoteIframeProps = ($autIframe, state) ->
state("$autIframe", $autIframe)


## We only set top.onerror once since we make it configurable:false
## but we update cy instance every run (page reload or rerun button)
curCy = null
setTopOnError = (cy) ->
if curCy
curCy = cy
return

curCy = cy

onTopError = ->
curCy.onUncaughtException.apply(curCy, arguments)

top.onerror = onTopError

## Prevent Mocha from setting top.onerror which would override our handler
## Since the setter will change which event handler gets invoked, we make it a noop
Object.defineProperty(top, 'onerror', {
set: ->
get: -> onTopError
configurable: false
enumerable: true
})


create = (specWindow, Cypress, Cookies, state, config, log) ->
stopped = false
commandFns = {}
Expand Down Expand Up @@ -1107,6 +1133,8 @@ create = (specWindow, Cypress, Cookies, state, config, log) ->
args: obj
})
})

setTopOnError(cy)

## make cy global in the specWindow
specWindow.cy = cy
Expand Down
28 changes: 28 additions & 0 deletions packages/driver/test/cypress/fixtures/global-error.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Page Title</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
<script>
setTimeout(()=>{
one()
}, 0)

function one() {
two()
}

function two() {
three()
}

function three() {
foo.bar()
}
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -780,13 +780,18 @@ describe "src/cy/commands/xhr", ->

it "sets err on log when caused by code errors", (done) ->
finalThenCalled = false
uncaughtException = cy.stub().returns(true)
cy.on 'uncaught:exception', uncaughtException

cy.on "fail", (err) =>
lastLog = @lastLog

expect(@logs.length).to.eq(1)
expect(lastLog.get("name")).to.eq("xhr")
expect(lastLog.get("error")).to.eq err
expect(lastLog.get("error").message).contain('foo is not defined')
## since this is AUT code, we should allow error to be caught in 'uncaught:exception' hook
## https://github.com/cypress-io/cypress/issues/987
expect(uncaughtException).calledOnce
done()

cy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,14 @@ describe "uncaught errors", ->
.appendTo(win.document.body)

.contains("visit").click()

## https://github.com/cypress-io/cypress/issues/987
it 'global onerror', (done) ->
cy.once 'uncaught:exception', (err) ->
expect(err.stack).contain('foo is not defined')
expect(err.stack).contain('one')
expect(err.stack).contain('two')
expect(err.stack).contain('three')
done()

cy.visit('/fixtures/global-error.html')
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,15 @@ https://on.cypress.io/uncaught-exception-from-application
at stack trace line
2) s1 without an afterEach hook t2:
ReferenceError: bar is not defined
Uncaught ReferenceError: bar is not defined
This error originated from your application code, not from Cypress.
When Cypress detects uncaught errors originating from your application it will automatically fail the current test.
This behavior is configurable, and you can choose to turn this off by listening to the 'uncaught:exception' event.
https://on.cypress.io/uncaught-exception-from-application
at stack trace line
at stack trace line
at stack trace line
Expand Down

2 comments on commit 232c004

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 232c004 Oct 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 x64 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

set CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.5.1/win32-x64/appveyor-develop-232c0046d3df808561a592e063ac977d06fe29c2-28504585/cypress.zip
npm install https://cdn.cypress.io/beta/binary/3.5.1/win32-x64/appveyor-develop-232c0046d3df808561a592e063ac977d06fe29c2-28504585/cypress.zip

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 232c004 Oct 30, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 ia32 version of the Test Runner.

You can install this pre-release platform-specific build using instructions at https://on.cypress.io/installing-cypress#Install-pre-release-version.

You will need to use custom CYPRESS_INSTALL_BINARY url and install Cypress using an url instead of the version.

set CYPRESS_INSTALL_BINARY=https://cdn.cypress.io/beta/binary/3.5.1/win32-ia32/appveyor-develop-232c0046d3df808561a592e063ac977d06fe29c2-28504585/cypress.zip
npm install https://cdn.cypress.io/beta/binary/3.5.1/win32-ia32/appveyor-develop-232c0046d3df808561a592e063ac977d06fe29c2-28504585/cypress.zip

Please sign in to comment.