dd-trace 5.104.0 → 5.105.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.
Files changed (151) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +82 -3
  3. package/package.json +15 -15
  4. package/packages/datadog-core/src/storage.js +1 -1
  5. package/packages/datadog-instrumentations/src/aerospike.js +1 -1
  6. package/packages/datadog-instrumentations/src/ai.js +8 -7
  7. package/packages/datadog-instrumentations/src/aws-sdk.js +13 -0
  8. package/packages/datadog-instrumentations/src/azure-cosmos.js +7 -0
  9. package/packages/datadog-instrumentations/src/azure-functions.js +3 -0
  10. package/packages/datadog-instrumentations/src/cucumber.js +78 -5
  11. package/packages/datadog-instrumentations/src/dns.js +54 -18
  12. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  13. package/packages/datadog-instrumentations/src/graphql.js +188 -62
  14. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  15. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  16. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  17. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  18. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  19. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  20. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -3
  21. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +37 -236
  26. package/packages/datadog-instrumentations/src/hono.js +54 -3
  27. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  28. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  29. package/packages/datadog-instrumentations/src/jest.js +360 -150
  30. package/packages/datadog-instrumentations/src/kafkajs.js +120 -16
  31. package/packages/datadog-instrumentations/src/mocha/main.js +128 -17
  32. package/packages/datadog-instrumentations/src/nats.js +182 -0
  33. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  34. package/packages/datadog-instrumentations/src/openai.js +33 -18
  35. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  36. package/packages/datadog-instrumentations/src/pino.js +17 -5
  37. package/packages/datadog-instrumentations/src/playwright.js +515 -292
  38. package/packages/datadog-instrumentations/src/router.js +76 -32
  39. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  40. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  41. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  42. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  43. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  44. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  45. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  46. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  47. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +199 -28
  48. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  49. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  50. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  51. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  52. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  53. package/packages/datadog-plugin-http/src/server.js +40 -15
  54. package/packages/datadog-plugin-jest/src/index.js +11 -3
  55. package/packages/datadog-plugin-jest/src/util.js +15 -8
  56. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  57. package/packages/datadog-plugin-kafkajs/src/producer.js +3 -0
  58. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  59. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  60. package/packages/datadog-plugin-mongodb-core/src/index.js +281 -40
  61. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  62. package/packages/datadog-plugin-nats/src/index.js +20 -0
  63. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  64. package/packages/datadog-plugin-nats/src/util.js +33 -0
  65. package/packages/datadog-plugin-next/src/index.js +5 -3
  66. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  67. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  68. package/packages/datadog-plugin-pino/src/index.js +42 -0
  69. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  70. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  71. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  72. package/packages/datadog-plugin-router/src/index.js +33 -44
  73. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  74. package/packages/datadog-plugin-vitest/src/index.js +5 -13
  75. package/packages/datadog-plugin-winston/src/index.js +30 -0
  76. package/packages/datadog-shimmer/src/shimmer.js +33 -40
  77. package/packages/dd-trace/src/aiguard/index.js +1 -1
  78. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  79. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  80. package/packages/dd-trace/src/appsec/index.js +1 -1
  81. package/packages/dd-trace/src/appsec/reporter.js +5 -6
  82. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  83. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  84. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  85. package/packages/dd-trace/src/baggage.js +7 -1
  86. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  87. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  88. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  89. package/packages/dd-trace/src/config/generated-config-types.d.ts +6 -2
  90. package/packages/dd-trace/src/config/supported-configurations.json +27 -8
  91. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  92. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  93. package/packages/dd-trace/src/encode/0.4.js +124 -108
  94. package/packages/dd-trace/src/encode/0.5.js +114 -26
  95. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +31 -23
  96. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  97. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  98. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  99. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  100. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  101. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  102. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  103. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  104. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  105. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  106. package/packages/dd-trace/src/llmobs/sdk.js +0 -16
  107. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  108. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  109. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  110. package/packages/dd-trace/src/llmobs/util.js +66 -3
  111. package/packages/dd-trace/src/log/index.js +1 -1
  112. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  113. package/packages/dd-trace/src/msgpack/index.js +96 -2
  114. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  115. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  116. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  117. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  118. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  119. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  120. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  121. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  122. package/packages/dd-trace/src/opentracing/span.js +59 -19
  123. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  124. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -20
  125. package/packages/dd-trace/src/plugins/database.js +7 -6
  126. package/packages/dd-trace/src/plugins/index.js +4 -0
  127. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  128. package/packages/dd-trace/src/plugins/log_plugin.js +3 -48
  129. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  130. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  131. package/packages/dd-trace/src/plugins/tracing.js +43 -5
  132. package/packages/dd-trace/src/plugins/util/test.js +236 -13
  133. package/packages/dd-trace/src/plugins/util/web.js +79 -65
  134. package/packages/dd-trace/src/priority_sampler.js +2 -2
  135. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  136. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  137. package/packages/dd-trace/src/sampling_rule.js +7 -7
  138. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  139. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  140. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  141. package/packages/dd-trace/src/span_format.js +190 -58
  142. package/packages/dd-trace/src/spanleak.js +1 -1
  143. package/packages/dd-trace/src/standalone/index.js +3 -3
  144. package/packages/dd-trace/src/tagger.js +0 -2
  145. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  146. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  147. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  148. package/vendor/dist/protobufjs/index.js +1 -1
  149. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  150. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  151. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
@@ -4,6 +4,7 @@
4
4
  const { performance } = require('perf_hooks')
5
5
  const dateNow = Date.now
6
6
 
7
+ const { createCoverageMap } = require('../../../vendor/dist/istanbul-lib-coverage')
7
8
  const satisfies = require('../../../vendor/dist/semifies')
8
9
  const {
9
10
  TEST_STATUS,
@@ -25,7 +26,12 @@ const {
25
26
  TEST_MODULE,
26
27
  TEST_SOURCE_START,
27
28
  finishAllTraceSpans,
28
- getCoveredFilenamesFromCoverage,
29
+ getCoveredFilesFromCoverage,
30
+ getExecutableFilesFromCoverage,
31
+ getRelativeCoverageFiles,
32
+ getTestCoverageLinesPercentage,
33
+ applySkippedCoverageToCoverage,
34
+ mergeCoverage,
29
35
  getTestSuitePath,
30
36
  addIntelligentTestRunnerSpanTags,
31
37
  TEST_SKIPPED_BY_ITR,
@@ -40,7 +46,6 @@ const {
40
46
  TEST_EARLY_FLAKE_ABORT_REASON,
41
47
  getTestSessionName,
42
48
  TEST_SESSION_NAME,
43
- TEST_LEVEL_EVENT_TYPES,
44
49
  TEST_RETRY_REASON,
45
50
  DD_TEST_IS_USER_PROVIDED_SERVICE,
46
51
  TEST_MANAGEMENT_IS_QUARANTINED,
@@ -74,6 +79,7 @@ const {
74
79
  } = require('../../dd-trace/src/plugins/util/test')
75
80
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
76
81
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
82
+ const { RESOURCE_NAME } = require('../../../ext/tags')
77
83
  const getConfig = require('../../dd-trace/src/config')
78
84
  const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
79
85
  const log = require('../../dd-trace/src/log')
@@ -201,13 +207,17 @@ function getSkippableTests (tracer, testConfiguration) {
201
207
  if (!tracer._tracer._exporter?.getSkippableSuites) {
202
208
  return resolve({ err: new Error('Test Optimization was not initialized correctly') })
203
209
  }
204
- tracer._tracer._exporter.getSkippableSuites(testConfiguration, (err, skippableTests, correlationId) => {
205
- resolve({
206
- err,
207
- skippableTests,
208
- correlationId,
209
- })
210
- })
210
+ tracer._tracer._exporter.getSkippableSuites(
211
+ testConfiguration,
212
+ (err, skippableTests, correlationId, skippableTestsCoverage) => {
213
+ resolve({
214
+ err,
215
+ skippableTests,
216
+ correlationId,
217
+ skippableTestsCoverage,
218
+ })
219
+ }
220
+ )
211
221
  })
212
222
  }
213
223
 
@@ -361,9 +371,11 @@ class CypressPlugin {
361
371
  finishedTestsByFile = {}
362
372
  testStatuses = {}
363
373
  hasLibraryConfiguration = false
374
+ isItrEnabled = false
364
375
  isTestsSkipped = false
365
376
  isSuitesSkippingEnabled = false
366
377
  isCodeCoverageEnabled = false
378
+ isCoverageReportUploadEnabled = false
367
379
  isFlakyTestRetriesEnabled = false
368
380
  flakyTestRetriesCount = 0
369
381
  isEarlyFlakeDetectionEnabled = false
@@ -376,6 +388,8 @@ class CypressPlugin {
376
388
  earlyFlakeDetectionFaultyThreshold = 0
377
389
  testsToSkip = []
378
390
  skippedTests = []
391
+ skippableTestsCoverage = {}
392
+ testSessionCoverageMap = createCoverageMap()
379
393
  hasForcedToRunSuites = false
380
394
  hasUnskippableSuites = false
381
395
  unskippableSuites = []
@@ -440,9 +454,11 @@ class CypressPlugin {
440
454
  this.finishedTestsByFile = {}
441
455
  this.testStatuses = {}
442
456
  this.hasLibraryConfiguration = false
457
+ this.isItrEnabled = false
443
458
  this.isTestsSkipped = false
444
459
  this.isSuitesSkippingEnabled = false
445
460
  this.isCodeCoverageEnabled = false
461
+ this.isCoverageReportUploadEnabled = false
446
462
  this.isFlakyTestRetriesEnabled = false
447
463
  this.flakyTestRetriesCount = 0
448
464
  this.isEarlyFlakeDetectionEnabled = false
@@ -455,6 +471,8 @@ class CypressPlugin {
455
471
  this.earlyFlakeDetectionFaultyThreshold = 0
456
472
  this.testsToSkip = []
457
473
  this.skippedTests = []
474
+ this.skippableTestsCoverage = {}
475
+ this.testSessionCoverageMap = createCoverageMap()
458
476
  this.hasForcedToRunSuites = false
459
477
  this.hasUnskippableSuites = false
460
478
  this.unskippableSuites = []
@@ -494,6 +512,117 @@ class CypressPlugin {
494
512
  return this._timeOrigin + performance.now() - this._perfOrigin
495
513
  }
496
514
 
515
+ /**
516
+ * Returns the directory used to normalize coverage file names.
517
+ *
518
+ * @returns {string}
519
+ */
520
+ getCoverageRootDir () {
521
+ return this.repositoryRoot || this.rootDir || process.cwd()
522
+ }
523
+
524
+ /**
525
+ * Returns whether the backend supplied skipped-test coverage data.
526
+ *
527
+ * @returns {boolean}
528
+ */
529
+ hasSkippableTestsCoverage () {
530
+ return !!(this.skippableTestsCoverage &&
531
+ typeof this.skippableTestsCoverage === 'object' &&
532
+ Object.keys(this.skippableTestsCoverage).length > 0)
533
+ }
534
+
535
+ /**
536
+ * Returns whether skipped test coverage should be backfilled into the session coverage map.
537
+ *
538
+ * @returns {boolean}
539
+ */
540
+ shouldBackfillSkippedCoverage () {
541
+ return this.isItrEnabled &&
542
+ this.isCoverageReportUploadEnabled &&
543
+ this.isTestsSkipped &&
544
+ this.hasSkippableTestsCoverage()
545
+ }
546
+
547
+ /**
548
+ * Adds a test's Istanbul coverage to the aggregated session coverage map.
549
+ *
550
+ * @param {object} coverage
551
+ * @returns {void}
552
+ */
553
+ addTestSessionCoverage (coverage) {
554
+ mergeCoverage(coverage, this.testSessionCoverageMap)
555
+ }
556
+
557
+ /**
558
+ * Applies backend skipped-test coverage to the aggregated session coverage map.
559
+ *
560
+ * @returns {boolean}
561
+ */
562
+ applySkippedCoverageToTestSessionCoverage () {
563
+ if (!this.shouldBackfillSkippedCoverage()) {
564
+ return false
565
+ }
566
+
567
+ return applySkippedCoverageToCoverage(
568
+ this.testSessionCoverageMap,
569
+ this.skippableTestsCoverage,
570
+ this.getCoverageRootDir()
571
+ )
572
+ }
573
+
574
+ /**
575
+ * Calculates the total session code coverage percentage when product rules allow reporting it.
576
+ *
577
+ * @param {boolean} hasBackfilledCoverage
578
+ * @returns {number | undefined}
579
+ */
580
+ getTestCodeCoverageLinesTotal (hasBackfilledCoverage) {
581
+ if (!this.testSessionCoverageMap.files().length || (this.isTestsSkipped && !hasBackfilledCoverage)) {
582
+ return
583
+ }
584
+
585
+ return getTestCoverageLinesPercentage(this.testSessionCoverageMap, undefined, this.getCoverageRootDir())
586
+ }
587
+
588
+ /**
589
+ * Returns repository-relative executable-line coverage files for the test session.
590
+ *
591
+ * @returns {Array<{ filename: string, bitmap: Buffer }>}
592
+ */
593
+ getTestSessionCoverageFiles () {
594
+ return getRelativeCoverageFiles(
595
+ getExecutableFilesFromCoverage(this.testSessionCoverageMap),
596
+ this.getCoverageRootDir()
597
+ )
598
+ }
599
+
600
+ /**
601
+ * Uploads executable-line coverage for the test session when backend configuration enables it.
602
+ *
603
+ * @returns {void}
604
+ */
605
+ reportTestSessionCoverage () {
606
+ const exporter = this.tracer._tracer._exporter
607
+ if (
608
+ !this.testSessionSpan ||
609
+ !this.isCoverageReportUploadEnabled ||
610
+ !exporter?.exportCoverage
611
+ ) {
612
+ return
613
+ }
614
+
615
+ const files = this.getTestSessionCoverageFiles()
616
+ if (!files.length) {
617
+ return
618
+ }
619
+
620
+ exporter.exportCoverage({
621
+ sessionId: this.testSessionSpan.context()._traceId,
622
+ files,
623
+ })
624
+ }
625
+
497
626
  // Init function returns a promise that resolves with the Cypress configuration
498
627
  // Depending on the received configuration, the Cypress configuration can be modified:
499
628
  // for example, to enable retries for failed tests.
@@ -529,8 +658,10 @@ class CypressPlugin {
529
658
  this.hasLibraryConfiguration = true
530
659
  const {
531
660
  libraryConfig: {
661
+ isItrEnabled,
532
662
  isSuitesSkippingEnabled,
533
663
  isCodeCoverageEnabled,
664
+ isCoverageReportUploadEnabled,
534
665
  isEarlyFlakeDetectionEnabled,
535
666
  earlyFlakeDetectionNumRetries,
536
667
  earlyFlakeDetectionSlowTestRetries,
@@ -543,8 +674,10 @@ class CypressPlugin {
543
674
  isImpactedTestsEnabled,
544
675
  },
545
676
  } = libraryConfigurationResponse
677
+ this.isItrEnabled = isItrEnabled
546
678
  this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
547
679
  this.isCodeCoverageEnabled = isCodeCoverageEnabled
680
+ this.isCoverageReportUploadEnabled = isCoverageReportUploadEnabled
548
681
  this.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
549
682
  this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
550
683
  this.earlyFlakeDetectionSlowTestRetries = earlyFlakeDetectionSlowTestRetries ?? {}
@@ -686,7 +819,6 @@ class CypressPlugin {
686
819
 
687
820
  getTestSpan ({ testName, testSuite, isUnskippable, isForcedToRun, testSourceFile, isDisabled, isQuarantined }) {
688
821
  const testSuiteTags = {
689
- [TEST_COMMAND]: this.command,
690
822
  [TEST_MODULE]: TEST_FRAMEWORK_NAME,
691
823
  }
692
824
  if (this.testSuiteSpan) {
@@ -798,7 +930,10 @@ class CypressPlugin {
798
930
  isSuitesSkippingEnabled: this.isSuitesSkippingEnabled,
799
931
  getKnownTests: () => getKnownTests(this.tracer, this.testConfiguration),
800
932
  getTestManagementTests: () => getTestManagementTests(this.tracer, this.testConfiguration),
801
- getSkippableSuites: () => getSkippableTests(this.tracer, this.testConfiguration),
933
+ getSkippableSuites: () => getSkippableTests(this.tracer, {
934
+ ...this.testConfiguration,
935
+ isCoverageReportUploadEnabled: this.isCoverageReportUploadEnabled,
936
+ }),
802
937
  })
803
938
 
804
939
  if (this.isKnownTestsEnabled) {
@@ -835,13 +970,17 @@ class CypressPlugin {
835
970
 
836
971
  if (this.isSuitesSkippingEnabled) {
837
972
  const skippableTestsResponse =
838
- skippableTestsRequestResponse || await getSkippableTests(this.tracer, this.testConfiguration)
973
+ skippableTestsRequestResponse || await getSkippableTests(this.tracer, {
974
+ ...this.testConfiguration,
975
+ isCoverageReportUploadEnabled: this.isCoverageReportUploadEnabled,
976
+ })
839
977
  if (skippableTestsResponse.err) {
840
978
  log.error('Cypress skippable tests response error', skippableTestsResponse.err)
841
979
  this._pendingRequestErrorTags.push({ tag: DD_CI_LIBRARY_CONFIGURATION_ERROR_SKIPPABLE_TESTS, value: 'true' })
842
980
  } else {
843
- const { skippableTests, correlationId } = skippableTestsResponse
981
+ const { skippableTests, correlationId, skippableTestsCoverage } = skippableTestsResponse
844
982
  this.testsToSkip = skippableTests || []
983
+ this.skippableTestsCoverage = skippableTestsCoverage || {}
845
984
  this.itrCorrelationId = correlationId
846
985
  incrementCountMetric(TELEMETRY_ITR_SKIPPED, { testLevel: 'test' }, this.testsToSkip.length)
847
986
  }
@@ -904,15 +1043,9 @@ class CypressPlugin {
904
1043
  )
905
1044
 
906
1045
  if (this.tracer._tracer._exporter?.addMetadataTags) {
907
- const metadataTags = {}
908
- for (const testLevel of TEST_LEVEL_EVENT_TYPES) {
909
- metadataTags[testLevel] = {
910
- [TEST_SESSION_NAME]: testSessionName,
911
- }
912
- }
1046
+ const metadataTags = { '*': { [TEST_COMMAND]: this.command, [TEST_SESSION_NAME]: testSessionName } }
913
1047
  const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, this.frameworkVersion)
914
1048
  metadataTags.test = {
915
- ...metadataTags.test,
916
1049
  ...libraryCapabilitiesTags,
917
1050
  }
918
1051
 
@@ -969,6 +1102,9 @@ class CypressPlugin {
969
1102
  }
970
1103
  if (this.testSessionSpan && this.testModuleSpan) {
971
1104
  const testStatus = getSessionStatus(suiteStats)
1105
+ const hasBackfilledCoverage = this.applySkippedCoverageToTestSessionCoverage()
1106
+ const testCodeCoverageLinesTotal = this.getTestCodeCoverageLinesTotal(hasBackfilledCoverage)
1107
+
972
1108
  this.testModuleSpan.setTag(TEST_STATUS, testStatus)
973
1109
  this.testSessionSpan.setTag(TEST_STATUS, testStatus)
974
1110
 
@@ -979,6 +1115,7 @@ class CypressPlugin {
979
1115
  isSuitesSkipped: this.isTestsSkipped,
980
1116
  isSuitesSkippingEnabled: this.isSuitesSkippingEnabled,
981
1117
  isCodeCoverageEnabled: this.isCodeCoverageEnabled,
1118
+ testCodeCoverageLinesTotal,
982
1119
  skippingType: 'test',
983
1120
  skippingCount: this.skippedTests.length,
984
1121
  hasForcedToRunSuites: this.hasForcedToRunSuites,
@@ -986,6 +1123,8 @@ class CypressPlugin {
986
1123
  }
987
1124
  )
988
1125
 
1126
+ this.reportTestSessionCoverage()
1127
+
989
1128
  if (this.isTestManagementTestsEnabled) {
990
1129
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
991
1130
  }
@@ -1145,7 +1284,7 @@ class CypressPlugin {
1145
1284
  }
1146
1285
  // Update test status - but NOT for non-ATF quarantined tests where we intentionally
1147
1286
  // report 'fail' to Datadog even though Cypress sees it as 'pass'
1148
- const isQuarantinedTest = finishedTest.testSpan?.context()?._tags?.[TEST_MANAGEMENT_IS_QUARANTINED] === 'true'
1287
+ const isQuarantinedTest = finishedTest.testSpan?.context()?.getTag(TEST_MANAGEMENT_IS_QUARANTINED) === 'true'
1149
1288
  if (cypressTestStatus !== finishedTest.testStatus && (!isQuarantinedTest || finishedTest.isAttemptToFix)) {
1150
1289
  finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
1151
1290
  finishedTest.testSpan.setTag('error', latestError)
@@ -1172,7 +1311,7 @@ class CypressPlugin {
1172
1311
  }
1173
1312
 
1174
1313
  if (isLastAttempt) {
1175
- const testSpanTags = finishedTest.testSpan.context()._tags
1314
+ const testSpanTags = finishedTest.testSpan.context().getTags()
1176
1315
  const retryKind = getFinalStatusRetryKind({
1177
1316
  finishedTest,
1178
1317
  finishedTestAttempts,
@@ -1280,7 +1419,7 @@ class CypressPlugin {
1280
1419
 
1281
1420
  return this.activeTestSpan ? { traceId: this.activeTestSpan.context().toTraceId() } : {}
1282
1421
  },
1283
- 'dd:afterEach': ({ test, coverage }) => {
1422
+ 'dd:afterEach': ({ test, coverage, commands }) => {
1284
1423
  if (!this.activeTestSpan) {
1285
1424
  log.warn('There is no active test span in dd:afterEach handler')
1286
1425
  return null
@@ -1303,11 +1442,18 @@ class CypressPlugin {
1303
1442
  isQuarantined: isQuarantinedFromSupport,
1304
1443
  isDisabled: isDisabledFromSupport,
1305
1444
  } = test
1445
+ if (coverage && (this.isCodeCoverageEnabled || this.isCoverageReportUploadEnabled)) {
1446
+ this.addTestSessionCoverage(coverage)
1447
+ }
1448
+
1306
1449
  if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
1307
- const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
1308
- const relativeCoverageFiles = [...coverageFiles, testSuiteAbsolutePath].map(
1309
- file => getTestSuitePath(file, this.repositoryRoot || this.rootDir)
1310
- )
1450
+ const coverageFiles = getCoveredFilesFromCoverage(coverage)
1451
+ const relativeCoverageFiles = getRelativeCoverageFiles(coverageFiles, this.getCoverageRootDir())
1452
+ if (testSuiteAbsolutePath) {
1453
+ relativeCoverageFiles.push({
1454
+ filename: getTestSuitePath(testSuiteAbsolutePath, this.getCoverageRootDir()),
1455
+ })
1456
+ }
1311
1457
  if (!relativeCoverageFiles.length) {
1312
1458
  incrementCountMetric(TELEMETRY_CODE_COVERAGE_EMPTY)
1313
1459
  }
@@ -1343,7 +1489,7 @@ class CypressPlugin {
1343
1489
  this.testStatuses[testName] = [testStatus]
1344
1490
  }
1345
1491
  const testStatuses = this.testStatuses[testName]
1346
- const activeSpanTags = this.activeTestSpan.context()._tags
1492
+ const activeSpanTags = this.activeTestSpan.context().getTags()
1347
1493
 
1348
1494
  if (error) {
1349
1495
  this.activeTestSpan.setTag('error', error)
@@ -1444,6 +1590,31 @@ class CypressPlugin {
1444
1590
  this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
1445
1591
  }
1446
1592
 
1593
+ if (Array.isArray(commands) && commands.length > 0) {
1594
+ for (const command of commands) {
1595
+ const { startTime, endTime } = command
1596
+ if (typeof startTime !== 'number' || typeof endTime !== 'number' || endTime < startTime) {
1597
+ continue
1598
+ }
1599
+ const stepSpan = this.tracer.startSpan('cypress.step', {
1600
+ childOf: this.activeTestSpan,
1601
+ startTime,
1602
+ tags: {
1603
+ [COMPONENT]: 'cypress',
1604
+ 'cypress.command': command.name,
1605
+ [RESOURCE_NAME]: command.name,
1606
+ },
1607
+ })
1608
+ if (command.error) {
1609
+ const errorObj = new Error(command.error.message || String(command.error))
1610
+ if (command.error.name) errorObj.name = command.error.name
1611
+ if (command.error.stack) errorObj.stack = command.error.stack
1612
+ stepSpan.setTag('error', errorObj)
1613
+ }
1614
+ stepSpan.finish(endTime)
1615
+ }
1616
+ }
1617
+
1447
1618
  const finishedTest = {
1448
1619
  testName,
1449
1620
  testStatus,
@@ -24,6 +24,40 @@ const suppressedTestFailures = new Map()
24
24
  // to a cross-origin URL, safeGetRum() handles the access error.
25
25
  let originalWindow
26
26
 
27
+ let currentTestCommands = []
28
+ const commandStartTimes = new Map()
29
+ const INTERNAL_CYPRESS_COMMANDS = new Set(['wrap', 'then', 'noop'])
30
+
31
+ Cypress.on('command:start', (command) => {
32
+ commandStartTimes.set(command.get('id'), { startTime: Date.now(), name: command.get('name') })
33
+ })
34
+
35
+ Cypress.on('command:end', (command) => {
36
+ const id = command.get('id')
37
+ const entry = commandStartTimes.get(id)
38
+ commandStartTimes.delete(id)
39
+
40
+ const name = command.get('name')
41
+ const args = command.get('args')
42
+ if (name === 'task' && args && typeof args[0] === 'string' && args[0].startsWith('dd:')) {
43
+ return
44
+ }
45
+ if (INTERNAL_CYPRESS_COMMANDS.has(name)) {
46
+ return
47
+ }
48
+ if (entry == null) {
49
+ return
50
+ }
51
+ const err = command.get('err')
52
+ currentTestCommands.push({
53
+ name,
54
+ startTime: entry.startTime,
55
+ endTime: Date.now(),
56
+ // Serialize the error to a plain object so it survives cy.task JSON transport.
57
+ error: err ? { message: err.message, stack: err.stack, name: err.name } : null,
58
+ })
59
+ })
60
+
27
61
  // If the test is using multi domain with cy.origin, trying to access
28
62
  // window properties will result in a cross origin error.
29
63
  function safeGetRum (window) {
@@ -56,6 +90,29 @@ function getTestProperties (testName) {
56
90
  // By not re-throwing the error, Cypress marks the test as passed
57
91
  // This allows quarantined tests to run but not affect the exit code
58
92
  Cypress.on('fail', (err, runnable) => {
93
+ // For commands that time out, command:end may never fire.
94
+ // Finalize any in-flight commands so their step spans carry the error.
95
+ const hadInFlightCommands = commandStartTimes.size > 0
96
+ for (const [, { startTime, name }] of commandStartTimes) {
97
+ if (INTERNAL_CYPRESS_COMMANDS.has(name)) continue
98
+ currentTestCommands.push({
99
+ name,
100
+ startTime,
101
+ endTime: Date.now(),
102
+ error: { message: err.message, stack: err.stack, name: err.name },
103
+ })
104
+ }
105
+ commandStartTimes.clear()
106
+
107
+ // If command:end fired for all commands (none in-flight) but the last command
108
+ // has no error, it means command:end fired before the error was attached to it.
109
+ if (!hadInFlightCommands && currentTestCommands.length > 0) {
110
+ const lastCommand = currentTestCommands[currentTestCommands.length - 1]
111
+ if (!lastCommand.error) {
112
+ lastCommand.error = { message: err.message, stack: err.stack, name: err.name }
113
+ }
114
+ }
115
+
59
116
  if (!isTestManagementEnabled) {
60
117
  throw err
61
118
  }
@@ -169,6 +226,9 @@ beforeEach(function () {
169
226
  retryReasonsByTestName.delete(testName)
170
227
  }
171
228
 
229
+ currentTestCommands = []
230
+ commandStartTimes.clear()
231
+
172
232
  cy.on('window:load', (win) => {
173
233
  originalWindow = win
174
234
  })
@@ -212,6 +272,11 @@ beforeEach(function () {
212
272
  if (shouldSkip) {
213
273
  this.skip()
214
274
  }
275
+ }).then(() => {
276
+ // Clear any commands accumulated during DD-owned setup (e.g. setCookie, RUM restart)
277
+ // so they are not reported as user test steps.
278
+ currentTestCommands = []
279
+ commandStartTimes.clear()
215
280
  })
216
281
  })
217
282
 
@@ -289,6 +354,9 @@ afterEach(function () {
289
354
  testInfo.testSourceStack = invocationDetails.stack
290
355
  } catch {}
291
356
 
357
+ // Snapshot before any DD-owned Cypress commands so they are not reported as test steps.
358
+ const commandsToReport = [...currentTestCommands]
359
+
292
360
  const rum = safeGetRum(originalWindow)
293
361
  if (rum) {
294
362
  testInfo.isRUMActive = true
@@ -310,5 +378,5 @@ afterEach(function () {
310
378
  suppressedTestFailures.delete(testName)
311
379
  }
312
380
 
313
- cy.task('dd:afterEach', { test: testInfo, coverage })
381
+ cy.task('dd:afterEach', { test: testInfo, coverage, commands: commandsToReport })
314
382
  })
@@ -23,22 +23,24 @@ class DNSLookupPlugin extends ClientPlugin {
23
23
  return ctx.currentStore
24
24
  }
25
25
 
26
- bindFinish (ctx) {
26
+ finish (ctx) {
27
27
  const span = ctx.currentStore.span
28
28
  const result = ctx.result
29
29
 
30
30
  if (Array.isArray(result)) {
31
- const addresses = Array.isArray(result)
32
- ? result.map(address => address.address).sort()
33
- : [result]
34
-
31
+ // `lookup(..., { all: true })` or `dns.promises.lookup(..., { all: true })`.
32
+ const addresses = result.map(entry => entry.address).sort()
35
33
  span.setTag('dns.address', addresses[0])
36
34
  span.setTag('dns.addresses', addresses.join(','))
35
+ } else if (result && typeof result === 'object') {
36
+ // `dns.promises.lookup(...)` resolves to `{ address, family }`; the callback variant
37
+ // passes the address as a string.
38
+ span.setTag('dns.address', result.address)
37
39
  } else {
38
40
  span.setTag('dns.address', result)
39
41
  }
40
42
 
41
- return ctx.parentStore
43
+ super.finish(ctx)
42
44
  }
43
45
  }
44
46
 
@@ -181,7 +181,7 @@ class GoogleCloudPubsubPushSubscriptionPlugin extends TracingPlugin {
181
181
 
182
182
  if (linkContext) {
183
183
  if (span.addLink) {
184
- span.addLink(linkContext, {})
184
+ span.addLink({ context: linkContext, attributes: {} })
185
185
  } else {
186
186
  span._links ??= []
187
187
  span._links.push({ context: linkContext, attributes: {} })
@@ -17,6 +17,8 @@ class GraphQLExecutePlugin extends TracingPlugin {
17
17
  const document = args.document
18
18
  const source = this.config.source && document && docSource
19
19
 
20
+ ctx.collapse = this.config.collapse
21
+
20
22
  const span = this.startSpan(this.operationName(), {
21
23
  service: this.config.service || this.serviceName(),
22
24
  resource: getSignature(document, name, type, this.config.signature),