dd-trace 5.4.0 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +98 -0
- package/README.md +4 -102
- package/ci/cypress/after-run.js +1 -0
- package/package.json +1 -1
- package/packages/datadog-instrumentations/src/cucumber.js +156 -42
- package/packages/datadog-instrumentations/src/jest.js +69 -35
- package/packages/datadog-plugin-amqplib/src/consumer.js +5 -2
- package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +60 -50
- package/packages/datadog-plugin-aws-sdk/src/services/sns.js +40 -17
- package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +62 -26
- package/packages/datadog-plugin-cucumber/src/index.js +25 -9
- package/packages/datadog-plugin-cypress/src/after-run.js +3 -0
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +560 -0
- package/packages/datadog-plugin-cypress/src/plugin.js +6 -549
- package/packages/datadog-plugin-rhea/src/consumer.js +4 -1
- package/packages/dd-trace/src/config.js +3 -2
- package/packages/dd-trace/src/opentracing/propagation/text_map.js +1 -1
- package/packages/dd-trace/src/opentracing/span.js +4 -4
- package/packages/dd-trace/src/profiling/exporters/agent.js +40 -31
|
@@ -0,0 +1,560 @@
|
|
|
1
|
+
const {
|
|
2
|
+
TEST_STATUS,
|
|
3
|
+
TEST_IS_RUM_ACTIVE,
|
|
4
|
+
TEST_CODE_OWNERS,
|
|
5
|
+
getTestEnvironmentMetadata,
|
|
6
|
+
CI_APP_ORIGIN,
|
|
7
|
+
getTestParentSpan,
|
|
8
|
+
getCodeOwnersFileEntries,
|
|
9
|
+
getCodeOwnersForFilename,
|
|
10
|
+
getTestCommonTags,
|
|
11
|
+
getTestSessionCommonTags,
|
|
12
|
+
getTestModuleCommonTags,
|
|
13
|
+
getTestSuiteCommonTags,
|
|
14
|
+
TEST_SUITE_ID,
|
|
15
|
+
TEST_MODULE_ID,
|
|
16
|
+
TEST_SESSION_ID,
|
|
17
|
+
TEST_COMMAND,
|
|
18
|
+
TEST_MODULE,
|
|
19
|
+
TEST_SOURCE_START,
|
|
20
|
+
finishAllTraceSpans,
|
|
21
|
+
getCoveredFilenamesFromCoverage,
|
|
22
|
+
getTestSuitePath,
|
|
23
|
+
addIntelligentTestRunnerSpanTags,
|
|
24
|
+
TEST_SKIPPED_BY_ITR,
|
|
25
|
+
TEST_ITR_UNSKIPPABLE,
|
|
26
|
+
TEST_ITR_FORCED_RUN,
|
|
27
|
+
ITR_CORRELATION_ID,
|
|
28
|
+
TEST_SOURCE_FILE
|
|
29
|
+
} = require('../../dd-trace/src/plugins/util/test')
|
|
30
|
+
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
31
|
+
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
32
|
+
const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
|
|
33
|
+
const log = require('../../dd-trace/src/log')
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
TELEMETRY_EVENT_CREATED,
|
|
37
|
+
TELEMETRY_EVENT_FINISHED,
|
|
38
|
+
TELEMETRY_ITR_FORCED_TO_RUN,
|
|
39
|
+
TELEMETRY_CODE_COVERAGE_EMPTY,
|
|
40
|
+
TELEMETRY_ITR_UNSKIPPABLE,
|
|
41
|
+
TELEMETRY_CODE_COVERAGE_NUM_FILES,
|
|
42
|
+
incrementCountMetric,
|
|
43
|
+
distributionMetric
|
|
44
|
+
} = require('../../dd-trace/src/ci-visibility/telemetry')
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
GIT_REPOSITORY_URL,
|
|
48
|
+
GIT_COMMIT_SHA,
|
|
49
|
+
GIT_BRANCH,
|
|
50
|
+
CI_PROVIDER_NAME,
|
|
51
|
+
CI_WORKSPACE_PATH
|
|
52
|
+
} = require('../../dd-trace/src/plugins/util/tags')
|
|
53
|
+
const {
|
|
54
|
+
OS_VERSION,
|
|
55
|
+
OS_PLATFORM,
|
|
56
|
+
OS_ARCHITECTURE,
|
|
57
|
+
RUNTIME_NAME,
|
|
58
|
+
RUNTIME_VERSION
|
|
59
|
+
} = require('../../dd-trace/src/plugins/util/env')
|
|
60
|
+
|
|
61
|
+
const TEST_FRAMEWORK_NAME = 'cypress'
|
|
62
|
+
|
|
63
|
+
const CYPRESS_STATUS_TO_TEST_STATUS = {
|
|
64
|
+
passed: 'pass',
|
|
65
|
+
failed: 'fail',
|
|
66
|
+
pending: 'skip',
|
|
67
|
+
skipped: 'skip'
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getSessionStatus (summary) {
|
|
71
|
+
if (summary.totalFailed !== undefined && summary.totalFailed > 0) {
|
|
72
|
+
return 'fail'
|
|
73
|
+
}
|
|
74
|
+
if (summary.totalSkipped !== undefined && summary.totalSkipped === summary.totalTests) {
|
|
75
|
+
return 'skip'
|
|
76
|
+
}
|
|
77
|
+
return 'pass'
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function getCypressVersion (details) {
|
|
81
|
+
if (details?.cypressVersion) {
|
|
82
|
+
return details.cypressVersion
|
|
83
|
+
}
|
|
84
|
+
if (details?.config?.version) {
|
|
85
|
+
return details.config.version
|
|
86
|
+
}
|
|
87
|
+
return ''
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getRootDir (details) {
|
|
91
|
+
if (details?.config) {
|
|
92
|
+
return details.config.projectRoot || details.config.repoRoot || process.cwd()
|
|
93
|
+
}
|
|
94
|
+
return process.cwd()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function getCypressCommand (details) {
|
|
98
|
+
if (!details) {
|
|
99
|
+
return TEST_FRAMEWORK_NAME
|
|
100
|
+
}
|
|
101
|
+
return `${TEST_FRAMEWORK_NAME} ${details.specPattern || ''}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function getLibraryConfiguration (tracer, testConfiguration) {
|
|
105
|
+
return new Promise(resolve => {
|
|
106
|
+
if (!tracer._tracer._exporter?.getLibraryConfiguration) {
|
|
107
|
+
return resolve({ err: new Error('CI Visibility was not initialized correctly') })
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
tracer._tracer._exporter.getLibraryConfiguration(testConfiguration, (err, libraryConfig) => {
|
|
111
|
+
resolve({ err, libraryConfig })
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function getSkippableTests (isSuitesSkippingEnabled, tracer, testConfiguration) {
|
|
117
|
+
if (!isSuitesSkippingEnabled) {
|
|
118
|
+
return Promise.resolve({ skippableTests: [] })
|
|
119
|
+
}
|
|
120
|
+
return new Promise(resolve => {
|
|
121
|
+
if (!tracer._tracer._exporter?.getLibraryConfiguration) {
|
|
122
|
+
return resolve({ err: new Error('CI Visibility was not initialized correctly') })
|
|
123
|
+
}
|
|
124
|
+
tracer._tracer._exporter.getSkippableSuites(testConfiguration, (err, skippableTests, correlationId) => {
|
|
125
|
+
resolve({
|
|
126
|
+
err,
|
|
127
|
+
skippableTests,
|
|
128
|
+
correlationId
|
|
129
|
+
})
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function getSuiteStatus (suiteStats) {
|
|
135
|
+
if (!suiteStats) {
|
|
136
|
+
return 'skip'
|
|
137
|
+
}
|
|
138
|
+
if (suiteStats.failures !== undefined && suiteStats.failures > 0) {
|
|
139
|
+
return 'fail'
|
|
140
|
+
}
|
|
141
|
+
if (suiteStats.tests !== undefined &&
|
|
142
|
+
(suiteStats.tests === suiteStats.pending || suiteStats.tests === suiteStats.skipped)) {
|
|
143
|
+
return 'skip'
|
|
144
|
+
}
|
|
145
|
+
return 'pass'
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
class CypressPlugin {
|
|
149
|
+
constructor () {
|
|
150
|
+
this._isInit = false
|
|
151
|
+
this.testEnvironmentMetadata = getTestEnvironmentMetadata(TEST_FRAMEWORK_NAME)
|
|
152
|
+
|
|
153
|
+
const {
|
|
154
|
+
[GIT_REPOSITORY_URL]: repositoryUrl,
|
|
155
|
+
[GIT_COMMIT_SHA]: sha,
|
|
156
|
+
[OS_VERSION]: osVersion,
|
|
157
|
+
[OS_PLATFORM]: osPlatform,
|
|
158
|
+
[OS_ARCHITECTURE]: osArchitecture,
|
|
159
|
+
[RUNTIME_NAME]: runtimeName,
|
|
160
|
+
[RUNTIME_VERSION]: runtimeVersion,
|
|
161
|
+
[GIT_BRANCH]: branch,
|
|
162
|
+
[CI_PROVIDER_NAME]: ciProviderName,
|
|
163
|
+
[CI_WORKSPACE_PATH]: repositoryRoot
|
|
164
|
+
} = this.testEnvironmentMetadata
|
|
165
|
+
|
|
166
|
+
this.repositoryRoot = repositoryRoot
|
|
167
|
+
this.isUnsupportedCIProvider = !ciProviderName
|
|
168
|
+
this.codeOwnersEntries = getCodeOwnersFileEntries(repositoryRoot)
|
|
169
|
+
|
|
170
|
+
this.testConfiguration = {
|
|
171
|
+
repositoryUrl,
|
|
172
|
+
sha,
|
|
173
|
+
osVersion,
|
|
174
|
+
osPlatform,
|
|
175
|
+
osArchitecture,
|
|
176
|
+
runtimeName,
|
|
177
|
+
runtimeVersion,
|
|
178
|
+
branch,
|
|
179
|
+
testLevel: 'test'
|
|
180
|
+
}
|
|
181
|
+
this.finishedTestsByFile = {}
|
|
182
|
+
|
|
183
|
+
this.isTestsSkipped = false
|
|
184
|
+
this.isSuitesSkippingEnabled = false
|
|
185
|
+
this.isCodeCoverageEnabled = false
|
|
186
|
+
this.skippedTests = []
|
|
187
|
+
this.hasForcedToRunSuites = false
|
|
188
|
+
this.hasUnskippableSuites = false
|
|
189
|
+
this.unskippableSuites = []
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
init (tracer, cypressConfig) {
|
|
193
|
+
this._isInit = true
|
|
194
|
+
this.tracer = tracer
|
|
195
|
+
this.cypressConfig = cypressConfig
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
getTestSuiteSpan (suite) {
|
|
199
|
+
const testSuiteSpanMetadata =
|
|
200
|
+
getTestSuiteCommonTags(this.command, this.frameworkVersion, suite, TEST_FRAMEWORK_NAME)
|
|
201
|
+
this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'suite')
|
|
202
|
+
return this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_suite`, {
|
|
203
|
+
childOf: this.testModuleSpan,
|
|
204
|
+
tags: {
|
|
205
|
+
[COMPONENT]: TEST_FRAMEWORK_NAME,
|
|
206
|
+
...this.testEnvironmentMetadata,
|
|
207
|
+
...testSuiteSpanMetadata
|
|
208
|
+
}
|
|
209
|
+
})
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
getTestSpan (testName, testSuite, isUnskippable, isForcedToRun) {
|
|
213
|
+
const testSuiteTags = {
|
|
214
|
+
[TEST_COMMAND]: this.command,
|
|
215
|
+
[TEST_COMMAND]: this.command,
|
|
216
|
+
[TEST_MODULE]: TEST_FRAMEWORK_NAME
|
|
217
|
+
}
|
|
218
|
+
if (this.testSuiteSpan) {
|
|
219
|
+
testSuiteTags[TEST_SUITE_ID] = this.testSuiteSpan.context().toSpanId()
|
|
220
|
+
}
|
|
221
|
+
if (this.testSessionSpan && this.testModuleSpan) {
|
|
222
|
+
testSuiteTags[TEST_SESSION_ID] = this.testSessionSpan.context().toTraceId()
|
|
223
|
+
testSuiteTags[TEST_MODULE_ID] = this.testModuleSpan.context().toSpanId()
|
|
224
|
+
// If testSuiteSpan couldn't be created, we'll use the testModuleSpan as the parent
|
|
225
|
+
if (!this.testSuiteSpan) {
|
|
226
|
+
testSuiteTags[TEST_SUITE_ID] = this.testModuleSpan.context().toSpanId()
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const childOf = getTestParentSpan(this.tracer)
|
|
231
|
+
const {
|
|
232
|
+
resource,
|
|
233
|
+
...testSpanMetadata
|
|
234
|
+
} = getTestCommonTags(testName, testSuite, this.cypressConfig.version, TEST_FRAMEWORK_NAME)
|
|
235
|
+
|
|
236
|
+
const codeOwners = getCodeOwnersForFilename(testSuite, this.codeOwnersEntries)
|
|
237
|
+
|
|
238
|
+
if (codeOwners) {
|
|
239
|
+
testSpanMetadata[TEST_CODE_OWNERS] = codeOwners
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (isUnskippable) {
|
|
243
|
+
this.hasUnskippableSuites = true
|
|
244
|
+
incrementCountMetric(TELEMETRY_ITR_UNSKIPPABLE, { testLevel: 'suite' })
|
|
245
|
+
testSpanMetadata[TEST_ITR_UNSKIPPABLE] = 'true'
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (isForcedToRun) {
|
|
249
|
+
this.hasForcedToRunSuites = true
|
|
250
|
+
incrementCountMetric(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
|
|
251
|
+
testSpanMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'test', { hasCodeOwners: !!codeOwners })
|
|
255
|
+
|
|
256
|
+
return this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
|
|
257
|
+
childOf,
|
|
258
|
+
tags: {
|
|
259
|
+
[COMPONENT]: TEST_FRAMEWORK_NAME,
|
|
260
|
+
[ORIGIN_KEY]: CI_APP_ORIGIN,
|
|
261
|
+
...testSpanMetadata,
|
|
262
|
+
...this.testEnvironmentMetadata,
|
|
263
|
+
...testSuiteTags
|
|
264
|
+
}
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
ciVisEvent (name, testLevel, tags = {}) {
|
|
269
|
+
incrementCountMetric(name, {
|
|
270
|
+
testLevel,
|
|
271
|
+
testFramework: 'cypress',
|
|
272
|
+
isUnsupportedCIProvider: this.isUnsupportedCIProvider,
|
|
273
|
+
...tags
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
beforeRun (details) {
|
|
278
|
+
this.command = getCypressCommand(details)
|
|
279
|
+
this.frameworkVersion = getCypressVersion(details)
|
|
280
|
+
this.rootDir = getRootDir(details)
|
|
281
|
+
|
|
282
|
+
return getLibraryConfiguration(this.tracer, this.testConfiguration).then(({ err, libraryConfig }) => {
|
|
283
|
+
if (err) {
|
|
284
|
+
log.error(err)
|
|
285
|
+
} else {
|
|
286
|
+
this.isSuitesSkippingEnabled = libraryConfig.isSuitesSkippingEnabled
|
|
287
|
+
this.isCodeCoverageEnabled = libraryConfig.isCodeCoverageEnabled
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return getSkippableTests(this.isSuitesSkippingEnabled, this.tracer, this.testConfiguration)
|
|
291
|
+
.then(({ err, skippableTests, correlationId }) => {
|
|
292
|
+
if (err) {
|
|
293
|
+
log.error(err)
|
|
294
|
+
} else {
|
|
295
|
+
this.testsToSkip = skippableTests || []
|
|
296
|
+
this.itrCorrelationId = correlationId
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// `details.specs` are test files
|
|
300
|
+
details.specs?.forEach(({ absolute, relative }) => {
|
|
301
|
+
const isUnskippableSuite = isMarkedAsUnskippable({ path: absolute })
|
|
302
|
+
if (isUnskippableSuite) {
|
|
303
|
+
this.unskippableSuites.push(relative)
|
|
304
|
+
}
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
const childOf = getTestParentSpan(this.tracer)
|
|
308
|
+
|
|
309
|
+
const testSessionSpanMetadata =
|
|
310
|
+
getTestSessionCommonTags(this.command, this.frameworkVersion, TEST_FRAMEWORK_NAME)
|
|
311
|
+
const testModuleSpanMetadata =
|
|
312
|
+
getTestModuleCommonTags(this.command, this.frameworkVersion, TEST_FRAMEWORK_NAME)
|
|
313
|
+
|
|
314
|
+
this.testSessionSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_session`, {
|
|
315
|
+
childOf,
|
|
316
|
+
tags: {
|
|
317
|
+
[COMPONENT]: TEST_FRAMEWORK_NAME,
|
|
318
|
+
...this.testEnvironmentMetadata,
|
|
319
|
+
...testSessionSpanMetadata
|
|
320
|
+
}
|
|
321
|
+
})
|
|
322
|
+
this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'session')
|
|
323
|
+
|
|
324
|
+
this.testModuleSpan = this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test_module`, {
|
|
325
|
+
childOf: this.testSessionSpan,
|
|
326
|
+
tags: {
|
|
327
|
+
[COMPONENT]: TEST_FRAMEWORK_NAME,
|
|
328
|
+
...this.testEnvironmentMetadata,
|
|
329
|
+
...testModuleSpanMetadata
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'module')
|
|
333
|
+
|
|
334
|
+
return details
|
|
335
|
+
})
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
afterRun (suiteStats) {
|
|
340
|
+
if (!this._isInit) {
|
|
341
|
+
log.warn('Attemping to call afterRun without initializating the plugin first')
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
if (this.testSessionSpan && this.testModuleSpan) {
|
|
345
|
+
const testStatus = getSessionStatus(suiteStats)
|
|
346
|
+
this.testModuleSpan.setTag(TEST_STATUS, testStatus)
|
|
347
|
+
this.testSessionSpan.setTag(TEST_STATUS, testStatus)
|
|
348
|
+
|
|
349
|
+
addIntelligentTestRunnerSpanTags(
|
|
350
|
+
this.testSessionSpan,
|
|
351
|
+
this.testModuleSpan,
|
|
352
|
+
{
|
|
353
|
+
isSuitesSkipped: this.isTestsSkipped,
|
|
354
|
+
isSuitesSkippingEnabled: this.isSuitesSkippingEnabled,
|
|
355
|
+
isCodeCoverageEnabled: this.isCodeCoverageEnabled,
|
|
356
|
+
skippingType: 'test',
|
|
357
|
+
skippingCount: this.skippedTests.length,
|
|
358
|
+
hasForcedToRunSuites: this.hasForcedToRunSuites,
|
|
359
|
+
hasUnskippableSuites: this.hasUnskippableSuites
|
|
360
|
+
}
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
this.testModuleSpan.finish()
|
|
364
|
+
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
|
|
365
|
+
this.testSessionSpan.finish()
|
|
366
|
+
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'session')
|
|
367
|
+
|
|
368
|
+
finishAllTraceSpans(this.testSessionSpan)
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return new Promise(resolve => {
|
|
372
|
+
const exporter = this.tracer._tracer._exporter
|
|
373
|
+
if (!exporter) {
|
|
374
|
+
return resolve(null)
|
|
375
|
+
}
|
|
376
|
+
if (exporter.flush) {
|
|
377
|
+
exporter.flush(() => {
|
|
378
|
+
appClosingTelemetry()
|
|
379
|
+
resolve(null)
|
|
380
|
+
})
|
|
381
|
+
} else if (exporter._writer) {
|
|
382
|
+
exporter._writer.flush(() => {
|
|
383
|
+
appClosingTelemetry()
|
|
384
|
+
resolve(null)
|
|
385
|
+
})
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
afterSpec (spec, results) {
|
|
391
|
+
const { tests, stats } = results || {}
|
|
392
|
+
const cypressTests = tests || []
|
|
393
|
+
const finishedTests = this.finishedTestsByFile[spec.relative] || []
|
|
394
|
+
|
|
395
|
+
if (!this.testSuiteSpan) {
|
|
396
|
+
// dd:testSuiteStart hasn't been triggered for whatever reason
|
|
397
|
+
// We will create the test suite span on the spot if that's the case
|
|
398
|
+
log.warn('There was an error creating the test suite event.')
|
|
399
|
+
this.testSuiteSpan = this.getTestSuiteSpan(spec.relative)
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Get tests that didn't go through `dd:afterEach`
|
|
403
|
+
// and create a skipped test span for each of them
|
|
404
|
+
cypressTests.filter(({ title }) => {
|
|
405
|
+
const cypressTestName = title.join(' ')
|
|
406
|
+
const isTestFinished = finishedTests.find(({ testName }) => cypressTestName === testName)
|
|
407
|
+
|
|
408
|
+
return !isTestFinished
|
|
409
|
+
}).forEach(({ title }) => {
|
|
410
|
+
const cypressTestName = title.join(' ')
|
|
411
|
+
const isSkippedByItr = this.testsToSkip.find(test =>
|
|
412
|
+
cypressTestName === test.name && spec.relative === test.suite
|
|
413
|
+
)
|
|
414
|
+
const skippedTestSpan = this.getTestSpan(cypressTestName, spec.relative)
|
|
415
|
+
if (spec.absolute && this.repositoryRoot) {
|
|
416
|
+
skippedTestSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, this.repositoryRoot))
|
|
417
|
+
} else {
|
|
418
|
+
skippedTestSpan.setTag(TEST_SOURCE_FILE, spec.relative)
|
|
419
|
+
}
|
|
420
|
+
skippedTestSpan.setTag(TEST_STATUS, 'skip')
|
|
421
|
+
if (isSkippedByItr) {
|
|
422
|
+
skippedTestSpan.setTag(TEST_SKIPPED_BY_ITR, 'true')
|
|
423
|
+
}
|
|
424
|
+
if (this.itrCorrelationId) {
|
|
425
|
+
skippedTestSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
|
|
426
|
+
}
|
|
427
|
+
skippedTestSpan.finish()
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
// Make sure that reported test statuses are the same as Cypress reports.
|
|
431
|
+
// This is not always the case, such as when an `after` hook fails:
|
|
432
|
+
// Cypress will report the last run test as failed, but we don't know that yet at `dd:afterEach`
|
|
433
|
+
let latestError
|
|
434
|
+
finishedTests.forEach((finishedTest) => {
|
|
435
|
+
const cypressTest = cypressTests.find(test => test.title.join(' ') === finishedTest.testName)
|
|
436
|
+
if (!cypressTest) {
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
if (cypressTest.displayError) {
|
|
440
|
+
latestError = new Error(cypressTest.displayError)
|
|
441
|
+
}
|
|
442
|
+
const cypressTestStatus = CYPRESS_STATUS_TO_TEST_STATUS[cypressTest.state]
|
|
443
|
+
// update test status
|
|
444
|
+
if (cypressTestStatus !== finishedTest.testStatus) {
|
|
445
|
+
finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
|
|
446
|
+
finishedTest.testSpan.setTag('error', latestError)
|
|
447
|
+
}
|
|
448
|
+
if (this.itrCorrelationId) {
|
|
449
|
+
finishedTest.testSpan.setTag(ITR_CORRELATION_ID, this.itrCorrelationId)
|
|
450
|
+
}
|
|
451
|
+
if (spec.absolute && this.repositoryRoot) {
|
|
452
|
+
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, getTestSuitePath(spec.absolute, this.repositoryRoot))
|
|
453
|
+
} else {
|
|
454
|
+
finishedTest.testSpan.setTag(TEST_SOURCE_FILE, spec.relative)
|
|
455
|
+
}
|
|
456
|
+
finishedTest.testSpan.finish(finishedTest.finishTime)
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
if (this.testSuiteSpan) {
|
|
460
|
+
const status = getSuiteStatus(stats)
|
|
461
|
+
this.testSuiteSpan.setTag(TEST_STATUS, status)
|
|
462
|
+
|
|
463
|
+
if (latestError) {
|
|
464
|
+
this.testSuiteSpan.setTag('error', latestError)
|
|
465
|
+
}
|
|
466
|
+
this.testSuiteSpan.finish()
|
|
467
|
+
this.testSuiteSpan = null
|
|
468
|
+
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
getTasks () {
|
|
473
|
+
return {
|
|
474
|
+
'dd:testSuiteStart': (suite) => {
|
|
475
|
+
if (this.testSuiteSpan) {
|
|
476
|
+
return null
|
|
477
|
+
}
|
|
478
|
+
this.testSuiteSpan = this.getTestSuiteSpan(suite)
|
|
479
|
+
return null
|
|
480
|
+
},
|
|
481
|
+
'dd:beforeEach': (test) => {
|
|
482
|
+
const { testName, testSuite } = test
|
|
483
|
+
const shouldSkip = !!this.testsToSkip.find(test => {
|
|
484
|
+
return testName === test.name && testSuite === test.suite
|
|
485
|
+
})
|
|
486
|
+
const isUnskippable = this.unskippableSuites.includes(testSuite)
|
|
487
|
+
const isForcedToRun = shouldSkip && isUnskippable
|
|
488
|
+
|
|
489
|
+
// skip test
|
|
490
|
+
if (shouldSkip && !isUnskippable) {
|
|
491
|
+
this.skippedTests.push(test)
|
|
492
|
+
this.isTestsSkipped = true
|
|
493
|
+
return { shouldSkip: true }
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (!this.activeTestSpan) {
|
|
497
|
+
this.activeTestSpan = this.getTestSpan(testName, testSuite, isUnskippable, isForcedToRun)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return this.activeTestSpan ? { traceId: this.activeTestSpan.context().toTraceId() } : {}
|
|
501
|
+
},
|
|
502
|
+
'dd:afterEach': ({ test, coverage }) => {
|
|
503
|
+
const { state, error, isRUMActive, testSourceLine, testSuite, testName } = test
|
|
504
|
+
if (this.activeTestSpan) {
|
|
505
|
+
if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
|
|
506
|
+
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
|
|
507
|
+
const relativeCoverageFiles = coverageFiles.map(file => getTestSuitePath(file, this.rootDir))
|
|
508
|
+
if (!relativeCoverageFiles.length) {
|
|
509
|
+
incrementCountMetric(TELEMETRY_CODE_COVERAGE_EMPTY)
|
|
510
|
+
}
|
|
511
|
+
distributionMetric(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
|
|
512
|
+
const { _traceId, _spanId } = this.testSuiteSpan.context()
|
|
513
|
+
const formattedCoverage = {
|
|
514
|
+
sessionId: _traceId,
|
|
515
|
+
suiteId: _spanId,
|
|
516
|
+
testId: this.activeTestSpan.context()._spanId,
|
|
517
|
+
files: relativeCoverageFiles
|
|
518
|
+
}
|
|
519
|
+
this.tracer._tracer._exporter.exportCoverage(formattedCoverage)
|
|
520
|
+
}
|
|
521
|
+
const testStatus = CYPRESS_STATUS_TO_TEST_STATUS[state]
|
|
522
|
+
this.activeTestSpan.setTag(TEST_STATUS, testStatus)
|
|
523
|
+
|
|
524
|
+
if (error) {
|
|
525
|
+
this.activeTestSpan.setTag('error', error)
|
|
526
|
+
}
|
|
527
|
+
if (isRUMActive) {
|
|
528
|
+
this.activeTestSpan.setTag(TEST_IS_RUM_ACTIVE, 'true')
|
|
529
|
+
}
|
|
530
|
+
if (testSourceLine) {
|
|
531
|
+
this.activeTestSpan.setTag(TEST_SOURCE_START, testSourceLine)
|
|
532
|
+
}
|
|
533
|
+
const finishedTest = {
|
|
534
|
+
testName,
|
|
535
|
+
testStatus,
|
|
536
|
+
finishTime: this.activeTestSpan._getTime(), // we store the finish time here
|
|
537
|
+
testSpan: this.activeTestSpan
|
|
538
|
+
}
|
|
539
|
+
if (this.finishedTestsByFile[testSuite]) {
|
|
540
|
+
this.finishedTestsByFile[testSuite].push(finishedTest)
|
|
541
|
+
} else {
|
|
542
|
+
this.finishedTestsByFile[testSuite] = [finishedTest]
|
|
543
|
+
}
|
|
544
|
+
// test spans are finished at after:spec
|
|
545
|
+
}
|
|
546
|
+
this.activeTestSpan = null
|
|
547
|
+
this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'test')
|
|
548
|
+
return null
|
|
549
|
+
},
|
|
550
|
+
'dd:addTags': (tags) => {
|
|
551
|
+
if (this.activeTestSpan) {
|
|
552
|
+
this.activeTestSpan.addTags(tags)
|
|
553
|
+
}
|
|
554
|
+
return null
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
module.exports = new CypressPlugin()
|