diff --git a/cli/CHANGELOG.md b/cli/CHANGELOG.md index 96466cc47ec..60efc3ade78 100644 --- a/cli/CHANGELOG.md +++ b/cli/CHANGELOG.md @@ -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:** diff --git a/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts index 328dc093481..26ecb2cf5d5 100644 --- a/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts +++ b/packages/app/cypress/e2e/runner/reporter.hooks.cy.ts @@ -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) + }) }) }) diff --git a/packages/driver/cypress/component/stack_utils-invocationDetails.cy.ts b/packages/driver/cypress/component/stack_utils-invocationDetails.cy.ts new file mode 100644 index 00000000000..39c5c75fe8f --- /dev/null +++ b/packages/driver/cypress/component/stack_utils-invocationDetails.cy.ts @@ -0,0 +1,74 @@ +describe('component testing stack utils', () => { + beforeEach(() => { + const root = document.querySelector('[data-cy-root]') + + if (root) { + root.innerHTML = 'component test' + } + }) + + // Test case for when users re-define Mocha's it function + // This creates additional stack frames that need to be trimmed + function myIt (name, fn) { + if (fn) { + it(name, fn) + } else { + it(name) + } + } + + myIt('does not trim component testing stack traces', () => { + const details = Cypress.state('test').invocationDetails + const isChromium = Cypress.isBrowser({ family: 'chromium' }) + const isFirefox = Cypress.isBrowser({ family: 'firefox' }) + + 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('myIt') + expect(details.line).to.equal(14) + 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') + + if (isChromium) { + expect(details.stack).to.equal(`Error + at myIt (http://localhost:8080/__cypress/src/spec-0.js:22:7) + at Suite. (http://localhost:8080/__cypress/src/spec-0.js:28:3) + 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) { + // the firefox traces are really long, so just validate the first line + const firstLine = details.stack.split('\n')[0] + + expect(firstLine).to.equal('myIt@http://localhost:8080/__cypress/src/spec-0.js:22:9') + } + }) + + it('does not trim component testing stack traces', () => { + const details = Cypress.state('test').invocationDetails + const isChromium = Cypress.isBrowser({ family: 'chromium' }) + const isFirefox = Cypress.isBrowser({ family: 'firefox' }) + + 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.line).to.equal(47) + 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') + + if (isChromium) { + expect(details.function).to.contain('Suite.') + + expect(details.stack).to.equal(`Error + at Suite. (http://localhost:8080/__cypress/src/spec-0.js:55:3) + 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) { + // the firefox traces are really long, so just validate the first line + const firstLine = details.stack.split('\n')[0] + + expect(firstLine).to.equal('./cypress/component/stack_utils-invocationDetails.cy.ts/<@http://localhost:8080/__cypress/src/spec-0.js:55:5') + } + }) +}) diff --git a/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts b/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts new file mode 100644 index 00000000000..67acad0aa76 --- /dev/null +++ b/packages/driver/cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts @@ -0,0 +1,149 @@ +import type { InvocationDetails } from '../../../src/cypress/stack_utils' + +// Create a custom it function that will add 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) + } +} + +// 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('has correct invocation details for a test at root level', () => { + 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(13) // 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:16:1) + at eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:155:12) + at eval () + 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) + expect(details.function).to.equal('') + + // 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:16:3') + } +}) + +myIt('has correct invocation details for myIt test at root level', 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(47) // the line number should be the line number of the invocation of this test + + if (isChromium) { + expect(details.function).to.equal('eval') + expect(details.column).to.equal(0) + expect(details.stack).to.equal(`Error + at eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:50:1) + at eval (http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:155:12) + at eval () + at eval (cypress:///../driver/src/cypress/script_utils.ts:38:23)`) + } else if (isFirefox) { + expect(details.column).to.equal(5) + expect(details.function).to.equal('') + + // 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:50:5') + } +}) + +describe('outer describe block', () => { + context('inner context block', () => { + it('has correctinvocation 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(82) // 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:85: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) + expect(details.function).to.equal('') + + // 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:85:7') + } + }) + + myIt('has correct invocation details for myIt test', 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(117) // 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:120: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:120:9') + } + }) + }) +}) diff --git a/packages/driver/src/cypress/mocha.ts b/packages/driver/src/cypress/mocha.ts index 4950f3732c5..50501bf41a9 100644 --- a/packages/driver/src/cypress/mocha.ts +++ b/packages/driver/src/cypress/mocha.ts @@ -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) diff --git a/packages/driver/src/cypress/stack_utils.ts b/packages/driver/src/cypress/stack_utils.ts index d06632950e3..be96592c421 100644 --- a/packages/driver/src/cypress/stack_utils.ts +++ b/packages/driver/src/cypress/stack_utils.ts @@ -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 @@ -114,7 +165,9 @@ const stackWithUserInvocationStackSpliced = (err, userInvocationStack): StackAnd } } -type InvocationDetails = { +export type InvocationDetails = { + function?: string + fileUrl?: string absoluteFile?: string column?: number line?: number @@ -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 @@ -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 = getSourceDetailsForFirstLine(stack, sourceMapProjectRoot) || {} diff --git a/packages/driver/test/unit/cypress/stack_utils.spec.ts b/packages/driver/test/unit/cypress/stack_utils.spec.ts index 1a912e88908..7b892e50da2 100644 --- a/packages/driver/test/unit/cypress/stack_utils.spec.ts +++ b/packages/driver/test/unit/cypress/stack_utils.spec.ts @@ -6,6 +6,7 @@ import { vi, describe, it, expect, beforeEach } from 'vitest' import source_map_utils from '../../../src/cypress/source_map_utils' import stack_utils from '../../../src/cypress/stack_utils' import stackFrameFixture from './__fixtures__/getInvocationDetails_spec_stackframes.json' +import { Browser } from '@packages/types' vi.mock('../../../src/cypress/source_map_utils', () => { return { @@ -20,6 +21,8 @@ describe('stack_utils', () => { // @ts-expect-error global.Cypress = { config: vi.fn(), + isBrowser: vi.fn(() => true), + testingType: 'e2e', } vi.resetAllMocks() @@ -37,7 +40,7 @@ describe('stack_utils', () => { return stack } } - const config = () => projectRoot + const config = projectRoot for (const scenario of scenarios) { const { browser, build, specFrame, stack: scenarioStack } = scenario @@ -64,6 +67,184 @@ describe('stack_utils', () => { }) }) } + + describe('stack trimming', () => { + let CypressWindow: Partial + + describe('chromium', () => { + beforeEach(() => { + CypressWindow = { + isBrowser: vi.fn(({ family }: Browser) => family === 'chromium') as any, + testingType: 'e2e', + } + }) + + it('returns the correct invocation details for a test stack trace that needs to be trimmed', () => { + const stack = `Error + at itGrep (http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js:444:14) + at eval (http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:14:1) + at eval (http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:18:12) + at eval () + at eval (cypress:///../driver/src/cypress/script_utils.ts:38:23)` + + class MockError { + get stack () { + return stack + } + } + + stack_utils.getInvocationDetails( + { Error: MockError, Cypress: CypressWindow }, + config, + 'test', + ) + + expect(source_map_utils.getSourcePosition).toHaveBeenCalledWith('http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js', expect.objectContaining({ + column: 1, + line: 14, + file: 'http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js', + })) + }) + + it('does not trim component testing stack traces', () => { + CypressWindow = { + isBrowser: vi.fn(({ family }: Browser) => family === 'chromium') as any, + testingType: 'component', + } + + const stack = `Error + at itGrep (http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js:444:14) + at context.notIt.only (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:98:46) + at createRunnable (cypress:///../driver/src/cypress/mocha.ts:126:31) + at itGrep.eval [as only] (cypress:///../driver/src/cypress/mocha.ts:187:14) + at Suite.eval (http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:12:6)` + + class MockError { + get stack () { + return stack + } + } + + stack_utils.getInvocationDetails( + { Error: MockError, Cypress: CypressWindow }, + config, + 'test', + ) + + // calls `getSourcePosition` with the top line of the stack because it was not modified + expect(source_map_utils.getSourcePosition).toHaveBeenCalledWith('http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js', expect.objectContaining({ + column: 14, + line: 444, + file: 'http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js', + })) + }) + + it('returns the correct invocation details for a .only test with a stack that needs to be trimmed', () => { + const stack = `Error + at itGrep (http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js:444:14) + at context.notIt.only (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:98:46) + at createRunnable (cypress:///../driver/src/cypress/mocha.ts:126:31) + at itGrep.eval [as only] (cypress:///../driver/src/cypress/mocha.ts:187:14) + at Suite.eval (http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:12:6)` + + class MockError { + get stack () { + return stack + } + } + + stack_utils.getInvocationDetails( + { Error: MockError, Cypress: CypressWindow }, + config, + 'test', + ) + + expect(source_map_utils.getSourcePosition).toHaveBeenCalledWith('http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js', expect.objectContaining({ + column: 6, + line: 12, + file: 'http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js', + })) + }) + + it('returns the original stack if it cannot be normalized for a test', () => { + const stack = `Error + at itGrep (http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js:444:14) + at context.notIt.only (cypress:///../driver/node_modules/mocha/lib/interfaces/bdd.js:98:46) + at createRunnable (cypress:///../driver/src/cypress/mocha.ts:126:31) + at itGrep.eval [as only] (cypress:///../driver/src/cypress/mocha.ts:187:14) + at somethingElse (http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:12:6)` + + class MockError { + get stack () { + return stack + } + } + + const result = stack_utils.getInvocationDetails( + { Error: MockError, Cypress: CypressWindow }, + config, + 'test', + ) + + expect(result.stack).toEqual(stack) + }) + }) + + describe('firefox', () => { + beforeEach(() => { + CypressWindow = { + isBrowser: vi.fn(({ family }: Browser) => family === 'firefox') as any, + testingType: 'e2e', + } + }) + + it('returns the correct invocation details for a test stack trace that needs to be trimmed', () => { + const stack = `myIt@http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:84:11 + @http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:87:9 + create@cypress:///../driver/node_modules/mocha/lib/interfaces/common.js:141:19 + bddInterface/ { + // stack does not contain the invocation details that we're looking for, so we should return the original stack + const stack = `myIt@http://localhost:3500/__cypress/tests?p=cypress/e2e/cypress/stack_utils-invocationDetails.cy.ts:84:11 + create@cypress:///../driver/node_modules/mocha/lib/interfaces/common.js:141:19 + bddInterface/ { diff --git a/system-tests/project-fixtures/runner-specs/cypress/e2e/hooks/wrapped-it.cy.js b/system-tests/project-fixtures/runner-specs/cypress/e2e/hooks/wrapped-it.cy.js new file mode 100644 index 00000000000..c049b3e51bd --- /dev/null +++ b/system-tests/project-fixtures/runner-specs/cypress/e2e/hooks/wrapped-it.cy.js @@ -0,0 +1,11 @@ +function myIt (name, fn) { + it(name, fn) +} + +myIt('test 1', () => { + cy.log('testBody 1') +}) + +myIt('test 2', () => { + cy.log('testBody 2') +})