dd-trace 5.103.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 (213) hide show
  1. package/LICENSE-3rdparty.csv +90 -102
  2. package/index.d.ts +107 -6
  3. package/package.json +18 -17
  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 +15 -2
  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/cassandra-driver.js +5 -2
  11. package/packages/datadog-instrumentations/src/cucumber.js +181 -35
  12. package/packages/datadog-instrumentations/src/dns.js +54 -18
  13. package/packages/datadog-instrumentations/src/elasticsearch.js +4 -4
  14. package/packages/datadog-instrumentations/src/fastify.js +142 -82
  15. package/packages/datadog-instrumentations/src/graphql.js +188 -67
  16. package/packages/datadog-instrumentations/src/grpc/client.js +48 -32
  17. package/packages/datadog-instrumentations/src/helpers/ai-messages.js +322 -14
  18. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +1 -1
  19. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  20. package/packages/datadog-instrumentations/src/helpers/instrument.js +2 -1
  21. package/packages/datadog-instrumentations/src/helpers/kafka.js +17 -0
  22. package/packages/datadog-instrumentations/src/helpers/openai-ai-guard.js +269 -0
  23. package/packages/datadog-instrumentations/src/helpers/promise-instrumentor.js +42 -0
  24. package/packages/datadog-instrumentations/src/helpers/register.js +1 -1
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +3 -2
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +19 -6
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/azure-cosmos.js +50 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +2 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/langgraph.js +4 -2
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +85 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +31 -229
  32. package/packages/datadog-instrumentations/src/hono.js +54 -3
  33. package/packages/datadog-instrumentations/src/http/client.js +2 -2
  34. package/packages/datadog-instrumentations/src/http/server.js +9 -4
  35. package/packages/datadog-instrumentations/src/ioredis.js +3 -3
  36. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +163 -0
  37. package/packages/datadog-instrumentations/src/jest.js +390 -183
  38. package/packages/datadog-instrumentations/src/kafkajs.js +140 -17
  39. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  40. package/packages/datadog-instrumentations/src/memcached.js +2 -1
  41. package/packages/datadog-instrumentations/src/mocha/main.js +399 -107
  42. package/packages/datadog-instrumentations/src/mocha/utils.js +48 -8
  43. package/packages/datadog-instrumentations/src/mongodb-core.js +1 -1
  44. package/packages/datadog-instrumentations/src/mongoose.js +10 -12
  45. package/packages/datadog-instrumentations/src/mysql.js +2 -2
  46. package/packages/datadog-instrumentations/src/mysql2.js +1 -1
  47. package/packages/datadog-instrumentations/src/nats.js +182 -0
  48. package/packages/datadog-instrumentations/src/nyc.js +38 -1
  49. package/packages/datadog-instrumentations/src/openai.js +33 -18
  50. package/packages/datadog-instrumentations/src/oracledb.js +6 -1
  51. package/packages/datadog-instrumentations/src/pg.js +1 -1
  52. package/packages/datadog-instrumentations/src/pino.js +17 -5
  53. package/packages/datadog-instrumentations/src/playwright.js +537 -297
  54. package/packages/datadog-instrumentations/src/router.js +80 -34
  55. package/packages/datadog-instrumentations/src/stripe.js +1 -1
  56. package/packages/datadog-instrumentations/src/vitest.js +246 -149
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -1
  58. package/packages/datadog-plugin-azure-cosmos/src/index.js +144 -0
  59. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -1
  60. package/packages/datadog-plugin-azure-functions/src/index.js +5 -2
  61. package/packages/datadog-plugin-azure-service-bus/src/producer.js +1 -1
  62. package/packages/datadog-plugin-bunyan/src/index.js +28 -0
  63. package/packages/datadog-plugin-cucumber/src/index.js +17 -3
  64. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +223 -45
  65. package/packages/datadog-plugin-cypress/src/support.js +69 -1
  66. package/packages/datadog-plugin-dns/src/lookup.js +8 -6
  67. package/packages/datadog-plugin-elasticsearch/src/index.js +28 -8
  68. package/packages/datadog-plugin-google-cloud-pubsub/src/pubsub-push-subscription.js +1 -1
  69. package/packages/datadog-plugin-graphql/src/execute.js +2 -0
  70. package/packages/datadog-plugin-graphql/src/resolve.js +64 -67
  71. package/packages/datadog-plugin-graphql/src/utils.js +4 -1
  72. package/packages/datadog-plugin-http/src/server.js +40 -15
  73. package/packages/datadog-plugin-jest/src/index.js +11 -3
  74. package/packages/datadog-plugin-jest/src/util.js +15 -8
  75. package/packages/datadog-plugin-kafkajs/src/batch-consumer.js +1 -1
  76. package/packages/datadog-plugin-kafkajs/src/producer.js +35 -0
  77. package/packages/datadog-plugin-langgraph/src/stream.js +1 -1
  78. package/packages/datadog-plugin-mocha/src/index.js +19 -4
  79. package/packages/datadog-plugin-mongodb-core/src/index.js +311 -35
  80. package/packages/datadog-plugin-nats/src/consumer.js +43 -0
  81. package/packages/datadog-plugin-nats/src/index.js +20 -0
  82. package/packages/datadog-plugin-nats/src/producer.js +62 -0
  83. package/packages/datadog-plugin-nats/src/util.js +33 -0
  84. package/packages/datadog-plugin-next/src/index.js +5 -3
  85. package/packages/datadog-plugin-openai/src/tracing.js +15 -2
  86. package/packages/datadog-plugin-oracledb/src/index.js +13 -2
  87. package/packages/datadog-plugin-pino/src/index.js +42 -0
  88. package/packages/datadog-plugin-playwright/src/index.js +4 -4
  89. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -1
  90. package/packages/datadog-plugin-redis/src/index.js +37 -2
  91. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  92. package/packages/datadog-plugin-router/src/index.js +33 -44
  93. package/packages/datadog-plugin-selenium/src/index.js +1 -1
  94. package/packages/datadog-plugin-undici/src/index.js +19 -0
  95. package/packages/datadog-plugin-vitest/src/index.js +24 -20
  96. package/packages/datadog-plugin-winston/src/index.js +30 -0
  97. package/packages/datadog-shimmer/src/shimmer.js +49 -21
  98. package/packages/dd-trace/src/aiguard/index.js +1 -1
  99. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  100. package/packages/dd-trace/src/appsec/api_security_sampler.js +1 -1
  101. package/packages/dd-trace/src/appsec/blocking.js +2 -2
  102. package/packages/dd-trace/src/appsec/index.js +11 -4
  103. package/packages/dd-trace/src/appsec/reporter.js +24 -11
  104. package/packages/dd-trace/src/appsec/sdk/user_blocking.js +1 -1
  105. package/packages/dd-trace/src/appsec/sdk/utils.js +1 -1
  106. package/packages/dd-trace/src/appsec/user_tracking.js +5 -4
  107. package/packages/dd-trace/src/baggage.js +7 -1
  108. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +0 -1
  109. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +25 -13
  110. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -1
  111. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +5 -3
  112. package/packages/dd-trace/src/ci-visibility/test-optimization-cache.js +70 -6
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +7 -2
  114. package/packages/dd-trace/src/config/supported-configurations.json +36 -8
  115. package/packages/dd-trace/src/crashtracking/crashtracker.js +15 -3
  116. package/packages/dd-trace/src/datastreams/context.js +4 -2
  117. package/packages/dd-trace/src/datastreams/writer.js +2 -4
  118. package/packages/dd-trace/src/debugger/devtools_client/condition.js +5 -8
  119. package/packages/dd-trace/src/encode/0.4.js +124 -108
  120. package/packages/dd-trace/src/encode/0.5.js +114 -26
  121. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +57 -42
  122. package/packages/dd-trace/src/encode/agentless-json.js +4 -2
  123. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +32 -13
  124. package/packages/dd-trace/src/encode/span-stats.js +16 -16
  125. package/packages/dd-trace/src/encode/tags-processors.js +16 -0
  126. package/packages/dd-trace/src/exporters/common/agents.js +3 -1
  127. package/packages/dd-trace/src/exporters/common/request.js +3 -1
  128. package/packages/dd-trace/src/id.js +17 -4
  129. package/packages/dd-trace/src/lambda/handler.js +2 -4
  130. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  131. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  132. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  133. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +9 -7
  134. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  135. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +1 -1
  136. package/packages/dd-trace/src/llmobs/sdk.js +10 -16
  137. package/packages/dd-trace/src/llmobs/span_processor.js +3 -3
  138. package/packages/dd-trace/src/llmobs/tagger.js +9 -1
  139. package/packages/dd-trace/src/llmobs/telemetry.js +1 -1
  140. package/packages/dd-trace/src/llmobs/util.js +66 -3
  141. package/packages/dd-trace/src/log/index.js +1 -1
  142. package/packages/dd-trace/src/log/writer.js +3 -1
  143. package/packages/dd-trace/src/msgpack/chunk.js +394 -10
  144. package/packages/dd-trace/src/msgpack/index.js +96 -2
  145. package/packages/dd-trace/src/noop/span.js +3 -1
  146. package/packages/dd-trace/src/openfeature/encoding.js +70 -0
  147. package/packages/dd-trace/src/openfeature/flagging_provider.js +20 -0
  148. package/packages/dd-trace/src/openfeature/span-enrichment-hook.js +143 -0
  149. package/packages/dd-trace/src/openfeature/span-enrichment.js +149 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +51 -20
  151. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/span-helpers.js +4 -3
  153. package/packages/dd-trace/src/opentelemetry/span.js +1 -1
  154. package/packages/dd-trace/src/opentracing/propagation/log.js +18 -7
  155. package/packages/dd-trace/src/opentracing/propagation/text_map.js +62 -67
  156. package/packages/dd-trace/src/opentracing/span.js +59 -19
  157. package/packages/dd-trace/src/opentracing/span_context.js +49 -0
  158. package/packages/dd-trace/src/plugins/apollo.js +3 -1
  159. package/packages/dd-trace/src/plugins/ci_plugin.js +23 -33
  160. package/packages/dd-trace/src/plugins/database.js +7 -6
  161. package/packages/dd-trace/src/plugins/index.js +4 -0
  162. package/packages/dd-trace/src/plugins/log_injection.js +56 -0
  163. package/packages/dd-trace/src/plugins/log_plugin.js +3 -46
  164. package/packages/dd-trace/src/plugins/outbound.js +1 -1
  165. package/packages/dd-trace/src/plugins/plugin.js +15 -17
  166. package/packages/dd-trace/src/plugins/tracing.js +48 -8
  167. package/packages/dd-trace/src/plugins/util/git.js +3 -1
  168. package/packages/dd-trace/src/plugins/util/test.js +318 -13
  169. package/packages/dd-trace/src/plugins/util/web.js +89 -64
  170. package/packages/dd-trace/src/priority_sampler.js +2 -2
  171. package/packages/dd-trace/src/profiling/profiler.js +2 -2
  172. package/packages/dd-trace/src/profiling/profilers/wall.js +10 -4
  173. package/packages/dd-trace/src/sampling_rule.js +7 -7
  174. package/packages/dd-trace/src/scope.js +7 -5
  175. package/packages/dd-trace/src/service-naming/extra-services.js +14 -0
  176. package/packages/dd-trace/src/service-naming/schemas/v0/messaging.js +10 -0
  177. package/packages/dd-trace/src/service-naming/schemas/v1/messaging.js +8 -0
  178. package/packages/dd-trace/src/service-naming/source-resolver.js +46 -0
  179. package/packages/dd-trace/src/span_format.js +190 -58
  180. package/packages/dd-trace/src/spanleak.js +1 -1
  181. package/packages/dd-trace/src/standalone/index.js +3 -3
  182. package/packages/dd-trace/src/tagger.js +0 -2
  183. package/vendor/dist/@apm-js-collab/code-transformer/index.js +70 -39
  184. package/vendor/dist/@datadog/sketches-js/LICENSE +10 -36
  185. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  186. package/vendor/dist/protobufjs/index.js +1 -1
  187. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  188. package/packages/dd-trace/src/msgpack/encoder.js +0 -308
  189. package/packages/dd-trace/src/plugins/structured_log_plugin.js +0 -9
  190. package/vendor/dist/opentracing/LICENSE +0 -201
  191. package/vendor/dist/opentracing/binary_carrier.d.ts +0 -11
  192. package/vendor/dist/opentracing/constants.d.ts +0 -61
  193. package/vendor/dist/opentracing/examples/demo/demo.d.ts +0 -2
  194. package/vendor/dist/opentracing/ext/tags.d.ts +0 -90
  195. package/vendor/dist/opentracing/functions.d.ts +0 -20
  196. package/vendor/dist/opentracing/global_tracer.d.ts +0 -14
  197. package/vendor/dist/opentracing/index.d.ts +0 -12
  198. package/vendor/dist/opentracing/index.js +0 -1
  199. package/vendor/dist/opentracing/mock_tracer/index.d.ts +0 -5
  200. package/vendor/dist/opentracing/mock_tracer/mock_context.d.ts +0 -13
  201. package/vendor/dist/opentracing/mock_tracer/mock_report.d.ts +0 -16
  202. package/vendor/dist/opentracing/mock_tracer/mock_span.d.ts +0 -50
  203. package/vendor/dist/opentracing/mock_tracer/mock_tracer.d.ts +0 -26
  204. package/vendor/dist/opentracing/noop.d.ts +0 -8
  205. package/vendor/dist/opentracing/reference.d.ts +0 -33
  206. package/vendor/dist/opentracing/span.d.ts +0 -147
  207. package/vendor/dist/opentracing/span_context.d.ts +0 -26
  208. package/vendor/dist/opentracing/test/api_compatibility.d.ts +0 -16
  209. package/vendor/dist/opentracing/test/mocktracer_implemenation.d.ts +0 -3
  210. package/vendor/dist/opentracing/test/noop_implementation.d.ts +0 -4
  211. package/vendor/dist/opentracing/test/opentracing_api.d.ts +0 -3
  212. package/vendor/dist/opentracing/test/unittest.d.ts +0 -2
  213. package/vendor/dist/opentracing/tracer.d.ts +0 -127
@@ -13,6 +13,7 @@ const triggerMap = {
13
13
  serviceBusQueue: 'ServiceBus',
14
14
  serviceBusTopic: 'ServiceBus',
15
15
  eventHub: 'EventHubs',
16
+ cosmosDB: 'CosmosDB',
16
17
  }
17
18
 
18
19
  class AzureFunctionsPlugin extends TracingPlugin {
@@ -53,7 +54,7 @@ class AzureFunctionsPlugin extends TracingPlugin {
53
54
  )
54
55
 
55
56
  span._integrationName = 'azure-functions'
56
- span.context()._tags.component = 'azure-functions'
57
+ span.context().setTag('component', 'azure-functions')
57
58
  span.addTags(meta)
58
59
  webContext.span = span
59
60
  webContext.azureFunctionCtx = ctx
@@ -127,6 +128,8 @@ function getMetaForTrigger ({ functionName, methodName, invocationContext }) {
127
128
  'resource.name': `EventHubs ${functionName}`,
128
129
  'span.kind': 'consumer',
129
130
  }
131
+ } else if (triggerMap[methodName] === 'CosmosDB') {
132
+ meta['resource.name'] = `CosmosDB ${functionName}`
130
133
  }
131
134
 
132
135
  return meta
@@ -155,7 +158,7 @@ function setSpanLinks (triggerType, tracer, span, ctx) {
155
158
  if (!props || Object.keys(props).length === 0) return
156
159
  const spanContext = tracer.extract('text_map', props)
157
160
  if (spanContext) {
158
- span.addLink(spanContext)
161
+ span.addLink({ context: spanContext })
159
162
  }
160
163
  }
161
164
 
@@ -56,7 +56,7 @@ class AzureServiceBusProducerPlugin extends ProducerPlugin {
56
56
  const contexts = spanContexts.get(messages)
57
57
  if (contexts) {
58
58
  for (const spanContext of contexts) {
59
- span.addLink(spanContext)
59
+ span.addLink({ context: spanContext })
60
60
  }
61
61
  }
62
62
  }
@@ -1,8 +1,36 @@
1
1
  'use strict'
2
2
 
3
+ const { buildLogHolder } = require('../../dd-trace/src/plugins/log_injection')
3
4
  const LogPlugin = require('../../dd-trace/src/plugins/log_plugin')
4
5
 
5
6
  class BunyanPlugin extends LogPlugin {
6
7
  static id = 'bunyan'
8
+
9
+ constructor (...args) {
10
+ super(...args)
11
+ this.addSub('apm:bunyan:log', (arg) => this.handleLog(arg))
12
+ }
13
+
14
+ /**
15
+ * Inject `dd` directly on the record bunyan hands us. bunyan builds the
16
+ * record inside `mkRecord` via `objCopy(log.fields)` and then copies the
17
+ * caller's fields onto the result, so the `rec` object that flows
18
+ * through `_emit` is always bunyan-owned, has `Object.prototype` for its
19
+ * prototype, and is never the caller's input directly. Mutating it adds
20
+ * `dd` for every consumer (JSON streams via `JSON.stringify(rec)`, raw
21
+ * streams via the record reference) without paying for a Proxy view.
22
+ *
23
+ * @param {{ message: object }} arg
24
+ */
25
+ handleLog (arg) {
26
+ const rec = arg.message
27
+ if (rec === null || typeof rec !== 'object' || Object.hasOwn(rec, 'dd')) return
28
+
29
+ const logHolder = buildLogHolder(this.tracer)
30
+ if (!logHolder) return
31
+
32
+ rec.dd = logHolder.dd
33
+ }
7
34
  }
35
+
8
36
  module.exports = BunyanPlugin
@@ -11,6 +11,7 @@ const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
11
11
  const {
12
12
  addIntelligentTestRunnerSpanTags,
13
13
  finishAllTraceSpans,
14
+ getRelativeCoverageFiles,
14
15
  getTestEndLine,
15
16
  getTestSuiteCommonTags,
16
17
  getTestSuitePath,
@@ -71,6 +72,7 @@ class CucumberPlugin extends CiPlugin {
71
72
  isSuitesSkipped,
72
73
  numSkippedSuites,
73
74
  testCodeCoverageLinesTotal,
75
+ testSessionCoverageFiles,
74
76
  hasUnskippableSuites,
75
77
  hasForcedToRunSuites,
76
78
  isEarlyFlakeDetectionEnabled,
@@ -78,7 +80,11 @@ class CucumberPlugin extends CiPlugin {
78
80
  isTestManagementTestsEnabled,
79
81
  isParallel,
80
82
  }) => {
81
- const { isSuitesSkippingEnabled, isCodeCoverageEnabled } = this.libraryConfig || {}
83
+ const {
84
+ isSuitesSkippingEnabled,
85
+ isCodeCoverageEnabled,
86
+ isCoverageReportUploadEnabled,
87
+ } = this.libraryConfig || {}
82
88
  addIntelligentTestRunnerSpanTags(
83
89
  this.testSessionSpan,
84
90
  this.testModuleSpan,
@@ -93,6 +99,12 @@ class CucumberPlugin extends CiPlugin {
93
99
  hasForcedToRunSuites,
94
100
  }
95
101
  )
102
+ if (testSessionCoverageFiles?.length && isCoverageReportUploadEnabled) {
103
+ this.tracer._exporter.exportCoverage({
104
+ sessionId: this.testSessionSpan.context()._traceId,
105
+ files: testSessionCoverageFiles,
106
+ })
107
+ }
96
108
  if (isEarlyFlakeDetectionEnabled) {
97
109
  this.testSessionSpan.setTag(TEST_EARLY_FLAKE_ENABLED, 'true')
98
110
  }
@@ -197,8 +209,10 @@ class CucumberPlugin extends CiPlugin {
197
209
  }
198
210
  const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuitePath)
199
211
 
200
- const relativeCoverageFiles = [...coverageFiles, suiteFile]
201
- .map(filename => getTestSuitePath(filename, this.repositoryRoot))
212
+ const relativeCoverageFiles = [
213
+ ...getRelativeCoverageFiles(coverageFiles, this.repositoryRoot),
214
+ getTestSuitePath(suiteFile, this.repositoryRoot),
215
+ ]
202
216
 
203
217
  this.telemetry.distribution(TELEMETRY_CODE_COVERAGE_NUM_FILES, {}, relativeCoverageFiles.length)
204
218
 
@@ -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,
@@ -70,9 +75,11 @@ const {
70
75
  getMaxEfdRetryCount,
71
76
  getPullRequestBaseBranch,
72
77
  TEST_FINAL_STATUS,
78
+ getTestOptimizationRequestResults,
73
79
  } = require('../../dd-trace/src/plugins/util/test')
74
80
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
75
81
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
82
+ const { RESOURCE_NAME } = require('../../../ext/tags')
76
83
  const getConfig = require('../../dd-trace/src/config')
77
84
  const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
78
85
  const log = require('../../dd-trace/src/log')
@@ -200,13 +207,17 @@ function getSkippableTests (tracer, testConfiguration) {
200
207
  if (!tracer._tracer._exporter?.getSkippableSuites) {
201
208
  return resolve({ err: new Error('Test Optimization was not initialized correctly') })
202
209
  }
203
- tracer._tracer._exporter.getSkippableSuites(testConfiguration, (err, skippableTests, correlationId) => {
204
- resolve({
205
- err,
206
- skippableTests,
207
- correlationId,
208
- })
209
- })
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
+ )
210
221
  })
211
222
  }
212
223
 
@@ -360,9 +371,11 @@ class CypressPlugin {
360
371
  finishedTestsByFile = {}
361
372
  testStatuses = {}
362
373
  hasLibraryConfiguration = false
374
+ isItrEnabled = false
363
375
  isTestsSkipped = false
364
376
  isSuitesSkippingEnabled = false
365
377
  isCodeCoverageEnabled = false
378
+ isCoverageReportUploadEnabled = false
366
379
  isFlakyTestRetriesEnabled = false
367
380
  flakyTestRetriesCount = 0
368
381
  isEarlyFlakeDetectionEnabled = false
@@ -375,6 +388,8 @@ class CypressPlugin {
375
388
  earlyFlakeDetectionFaultyThreshold = 0
376
389
  testsToSkip = []
377
390
  skippedTests = []
391
+ skippableTestsCoverage = {}
392
+ testSessionCoverageMap = createCoverageMap()
378
393
  hasForcedToRunSuites = false
379
394
  hasUnskippableSuites = false
380
395
  unskippableSuites = []
@@ -439,9 +454,11 @@ class CypressPlugin {
439
454
  this.finishedTestsByFile = {}
440
455
  this.testStatuses = {}
441
456
  this.hasLibraryConfiguration = false
457
+ this.isItrEnabled = false
442
458
  this.isTestsSkipped = false
443
459
  this.isSuitesSkippingEnabled = false
444
460
  this.isCodeCoverageEnabled = false
461
+ this.isCoverageReportUploadEnabled = false
445
462
  this.isFlakyTestRetriesEnabled = false
446
463
  this.flakyTestRetriesCount = 0
447
464
  this.isEarlyFlakeDetectionEnabled = false
@@ -454,6 +471,8 @@ class CypressPlugin {
454
471
  this.earlyFlakeDetectionFaultyThreshold = 0
455
472
  this.testsToSkip = []
456
473
  this.skippedTests = []
474
+ this.skippableTestsCoverage = {}
475
+ this.testSessionCoverageMap = createCoverageMap()
457
476
  this.hasForcedToRunSuites = false
458
477
  this.hasUnskippableSuites = false
459
478
  this.unskippableSuites = []
@@ -493,6 +512,117 @@ class CypressPlugin {
493
512
  return this._timeOrigin + performance.now() - this._perfOrigin
494
513
  }
495
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
+
496
626
  // Init function returns a promise that resolves with the Cypress configuration
497
627
  // Depending on the received configuration, the Cypress configuration can be modified:
498
628
  // for example, to enable retries for failed tests.
@@ -528,8 +658,10 @@ class CypressPlugin {
528
658
  this.hasLibraryConfiguration = true
529
659
  const {
530
660
  libraryConfig: {
661
+ isItrEnabled,
531
662
  isSuitesSkippingEnabled,
532
663
  isCodeCoverageEnabled,
664
+ isCoverageReportUploadEnabled,
533
665
  isEarlyFlakeDetectionEnabled,
534
666
  earlyFlakeDetectionNumRetries,
535
667
  earlyFlakeDetectionSlowTestRetries,
@@ -542,8 +674,10 @@ class CypressPlugin {
542
674
  isImpactedTestsEnabled,
543
675
  },
544
676
  } = libraryConfigurationResponse
677
+ this.isItrEnabled = isItrEnabled
545
678
  this.isSuitesSkippingEnabled = isSuitesSkippingEnabled
546
679
  this.isCodeCoverageEnabled = isCodeCoverageEnabled
680
+ this.isCoverageReportUploadEnabled = isCoverageReportUploadEnabled
547
681
  this.isEarlyFlakeDetectionEnabled = isEarlyFlakeDetectionEnabled
548
682
  this.earlyFlakeDetectionNumRetries = earlyFlakeDetectionNumRetries
549
683
  this.earlyFlakeDetectionSlowTestRetries = earlyFlakeDetectionSlowTestRetries ?? {}
@@ -685,7 +819,6 @@ class CypressPlugin {
685
819
 
686
820
  getTestSpan ({ testName, testSuite, isUnskippable, isForcedToRun, testSourceFile, isDisabled, isQuarantined }) {
687
821
  const testSuiteTags = {
688
- [TEST_COMMAND]: this.command,
689
822
  [TEST_MODULE]: TEST_FRAMEWORK_NAME,
690
823
  }
691
824
  if (this.testSuiteSpan) {
@@ -787,19 +920,32 @@ class CypressPlugin {
787
920
  this.frameworkVersion = getCypressVersion(details)
788
921
  this.rootDir = getRootDir(details)
789
922
 
923
+ const {
924
+ knownTestsResponse,
925
+ testManagementTestsResponse,
926
+ skippableSuitesResponse: skippableTestsRequestResponse,
927
+ } = await getTestOptimizationRequestResults({
928
+ isKnownTestsEnabled: this.isKnownTestsEnabled,
929
+ isTestManagementTestsEnabled: this.isTestManagementTestsEnabled,
930
+ isSuitesSkippingEnabled: this.isSuitesSkippingEnabled,
931
+ getKnownTests: () => getKnownTests(this.tracer, this.testConfiguration),
932
+ getTestManagementTests: () => getTestManagementTests(this.tracer, this.testConfiguration),
933
+ getSkippableSuites: () => getSkippableTests(this.tracer, {
934
+ ...this.testConfiguration,
935
+ isCoverageReportUploadEnabled: this.isCoverageReportUploadEnabled,
936
+ }),
937
+ })
938
+
790
939
  if (this.isKnownTestsEnabled) {
791
- const knownTestsResponse = await getKnownTests(
792
- this.tracer,
793
- this.testConfiguration
794
- )
795
- if (knownTestsResponse.err) {
796
- log.error('Cypress known tests response error', knownTestsResponse.err)
940
+ const currentKnownTestsResponse = knownTestsResponse || await getKnownTests(this.tracer, this.testConfiguration)
941
+ if (currentKnownTestsResponse.err) {
942
+ log.error('Cypress known tests response error', currentKnownTestsResponse.err)
797
943
  this._pendingRequestErrorTags.push({ tag: DD_CI_LIBRARY_CONFIGURATION_ERROR_KNOWN_TESTS, value: 'true' })
798
944
  this.isEarlyFlakeDetectionEnabled = false
799
945
  this.isKnownTestsEnabled = false
800
946
  } else {
801
- if (knownTestsResponse.knownTests?.[TEST_FRAMEWORK_NAME]) {
802
- this.knownTestsByTestSuite = knownTestsResponse.knownTests[TEST_FRAMEWORK_NAME]
947
+ if (currentKnownTestsResponse.knownTests?.[TEST_FRAMEWORK_NAME]) {
948
+ this.knownTestsByTestSuite = currentKnownTestsResponse.knownTests[TEST_FRAMEWORK_NAME]
803
949
  } else {
804
950
  this.isEarlyFlakeDetectionEnabled = false
805
951
  this.isKnownTestsEnabled = false
@@ -823,35 +969,35 @@ class CypressPlugin {
823
969
  }
824
970
 
825
971
  if (this.isSuitesSkippingEnabled) {
826
- const skippableTestsResponse = await getSkippableTests(
827
- this.tracer,
828
- this.testConfiguration
829
- )
972
+ const skippableTestsResponse =
973
+ skippableTestsRequestResponse || await getSkippableTests(this.tracer, {
974
+ ...this.testConfiguration,
975
+ isCoverageReportUploadEnabled: this.isCoverageReportUploadEnabled,
976
+ })
830
977
  if (skippableTestsResponse.err) {
831
978
  log.error('Cypress skippable tests response error', skippableTestsResponse.err)
832
979
  this._pendingRequestErrorTags.push({ tag: DD_CI_LIBRARY_CONFIGURATION_ERROR_SKIPPABLE_TESTS, value: 'true' })
833
980
  } else {
834
- const { skippableTests, correlationId } = skippableTestsResponse
981
+ const { skippableTests, correlationId, skippableTestsCoverage } = skippableTestsResponse
835
982
  this.testsToSkip = skippableTests || []
983
+ this.skippableTestsCoverage = skippableTestsCoverage || {}
836
984
  this.itrCorrelationId = correlationId
837
985
  incrementCountMetric(TELEMETRY_ITR_SKIPPED, { testLevel: 'test' }, this.testsToSkip.length)
838
986
  }
839
987
  }
840
988
 
841
989
  if (this.isTestManagementTestsEnabled) {
842
- const testManagementTestsResponse = await getTestManagementTests(
843
- this.tracer,
844
- this.testConfiguration
845
- )
846
- if (testManagementTestsResponse.err) {
847
- log.error('Cypress test management tests response error', testManagementTestsResponse.err)
990
+ const currentTestManagementTestsResponse =
991
+ testManagementTestsResponse || await getTestManagementTests(this.tracer, this.testConfiguration)
992
+ if (currentTestManagementTestsResponse.err) {
993
+ log.error('Cypress test management tests response error', currentTestManagementTestsResponse.err)
848
994
  this._pendingRequestErrorTags.push({
849
995
  tag: DD_CI_LIBRARY_CONFIGURATION_ERROR_TEST_MANAGEMENT_TESTS,
850
996
  value: 'true',
851
997
  })
852
998
  this.isTestManagementTestsEnabled = false
853
999
  } else {
854
- this.testManagementTests = testManagementTestsResponse.testManagementTests
1000
+ this.testManagementTests = currentTestManagementTestsResponse.testManagementTests
855
1001
  }
856
1002
  }
857
1003
 
@@ -897,15 +1043,9 @@ class CypressPlugin {
897
1043
  )
898
1044
 
899
1045
  if (this.tracer._tracer._exporter?.addMetadataTags) {
900
- const metadataTags = {}
901
- for (const testLevel of TEST_LEVEL_EVENT_TYPES) {
902
- metadataTags[testLevel] = {
903
- [TEST_SESSION_NAME]: testSessionName,
904
- }
905
- }
1046
+ const metadataTags = { '*': { [TEST_COMMAND]: this.command, [TEST_SESSION_NAME]: testSessionName } }
906
1047
  const libraryCapabilitiesTags = getLibraryCapabilitiesTags(this.constructor.id, this.frameworkVersion)
907
1048
  metadataTags.test = {
908
- ...metadataTags.test,
909
1049
  ...libraryCapabilitiesTags,
910
1050
  }
911
1051
 
@@ -962,6 +1102,9 @@ class CypressPlugin {
962
1102
  }
963
1103
  if (this.testSessionSpan && this.testModuleSpan) {
964
1104
  const testStatus = getSessionStatus(suiteStats)
1105
+ const hasBackfilledCoverage = this.applySkippedCoverageToTestSessionCoverage()
1106
+ const testCodeCoverageLinesTotal = this.getTestCodeCoverageLinesTotal(hasBackfilledCoverage)
1107
+
965
1108
  this.testModuleSpan.setTag(TEST_STATUS, testStatus)
966
1109
  this.testSessionSpan.setTag(TEST_STATUS, testStatus)
967
1110
 
@@ -972,6 +1115,7 @@ class CypressPlugin {
972
1115
  isSuitesSkipped: this.isTestsSkipped,
973
1116
  isSuitesSkippingEnabled: this.isSuitesSkippingEnabled,
974
1117
  isCodeCoverageEnabled: this.isCodeCoverageEnabled,
1118
+ testCodeCoverageLinesTotal,
975
1119
  skippingType: 'test',
976
1120
  skippingCount: this.skippedTests.length,
977
1121
  hasForcedToRunSuites: this.hasForcedToRunSuites,
@@ -979,6 +1123,8 @@ class CypressPlugin {
979
1123
  }
980
1124
  )
981
1125
 
1126
+ this.reportTestSessionCoverage()
1127
+
982
1128
  if (this.isTestManagementTestsEnabled) {
983
1129
  this.testSessionSpan.setTag(TEST_MANAGEMENT_ENABLED, 'true')
984
1130
  }
@@ -1138,7 +1284,7 @@ class CypressPlugin {
1138
1284
  }
1139
1285
  // Update test status - but NOT for non-ATF quarantined tests where we intentionally
1140
1286
  // report 'fail' to Datadog even though Cypress sees it as 'pass'
1141
- const isQuarantinedTest = finishedTest.testSpan?.context()?._tags?.[TEST_MANAGEMENT_IS_QUARANTINED] === 'true'
1287
+ const isQuarantinedTest = finishedTest.testSpan?.context()?.getTag(TEST_MANAGEMENT_IS_QUARANTINED) === 'true'
1142
1288
  if (cypressTestStatus !== finishedTest.testStatus && (!isQuarantinedTest || finishedTest.isAttemptToFix)) {
1143
1289
  finishedTest.testSpan.setTag(TEST_STATUS, cypressTestStatus)
1144
1290
  finishedTest.testSpan.setTag('error', latestError)
@@ -1165,7 +1311,7 @@ class CypressPlugin {
1165
1311
  }
1166
1312
 
1167
1313
  if (isLastAttempt) {
1168
- const testSpanTags = finishedTest.testSpan.context()._tags
1314
+ const testSpanTags = finishedTest.testSpan.context().getTags()
1169
1315
  const retryKind = getFinalStatusRetryKind({
1170
1316
  finishedTest,
1171
1317
  finishedTestAttempts,
@@ -1273,7 +1419,7 @@ class CypressPlugin {
1273
1419
 
1274
1420
  return this.activeTestSpan ? { traceId: this.activeTestSpan.context().toTraceId() } : {}
1275
1421
  },
1276
- 'dd:afterEach': ({ test, coverage }) => {
1422
+ 'dd:afterEach': ({ test, coverage, commands }) => {
1277
1423
  if (!this.activeTestSpan) {
1278
1424
  log.warn('There is no active test span in dd:afterEach handler')
1279
1425
  return null
@@ -1296,11 +1442,18 @@ class CypressPlugin {
1296
1442
  isQuarantined: isQuarantinedFromSupport,
1297
1443
  isDisabled: isDisabledFromSupport,
1298
1444
  } = test
1445
+ if (coverage && (this.isCodeCoverageEnabled || this.isCoverageReportUploadEnabled)) {
1446
+ this.addTestSessionCoverage(coverage)
1447
+ }
1448
+
1299
1449
  if (coverage && this.isCodeCoverageEnabled && this.tracer._tracer._exporter?.exportCoverage) {
1300
- const coverageFiles = getCoveredFilenamesFromCoverage(coverage)
1301
- const relativeCoverageFiles = [...coverageFiles, testSuiteAbsolutePath].map(
1302
- file => getTestSuitePath(file, this.repositoryRoot || this.rootDir)
1303
- )
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
+ }
1304
1457
  if (!relativeCoverageFiles.length) {
1305
1458
  incrementCountMetric(TELEMETRY_CODE_COVERAGE_EMPTY)
1306
1459
  }
@@ -1336,7 +1489,7 @@ class CypressPlugin {
1336
1489
  this.testStatuses[testName] = [testStatus]
1337
1490
  }
1338
1491
  const testStatuses = this.testStatuses[testName]
1339
- const activeSpanTags = this.activeTestSpan.context()._tags
1492
+ const activeSpanTags = this.activeTestSpan.context().getTags()
1340
1493
 
1341
1494
  if (error) {
1342
1495
  this.activeTestSpan.setTag('error', error)
@@ -1437,6 +1590,31 @@ class CypressPlugin {
1437
1590
  this.activeTestSpan.setTag(TEST_MANAGEMENT_IS_DISABLED, 'true')
1438
1591
  }
1439
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
+
1440
1618
  const finishedTest = {
1441
1619
  testName,
1442
1620
  testStatus,