Skip to content

Commit 7b4e806

Browse files
authored
fix: trim test stack traces in invocationDetails (#32699)
1 parent 706cf01 commit 7b4e806

File tree

8 files changed

+534
-16
lines changed

8 files changed

+534
-16
lines changed

cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ _Released 11/18/2025 (PENDING)_
1111

1212
- Fixed an issue where [`cy.wrap()`](https://docs.cypress.io/api/commands/wrap) would cause infinite recursion and freeze the Cypress App when called with objects containing circular references. Fixes [#24715](https://github.com/cypress-io/cypress/issues/24715). Addressed in [#32917](https://github.com/cypress-io/cypress/pull/32917).
1313
- Fixed an issue where top changes on test retries could cause attempt numbers to show up more than one time in the reporter and cause attempts to be lost in Test Replay. Addressed in [#32888](https://github.com/cypress-io/cypress/pull/32888).
14+
- Fixed an issue where stack traces that are used to determine a test's invocation details are sometimes incorrect. Addressed in [#32699](https://github.com/cypress-io/cypress/pull/32699)
1415

1516
**Misc:**
1617

packages/app/cypress/e2e/runner/reporter.hooks.cy.ts

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,69 @@ describe('hooks', {
3838
cy.contains('after all (2)').closest('.collapsible').should('contain', 'afterHook 1')
3939
})
4040

41-
it('creates open in IDE button', () => {
42-
loadSpec({
43-
filePath: 'hooks/basic.cy.js',
44-
passCount: 2,
45-
hasPreferredIde: true,
41+
describe('open in IDE button', () => {
42+
it('sends the correct invocation details for before hook', () => {
43+
loadSpec({
44+
filePath: 'hooks/basic.cy.js',
45+
passCount: 2,
46+
hasPreferredIde: true,
47+
})
48+
49+
cy.contains('tests 1').click()
50+
51+
cy.get('.hook-open-in-ide').should('have.length', 4)
52+
53+
cy.withCtx((ctx, o) => {
54+
o.sinon.stub(ctx.actions.file, 'openFile')
55+
})
56+
57+
cy.contains('before all').closest('.hook-header').find('.hook-open-in-ide').invoke('show').click()
58+
59+
cy.withCtx((ctx, o) => {
60+
expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`hooks/basic\.cy\.js$`)), 2, 2)
61+
})
4662
})
4763

48-
cy.contains('tests 1').click()
64+
it('sends the correct invocation details for basic test body', () => {
65+
loadSpec({
66+
filePath: 'hooks/basic.cy.js',
67+
passCount: 2,
68+
hasPreferredIde: true,
69+
})
70+
71+
cy.contains('tests 1').click()
72+
73+
cy.get('.hook-open-in-ide').should('have.length', 4)
74+
75+
cy.withCtx((ctx, o) => {
76+
o.sinon.stub(ctx.actions.file, 'openFile')
77+
})
4978

50-
cy.get('.hook-open-in-ide').should('have.length', 4)
79+
cy.contains('test body').closest('.hook-header').find('.hook-open-in-ide').invoke('show').click()
5180

52-
cy.withCtx((ctx, o) => {
53-
o.sinon.stub(ctx.actions.file, 'openFile')
81+
cy.withCtx((ctx, o) => {
82+
expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`hooks/basic\.cy\.js$`)), 10, 2)
83+
})
5484
})
5585

56-
cy.get('.hook-open-in-ide').first().invoke('show').click()
86+
it('sends the correct invocation details for wrapped it', () => {
87+
loadSpec({
88+
filePath: 'hooks/wrapped-it.cy.js',
89+
passCount: 2,
90+
hasPreferredIde: true,
91+
})
92+
93+
cy.contains('test 1').click()
94+
95+
cy.withCtx((ctx, o) => {
96+
o.sinon.stub(ctx.actions.file, 'openFile')
97+
})
98+
99+
cy.contains('test body').closest('.hook-header').find('.hook-open-in-ide').invoke('show').click()
57100

58-
cy.withCtx((ctx, o) => {
59-
expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`hooks/basic\.cy\.js$`)), 2, 2)
101+
cy.withCtx((ctx, o) => {
102+
expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`hooks/wrapped-it\.cy\.js$`)), 5, 1)
103+
})
60104
})
61105
})
62106

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
describe('component testing stack utils', () => {
2+
beforeEach(() => {
3+
const root = document.querySelector('[data-cy-root]')
4+
5+
if (root) {
6+
root.innerHTML = 'component test'
7+
}
8+
})
9+
10+
// Test case for when users re-define Mocha's it function
11+
// This creates additional stack frames that need to be trimmed
12+
function myIt (name, fn) {
13+
if (fn) {
14+
it(name, fn)
15+
} else {
16+
it(name)
17+
}
18+
}
19+
20+
myIt('does not trim component testing stack traces', () => {
21+
const details = Cypress.state('test').invocationDetails
22+
const isChromium = Cypress.isBrowser({ family: 'chromium' })
23+
const isFirefox = Cypress.isBrowser({ family: 'firefox' })
24+
25+
expect(details.absoluteFile).to.contain('cypress/packages/driver/cypress/component/stack_utils-invocationDetails.cy.ts')
26+
expect(details.fileUrl).to.contain('http://localhost:8080/__cypress/src/spec-0.js')
27+
expect(details.function).to.contain('myIt')
28+
expect(details.line).to.equal(14)
29+
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/component/stack_utils-invocationDetails.cy.ts')
30+
expect(details.relativeFile).to.contain('cypress/component/stack_utils-invocationDetails.cy.ts')
31+
32+
if (isChromium) {
33+
expect(details.stack).to.equal(`Error
34+
at myIt (http://localhost:8080/__cypress/src/spec-0.js:22:7)
35+
at Suite.<anonymous> (http://localhost:8080/__cypress/src/spec-0.js:28:3)
36+
at Object.create (cypress:///../driver/node_modules/mocha/lib/interfaces/common.js:141:19)
37+
at context.describe.context.context (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:42:27)
38+
at createRunnable (cypress:///../driver/src/cypress/mocha.ts:128:31)`)
39+
} else if (isFirefox) {
40+
// the firefox traces are really long, so just validate the first line
41+
const firstLine = details.stack.split('\n')[0]
42+
43+
expect(firstLine).to.equal('myIt@http://localhost:8080/__cypress/src/spec-0.js:22:9')
44+
}
45+
})
46+
47+
it('does not trim component testing stack traces', () => {
48+
const details = Cypress.state('test').invocationDetails
49+
const isChromium = Cypress.isBrowser({ family: 'chromium' })
50+
const isFirefox = Cypress.isBrowser({ family: 'firefox' })
51+
52+
expect(details.absoluteFile).to.contain('cypress/packages/driver/cypress/component/stack_utils-invocationDetails.cy.ts')
53+
expect(details.fileUrl).to.contain('http://localhost:8080/__cypress/src/spec-0.js')
54+
expect(details.line).to.equal(47)
55+
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/component/stack_utils-invocationDetails.cy.ts')
56+
expect(details.relativeFile).to.contain('cypress/component/stack_utils-invocationDetails.cy.ts')
57+
58+
if (isChromium) {
59+
expect(details.function).to.contain('Suite.<anonymous>')
60+
61+
expect(details.stack).to.equal(`Error
62+
at Suite.<anonymous> (http://localhost:8080/__cypress/src/spec-0.js:55:3)
63+
at Object.create (cypress:///../driver/node_modules/mocha/lib/interfaces/common.js:141:19)
64+
at context.describe.context.context (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:42:27)
65+
at createRunnable (cypress:///../driver/src/cypress/mocha.ts:128:31)
66+
at eval (cypress:///../driver/src/cypress/mocha.ts:189:14)`)
67+
} else if (isFirefox) {
68+
// the firefox traces are really long, so just validate the first line
69+
const firstLine = details.stack.split('\n')[0]
70+
71+
expect(firstLine).to.equal('./cypress/component/stack_utils-invocationDetails.cy.ts/<@http://localhost:8080/__cypress/src/spec-0.js:55:5')
72+
}
73+
})
74+
})
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import type { InvocationDetails } from '../../../src/cypress/stack_utils'
2+
3+
// Create a custom it function that will add additional stack frames that need to be trimmed correctly
4+
function myIt (name: string, optionsOrFn: any, fn?: () => void) {
5+
if (fn) {
6+
it(name, optionsOrFn, fn)
7+
} else {
8+
it(name, optionsOrFn)
9+
}
10+
}
11+
12+
// Note: the tests in this spec assert against their own invocation details. So if any of the line numbers change in this file, the assertions will need to be updated.
13+
it('has correct invocation details for a test at root level', () => {
14+
const details = Cypress.state('test').invocationDetails as InvocationDetails
15+
const isChromium = Cypress.isBrowser({ family: 'chromium' })
16+
const isFirefox = Cypress.isBrowser({ family: 'firefox' })
17+
18+
expect(details.absoluteFile).to.satisfy((file: string) => {
19+
return file.endsWith('cypress/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
20+
})
21+
22+
expect(details.fileUrl).to.equal('http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
23+
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
24+
expect(details.relativeFile).to.equal('cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
25+
expect(details.line).to.equal(13) // the line number should be the line number of the invocation of this test
26+
27+
if (isChromium) {
28+
expect(details.column).to.equal(0)
29+
expect(details.function).to.equal('eval')
30+
expect(details.stack).to.equal(`Error
31+
at eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:16:1)
32+
at eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:155:12)
33+
at eval (<anonymous>)
34+
at eval (cypress:///../driver/src/cypress/script_utils.ts:38:23)
35+
at tryCatcher (cypress:///../../node_modules/bluebird/js/release/util.js:17:23)`)
36+
} else if (isFirefox) {
37+
expect(details.column).to.equal(3)
38+
expect(details.function).to.equal('<unknown>')
39+
40+
// the firefox traces are really long, so just validate the first line
41+
const firstLine = details.stack.split('\n')[0]
42+
43+
expect(firstLine).to.equal('@http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:16:3')
44+
}
45+
})
46+
47+
myIt('has correct invocation details for myIt test at root level', function () {
48+
const details = Cypress.state('test').invocationDetails as InvocationDetails
49+
const isChromium = Cypress.isBrowser({ family: 'chromium' })
50+
const isFirefox = Cypress.isBrowser({ family: 'firefox' })
51+
52+
expect(details.absoluteFile).to.satisfy((file: string) => {
53+
return file.endsWith('cypress/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
54+
})
55+
56+
expect(details.fileUrl).to.equal('http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
57+
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
58+
expect(details.relativeFile).to.equal('cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
59+
expect(details.line).to.equal(47) // the line number should be the line number of the invocation of this test
60+
61+
if (isChromium) {
62+
expect(details.function).to.equal('eval')
63+
expect(details.column).to.equal(0)
64+
expect(details.stack).to.equal(`Error
65+
at eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:50:1)
66+
at eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:155:12)
67+
at eval (<anonymous>)
68+
at eval (cypress:///../driver/src/cypress/script_utils.ts:38:23)`)
69+
} else if (isFirefox) {
70+
expect(details.column).to.equal(5)
71+
expect(details.function).to.equal('<unknown>')
72+
73+
// the firefox traces are really long, so just validate the first line
74+
const firstLine = details.stack.split('\n')[0]
75+
76+
expect(firstLine).to.equal('@http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:50:5')
77+
}
78+
})
79+
80+
describe('outer describe block', () => {
81+
context('inner context block', () => {
82+
it('has correctinvocation details', function () {
83+
// Get invocation details from Cypress object
84+
const details = Cypress.state('test').invocationDetails as InvocationDetails
85+
const isChromium = Cypress.isBrowser({ family: 'chromium' })
86+
const isFirefox = Cypress.isBrowser({ family: 'firefox' })
87+
88+
expect(details.absoluteFile).to.satisfy((file: string) => {
89+
return file.endsWith('cypress/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
90+
})
91+
92+
expect(details.fileUrl).to.equal('http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
93+
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
94+
expect(details.relativeFile).to.equal('cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
95+
expect(details.line).to.equal(82) // the line number should be the line number of the invocation of this test
96+
97+
if (isChromium) {
98+
expect(details.function).to.equal('Suite.eval')
99+
expect(details.column).to.equal(4)
100+
expect(details.stack).to.equal(`Error
101+
at Suite.eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:85:5)
102+
at Object.create (cypress:///../driver/node_modules/mocha/lib/interfaces/common.js:141:19)
103+
at context.describe.context.context (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:42:27)
104+
at createRunnable (cypress:///../driver/src/cypress/mocha.ts:128:31)
105+
at eval (cypress:///../driver/src/cypress/mocha.ts:189:14)`)
106+
} else if (isFirefox) {
107+
expect(details.column).to.equal(7)
108+
expect(details.function).to.equal('<unknown>')
109+
110+
// the firefox traces are really long, so just validate the first line
111+
const firstLine = details.stack.split('\n')[0]
112+
113+
expect(firstLine).to.equal('@http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:85:7')
114+
}
115+
})
116+
117+
myIt('has correct invocation details for myIt test', function () {
118+
const details = Cypress.state('test').invocationDetails as InvocationDetails
119+
const isChromium = Cypress.isBrowser({ family: 'chromium' })
120+
const isFirefox = Cypress.isBrowser({ family: 'firefox' })
121+
122+
expect(details.absoluteFile).to.satisfy((file: string) => {
123+
return file.endsWith('cypress/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
124+
})
125+
126+
expect(details.fileUrl).to.equal('http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
127+
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
128+
expect(details.relativeFile).to.equal('cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
129+
expect(details.line).to.equal(117) // the line number should be the line number of the invocation of this test
130+
131+
if (isChromium) {
132+
expect(details.function).to.equal('Suite.eval')
133+
expect(details.column).to.equal(4)
134+
expect(details.stack).to.equal(`Error
135+
at Suite.eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:120:5)
136+
at Object.create (cypress:///../driver/node_modules/mocha/lib/interfaces/common.js:141:19)
137+
at context.describe.context.context (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:42:27)
138+
at createRunnable (cypress:///../driver/src/cypress/mocha.ts:128:31)`)
139+
} else if (isFirefox) {
140+
expect(details.column).to.equal(9)
141+
142+
// the firefox traces are really long, so just validate the first line
143+
const firstLine = details.stack.split('\n')[0]
144+
145+
expect(firstLine).to.equal('@http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:120:9')
146+
}
147+
})
148+
})
149+
})

packages/driver/src/cypress/mocha.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ const patchSuiteAddTest = (specWindow) => {
535535
const test = args[0]
536536

537537
if (!test.invocationDetails) {
538-
test.invocationDetails = $stackUtils.getInvocationDetails(specWindow, $sourceMapUtils.getSourceMapProjectRoot())
538+
test.invocationDetails = $stackUtils.getInvocationDetails(specWindow, $sourceMapUtils.getSourceMapProjectRoot(), 'test')
539539
}
540540

541541
const ret = suiteAddTest.apply(this, args)

0 commit comments

Comments
 (0)