Skip to content
Open
28 changes: 28 additions & 0 deletions packages/driver/src/cypress/stack_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,27 @@ const stackWithLinesRemoved = (stack, cb) => {
return unsplitStack(messageLines, remainingStackLines)
}

const stackWithGrepLinesRemoved = (stack) => {
return stackWithLinesRemoved(stack, (lines) => {
// Remove any lines containing 'itGrep' first
let cleanedLines = _.reject(lines, (line) => line.includes('itGrep'))

// There are cases where there are other lines in the stack trace before the invocation (eg. `context.it.only`, `createRunnable`, etc)
// Remove lines from the start until the top line starts with 'at eval' or 'at Suite.eval' so that we only keep the actual invocation line.
while (
cleanedLines.length > 0 &&
!(
cleanedLines[0].trim().startsWith('at eval') ||
cleanedLines[0].trim().startsWith('at Suite.eval')
)
) {
cleanedLines.shift()
}

return cleanedLines
})
}

const stackWithLinesDroppedFromMarker = (stack, marker, includeLast = false) => {
return stackWithLinesRemoved(stack, (lines) => {
// drop lines above the marker
Expand Down Expand Up @@ -146,6 +167,13 @@ const getInvocationDetails = (specWindow, sourceMapProjectRoot: string): Invocat
}
}

// if the stack includes the 'itGrep' function, this suggests that the user has registered
// the @cypress/grep plugin. In this case, we need to remove the lines that include 'itGrep'
// so that the first line in the stack is the spec invocation.
if (stack.includes('itGrep')) {
stack = stackWithGrepLinesRemoved(stack)
}

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

;(details as any).stack = stack
Expand Down
78 changes: 78 additions & 0 deletions packages/driver/test/unit/cypress/stack_utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,84 @@ describe('stack_utils', () => {
})
})
}

it('returns the correct invocation details for a grep stack trace', () => {
const stack = `Error\n
at itGrep (http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js:444:14)\n
at eval (http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:14:1)\n
at eval (http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:18:12)\n
at eval (<anonymous>)\n
at eval (cypress:///../driver/src/cypress/script_utils.ts:38:23)`

class GrepError {
get stack () {
return stack
}
}

stack_utils.getInvocationDetails(
{ Error: GrepError, Cypress: {} },
config,
)

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('returns the correct invocation details for a grep stack trace for suites', () => {
const stack = `Error
at itGrep (http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js:444:14)
at context.it.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 GrepError {
get stack () {
return stack
}
}

stack_utils.getInvocationDetails(
{ Error: GrepError, Cypress: {} },
config,
)

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 correct invocation details for a grep stack trace for tests with only', () => {
const stack = `Error
at itGrep (http://localhost:3000/__cypress/tests?p=cypress/support/e2e.js:444:14)
at context.it.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 eval (http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js:11:4)`

class GrepError {
get stack () {
return stack
}
}

stack_utils.getInvocationDetails(
{ Error: GrepError, Cypress: {} },
config,
)

expect(source_map_utils.getSourcePosition).toHaveBeenCalledWith('http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js', expect.objectContaining({
column: 4,
line: 11,
file: 'http://localhost:3000/__cypress/tests?p=cypress/e2e/spec.cy.js',
}))
})
})

describe('normalizedUserInvocationStack', () => {
Expand Down
Loading