Skip to content

Commit f49fafe

Browse files
[test optimization] Fix cypress report of typescript test files (#7680)
1 parent f875381 commit f49fafe

File tree

9 files changed

+746
-8
lines changed

9 files changed

+746
-8
lines changed

integration-tests/cypress/cypress.spec.js

Lines changed: 328 additions & 2 deletions
Large diffs are not rendered by default.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* global describe, it, cy, Cypress */
2+
interface TypeOnly {
3+
field: string
4+
}
5+
6+
describe('spec source line fallback branch', () => {
7+
it('fallback branch literal title', () => {
8+
Cypress.mocha.getRunner().currentRunnable.invocationDetails.line = 9999
9+
cy.visit('/')
10+
.get('.hello-world')
11+
.should('have.text', 'Hello World')
12+
})
13+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/* global describe, it, cy */
2+
'use strict'
3+
4+
// Deliberate: keep this fixture as plain JS with no source map.
5+
// We validate the fast path that trusts invocationDetails.line directly
6+
// and skips source-map/declaration resolution.
7+
8+
describe('spec source line invocation details js', () => {
9+
it('uses invocation details line as source line', () => {
10+
cy.visit('/')
11+
.get('.hello-world')
12+
.should('have.text', 'Hello World')
13+
})
14+
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/* global describe, it, cy, Cypress */
2+
interface TypeOnly {
3+
field: string
4+
}
5+
6+
const NO_MATCH_TITLE = ['no', 'match', 'title'].join(' ')
7+
8+
describe('spec source line no match', () => {
9+
it(NO_MATCH_TITLE, () => {
10+
Cypress.mocha.getRunner().currentRunnable.invocationDetails.line = 9999
11+
cy.visit('/')
12+
.get('.hello-world')
13+
.should('have.text', 'Hello World')
14+
})
15+
})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* global describe, it, specify, cy */
2+
// This file is used to reproduce the issue where testSourceLine is not correctly
3+
// detected for test files compiled from TypeScript.
4+
// The TypeScript-only declarations below are removed during compilation,
5+
// which may cause the line numbers to shift in the compiled output.
6+
interface TypeScriptOnlyInterface {
7+
field: string
8+
}
9+
const SOME_VARIABLE = 'interpolated'
10+
describe('spec source line', () => {
11+
it('reports correct line number', () => {
12+
cy.visit('/')
13+
.get('.hello-world')
14+
.should('have.text', 'Hello World')
15+
})
16+
specify(`template ${SOME_VARIABLE} string test name`, () => {
17+
cy.visit('/')
18+
.get('.hello-world')
19+
.should('have.text', 'Hello World')
20+
})
21+
})
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2020",
4+
"module": "commonjs",
5+
"outDir": "e2e/dist",
6+
"rootDir": "e2e",
7+
"strict": false,
8+
"esModuleInterop": true,
9+
"skipLibCheck": true,
10+
"sourceMap": true,
11+
"noEmitOnError": false
12+
},
13+
"include": [
14+
"e2e/spec-source-line.cy.ts",
15+
"e2e/spec-source-line-fallback.cy.ts",
16+
"e2e/spec-source-line-no-match.cy.ts"
17+
]
18+
}

packages/datadog-plugin-cypress/src/cypress-plugin.js

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ const {
9191
RUNTIME_VERSION,
9292
} = require('../../dd-trace/src/plugins/util/env')
9393
const { DD_MAJOR } = require('../../../version')
94+
const {
95+
resolveOriginalSourcePosition,
96+
resolveSourceLineForTest,
97+
shouldTrustInvocationDetailsLine,
98+
} = require('./source-map-utils')
9499

95100
const TEST_FRAMEWORK_NAME = 'cypress'
96101

@@ -238,7 +243,6 @@ class CypressPlugin {
238243

239244
finishedTestsByFile = {}
240245
testStatuses = {}
241-
242246
isTestsSkipped = false
243247
isSuitesSkippingEnabled = false
244248
isCodeCoverageEnabled = false
@@ -391,7 +395,9 @@ class CypressPlugin {
391395
this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
392396

393397
if (testSuiteAbsolutePath) {
394-
const testSourceFile = getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
398+
const resolvedSuitePosition = resolveOriginalSourcePosition(testSuiteAbsolutePath, 1)
399+
const resolvedSuiteAbsolutePath = resolvedSuitePosition ? resolvedSuitePosition.sourceFile : testSuiteAbsolutePath
400+
const testSourceFile = getTestSuitePath(resolvedSuiteAbsolutePath, this.repositoryRoot)
395401
testSuiteSpanMetadata[TEST_SOURCE_FILE] = testSourceFile
396402
testSuiteSpanMetadata[TEST_SOURCE_START] = 1
397403
const codeOwners = this.getTestCodeOwners({ testSuite, testSourceFile })
@@ -804,8 +810,10 @@ class CypressPlugin {
804810
if (this.itrCorrelationId) {
805811
finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
806812
}
807-
const testSourceFile = spec.absolute && this.repositoryRoot
808-
? getTestSuitePath(spec.absolute, this.repositoryRoot)
813+
const resolvedSpecPosition = spec.absolute ? resolveOriginalSourcePosition(spec.absolute, 1) : null
814+
const resolvedSpecAbsolutePath = resolvedSpecPosition ? resolvedSpecPosition.sourceFile : spec.absolute
815+
const testSourceFile = resolvedSpecAbsolutePath && this.repositoryRoot
816+
? getTestSuitePath(resolvedSpecAbsolutePath, this.repositoryRoot)
809817
: spec.relative
810818
if (testSourceFile) {
811819
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, testSourceFile)
@@ -902,9 +910,11 @@ class CypressPlugin {
902910
error,
903911
isRUMActive,
904912
testSourceLine,
913+
testSourceStack,
905914
testSuite,
906915
testSuiteAbsolutePath,
907916
testName,
917+
testItTitle,
908918
isNew,
909919
isEfdRetry,
910920
isAttemptToFix,
@@ -946,8 +956,29 @@ class CypressPlugin {
946956
if (isRUMActive) {
947957
this.activeTestSpan.setTag(TEST_IS_RUM_ACTIVE, 'true')
948958
}
959+
// Source-line resolution strategy:
960+
// 1. If plain JS and no source map, trust invocationDetails.line directly.
961+
// 2. Otherwise, try invocationDetails.stack line mapped through source map.
962+
// 3. If that fails, scan generated file for it/test/specify declaration by test name.
963+
// 4. If declaration found:
964+
// - .ts file: use declaration line directly.
965+
// - .js file: map declaration line through source map.
966+
// 5. If all fail, keep original invocationDetails.line.
949967
if (testSourceLine) {
950-
this.activeTestSpan.setTag(TEST_SOURCE_START, testSourceLine)
968+
let resolvedLine = testSourceLine
969+
if (testSuiteAbsolutePath && testItTitle) {
970+
// Use invocationDetails directly only for plain JS specs without source maps.
971+
// Otherwise, resolve from the test declaration in the spec and map via source map.
972+
const shouldTrustInvocationDetails = shouldTrustInvocationDetailsLine(testSuiteAbsolutePath, testSourceLine)
973+
if (!shouldTrustInvocationDetails) {
974+
resolvedLine = resolveSourceLineForTest(
975+
testSuiteAbsolutePath,
976+
testItTitle,
977+
testSourceStack
978+
) ?? testSourceLine
979+
}
980+
}
981+
this.activeTestSpan.setTag(TEST_SOURCE_START, resolvedLine)
951982
}
952983
if (isNew) {
953984
this.activeTestSpan.setTag(TEST_IS_NEW, 'true')

0 commit comments

Comments
 (0)