dd-trace 5.43.0 → 5.45.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/package.json +4 -4
- package/packages/datadog-instrumentations/src/cucumber.js +61 -23
- package/packages/datadog-instrumentations/src/dd-trace-api.js +7 -0
- package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
- package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
- package/packages/datadog-instrumentations/src/jest.js +134 -48
- package/packages/datadog-instrumentations/src/mocha/main.js +20 -4
- package/packages/datadog-instrumentations/src/mocha/utils.js +89 -30
- package/packages/datadog-instrumentations/src/mocha/worker.js +3 -1
- package/packages/datadog-instrumentations/src/playwright.js +97 -17
- package/packages/datadog-instrumentations/src/router.js +1 -0
- package/packages/datadog-instrumentations/src/tedious.js +13 -10
- package/packages/datadog-instrumentations/src/vitest.js +77 -17
- package/packages/datadog-plugin-cucumber/src/index.js +24 -1
- package/packages/datadog-plugin-cypress/src/cypress-plugin.js +69 -20
- package/packages/datadog-plugin-cypress/src/support.js +39 -10
- package/packages/datadog-plugin-google-cloud-vertexai/src/index.js +8 -186
- package/packages/datadog-plugin-google-cloud-vertexai/src/tracing.js +186 -0
- package/packages/datadog-plugin-google-cloud-vertexai/src/utils.js +19 -0
- package/packages/datadog-plugin-jest/src/index.js +38 -5
- package/packages/datadog-plugin-mocha/src/index.js +28 -5
- package/packages/datadog-plugin-playwright/src/index.js +22 -2
- package/packages/datadog-plugin-tedious/src/index.js +14 -9
- package/packages/datadog-plugin-vitest/src/index.js +46 -14
- package/packages/dd-trace/src/appsec/blocking.js +2 -0
- package/packages/dd-trace/src/appsec/graphql.js +3 -1
- package/packages/dd-trace/src/appsec/reporter.js +13 -8
- package/packages/dd-trace/src/appsec/sdk/track_event.js +7 -0
- package/packages/dd-trace/src/appsec/telemetry/common.js +6 -3
- package/packages/dd-trace/src/appsec/telemetry/index.js +28 -5
- package/packages/dd-trace/src/appsec/telemetry/user.js +9 -1
- package/packages/dd-trace/src/appsec/telemetry/waf.js +29 -9
- package/packages/dd-trace/src/appsec/waf/waf_manager.js +16 -7
- package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +5 -2
- package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +3 -1
- package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +4 -2
- package/packages/dd-trace/src/dogstatsd.js +94 -77
- package/packages/dd-trace/src/histogram.js +12 -23
- package/packages/dd-trace/src/llmobs/constants/tags.js +2 -0
- package/packages/dd-trace/src/llmobs/index.js +3 -0
- package/packages/dd-trace/src/llmobs/noop.js +3 -3
- package/packages/dd-trace/src/llmobs/plugins/base.js +1 -1
- package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +2 -1
- package/packages/dd-trace/src/llmobs/plugins/vertexai.js +196 -0
- package/packages/dd-trace/src/llmobs/sdk.js +2 -0
- package/packages/dd-trace/src/llmobs/span_processor.js +6 -0
- package/packages/dd-trace/src/llmobs/tagger.js +8 -2
- package/packages/dd-trace/src/llmobs/telemetry.js +108 -1
- package/packages/dd-trace/src/llmobs/writers/base.js +4 -0
- package/packages/dd-trace/src/llmobs/writers/spans/base.js +10 -1
- package/packages/dd-trace/src/plugin_manager.js +0 -3
- package/packages/dd-trace/src/plugins/ci_plugin.js +16 -26
- package/packages/dd-trace/src/plugins/database.js +4 -4
- package/packages/dd-trace/src/plugins/plugin.js +2 -0
- package/packages/dd-trace/src/plugins/util/test.js +62 -1
- package/packages/dd-trace/src/remote_config/manager.js +5 -0
- package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +7 -6
- package/packages/dd-trace/src/telemetry/send-data.js +5 -1
|
@@ -9,6 +9,7 @@ const testPassCh = channel('ci:vitest:test:pass')
|
|
|
9
9
|
const testErrorCh = channel('ci:vitest:test:error')
|
|
10
10
|
const testSkipCh = channel('ci:vitest:test:skip')
|
|
11
11
|
const isNewTestCh = channel('ci:vitest:test:is-new')
|
|
12
|
+
const isAttemptToFixCh = channel('ci:vitest:test:is-attempt-to-fix')
|
|
12
13
|
const isDisabledCh = channel('ci:vitest:test:is-disabled')
|
|
13
14
|
const isQuarantinedCh = channel('ci:vitest:test:is-quarantined')
|
|
14
15
|
|
|
@@ -30,7 +31,9 @@ const taskToStatuses = new WeakMap()
|
|
|
30
31
|
const newTasks = new WeakSet()
|
|
31
32
|
const disabledTasks = new WeakSet()
|
|
32
33
|
const quarantinedTasks = new WeakSet()
|
|
34
|
+
const attemptToFixTasks = new WeakSet()
|
|
33
35
|
let isRetryReasonEfd = false
|
|
36
|
+
let isRetryReasonAttemptToFix = false
|
|
34
37
|
const switchedStatuses = new WeakSet()
|
|
35
38
|
const sessionAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
36
39
|
|
|
@@ -53,6 +56,7 @@ function getProvidedContext () {
|
|
|
53
56
|
_ddEarlyFlakeDetectionNumRetries: numRepeats,
|
|
54
57
|
_ddIsKnownTestsEnabled: isKnownTestsEnabled,
|
|
55
58
|
_ddIsTestManagementTestsEnabled: isTestManagementTestsEnabled,
|
|
59
|
+
_ddTestManagementAttemptToFixRetries: testManagementAttemptToFixRetries,
|
|
56
60
|
_ddTestManagementTests: testManagementTests,
|
|
57
61
|
_ddIsFlakyTestRetriesEnabled: isFlakyTestRetriesEnabled
|
|
58
62
|
} = globalThis.__vitest_worker__.providedContext
|
|
@@ -64,6 +68,7 @@ function getProvidedContext () {
|
|
|
64
68
|
numRepeats,
|
|
65
69
|
isKnownTestsEnabled,
|
|
66
70
|
isTestManagementTestsEnabled,
|
|
71
|
+
testManagementAttemptToFixRetries,
|
|
67
72
|
testManagementTests,
|
|
68
73
|
isFlakyTestRetriesEnabled
|
|
69
74
|
}
|
|
@@ -76,6 +81,7 @@ function getProvidedContext () {
|
|
|
76
81
|
numRepeats: 0,
|
|
77
82
|
isKnownTestsEnabled: false,
|
|
78
83
|
isTestManagementTestsEnabled: false,
|
|
84
|
+
testManagementAttemptToFixRetries: 0,
|
|
79
85
|
testManagementTests: {}
|
|
80
86
|
}
|
|
81
87
|
}
|
|
@@ -176,6 +182,7 @@ function getSortWrapper (sort) {
|
|
|
176
182
|
let isEarlyFlakeDetectionFaulty = false
|
|
177
183
|
let isKnownTestsEnabled = false
|
|
178
184
|
let isTestManagementTestsEnabled = false
|
|
185
|
+
let testManagementAttemptToFixRetries = 0
|
|
179
186
|
let isDiEnabled = false
|
|
180
187
|
let knownTests = {}
|
|
181
188
|
let testManagementTests = {}
|
|
@@ -190,6 +197,7 @@ function getSortWrapper (sort) {
|
|
|
190
197
|
isDiEnabled = libraryConfig.isDiEnabled
|
|
191
198
|
isKnownTestsEnabled = libraryConfig.isKnownTestsEnabled
|
|
192
199
|
isTestManagementTestsEnabled = libraryConfig.isTestManagementEnabled
|
|
200
|
+
testManagementAttemptToFixRetries = libraryConfig.testManagementAttemptToFixRetries
|
|
193
201
|
}
|
|
194
202
|
} catch (e) {
|
|
195
203
|
isFlakyTestRetriesEnabled = false
|
|
@@ -262,6 +270,7 @@ function getSortWrapper (sort) {
|
|
|
262
270
|
try {
|
|
263
271
|
const workspaceProject = this.ctx.getCoreWorkspaceProject()
|
|
264
272
|
workspaceProject._provided._ddIsTestManagementTestsEnabled = isTestManagementTestsEnabled
|
|
273
|
+
workspaceProject._provided._ddTestManagementAttemptToFixRetries = testManagementAttemptToFixRetries
|
|
265
274
|
workspaceProject._provided._ddTestManagementTests = testManagementTests
|
|
266
275
|
} catch (e) {
|
|
267
276
|
log.warn('Could not send test management tests to workers so Test Management will not work.')
|
|
@@ -353,10 +362,24 @@ addHook({
|
|
|
353
362
|
isKnownTestsEnabled,
|
|
354
363
|
numRepeats,
|
|
355
364
|
isTestManagementTestsEnabled,
|
|
365
|
+
testManagementAttemptToFixRetries,
|
|
356
366
|
testManagementTests
|
|
357
367
|
} = getProvidedContext()
|
|
358
368
|
|
|
359
369
|
if (isTestManagementTestsEnabled) {
|
|
370
|
+
isAttemptToFixCh.publish({
|
|
371
|
+
testManagementTests,
|
|
372
|
+
testSuiteAbsolutePath: task.file.filepath,
|
|
373
|
+
testName,
|
|
374
|
+
onDone: (isAttemptToFix) => {
|
|
375
|
+
if (isAttemptToFix) {
|
|
376
|
+
isRetryReasonAttemptToFix = task.repeats !== testManagementAttemptToFixRetries
|
|
377
|
+
task.repeats = testManagementAttemptToFixRetries
|
|
378
|
+
attemptToFixTasks.add(task)
|
|
379
|
+
taskToStatuses.set(task, [])
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
})
|
|
360
383
|
isDisabledCh.publish({
|
|
361
384
|
testManagementTests,
|
|
362
385
|
testSuiteAbsolutePath: task.file.filepath,
|
|
@@ -364,7 +387,10 @@ addHook({
|
|
|
364
387
|
onDone: (isTestDisabled) => {
|
|
365
388
|
if (isTestDisabled) {
|
|
366
389
|
disabledTasks.add(task)
|
|
367
|
-
task
|
|
390
|
+
if (!attemptToFixTasks.has(task)) {
|
|
391
|
+
// we only actually skip if the test is not being attempted to be fixed
|
|
392
|
+
task.mode = 'skip'
|
|
393
|
+
}
|
|
368
394
|
}
|
|
369
395
|
}
|
|
370
396
|
})
|
|
@@ -376,7 +402,7 @@ addHook({
|
|
|
376
402
|
testSuiteAbsolutePath: task.file.filepath,
|
|
377
403
|
testName,
|
|
378
404
|
onDone: (isNew) => {
|
|
379
|
-
if (isNew) {
|
|
405
|
+
if (isNew && !attemptToFixTasks.has(task)) {
|
|
380
406
|
if (isEarlyFlakeDetectionEnabled) {
|
|
381
407
|
isRetryReasonEfd = task.repeats !== numRepeats
|
|
382
408
|
task.repeats = numRepeats
|
|
@@ -396,19 +422,28 @@ addHook({
|
|
|
396
422
|
shimmer.wrap(VitestTestRunner.prototype, 'onAfterRunTask', onAfterRunTask => function (task) {
|
|
397
423
|
const { isEarlyFlakeDetectionEnabled, isTestManagementTestsEnabled } = getProvidedContext()
|
|
398
424
|
|
|
399
|
-
if (
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
425
|
+
if (isTestManagementTestsEnabled) {
|
|
426
|
+
const isAttemptingToFix = attemptToFixTasks.has(task)
|
|
427
|
+
const isDisabled = disabledTasks.has(task)
|
|
428
|
+
const isQuarantined = quarantinedTasks.has(task)
|
|
429
|
+
|
|
430
|
+
if (isAttemptingToFix && (isDisabled || isQuarantined)) {
|
|
403
431
|
if (task.result.state === 'fail') {
|
|
404
432
|
switchedStatuses.add(task)
|
|
405
433
|
}
|
|
406
434
|
task.result.state = 'pass'
|
|
435
|
+
} else if (isQuarantined) {
|
|
436
|
+
task.result.state = 'pass'
|
|
407
437
|
}
|
|
408
438
|
}
|
|
409
439
|
|
|
410
|
-
if (
|
|
411
|
-
|
|
440
|
+
if (isEarlyFlakeDetectionEnabled && taskToStatuses.has(task) && !attemptToFixTasks.has(task)) {
|
|
441
|
+
const statuses = taskToStatuses.get(task)
|
|
442
|
+
// If the test has passed at least once, we consider it passed
|
|
443
|
+
if (statuses.includes('pass')) {
|
|
444
|
+
if (task.result.state === 'fail') {
|
|
445
|
+
switchedStatuses.add(task)
|
|
446
|
+
}
|
|
412
447
|
task.result.state = 'pass'
|
|
413
448
|
}
|
|
414
449
|
}
|
|
@@ -481,6 +516,8 @@ addHook({
|
|
|
481
516
|
}
|
|
482
517
|
|
|
483
518
|
const lastExecutionStatus = task.result.state
|
|
519
|
+
const shouldFlipStatus = isEarlyFlakeDetectionEnabled || attemptToFixTasks.has(task)
|
|
520
|
+
const statuses = taskToStatuses.get(task)
|
|
484
521
|
|
|
485
522
|
// These clauses handle task.repeats, whether EFD is enabled or not
|
|
486
523
|
// The only thing that EFD does is to forcefully pass the test if it has passed at least once
|
|
@@ -501,15 +538,21 @@ addHook({
|
|
|
501
538
|
testPassCh.publish({ task })
|
|
502
539
|
})
|
|
503
540
|
}
|
|
504
|
-
if (
|
|
505
|
-
const statuses = taskToStatuses.get(task)
|
|
541
|
+
if (shouldFlipStatus) {
|
|
506
542
|
statuses.push(lastExecutionStatus)
|
|
507
543
|
// If we don't "reset" the result.state to "pass", once a repetition fails,
|
|
508
544
|
// vitest will always consider the test as failed, so we can't read the actual status
|
|
545
|
+
// This means that we change vitest's behavior:
|
|
546
|
+
// if the last attempt passes, vitest would consider the test as failed
|
|
547
|
+
// but after this change, it will consider the test as passed
|
|
509
548
|
task.result.state = 'pass'
|
|
510
549
|
}
|
|
511
550
|
}
|
|
512
551
|
} else if (numRepetition === task.repeats) {
|
|
552
|
+
if (shouldFlipStatus) {
|
|
553
|
+
statuses.push(lastExecutionStatus)
|
|
554
|
+
}
|
|
555
|
+
|
|
513
556
|
const asyncResource = taskToAsync.get(task)
|
|
514
557
|
if (lastExecutionStatus === 'fail') {
|
|
515
558
|
const testError = task.result?.errors?.[0]
|
|
@@ -532,8 +575,11 @@ addHook({
|
|
|
532
575
|
testSuiteAbsolutePath: task.file.filepath,
|
|
533
576
|
isRetry: numAttempt > 0 || numRepetition > 0,
|
|
534
577
|
isRetryReasonEfd,
|
|
578
|
+
isRetryReasonAttemptToFix: isRetryReasonAttemptToFix && numRepetition > 0,
|
|
535
579
|
isNew,
|
|
536
580
|
mightHitProbe: isDiEnabled && numAttempt > 0,
|
|
581
|
+
isAttemptToFix: attemptToFixTasks.has(task),
|
|
582
|
+
isDisabled: disabledTasks.has(task),
|
|
537
583
|
isQuarantined
|
|
538
584
|
})
|
|
539
585
|
})
|
|
@@ -548,6 +594,8 @@ addHook({
|
|
|
548
594
|
}
|
|
549
595
|
const result = await onAfterTryTask.apply(this, arguments)
|
|
550
596
|
|
|
597
|
+
const { testManagementAttemptToFixRetries } = getProvidedContext()
|
|
598
|
+
|
|
551
599
|
const status = getVitestTestStatus(task, retryCount)
|
|
552
600
|
const asyncResource = taskToAsync.get(task)
|
|
553
601
|
|
|
@@ -557,10 +605,18 @@ addHook({
|
|
|
557
605
|
await waitForHitProbe()
|
|
558
606
|
}
|
|
559
607
|
|
|
608
|
+
let attemptToFixPassed = false
|
|
609
|
+
if (attemptToFixTasks.has(task)) {
|
|
610
|
+
const statuses = taskToStatuses.get(task)
|
|
611
|
+
if (statuses.length === testManagementAttemptToFixRetries && statuses.every(status => status === 'pass')) {
|
|
612
|
+
attemptToFixPassed = true
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
560
616
|
if (asyncResource) {
|
|
561
617
|
// We don't finish here because the test might fail in a later hook (afterEach)
|
|
562
618
|
asyncResource.runInAsyncScope(() => {
|
|
563
|
-
testFinishTimeCh.publish({ status, task })
|
|
619
|
+
testFinishTimeCh.publish({ status, task, attemptToFixPassed })
|
|
564
620
|
})
|
|
565
621
|
}
|
|
566
622
|
|
|
@@ -665,15 +721,11 @@ addHook({
|
|
|
665
721
|
// From >=3.0.1, the first arguments changes from a string to an object containing the filepath
|
|
666
722
|
const testSuiteAbsolutePath = testPaths[0]?.filepath || testPaths[0]
|
|
667
723
|
|
|
668
|
-
const { isEarlyFlakeDetectionEnabled, isFlakyTestRetriesEnabled } = getProvidedContext()
|
|
669
|
-
|
|
670
724
|
const testSuiteAsyncResource = new AsyncResource('bound-anonymous-fn')
|
|
671
725
|
testSuiteAsyncResource.runInAsyncScope(() => {
|
|
672
726
|
testSuiteStartCh.publish({
|
|
673
727
|
testSuiteAbsolutePath,
|
|
674
|
-
frameworkVersion
|
|
675
|
-
isFlakyTestRetriesEnabled,
|
|
676
|
-
isEarlyFlakeDetectionEnabled
|
|
728
|
+
frameworkVersion
|
|
677
729
|
})
|
|
678
730
|
})
|
|
679
731
|
const startTestsResponse = await startTests.apply(this, arguments)
|
|
@@ -715,11 +767,19 @@ addHook({
|
|
|
715
767
|
testError = errors[0]
|
|
716
768
|
}
|
|
717
769
|
|
|
770
|
+
let hasFailedAllRetries = false
|
|
771
|
+
if (attemptToFixTasks.has(task)) {
|
|
772
|
+
const statuses = taskToStatuses.get(task)
|
|
773
|
+
if (statuses.every(status => status === 'fail')) {
|
|
774
|
+
hasFailedAllRetries = true
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
718
778
|
if (testAsyncResource) {
|
|
719
779
|
const isRetry = task.result?.retryCount > 0
|
|
720
780
|
// `duration` is the duration of all the retries, so it can't be used if there are retries
|
|
721
781
|
testAsyncResource.runInAsyncScope(() => {
|
|
722
|
-
testErrorCh.publish({ duration: !isRetry ? duration : undefined, error: testError })
|
|
782
|
+
testErrorCh.publish({ duration: !isRetry ? duration : undefined, error: testError, hasFailedAllRetries })
|
|
723
783
|
})
|
|
724
784
|
}
|
|
725
785
|
if (errors?.length) {
|
|
@@ -30,7 +30,10 @@ const {
|
|
|
30
30
|
TEST_RETRY_REASON,
|
|
31
31
|
TEST_MANAGEMENT_ENABLED,
|
|
32
32
|
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
33
|
-
TEST_MANAGEMENT_IS_DISABLED
|
|
33
|
+
TEST_MANAGEMENT_IS_DISABLED,
|
|
34
|
+
TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
|
|
35
|
+
TEST_HAS_FAILED_ALL_RETRIES,
|
|
36
|
+
TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED
|
|
34
37
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
35
38
|
const { RESOURCE_NAME } = require('../../../ext/tags')
|
|
36
39
|
const { COMPONENT, ERROR_MESSAGE } = require('../../dd-trace/src/constants')
|
|
@@ -328,6 +331,10 @@ class CucumberPlugin extends CiPlugin {
|
|
|
328
331
|
isNew,
|
|
329
332
|
isEfdRetry,
|
|
330
333
|
isFlakyRetry,
|
|
334
|
+
isAttemptToFix,
|
|
335
|
+
isAttemptToFixRetry,
|
|
336
|
+
hasFailedAllRetries,
|
|
337
|
+
hasPassedAllRetries,
|
|
331
338
|
isDisabled,
|
|
332
339
|
isQuarantined
|
|
333
340
|
}) => {
|
|
@@ -358,6 +365,22 @@ class CucumberPlugin extends CiPlugin {
|
|
|
358
365
|
span.setTag(TEST_IS_RETRY, 'true')
|
|
359
366
|
}
|
|
360
367
|
|
|
368
|
+
if (hasFailedAllRetries) {
|
|
369
|
+
span.setTag(TEST_HAS_FAILED_ALL_RETRIES, 'true')
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
if (isAttemptToFix) {
|
|
373
|
+
span.setTag(TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX, 'true')
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (isAttemptToFixRetry) {
|
|
377
|
+
span.setTag(TEST_IS_RETRY, 'true')
|
|
378
|
+
span.setTag(TEST_RETRY_REASON, 'attempt_to_fix')
|
|
379
|
+
if (hasPassedAllRetries) {
|
|
380
|
+
span.setTag(TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED, 'true')
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
361
384
|
if (isDisabled) {
|
|
362
385
|
span.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
|
|
363
386
|
}
|
|
@@ -37,9 +37,10 @@ const {
|
|
|
37
37
|
TEST_MANAGEMENT_IS_QUARANTINED,
|
|
38
38
|
TEST_MANAGEMENT_ENABLED,
|
|
39
39
|
TEST_MANAGEMENT_IS_DISABLED,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX,
|
|
41
|
+
TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED,
|
|
42
|
+
TEST_HAS_FAILED_ALL_RETRIES,
|
|
43
|
+
getLibraryCapabilitiesTags
|
|
43
44
|
} = require('../../dd-trace/src/plugins/util/test')
|
|
44
45
|
const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
|
|
45
46
|
const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
|
|
@@ -64,7 +65,8 @@ const {
|
|
|
64
65
|
GIT_COMMIT_SHA,
|
|
65
66
|
GIT_BRANCH,
|
|
66
67
|
CI_PROVIDER_NAME,
|
|
67
|
-
CI_WORKSPACE_PATH
|
|
68
|
+
CI_WORKSPACE_PATH,
|
|
69
|
+
GIT_COMMIT_MESSAGE
|
|
68
70
|
} = require('../../dd-trace/src/plugins/util/tags')
|
|
69
71
|
const {
|
|
70
72
|
OS_VERSION,
|
|
@@ -201,7 +203,8 @@ class CypressPlugin {
|
|
|
201
203
|
[RUNTIME_VERSION]: runtimeVersion,
|
|
202
204
|
[GIT_BRANCH]: branch,
|
|
203
205
|
[CI_PROVIDER_NAME]: ciProviderName,
|
|
204
|
-
[CI_WORKSPACE_PATH]: repositoryRoot
|
|
206
|
+
[CI_WORKSPACE_PATH]: repositoryRoot,
|
|
207
|
+
[GIT_COMMIT_MESSAGE]: commitMessage
|
|
205
208
|
} = this.testEnvironmentMetadata
|
|
206
209
|
|
|
207
210
|
this.repositoryRoot = repositoryRoot
|
|
@@ -217,9 +220,11 @@ class CypressPlugin {
|
|
|
217
220
|
runtimeName,
|
|
218
221
|
runtimeVersion,
|
|
219
222
|
branch,
|
|
220
|
-
testLevel: 'test'
|
|
223
|
+
testLevel: 'test',
|
|
224
|
+
commitMessage
|
|
221
225
|
}
|
|
222
226
|
this.finishedTestsByFile = {}
|
|
227
|
+
this.testStatuses = {}
|
|
223
228
|
|
|
224
229
|
this.isTestsSkipped = false
|
|
225
230
|
this.isSuitesSkippingEnabled = false
|
|
@@ -233,6 +238,8 @@ class CypressPlugin {
|
|
|
233
238
|
this.hasUnskippableSuites = false
|
|
234
239
|
this.unskippableSuites = []
|
|
235
240
|
this.knownTests = []
|
|
241
|
+
this.isTestManagementTestsEnabled = false
|
|
242
|
+
this.testManagementAttemptToFixRetries = 0
|
|
236
243
|
}
|
|
237
244
|
|
|
238
245
|
// Init function returns a promise that resolves with the Cypress configuration
|
|
@@ -261,7 +268,8 @@ class CypressPlugin {
|
|
|
261
268
|
isFlakyTestRetriesEnabled,
|
|
262
269
|
flakyTestRetriesCount,
|
|
263
270
|
isKnownTestsEnabled,
|
|
264
|
-
isTestManagementEnabled
|
|
271
|
+
isTestManagementEnabled,
|
|
272
|
+
testManagementAttemptToFixRetries
|
|
265
273
|
}
|
|
266
274
|
} = libraryConfigurationResponse
|
|
267
275
|
this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
|
|
@@ -273,17 +281,22 @@ class CypressPlugin {
|
|
|
273
281
|
this.cypressConfig.retries.runMode = flakyTestRetriesCount
|
|
274
282
|
}
|
|
275
283
|
this.isTestManagementTestsEnabled = isTestManagementEnabled
|
|
284
|
+
this.testManagementAttemptToFixRetries = testManagementAttemptToFixRetries
|
|
276
285
|
}
|
|
277
286
|
return this.cypressConfig
|
|
278
287
|
})
|
|
279
288
|
return this.libraryConfigurationPromise
|
|
280
289
|
}
|
|
281
290
|
|
|
291
|
+
getTestSuiteProperties (testSuite) {
|
|
292
|
+
return this.testManagementTests?.cypress?.suites?.[testSuite]?.tests || {}
|
|
293
|
+
}
|
|
294
|
+
|
|
282
295
|
getTestProperties (testSuite, testName) {
|
|
283
|
-
const { disabled: isDisabled, quarantined: isQuarantined } =
|
|
284
|
-
this.
|
|
296
|
+
const { attempt_to_fix: isAttemptToFix, disabled: isDisabled, quarantined: isQuarantined } =
|
|
297
|
+
this.getTestSuiteProperties(testSuite)?.[testName]?.properties || {}
|
|
285
298
|
|
|
286
|
-
return { isDisabled, isQuarantined }
|
|
299
|
+
return { isAttemptToFix, isDisabled, isQuarantined }
|
|
287
300
|
}
|
|
288
301
|
|
|
289
302
|
getTestSuiteSpan ({ testSuite, testSuiteAbsolutePath }) {
|
|
@@ -312,7 +325,7 @@ class CypressPlugin {
|
|
|
312
325
|
})
|
|
313
326
|
}
|
|
314
327
|
|
|
315
|
-
getTestSpan ({ testName, testSuite, isUnskippable, isForcedToRun, testSourceFile }) {
|
|
328
|
+
getTestSpan ({ testName, testSuite, isUnskippable, isForcedToRun, testSourceFile, isDisabled, isQuarantined }) {
|
|
316
329
|
const testSuiteTags = {
|
|
317
330
|
[TEST_COMMAND]: this.command,
|
|
318
331
|
[TEST_COMMAND]: this.command,
|
|
@@ -357,6 +370,14 @@ class CypressPlugin {
|
|
|
357
370
|
testSpanMetadata[TEST_ITR_FORCED_RUN] = 'true'
|
|
358
371
|
}
|
|
359
372
|
|
|
373
|
+
if (isDisabled) {
|
|
374
|
+
testSpanMetadata[TEST_MANAGEMENT_IS_DISABLED] = 'true'
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (isQuarantined) {
|
|
378
|
+
testSpanMetadata[TEST_MANAGEMENT_IS_QUARANTINED] = 'true'
|
|
379
|
+
}
|
|
380
|
+
|
|
360
381
|
this.ciVisEvent(TELEMETRY_EVENT_CREATED, 'test', { hasCodeOwners: !!codeOwners })
|
|
361
382
|
|
|
362
383
|
return this.tracer.startSpan(`${TEST_FRAMEWORK_NAME}.test`, {
|
|
@@ -459,11 +480,10 @@ class CypressPlugin {
|
|
|
459
480
|
[TEST_SESSION_NAME]: testSessionName
|
|
460
481
|
}
|
|
461
482
|
}
|
|
483
|
+
const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id)
|
|
462
484
|
metadataTags.test = {
|
|
463
485
|
...metadataTags.test,
|
|
464
|
-
|
|
465
|
-
[DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: this.isEarlyFlakeDetectionEnabled ? 'true' : 'false',
|
|
466
|
-
[DD_CAPABILITIES_AUTO_TEST_RETRIES]: this.isFlakyTestRetriesEnabled ? 'true' : 'false'
|
|
486
|
+
...libraryCapabilitiesTags
|
|
467
487
|
}
|
|
468
488
|
|
|
469
489
|
this.tracer._tracer._exporter.addMetadataTags(metadataTags)
|
|
@@ -691,7 +711,10 @@ class CypressPlugin {
|
|
|
691
711
|
isEarlyFlakeDetectionEnabled: this.isEarlyFlakeDetectionEnabled,
|
|
692
712
|
knownTestsForSuite: this.knownTestsByTestSuite?.[testSuite] || [],
|
|
693
713
|
earlyFlakeDetectionNumRetries: this.earlyFlakeDetectionNumRetries,
|
|
694
|
-
isKnownTestsEnabled: this.isKnownTestsEnabled
|
|
714
|
+
isKnownTestsEnabled: this.isKnownTestsEnabled,
|
|
715
|
+
isTestManagementEnabled: this.isTestManagementTestsEnabled,
|
|
716
|
+
testManagementAttemptToFixRetries: this.testManagementAttemptToFixRetries,
|
|
717
|
+
testManagementTests: this.getTestSuiteProperties(testSuite)
|
|
695
718
|
}
|
|
696
719
|
|
|
697
720
|
if (this.testSuiteSpan) {
|
|
@@ -707,8 +730,7 @@ class CypressPlugin {
|
|
|
707
730
|
})
|
|
708
731
|
const isUnskippable = this.unskippableSuites.includes(testSuite)
|
|
709
732
|
const isForcedToRun = shouldSkip && isUnskippable
|
|
710
|
-
const { isDisabled, isQuarantined } = this.getTestProperties(testSuite, testName)
|
|
711
|
-
|
|
733
|
+
const { isAttemptToFix, isDisabled, isQuarantined } = this.getTestProperties(testSuite, testName)
|
|
712
734
|
// skip test
|
|
713
735
|
if (shouldSkip && !isUnskippable) {
|
|
714
736
|
this.skippedTests.push(test)
|
|
@@ -718,7 +740,7 @@ class CypressPlugin {
|
|
|
718
740
|
|
|
719
741
|
// TODO: I haven't found a way to trick cypress into ignoring a test
|
|
720
742
|
// The way we'll implement quarantine in cypress is by skipping the test altogether
|
|
721
|
-
if (isDisabled || isQuarantined) {
|
|
743
|
+
if (!isAttemptToFix && (isDisabled || isQuarantined)) {
|
|
722
744
|
return { shouldSkip: true }
|
|
723
745
|
}
|
|
724
746
|
|
|
@@ -727,7 +749,9 @@ class CypressPlugin {
|
|
|
727
749
|
testName,
|
|
728
750
|
testSuite,
|
|
729
751
|
isUnskippable,
|
|
730
|
-
isForcedToRun
|
|
752
|
+
isForcedToRun,
|
|
753
|
+
isDisabled,
|
|
754
|
+
isQuarantined
|
|
731
755
|
})
|
|
732
756
|
}
|
|
733
757
|
|
|
@@ -747,7 +771,8 @@ class CypressPlugin {
|
|
|
747
771
|
testSuiteAbsolutePath,
|
|
748
772
|
testName,
|
|
749
773
|
isNew,
|
|
750
|
-
isEfdRetry
|
|
774
|
+
isEfdRetry,
|
|
775
|
+
isAttemptToFix
|
|
751
776
|
} = test
|
|
752
777
|
if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
|
|
753
778
|
const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
|
|
@@ -770,6 +795,14 @@ class CypressPlugin {
|
|
|
770
795
|
const testStatus = CYPRESS_STATUS_TO_TEST_STATUS[state]
|
|
771
796
|
this.activeTestSpan.setTag(TEST_STATUS, testStatus)
|
|
772
797
|
|
|
798
|
+
// Save the test status to know if it has passed all retries
|
|
799
|
+
if (!this.testStatuses[testName]) {
|
|
800
|
+
this.testStatuses[testName] = [testStatus]
|
|
801
|
+
} else {
|
|
802
|
+
this.testStatuses[testName].push(testStatus)
|
|
803
|
+
}
|
|
804
|
+
const testStatuses = this.testStatuses[testName]
|
|
805
|
+
|
|
773
806
|
if (error) {
|
|
774
807
|
this.activeTestSpan.setTag('error', error)
|
|
775
808
|
}
|
|
@@ -786,6 +819,22 @@ class CypressPlugin {
|
|
|
786
819
|
this.activeTestSpan.setTag(TEST_RETRY_REASON, 'efd')
|
|
787
820
|
}
|
|
788
821
|
}
|
|
822
|
+
if (isAttemptToFix) {
|
|
823
|
+
this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX, 'true')
|
|
824
|
+
if (testStatuses.length > 1) {
|
|
825
|
+
this.activeTestSpan.setTag(TEST_IS_RETRY, 'true')
|
|
826
|
+
this.activeTestSpan.setTag(TEST_RETRY_REASON, 'attempt_to_fix')
|
|
827
|
+
}
|
|
828
|
+
const isLastAttempt = testStatuses.length === this.testManagementAttemptToFixRetries + 1
|
|
829
|
+
if (isLastAttempt) {
|
|
830
|
+
if (testStatuses.every(status => status === 'fail')) {
|
|
831
|
+
this.activeTestSpan.setTag(TEST_HAS_FAILED_ALL_RETRIES, 'true')
|
|
832
|
+
} else if (testStatuses.every(status => status === 'pass')) {
|
|
833
|
+
this.activeTestSpan.setTag(TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED, 'true')
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
789
838
|
const finishedTest = {
|
|
790
839
|
testName,
|
|
791
840
|
testStatus,
|
|
@@ -4,6 +4,9 @@ let isKnownTestsEnabled = false
|
|
|
4
4
|
let knownTestsForSuite = []
|
|
5
5
|
let suiteTests = []
|
|
6
6
|
let earlyFlakeDetectionNumRetries = 0
|
|
7
|
+
let isTestManagementEnabled = false
|
|
8
|
+
let testManagementAttemptToFixRetries = 0
|
|
9
|
+
let testManagementTests = {}
|
|
7
10
|
// We need to grab the original window as soon as possible,
|
|
8
11
|
// in case the test changes the origin. If the test does change the origin,
|
|
9
12
|
// any call to `cy.window()` will result in a cross origin error.
|
|
@@ -23,31 +26,53 @@ function isNewTest (test) {
|
|
|
23
26
|
return !knownTestsForSuite.includes(test.fullTitle())
|
|
24
27
|
}
|
|
25
28
|
|
|
26
|
-
function
|
|
27
|
-
|
|
29
|
+
function getTestProperties (testName) {
|
|
30
|
+
// We neeed to do it in this way because of compatibility with older versions as '?' is not supported in older versions of Cypress
|
|
31
|
+
const properties = testManagementTests[testName] && testManagementTests[testName].properties || {};
|
|
32
|
+
|
|
33
|
+
const { attempt_to_fix: isAttemptToFix, disabled: isDisabled, quarantined: isQuarantined } = properties;
|
|
34
|
+
|
|
35
|
+
return { isAttemptToFix, isDisabled, isQuarantined };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function retryTest (test, suiteTests, numRetries, tags) {
|
|
39
|
+
for (let retryIndex = 0; retryIndex < numRetries; retryIndex++) {
|
|
28
40
|
const clonedTest = test.clone()
|
|
29
41
|
// TODO: signal in framework logs that this is a retry.
|
|
30
42
|
// TODO: Change it so these tests are allowed to fail.
|
|
31
43
|
// TODO: figure out if reported duration is skewed.
|
|
32
44
|
suiteTests.unshift(clonedTest)
|
|
33
|
-
|
|
34
|
-
|
|
45
|
+
tags.forEach(tag => {
|
|
46
|
+
clonedTest[tag] = true
|
|
47
|
+
})
|
|
35
48
|
}
|
|
36
49
|
}
|
|
37
50
|
|
|
38
51
|
|
|
39
52
|
const oldRunTests = Cypress.mocha.getRunner().runTests
|
|
40
53
|
Cypress.mocha.getRunner().runTests = function (suite, fn) {
|
|
41
|
-
if (!isKnownTestsEnabled) {
|
|
54
|
+
if (!isKnownTestsEnabled && !isTestManagementEnabled) {
|
|
42
55
|
return oldRunTests.apply(this, arguments)
|
|
43
56
|
}
|
|
44
57
|
// We copy the new tests at the beginning of the suite run (runTests), so that they're run
|
|
45
58
|
// multiple times.
|
|
46
59
|
suite.tests.forEach(test => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
60
|
+
const testName = test.fullTitle()
|
|
61
|
+
|
|
62
|
+
const { isAttemptToFix } = getTestProperties(testName)
|
|
63
|
+
|
|
64
|
+
if (isTestManagementEnabled) {
|
|
65
|
+
if (isAttemptToFix && !test.isPending()) {
|
|
66
|
+
test._ddIsAttemptToFix = true
|
|
67
|
+
retryTest(test, suite.tests, testManagementAttemptToFixRetries, ['_ddIsAttemptToFix'])
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (isKnownTestsEnabled) {
|
|
71
|
+
if (!test._ddIsNew && !test.isPending() && isNewTest(test)) {
|
|
72
|
+
test._ddIsNew = true
|
|
73
|
+
if (isEarlyFlakeDetectionEnabled && !isAttemptToFix) {
|
|
74
|
+
retryTest(test, suite.tests, earlyFlakeDetectionNumRetries, ['_ddIsNew', '_ddIsEfdRetry'])
|
|
75
|
+
}
|
|
51
76
|
}
|
|
52
77
|
}
|
|
53
78
|
})
|
|
@@ -80,6 +105,9 @@ before(function () {
|
|
|
80
105
|
isKnownTestsEnabled = suiteConfig.isKnownTestsEnabled
|
|
81
106
|
knownTestsForSuite = suiteConfig.knownTestsForSuite
|
|
82
107
|
earlyFlakeDetectionNumRetries = suiteConfig.earlyFlakeDetectionNumRetries
|
|
108
|
+
isTestManagementEnabled = suiteConfig.isTestManagementEnabled
|
|
109
|
+
testManagementAttemptToFixRetries = suiteConfig.testManagementAttemptToFixRetries
|
|
110
|
+
testManagementTests = suiteConfig.testManagementTests
|
|
83
111
|
}
|
|
84
112
|
})
|
|
85
113
|
})
|
|
@@ -104,7 +132,8 @@ afterEach(function () {
|
|
|
104
132
|
state: currentTest.state,
|
|
105
133
|
error: currentTest.err,
|
|
106
134
|
isNew: currentTest._ddIsNew,
|
|
107
|
-
isEfdRetry: currentTest._ddIsEfdRetry
|
|
135
|
+
isEfdRetry: currentTest._ddIsEfdRetry,
|
|
136
|
+
isAttemptToFix: currentTest._ddIsAttemptToFix
|
|
108
137
|
}
|
|
109
138
|
try {
|
|
110
139
|
testInfo.testSourceLine = Cypress.mocha.getRunner().currentRunnable.invocationDetails.line
|