Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
3bb1471
fix: (studio) remove `itGrep` lines from stack trace when determining…
astone123 Oct 10, 2025
b6bc0f5
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Oct 28, 2025
926e1fc
handle .only and suites
astone123 Nov 3, 2025
49165cf
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 3, 2025
61311ee
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 3, 2025
0a8de00
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 6, 2025
a1480d8
for test block hooks, trim the stack to find the invocation details
astone123 Nov 6, 2025
36064a6
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 6, 2025
65d656c
fix stop only
astone123 Nov 6, 2025
c0fb620
fix cypress object access
astone123 Nov 7, 2025
96a9a10
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 7, 2025
d0dfb88
update comment to use generic host
astone123 Nov 11, 2025
5d5cc2d
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 11, 2025
79c51da
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 11, 2025
a7e5bb2
Update packages/driver/src/cypress/stack_utils.ts
astone123 Nov 12, 2025
7bb0f76
Update packages/driver/src/cypress/stack_utils.ts
astone123 Nov 12, 2025
42d6495
Update packages/driver/src/cypress/mocha.ts
astone123 Nov 12, 2025
1929cba
Update packages/driver/src/cypress/stack_utils.ts
astone123 Nov 12, 2025
ec9dad3
Update packages/driver/src/cypress/stack_utils.ts
astone123 Nov 12, 2025
85756f0
feedback
astone123 Nov 12, 2025
1bc1d68
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 12, 2025
4a53ed1
changelog entry
astone123 Nov 12, 2025
cde2e2c
remove unnecessary test, add driver integration tests
astone123 Nov 13, 2025
badf190
fix .only
astone123 Nov 13, 2025
d7f64f0
update test name
astone123 Nov 13, 2025
76e2319
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 13, 2025
31af0fb
fix types
astone123 Nov 13, 2025
dbe8c6f
add integration test
astone123 Nov 14, 2025
e9d72cf
logic and test updates
astone123 Nov 14, 2025
4df6616
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 14, 2025
1dfe50c
fix types
astone123 Nov 14, 2025
6fa202f
fix test
astone123 Nov 14, 2025
50418a8
only modify stacks for e2e tests
astone123 Nov 14, 2025
eecaaff
fix integration test
astone123 Nov 17, 2025
1d324d1
update stack utils logic
astone123 Nov 17, 2025
0f3c72b
update unit tests, add component test
astone123 Nov 17, 2025
5553ddf
Merge branch 'develop' into astone123/fix-itgrep-trace
astone123 Nov 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ _Released 11/18/2025 (PENDING)_

- 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).
- 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).
- 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)

**Misc:**

Expand Down
68 changes: 56 additions & 12 deletions packages/app/cypress/e2e/runner/reporter.hooks.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,69 @@ describe('hooks', {
cy.contains('after all (2)').closest('.collapsible').should('contain', 'afterHook 1')
})

it('creates open in IDE button', () => {
loadSpec({
filePath: 'hooks/basic.cy.js',
passCount: 2,
hasPreferredIde: true,
describe('open in IDE button', () => {
it('sends the correct invocation details for before hook', () => {
loadSpec({
filePath: 'hooks/basic.cy.js',
passCount: 2,
hasPreferredIde: true,
})

cy.contains('tests 1').click()

cy.get('.hook-open-in-ide').should('have.length', 4)

cy.withCtx((ctx, o) => {
o.sinon.stub(ctx.actions.file, 'openFile')
})

cy.contains('before all').closest('.hook-header').find('.hook-open-in-ide').invoke('show').click()

cy.withCtx((ctx, o) => {
expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`hooks/basic\.cy\.js$`)), 2, 2)
})
})

cy.contains('tests 1').click()
it('sends the correct invocation details for basic test body', () => {
loadSpec({
filePath: 'hooks/basic.cy.js',
passCount: 2,
hasPreferredIde: true,
})

cy.contains('tests 1').click()

cy.get('.hook-open-in-ide').should('have.length', 4)

cy.withCtx((ctx, o) => {
o.sinon.stub(ctx.actions.file, 'openFile')
})

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

cy.withCtx((ctx, o) => {
o.sinon.stub(ctx.actions.file, 'openFile')
cy.withCtx((ctx, o) => {
expect(ctx.actions.file.openFile).to.have.been.calledWith(o.sinon.match(new RegExp(`hooks/basic\.cy\.js$`)), 10, 2)
})
})

cy.get('.hook-open-in-ide').first().invoke('show').click()
it('sends the correct invocation details for wrapped it', () => {
loadSpec({
filePath: 'hooks/wrapped-it.cy.js',
passCount: 2,
hasPreferredIde: true,
})

cy.contains('test 1').click()

cy.withCtx((ctx, o) => {
o.sinon.stub(ctx.actions.file, 'openFile')
})

cy.contains('test body').closest('.hook-header').find('.hook-open-in-ide').invoke('show').click()

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
describe('component testing stack utils', () => {
beforeEach(() => {
const root = document.querySelector('[data-cy-root]')

if (root) {
root.innerHTML = 'component test'
}
})

it('does not trim component testing stack traces', () => {
const details = Cypress.state('test').invocationDetails

expect(details.absoluteFile).to.contain('cypress/packages/driver/cypress/component/stack_utils-invocationDetails.cy.ts')
expect(details.fileUrl).to.contain('http://localhost:8080/__cypress/src/spec-0.js')
expect(details.function).to.contain('Suite.<anonymous>')
expect(details.line).to.equal(10)
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/component/stack_utils-invocationDetails.cy.ts')
expect(details.relativeFile).to.contain('cypress/component/stack_utils-invocationDetails.cy.ts')
expect(details.stack).to.equal(`Error
at Suite.<anonymous> (http://localhost:8080/__cypress/src/spec-0.js:18:3)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you not need to flex on the browser family?

at Object.create (cypress:///../driver/node_modules/mocha/lib/interfaces/common.js:141:19)
at context.describe.context.context (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:42:27)
at createRunnable (cypress:///../driver/src/cypress/mocha.ts:128:31)
at eval (cypress:///../driver/src/cypress/mocha.ts:189:14)`)
})
})
Copy link
Contributor

Choose a reason for hiding this comment

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

We also need to add tests outside of the describe block so we can test the at eval branch.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

added this test

Copy link
Contributor

Choose a reason for hiding this comment

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

You'll also want a test that uses myIt outside of the describe.

Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import type { InvocationDetails } from '../../../src/cypress/stack_utils'

// 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.
it('should be able to get invocation details for a test outside of a describe block', () => {
const details = Cypress.state('test').invocationDetails as InvocationDetails
const isChromium = Cypress.isBrowser({ family: 'chromium' })
const isFirefox = Cypress.isBrowser({ family: 'firefox' })

expect(details.absoluteFile).to.satisfy((file: string) => {
return file.endsWith('cypress/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
})

expect(details.fileUrl).to.equal('http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
expect(details.relativeFile).to.equal('cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
expect(details.line).to.equal(4) // the line number should be the line number of the invocation of this test

if (isChromium) {
expect(details.column).to.equal(0)
expect(details.function).to.equal('eval')
expect(details.stack).to.equal(`Error
at eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:7:1)
at eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:122:12)
at eval (<anonymous>)
at eval (cypress:///../driver/src/cypress/script_utils.ts:38:23)
at tryCatcher (cypress:///../../node_modules/bluebird/js/release/util.js:17:23)`)
} else if (isFirefox) {
expect(details.column).to.equal(3)

// the firefox traces are really long, so just validate the first line
const firstLine = details.stack.split('\n')[0]

expect(firstLine).to.equal('@http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:7:3')
}
})

describe('stack_utils getInvocationDetails', () => {
context('basic test invocation', () => {
it('correctly extracts invocation details', function () {
// Get invocation details from Cypress object
const details = Cypress.state('test').invocationDetails as InvocationDetails
const isChromium = Cypress.isBrowser({ family: 'chromium' })
const isFirefox = Cypress.isBrowser({ family: 'firefox' })

expect(details.absoluteFile).to.satisfy((file: string) => {
return file.endsWith('cypress/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
})

expect(details.fileUrl).to.equal('http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
expect(details.relativeFile).to.equal('cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
expect(details.line).to.equal(39) // the line number should be the line number of the invocation of this test

if (isChromium) {
expect(details.function).to.equal('Suite.eval')
expect(details.column).to.equal(4)
expect(details.stack).to.equal(`Error
at Suite.eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:42:5)
at Object.create (cypress:///../driver/node_modules/mocha/lib/interfaces/common.js:141:19)
at context.describe.context.context (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:42:27)
at createRunnable (cypress:///../driver/src/cypress/mocha.ts:128:31)
at eval (cypress:///../driver/src/cypress/mocha.ts:189:14)`)
} else if (isFirefox) {
expect(details.column).to.equal(7)

// the firefox traces are really long, so just validate the first line
const firstLine = details.stack.split('\n')[0]

expect(firstLine).to.equal('@http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:42:7')
}
})
})

context('wrapped it function', () => {
// Test case for when users re-define Mocha's it function
// This creates additional stack frames that need to be trimmed correctly
function myIt (name: string, optionsOrFn: any, fn?: () => void) {
if (fn) {
it(name, optionsOrFn, fn)
} else {
it(name, optionsOrFn)
}
}

myIt('correctly extracts invocation details for wrapped it', function () {
const details = Cypress.state('test').invocationDetails as InvocationDetails
const isChromium = Cypress.isBrowser({ family: 'chromium' })
const isFirefox = Cypress.isBrowser({ family: 'firefox' })

expect(details.absoluteFile).to.satisfy((file: string) => {
return file.endsWith('cypress/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
})

expect(details.fileUrl).to.equal('http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
expect(details.originalFile).to.equal('webpack://@packages/driver/./cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
expect(details.relativeFile).to.equal('cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts')
expect(details.line).to.equal(85) // the line number should be the line number of the invocation of this test

if (isChromium) {
expect(details.function).to.equal('Suite.eval')
expect(details.column).to.equal(4)
expect(details.stack).to.equal(`Error
at Suite.eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:87:5)
at Object.create (cypress:///../driver/node_modules/mocha/lib/interfaces/common.js:141:19)
at context.describe.context.context (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:42:27)
at createRunnable (cypress:///../driver/src/cypress/mocha.ts:128:31)`)
} else if (isFirefox) {
expect(details.column).to.equal(9)

// the firefox traces are really long, so just validate the first line
const firstLine = details.stack.split('\n')[0]

expect(firstLine).to.equal('@http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:87:9')
}
})
})
})
2 changes: 1 addition & 1 deletion packages/driver/src/cypress/mocha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ const patchSuiteAddTest = (specWindow) => {
const test = args[0]

if (!test.invocationDetails) {
test.invocationDetails = $stackUtils.getInvocationDetails(specWindow, $sourceMapUtils.getSourceMapProjectRoot())
test.invocationDetails = $stackUtils.getInvocationDetails(specWindow, $sourceMapUtils.getSourceMapProjectRoot(), 'test')
}

const ret = suiteAddTest.apply(this, args)
Expand Down
62 changes: 60 additions & 2 deletions packages/driver/src/cypress/stack_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,57 @@ const stackWithLinesRemoved = (stack, cb) => {
return unsplitStack(messageLines, remainingStackLines)
}

const stackTrimmedToTestInvocation = (stack: string, specWindow) => {
const modifiedStack = stackWithLinesRemoved(stack, (lines: string[]) => {
// Guard against Cypress being undefined/null (can happen when users quickly reload tests)
if (!specWindow?.Cypress) {
return lines
}

const originalLines = lines
let processedLines: string[]

if (specWindow.Cypress.isBrowser({ family: 'chromium' })) {
// The actual test invocation line starts with either 'at eval' or 'at Suite.eval',
// so remove all lines until we reach the test invocation line
processedLines = _.dropWhile(lines, (line) => {
return !(
line.trim().startsWith('at eval ') ||
line.trim().startsWith('at Suite.eval ')
)
})
} else if (specWindow.Cypress.isBrowser({ family: 'firefox' })) {
const isTestInvocationLine = (line: string) => {
const splitAtAt = line.split('@')

// firefox stacks traces look like:
// functionName@http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js:444:14
// @http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:43:3
// @http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:45:12
// evalScripts/<@cypress:///../driver/src/cypress/script_utils.ts:38:23
//
// the actual invocation details will be at the first line with no function name
return splitAtAt.length > 1 && splitAtAt[0].trim().length === 0
}

processedLines = _.dropWhile(lines, (line) => {
return !isTestInvocationLine(line)
})
} else {
processedLines = lines
}

// if we removed all the lines then something went wrong with parsing. Return the original lines instead
if (processedLines.length === 0) {
return originalLines
}

return processedLines
})

return modifiedStack
}

const stackWithLinesDroppedFromMarker = (stack, marker, includeLast = false) => {
return stackWithLinesRemoved(stack, (lines) => {
// drop lines above the marker
Expand Down Expand Up @@ -114,7 +165,9 @@ const stackWithUserInvocationStackSpliced = (err, userInvocationStack): StackAnd
}
}

type InvocationDetails = {
export type InvocationDetails = {
function?: string
fileUrl?: string
absoluteFile?: string
column?: number
line?: number
Expand All @@ -124,7 +177,7 @@ type InvocationDetails = {
}

// used to determine codeframes for hook/test/etc definitions rather than command invocations
const getInvocationDetails = (specWindow, sourceMapProjectRoot: string): InvocationDetails | undefined => {
const getInvocationDetails = (specWindow, sourceMapProjectRoot: string, type?: 'test'): InvocationDetails | undefined => {
if (specWindow.Error) {
let stack = (new specWindow.Error()).stack

Expand All @@ -144,6 +197,11 @@ const getInvocationDetails = (specWindow, sourceMapProjectRoot: string): Invocat
// CT error contexts include the `__cypress` marker but not the `/tests` portion
stack = stackWithLinesDroppedFromMarker(stack, '__cypress', true)
}

// if the hook is the test, and it's an e2e test, we will try to remove the lines that are not the actual invocation of the test
if (type === 'test' && specWindow.Cypress.testingType === 'e2e') {
stack = stackTrimmedToTestInvocation(stack, specWindow)
}
}

const details: Omit<InvocationDetails, 'stack'> = getSourceDetailsForFirstLine(stack, sourceMapProjectRoot) || {}
Expand Down
Loading
Loading