Skip to content

Commit

Permalink
fix: do not assume valid file info in error stack line (#9081)
Browse files Browse the repository at this point in the history
  • Loading branch information
bahmutov authored Nov 5, 2020
1 parent e58f132 commit 1c2a175
Show file tree
Hide file tree
Showing 8 changed files with 535 additions and 5 deletions.
27 changes: 27 additions & 0 deletions packages/driver/cypress/fixtures/error-stack-with-http-links.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
ReferenceError: The following error originated from your application code, not from Cypress.

> b is not defined

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.
at http://localhost:8888/js/utils.js:9:3
at http://localhost:8888/js/utils.js:60:3
From previous event:
at run (http://localhost:8888/__cypress/runner/cypress_runner.js:169474:21)
at $Cy.cy.<computed> [as visit] (http://localhost:8888/__cypress/runner/cypress_runner.js:169931:11)
at Context.runnable.fn (http://localhost:8888/__cypress/runner/cypress_runner.js:170155:21)
at callFn (http://localhost:8888/__cypress/runner/cypress_runner.js:104227:21)
at Test.../driver/node_modules/mocha/lib/runnable.js.Runnable.run (http://localhost:8888/__cypress/runner/cypress_runner.js:104214:7)
at http://localhost:8888/__cypress/runner/cypress_runner.js:175754:28
From previous event:
at Object.onRunnableRun (http://localhost:8888/__cypress/runner/cypress_runner.js:175742:17)
at $Cypress.action (http://localhost:8888/__cypress/runner/cypress_runner.js:166291:28)
at Test.Runnable.run (http://localhost:8888/__cypress/runner/cypress_runner.js:174128:13)
at Runner.../driver/node_modules/mocha/lib/runner.js.Runner.runTest (http://localhost:8888/__cypress/runner/cypress_runner.js:104886:10)
at http://localhost:8888/__cypress/runner/cypress_runner.js:105012:12
at next (http://localhost:8888/__cypress/runner/cypress_runner.js:104795:14)
at http://localhost:8888/__cypress/runner/cypress_runner.js:104805:7
at next (http://localhost:8888/__cypress/runner/cypress_runner.js:104707:14)
at http://localhost:8888/__cypress/runner/cypress_runner.js:104773:5
at timeslice (http://localhost:8888/__cypress/runner/cypress_runner.js:98699:27)
105 changes: 105 additions & 0 deletions packages/driver/cypress/integration/cypress/stack_utils_spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const $stackUtils = require('@packages/driver/src/cypress/stack_utils')
const $sourceMapUtils = require('@packages/driver/src/cypress/source_map_utils')
const { stripIndent } = require('common-tags')

describe('driver/src/cypress/stack_utils', () => {
context('.replacedStack', () => {
Expand Down Expand Up @@ -95,6 +96,33 @@ describe('driver/src/cypress/stack_utils', () => {
})
})

context('.getSourceStack when http links', () => {
it('does not have absolute files', () => {
const projectRoot = '/dev/app'

cy.fixture('error-stack-with-http-links.txt')
.then((stack) => {
return $stackUtils.getSourceStack(stack, projectRoot)
})
.its('parsed')
.then((parsed) => {
return Cypress._.find(parsed, { fileUrl: 'http://localhost:8888/js/utils.js' })
})
.then((errorLocation) => {
expect(errorLocation, 'does not have disk information').to.deep.equal({
absoluteFile: undefined,
column: 4,
fileUrl: 'http://localhost:8888/js/utils.js',
function: '<unknown>',
line: 9,
originalFile: 'http://localhost:8888/js/utils.js',
relativeFile: undefined,
whitespace: ' ',
})
})
})
})

context('.getSourceStack', () => {
let generatedStack
const projectRoot = '/dev/app'
Expand Down Expand Up @@ -239,6 +267,83 @@ Error: spec iframe stack
})
})

context('.getSourceDetailsForFirstLine', () => {
it('parses good stack trace', () => {
const stack = stripIndent`
Error
at Suite.eval (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:101:3)
at Object../cypress/integration/spec.js (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:100:1)
at __webpack_require__ (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:20:30)
at Object.0 (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:119:18)
at __webpack_require__ (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:20:30)
at eval (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:84:18)
at eval (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:87:10)
at eval (<anonymous>)
`
const projectRoot = '/Users/gleb/git/cypress-example-todomvc'
const details = $stackUtils.getSourceDetailsForFirstLine(stack, projectRoot)

expect(details.function, 'function name').to.equal('Suite.eval')
expect(details.fileUrl, 'file url').to.equal('http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js')
})

it('parses anonymous eval line', () => {
const stack = stripIndent`
SyntaxError: The following error originated from your application code, not from Cypress.
> Identifier 'app' has already been declared
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.
SyntaxError: The following error originated from your application code, not from Cypress.
> Identifier 'app' has already been declared
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.
at <anonymous>:1:1
at run (http://localhost:8888/node_modules/react/dist/JSXTransformer.js:184:10)
at check (http://localhost:8888/node_modules/react/dist/JSXTransformer.js:238:9)
at result.<computed>.async (http://localhost:8888/node_modules/react/dist/JSXTransformer.js:273:9)
at XMLHttpRequest.xhr.onreadystatechange (http://localhost:8888/node_modules/react/dist/JSXTransformer.js:208:9)
From previous event:
at run (cypress:///../driver/src/cypress/cy.js:561:21)
at $Cy.cy.<computed> [as visit] (cypress:///../driver/src/cypress/cy.js:1018:11)
at Context.runnable.fn (cypress:///../driver/src/cypress/cy.js:1242:21)
at callFn (cypress:///../driver/node_modules/mocha/lib/runnable.js:395:21)
at Test.Runnable.run (cypress:///../driver/node_modules/mocha/lib/runnable.js:382:7)
at eval (cypress:///../driver/src/cypress/runner.js:1249:28)
From previous event:
at Object.onRunnableRun (cypress:///../driver/src/cypress/runner.js:1237:17)
at $Cypress.action (cypress:///../driver/src/cypress.js:397:28)
at Test.Runnable.run (cypress:///../driver/src/cypress/mocha.js:348:13)
at Runner.runTest (cypress:///../driver/node_modules/mocha/lib/runner.js:541:10)
at eval (cypress:///../driver/node_modules/mocha/lib/runner.js:667:12)
at next (cypress:///../driver/node_modules/mocha/lib/runner.js:450:14)
at eval (cypress:///../driver/node_modules/mocha/lib/runner.js:460:7)
at next (cypress:///../driver/node_modules/mocha/lib/runner.js:362:14)
at eval (cypress:///../driver/node_modules/mocha/lib/runner.js:428:5)
at timeslice (cypress:///../driver/node_modules/mocha/browser-entry.js:80:27)
`

const projectRoot = '/Users/gleb/git/cypress-example-todomvc'
const details = $stackUtils.getSourceDetailsForFirstLine(stack, projectRoot)

expect(details, 'minimal details').to.deep.equal({
absoluteFile: undefined,
column: 2,
fileUrl: undefined,
function: '<unknown>',
line: 1,
originalFile: undefined,
relativeFile: undefined,
whitespace: ' ',
})
})
})

context('.stackWithUserInvocationStackSpliced', () => {
let err
let userInvocationStack
Expand Down
4 changes: 3 additions & 1 deletion packages/driver/src/cypress/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,10 @@ function getInvocationDetails (specWindow, config) {
stack = $stackUtils.stackWithLinesDroppedFromMarker(stack, '__cypress/tests', true)
}

const details = $stackUtils.getSourceDetailsForFirstLine(stack, config('projectRoot'))

return {
details: $stackUtils.getSourceDetailsForFirstLine(stack, config('projectRoot')),
details,
stack,
}
}
Expand Down
24 changes: 22 additions & 2 deletions packages/driver/src/cypress/stack_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,20 @@ const parseLine = (line) => {
}

const stripCustomProtocol = (filePath) => {
if (!filePath) {
return
}

// if the file path (after all said and done)
// still starts with "http://" or "https://" then
// it is an URL and we have no idea how it maps
// to a physical file location on disk. Let it be.
const httpProtocolRegex = /^https?:\/\//

if (httpProtocolRegex.test(filePath)) {
return
}

return filePath.replace(customProtocolRegex, '')
}

Expand All @@ -216,14 +230,20 @@ const getSourceDetailsForLine = (projectRoot, line) => {

const sourceDetails = getSourceDetails(generatedDetails)
const originalFile = sourceDetails.file

if (!originalFile) {
// this is an edge case: could not parse the stack trace
// maybe there was some code that was evaluated in the browser?
}

const relativeFile = stripCustomProtocol(originalFile)

return {
function: sourceDetails.function,
fileUrl: generatedDetails.file,
originalFile,
relativeFile,
absoluteFile: path.join(projectRoot, relativeFile),
absoluteFile: relativeFile ? path.join(projectRoot, relativeFile) : undefined,
line: sourceDetails.line,
// adding 1 to column makes more sense for code frame and opening in editor
column: sourceDetails.column + 1,
Expand Down Expand Up @@ -308,7 +328,7 @@ const normalizedUserInvocationStack = (userInvocationStack) => {
const stackLines = getStackLines(userInvocationStack)
const winnowedStackLines = _.reject(stackLines, (line) => {
// WARNING: STACK TRACE WILL BE DIFFERENT IN DEVELOPMENT vs PRODUCTOIN
// stacks in developemnt builds look like:
// stacks in development builds look like:
// at cypressErr (cypress:///../driver/src/cypress/error_utils.js:259:17)
// stacks in prod builds look like:
// at cypressErr (http://localhost:3500/isolated-runner/cypress_runner.js:173123:17)
Expand Down
43 changes: 43 additions & 0 deletions packages/reporter/cypress/fixtures/cy_command_failed_error.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "AssertionError",
"message": "Timed out retrying: Expected to find element: `.new-todo`, but never found it.",
"stack": "AssertionError: Timed out retrying: Expected to find element: `.new-todo`, but never found it.\n at getInputBox (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:130:13)\n at Context.eval (http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js:105:32)",
"sourceMappedStack": "AssertionError: Timed out retrying: Expected to find element: `.new-todo`, but never found it.\n at getInputBox (webpack:///cypress/integration/test-utils.js:2:13)\n at Context.eval (webpack:///cypress/integration/spec.js:7:5)",
"parsedStack": [
{
"message": "AssertionError: Timed out retrying: Expected to find element: `.new-todo`, but never found it.",
"whitespace": ""
},
{
"function": "getInputBox",
"fileUrl": "http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js",
"originalFile": "webpack:///cypress/integration/test-utils.js",
"relativeFile": "cypress/integration/test-utils.js",
"absoluteFile": "/Users/gleb/git/cypress-example-todomvc/cypress/integration/test-utils.js",
"line": 2,
"column": 13,
"whitespace": " "
},
{
"function": "Context.eval",
"fileUrl": "http://localhost:8888/__cypress/tests?p=cypress/integration/spec.js",
"originalFile": "webpack:///cypress/integration/spec.js",
"relativeFile": "cypress/integration/spec.js",
"absoluteFile": "/Users/gleb/git/cypress-example-todomvc/cypress/integration/spec.js",
"line": 7,
"column": 5,
"whitespace": " "
}
],
"docsUrl": "",
"templateType": "",
"codeFrame": {
"line": 2,
"column": 13,
"originalFile": "cypress/integration/test-utils.js",
"relativeFile": "cypress/integration/test-utils.js",
"absoluteFile": "/Users/gleb/git/cypress-example-todomvc/cypress/integration/test-utils.js",
"frame": " 1 | export const getInputBox = () => {\n> 2 | return cy.get('.new-todo')\n | ^\n 3 | }\n 4 | ",
"language": "js"
}
}
Loading

3 comments on commit 1c2a175

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 1c2a175 Nov 5, 2020

Choose a reason for hiding this comment

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

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/5.6.0/circle-develop-1c2a17560b704304bef4940aa924b2ad5373c50e/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 1c2a175 Nov 5, 2020

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.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/5.5.1/appveyor-develop-1c2a17560b704304bef4940aa924b2ad5373c50e/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 1c2a175 Nov 5, 2020

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.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/5.5.1/appveyor-develop-1c2a17560b704304bef4940aa924b2ad5373c50e/cypress.tgz

Please sign in to comment.