mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-07 07:20:42 -05:00
fix: Show stacks and code frames in specs named with spaces (#9201)
Co-authored-by: Chris Breiding <chrisbreiding@gmail.com> Co-authored-by: Chris Breiding <chrisbreiding@users.noreply.github.com>
This commit is contained in:
@@ -12,7 +12,7 @@ const fetchScript = (scriptWindow, script) => {
|
||||
}
|
||||
|
||||
const extractSourceMap = ([script, contents]) => {
|
||||
script.fullyQualifiedUrl = `${window.top.location.origin}${script.relativeUrl}`
|
||||
script.fullyQualifiedUrl = `${window.top.location.origin}${script.relativeUrl}`.replace(/ /g, '%20')
|
||||
|
||||
const sourceMap = $sourceMapUtils.extractSourceMap(script, contents)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "CommandError",
|
||||
"message": "`foo` \\`bar\\` **baz** *fizz* ** buzz **",
|
||||
"stack": "Some Error\n at foo.bar (my/app.js:2:7)\n at baz.qux (cypress/integration/foo_spec.js:5:2)\n From previous event:\n at bar.baz (my/app.js:8:11)\n ",
|
||||
"stack": "Some Error\n at foo.bar (my/app.js:2:7)\n at baz.qux (cypress/integration/foo_spec.js:5:2)\n at space (cypress/integration/a b.js:34:99)\n From previous event:\n at bar.baz (my/app.js:8:11)\n ",
|
||||
"parsedStack": [
|
||||
{
|
||||
"message": "Some Error",
|
||||
@@ -35,6 +35,15 @@
|
||||
"column": 2,
|
||||
"whitespace": " "
|
||||
},
|
||||
{
|
||||
"originalFile": "cypress/integration/a%20b.js",
|
||||
"relativeFile": "cypress/integration/a%20b.js",
|
||||
"absoluteFile": "/me/dev/cypress/integration/a%20b.js",
|
||||
"function": "space",
|
||||
"line": 34,
|
||||
"column": 99,
|
||||
"whitespace": " "
|
||||
},
|
||||
{
|
||||
"message": "At previous event:",
|
||||
"whitespace": " "
|
||||
|
||||
@@ -95,20 +95,23 @@ describe('test errors', () => {
|
||||
|
||||
cy.get('.runnable-err-stack-trace').within(() => {
|
||||
cy.get('.err-stack-line')
|
||||
.should('have.length', 9)
|
||||
.should('have.length', 10)
|
||||
.first().should('have.text', 'at foo.bar (my/app.js:2:7)')
|
||||
|
||||
cy.get('.err-stack-line')
|
||||
.eq(1).should('have.text', ' at baz.qux (cypress/integration/foo_spec.js:5:2)')
|
||||
|
||||
cy.get('.err-stack-line')
|
||||
.eq(2).should('have.text', 'At previous event:')
|
||||
.eq(2).should('have.text', ' at space (cypress/integration/a b.js:34:99)')
|
||||
|
||||
cy.get('.err-stack-line')
|
||||
.eq(3).should('have.text', ' at bar.baz (http://localhost:1234/me/dev/my/app.js:8:11)')
|
||||
.eq(3).should('have.text', 'At previous event:')
|
||||
|
||||
cy.get('.err-stack-line')
|
||||
.eq(4).should('have.text', ' at callFn (cypress://../driver/src/cypress/runner.js:9:12)')
|
||||
.eq(4).should('have.text', ' at bar.baz (http://localhost:1234/me/dev/my/app.js:8:11)')
|
||||
|
||||
cy.get('.err-stack-line')
|
||||
.eq(5).should('have.text', ' at callFn (cypress://../driver/src/cypress/runner.js:9:12)')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -124,12 +127,15 @@ describe('test errors', () => {
|
||||
cy.contains('View stack trace').click()
|
||||
|
||||
cy.get('.runnable-err-stack-trace .runnable-err-file-path')
|
||||
.should('have.length', 2)
|
||||
.should('have.length', 3)
|
||||
.first()
|
||||
.should('have.text', 'my/app.js:2:7')
|
||||
|
||||
cy.get('.runnable-err-stack-trace .runnable-err-file-path').eq(1)
|
||||
.should('have.text', 'cypress/integration/foo_spec.js:5:2')
|
||||
|
||||
cy.get('.runnable-err-stack-trace .runnable-err-file-path').eq(2)
|
||||
.should('have.text', 'cypress/integration/a b.js:34:99')
|
||||
})
|
||||
|
||||
it('does not turn cypress:// files into links', () => {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { FileDetails } from '@packages/ui-components'
|
||||
|
||||
export function decodeFilePaths (fileDetails: FileDetails) {
|
||||
const { absoluteFile, originalFile, relativeFile } = fileDetails
|
||||
|
||||
return {
|
||||
...fileDetails,
|
||||
absoluteFile: absoluteFile ? absoluteFile.replace(/%20/g, ' ') : absoluteFile,
|
||||
originalFile: originalFile ? originalFile.replace(/%20/g, ' ') : originalFile,
|
||||
relativeFile: relativeFile ? relativeFile.replace(/%20/g, ' ') : relativeFile,
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import Prism from 'prismjs'
|
||||
|
||||
import { CodeFrame } from './err-model'
|
||||
import FileNameOpener from '../lib/file-name-opener'
|
||||
import { decodeFilePaths } from './decode-file-paths'
|
||||
|
||||
interface Props {
|
||||
codeFrame: CodeFrame
|
||||
@@ -24,7 +25,7 @@ class ErrorCodeFrame extends Component<Props> {
|
||||
|
||||
return (
|
||||
<div className='test-err-code-frame'>
|
||||
<FileNameOpener className="runnable-err-file-path" fileDetails={this.props.codeFrame} />
|
||||
<FileNameOpener className="runnable-err-file-path" fileDetails={decodeFilePaths(this.props.codeFrame)} />
|
||||
<pre ref='codeFrame' data-line={highlightLine}>
|
||||
<code className={`language-${language || 'text'}`}>{frame}</code>
|
||||
</pre>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { observer } from 'mobx-react'
|
||||
import React, { ReactElement } from 'react'
|
||||
|
||||
import FileNameOpener from '../lib/file-name-opener'
|
||||
import { decodeFilePaths } from './decode-file-paths'
|
||||
import Err, { ParsedStackFileLine, ParsedStackMessageLine } from './err-model'
|
||||
|
||||
const cypressLineRegex = /(cypress:\/\/|cypress_runner\.js)/
|
||||
@@ -13,21 +14,26 @@ interface Props {
|
||||
|
||||
type StringOrElement = string | ReactElement
|
||||
|
||||
const isMessageLine = (stackLine: ParsedStackFileLine | ParsedStackMessageLine) => {
|
||||
return (stackLine as ParsedStackMessageLine).message != null
|
||||
}
|
||||
|
||||
const ErrorStack = observer(({ err }: Props) => {
|
||||
if (!err.parsedStack) return <>err.stack</>
|
||||
|
||||
// only display stack lines beyond the original message, since it's already
|
||||
// displayed above this
|
||||
// displayed above this in the UI
|
||||
let foundFirstStackLine = false
|
||||
const stackLines = _.filter(err.parsedStack, (line) => {
|
||||
if (foundFirstStackLine) return true
|
||||
|
||||
if ((line as ParsedStackMessageLine).message != null) return false
|
||||
if (isMessageLine(line)) return false
|
||||
|
||||
foundFirstStackLine = true
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
// instead of having every line indented, get rid of the smallest amount of
|
||||
// whitespace common to each line so the stack is aligned left but lines
|
||||
// with extra whitespace still have it
|
||||
@@ -42,38 +48,39 @@ const ErrorStack = observer(({ err }: Props) => {
|
||||
|
||||
let stopLinking = false
|
||||
const lines = _.map(stackLines, (stackLine, index) => {
|
||||
const { originalFile, function: fn, line, column, absoluteFile } = stackLine as ParsedStackFileLine
|
||||
const key = `${originalFile}${index}`
|
||||
|
||||
const whitespace = stackLine.whitespace.slice(commonWhitespaceLength)
|
||||
|
||||
if ((stackLine as ParsedStackMessageLine).message != null) {
|
||||
if (isMessageLine(stackLine)) {
|
||||
const message = (stackLine as ParsedStackMessageLine).message
|
||||
|
||||
// we append some errors with 'node internals', which we don't want to link
|
||||
// so stop linking anything after 'From Node.js Internals'
|
||||
if ((stackLine as ParsedStackMessageLine).message.includes('From Node')) {
|
||||
if (message.includes('From Node')) {
|
||||
stopLinking = true
|
||||
}
|
||||
|
||||
return makeLine(key, [whitespace, (stackLine as ParsedStackMessageLine).message])
|
||||
return makeLine(`${message}${index}`, [whitespace, message])
|
||||
}
|
||||
|
||||
if (stopLinking) {
|
||||
// we have already shown a good link, so no need to make
|
||||
// clickable links deep in the woods of the stack trace
|
||||
const { originalFile, function: fn, line, column, absoluteFile } = stackLine as ParsedStackFileLine
|
||||
const key = `${originalFile}${index}`
|
||||
|
||||
const dontLink = (
|
||||
// don't link to Node files, opening them in IDE won't work
|
||||
stopLinking
|
||||
// sometimes we can determine the file on disk, but if there are no
|
||||
// source maps or the file was transpiled in the browser, there
|
||||
// is no absoluteFile to link to
|
||||
|| !absoluteFile
|
||||
// don't link to cypress internals, opening them in IDE won't work
|
||||
|| cypressLineRegex.test(originalFile || '')
|
||||
)
|
||||
|
||||
if (dontLink) {
|
||||
return makeLine(key, [whitespace, `at ${fn} (${originalFile}:${line}:${column})`])
|
||||
}
|
||||
|
||||
// decide if can show "open file in IDE" link or not
|
||||
// sometimes we can determine the file on disk, but if
|
||||
// there are no source maps, or the file was transpiled in the browser
|
||||
// then all we can do is show the link as is without making it clickable
|
||||
if (!absoluteFile) {
|
||||
return makeLine(key, [whitespace, `at ${fn} (${originalFile}:${line}:${column})`])
|
||||
}
|
||||
|
||||
if (cypressLineRegex.test(originalFile || '')) {
|
||||
return makeLine(key, [whitespace, `at ${fn} (${originalFile}:${line}:${column})`])
|
||||
}
|
||||
stackLine = decodeFilePaths(stackLine as ParsedStackFileLine) as ParsedStackFileLine
|
||||
|
||||
const link = (
|
||||
<FileNameOpener key={key} className="runnable-err-file-path" fileDetails={stackLine as ParsedStackFileLine} />
|
||||
|
||||
Reference in New Issue
Block a user