dd-trace 6.3.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/README.electron.md +7 -0
  2. package/README.md +17 -0
  3. package/ci/diagnose.js +2100 -0
  4. package/ci/init.js +23 -2
  5. package/ci/runbook.md +198 -0
  6. package/ci/test-optimization-validation/approval-artifacts.js +143 -0
  7. package/ci/test-optimization-validation/approval.js +299 -0
  8. package/ci/test-optimization-validation/artifact-id.js +20 -0
  9. package/ci/test-optimization-validation/bounded-json.js +121 -0
  10. package/ci/test-optimization-validation/ci-command-candidate.js +83 -0
  11. package/ci/test-optimization-validation/ci-discovery.js +181 -0
  12. package/ci/test-optimization-validation/ci-remediation.js +210 -0
  13. package/ci/test-optimization-validation/cli.js +903 -0
  14. package/ci/test-optimization-validation/command-blocker.js +81 -0
  15. package/ci/test-optimization-validation/command-output-policy.js +206 -0
  16. package/ci/test-optimization-validation/command-runner.js +707 -0
  17. package/ci/test-optimization-validation/command-suitability.js +471 -0
  18. package/ci/test-optimization-validation/executable-approval.js +48 -0
  19. package/ci/test-optimization-validation/executable.js +480 -0
  20. package/ci/test-optimization-validation/generated-file-policy.js +63 -0
  21. package/ci/test-optimization-validation/generated-files.js +351 -0
  22. package/ci/test-optimization-validation/generated-verifier.js +196 -0
  23. package/ci/test-optimization-validation/init-probe-preload.js +163 -0
  24. package/ci/test-optimization-validation/init-probe.js +246 -0
  25. package/ci/test-optimization-validation/late-initialization.js +63 -0
  26. package/ci/test-optimization-validation/local-command.js +163 -0
  27. package/ci/test-optimization-validation/manifest-loader.js +133 -0
  28. package/ci/test-optimization-validation/manifest-scaffold.js +738 -0
  29. package/ci/test-optimization-validation/manifest-schema.js +862 -0
  30. package/ci/test-optimization-validation/offline-fixtures.js +327 -0
  31. package/ci/test-optimization-validation/offline-output.js +406 -0
  32. package/ci/test-optimization-validation/payload-normalizer.js +72 -0
  33. package/ci/test-optimization-validation/plan-writer.js +1120 -0
  34. package/ci/test-optimization-validation/preflight-runner.js +114 -0
  35. package/ci/test-optimization-validation/redaction.js +331 -0
  36. package/ci/test-optimization-validation/report-writer.js +1508 -0
  37. package/ci/test-optimization-validation/safe-files.js +203 -0
  38. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +159 -0
  39. package/ci/test-optimization-validation/scenarios/basic-reporting.js +657 -0
  40. package/ci/test-optimization-validation/scenarios/ci-wiring.js +780 -0
  41. package/ci/test-optimization-validation/scenarios/early-flake-detection.js +141 -0
  42. package/ci/test-optimization-validation/scenarios/helpers.js +539 -0
  43. package/ci/test-optimization-validation/scenarios/test-management.js +218 -0
  44. package/ci/test-optimization-validation/setup-runner.js +97 -0
  45. package/ci/test-optimization-validation/static-diagnosis.js +159 -0
  46. package/ci/test-optimization-validation/test-output.js +129 -0
  47. package/ci/test-optimization-validation-manifest.schema.json +1 -0
  48. package/ci/validate-test-optimization.js +3 -0
  49. package/ext/exporters.js +1 -0
  50. package/index.d.ts +121 -8
  51. package/index.electron.js +3 -0
  52. package/init.js +18 -0
  53. package/initialize.mjs +1 -1
  54. package/loader-hook.mjs +28 -18
  55. package/openfeature.d.ts +1 -0
  56. package/openfeature.js +4 -0
  57. package/package.json +15 -8
  58. package/packages/datadog-instrumentations/src/ai.js +37 -26
  59. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  60. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  61. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  62. package/packages/datadog-instrumentations/src/cucumber.js +24 -10
  63. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  64. package/packages/datadog-instrumentations/src/express.js +20 -2
  65. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  66. package/packages/datadog-instrumentations/src/grpc/server.js +18 -0
  67. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  68. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  69. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  70. package/packages/datadog-instrumentations/src/helpers/router-helper.js +8 -8
  71. package/packages/datadog-instrumentations/src/http2/server.js +143 -17
  72. package/packages/datadog-instrumentations/src/jest.js +36 -4
  73. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  74. package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
  75. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  76. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  77. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  78. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  79. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  80. package/packages/datadog-instrumentations/src/next.js +24 -0
  81. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  82. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  83. package/packages/datadog-instrumentations/src/pg.js +2 -2
  84. package/packages/datadog-instrumentations/src/playwright.js +159 -22
  85. package/packages/datadog-instrumentations/src/process.js +3 -0
  86. package/packages/datadog-instrumentations/src/router.js +191 -106
  87. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  88. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
  89. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  90. package/packages/datadog-instrumentations/src/ws.js +2 -1
  91. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  92. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  93. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +41 -16
  94. package/packages/datadog-plugin-aws-sdk/src/services/sns.js +21 -21
  95. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +14 -8
  96. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -24
  97. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  98. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +24 -62
  99. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  100. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  101. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  102. package/packages/datadog-plugin-graphql/src/execute.js +177 -59
  103. package/packages/datadog-plugin-graphql/src/parse.js +22 -1
  104. package/packages/datadog-plugin-graphql/src/request.js +32 -1
  105. package/packages/datadog-plugin-graphql/src/utils.js +42 -0
  106. package/packages/datadog-plugin-graphql/src/validate.js +15 -3
  107. package/packages/datadog-plugin-http2/src/client.js +1 -1
  108. package/packages/datadog-plugin-http2/src/server.js +38 -6
  109. package/packages/datadog-plugin-jest/src/util.js +21 -6
  110. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  111. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  112. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  113. package/packages/datadog-plugin-playwright/src/index.js +177 -64
  114. package/packages/datadog-plugin-router/src/index.js +11 -2
  115. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  116. package/packages/datadog-plugin-vitest/src/index.js +4 -2
  117. package/packages/datadog-plugin-ws/src/close.js +2 -1
  118. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  119. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  120. package/packages/datadog-plugin-ws/src/server.js +2 -1
  121. package/packages/dd-trace/index.electron.js +3 -0
  122. package/packages/dd-trace/index.js +2 -37
  123. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  124. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  125. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  126. package/packages/dd-trace/src/azure_metadata.js +2 -1
  127. package/packages/dd-trace/src/bootstrap.js +39 -0
  128. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  129. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  130. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  131. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  132. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  133. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  134. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  135. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +55 -5
  136. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +22 -6
  137. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +5 -0
  138. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +62 -7
  139. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -2
  140. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  141. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  142. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  143. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  144. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  145. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +173 -9
  146. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  147. package/packages/dd-trace/src/config/generated-config-types.d.ts +20 -0
  148. package/packages/dd-trace/src/config/helper.js +1 -0
  149. package/packages/dd-trace/src/config/index.js +10 -0
  150. package/packages/dd-trace/src/config/supported-configurations.json +83 -0
  151. package/packages/dd-trace/src/constants.js +7 -0
  152. package/packages/dd-trace/src/datastreams/pathway.js +6 -4
  153. package/packages/dd-trace/src/exporter.js +2 -0
  154. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  155. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  156. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  157. package/packages/dd-trace/src/feature-registry.js +29 -0
  158. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -0
  159. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  160. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  161. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  162. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  163. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  164. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  165. package/packages/dd-trace/src/llmobs/index.js +10 -2
  166. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  167. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  168. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  169. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  170. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  171. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  172. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  173. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  174. package/packages/dd-trace/src/llmobs/tagger.js +79 -7
  175. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  176. package/packages/dd-trace/src/llmobs/util.js +32 -0
  177. package/packages/dd-trace/src/noop/proxy.js +5 -4
  178. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  179. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  180. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  181. package/packages/dd-trace/src/openfeature/index.js +0 -2
  182. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  183. package/packages/dd-trace/src/openfeature/register.js +35 -0
  184. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  185. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  186. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  187. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  188. package/packages/dd-trace/src/opentracing/propagation/log.js +11 -2
  189. package/packages/dd-trace/src/opentracing/propagation/text_map.js +80 -14
  190. package/packages/dd-trace/src/opentracing/propagation/text_map_dsm.js +10 -1
  191. package/packages/dd-trace/src/opentracing/tracer.js +7 -1
  192. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  193. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  194. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  195. package/packages/dd-trace/src/plugins/util/test.js +65 -8
  196. package/packages/dd-trace/src/plugins/util/web.js +12 -0
  197. package/packages/dd-trace/src/proxy.js +99 -10
  198. package/packages/dd-trace/src/span_processor.js +5 -0
  199. package/packages/dd-trace/src/standalone/index.js +0 -36
  200. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  201. package/packages/dd-trace/src/util.js +26 -0
  202. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  203. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  204. package/vendor/dist/protobufjs/index.js +1 -1
  205. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  206. package/vendor/dist/shell-quote/index.js +1 -1
  207. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -2,7 +2,6 @@
2
2
  const { DsmPathwayCodec, getHeadersSize } = require('../../../dd-trace/src/datastreams')
3
3
  const log = require('../../../dd-trace/src/log')
4
4
  const BaseAwsSdkPlugin = require('../base')
5
- const { isEmpty } = require('../util')
6
5
 
7
6
  class Sns extends BaseAwsSdkPlugin {
8
7
  static id = 'sns'
@@ -71,6 +70,12 @@ class Sns extends BaseAwsSdkPlugin {
71
70
  }
72
71
  }
73
72
 
73
+ /**
74
+ * @param {import('../../../dd-trace/src/opentracing/span') | null} span
75
+ * @param {{ Message?: string, MessageAttributes?: Record<string, object> }} params
76
+ * @param {string | undefined} topicArn
77
+ * @param {boolean} injectTraceContext
78
+ */
74
79
  injectToMessage (span, params, topicArn, injectTraceContext) {
75
80
  if (!params.MessageAttributes) {
76
81
  params.MessageAttributes = {}
@@ -79,37 +84,32 @@ class Sns extends BaseAwsSdkPlugin {
79
84
  return
80
85
  }
81
86
 
82
- const ddInfo = {}
87
+ let ddInfo
83
88
  // for now, we only want to inject to the first message, this may change for batches in the future
84
89
  if (injectTraceContext) {
85
- this.tracer.inject(span, 'text_map', ddInfo)
86
- // add ddInfo before checking DSM so we can include DD attributes in payload size
87
- params.MessageAttributes._datadog = {
88
- DataType: 'Binary',
89
- BinaryValue: ddInfo,
90
- }
90
+ ddInfo = this.tracer.inject(span, 'text_map')
91
91
  }
92
92
 
93
93
  if (this.config.dsmEnabled) {
94
- if (!params.MessageAttributes._datadog) {
95
- params.MessageAttributes._datadog = {
96
- DataType: 'Binary',
97
- BinaryValue: ddInfo,
98
- }
94
+ // Add the placeholder before the checkpoint so its payload size includes DD attributes.
95
+ params.MessageAttributes._datadog = {
96
+ DataType: 'Binary',
97
+ BinaryValue: ddInfo ?? {},
99
98
  }
100
99
 
101
100
  const dataStreamsContext = this.setDSMCheckpoint(span, params, topicArn)
102
- DsmPathwayCodec.encode(dataStreamsContext, ddInfo)
101
+ ddInfo = DsmPathwayCodec.encode(dataStreamsContext, ddInfo) ?? ddInfo
103
102
  }
104
103
 
105
- if (isEmpty(ddInfo)) {
106
- if (params.MessageAttributes._datadog) {
107
- // let's avoid adding any additional information to payload if we failed to inject
108
- delete params.MessageAttributes._datadog
109
- }
110
- } else {
104
+ if (ddInfo) {
111
105
  // BINARY types are automatically base64 encoded
112
- params.MessageAttributes._datadog.BinaryValue = Buffer.from(JSON.stringify(ddInfo))
106
+ params.MessageAttributes._datadog = {
107
+ DataType: 'Binary',
108
+ BinaryValue: Buffer.from(JSON.stringify(ddInfo)),
109
+ }
110
+ } else if (params.MessageAttributes._datadog) {
111
+ // let's avoid adding any additional information to payload if we failed to inject
112
+ delete params.MessageAttributes._datadog
113
113
  }
114
114
  }
115
115
 
@@ -3,7 +3,7 @@
3
3
  const log = require('../../../dd-trace/src/log')
4
4
  const BaseAwsSdkPlugin = require('../base')
5
5
  const { DsmPathwayCodec, getHeadersSize } = require('../../../dd-trace/src/datastreams')
6
- const { extractQueueMetadata, isEmpty } = require('../util')
6
+ const { extractQueueMetadata } = require('../util')
7
7
 
8
8
  /**
9
9
  * @typedef {{
@@ -322,6 +322,12 @@ class Sqs extends BaseAwsSdkPlugin {
322
322
  }
323
323
  }
324
324
 
325
+ /**
326
+ * @param {import('../../../dd-trace/src/opentracing/span') | null} span
327
+ * @param {{ MessageBody?: string, MessageAttributes?: Record<string, object> } | undefined} params
328
+ * @param {string} queueUrl
329
+ * @param {boolean} injectTraceContext
330
+ */
325
331
  injectToMessage (span, params, queueUrl, injectTraceContext) {
326
332
  if (!params) {
327
333
  params = {}
@@ -333,10 +339,10 @@ class Sqs extends BaseAwsSdkPlugin {
333
339
  return
334
340
  }
335
341
 
336
- const ddInfo = {}
342
+ let ddInfo
337
343
  // For now we only inject to the first message; batches may change later.
338
344
  if (injectTraceContext) {
339
- this.tracer.inject(span, 'text_map', ddInfo)
345
+ ddInfo = this.tracer.inject(span, 'text_map')
340
346
  }
341
347
 
342
348
  if (this.config.dsmEnabled) {
@@ -344,19 +350,19 @@ class Sqs extends BaseAwsSdkPlugin {
344
350
  // matches the on-wire payload, then update with the encoded context.
345
351
  params.MessageAttributes._datadog = {
346
352
  DataType: 'String',
347
- StringValue: JSON.stringify(ddInfo),
353
+ StringValue: JSON.stringify(ddInfo ?? {}),
348
354
  }
349
355
  const dataStreamsContext = this.setDSMCheckpoint(span, params, queueUrl)
350
- if (dataStreamsContext) {
351
- DsmPathwayCodec.encode(dataStreamsContext, ddInfo)
356
+ ddInfo = DsmPathwayCodec.encode(dataStreamsContext, ddInfo) ?? ddInfo
357
+ if (ddInfo) {
352
358
  params.MessageAttributes._datadog.StringValue = JSON.stringify(ddInfo)
353
- } else if (isEmpty(ddInfo)) {
359
+ } else {
354
360
  delete params.MessageAttributes._datadog
355
361
  }
356
362
  return
357
363
  }
358
364
 
359
- if (isEmpty(ddInfo)) return
365
+ if (!ddInfo) return
360
366
 
361
367
  params.MessageAttributes._datadog = {
362
368
  DataType: 'String',
@@ -120,8 +120,8 @@ const extractQueueMetadata = queueURL => {
120
120
 
121
121
  if (parts.length < minParts) return null
122
122
 
123
- const accountId = parts[parts.length - 2]
124
- const queueName = parts[parts.length - 1]
123
+ const accountId = parts.at(-2)
124
+ const queueName = parts.at(-1)
125
125
  const host = hasScheme ? parts[1] : parts[0]
126
126
 
127
127
  let region = 'us-east-1' // Default region if not found in URL
@@ -143,31 +143,9 @@ const extractQueueMetadata = queueURL => {
143
143
  return { queueName, arn }
144
144
  }
145
145
 
146
- /**
147
- * Returns true when `obj` has no own enumerable properties. The
148
- * `for-in` loop with an early return is the only allocation-free shape
149
- * for this check; benchmarks pin it as 1.3-1.4x faster than
150
- * `Object.keys(obj).length === 0` across small / medium / large
151
- * objects, and this is the hot path on every AWS messaging send.
152
- *
153
- * Callers in this package only pass plain objects they construct
154
- * locally, so prototype-enumerable keys are not a concern here. Do not
155
- * reuse this helper on caller-supplied objects without revisiting that
156
- * assumption.
157
- *
158
- * @param {object} obj
159
- * @returns {boolean}
160
- */
161
- const isEmpty = obj => {
162
- // eslint-disable-next-line no-unreachable-loop
163
- for (const _ in obj) return false
164
- return true
165
- }
166
-
167
146
  module.exports = {
168
147
  generatePointerHash,
169
148
  encodeValue,
170
149
  extractPrimaryKeys,
171
150
  extractQueueMetadata,
172
- isEmpty,
173
151
  }
@@ -438,7 +438,7 @@ class CucumberPlugin extends CiPlugin {
438
438
  const isModified = isModifiedTest(
439
439
  testScenarioPath,
440
440
  scenario.location.line,
441
- scenario.steps[scenario.steps.length - 1].location.line,
441
+ scenario.steps.at(-1).location.line,
442
442
  modifiedFiles,
443
443
  'cucumber'
444
444
  )
@@ -2,15 +2,15 @@
2
2
 
3
3
  // Capture real timers at module load, before any test can install fake timers.
4
4
  const { performance } = require('perf_hooks')
5
- const { statSync } = require('node:fs')
6
5
  const { basename } = require('node:path')
7
6
  const dateNow = Date.now
8
7
 
9
8
  const { createCoverageMap } = require('../../../vendor/dist/istanbul-lib-coverage')
10
9
  const satisfies = require('../../../vendor/dist/semifies')
10
+ const { RUM_TEST_EXECUTION_ID_COOKIE_NAME } = require('../../dd-trace/src/ci-visibility/rum')
11
11
  const {
12
12
  TEST_STATUS,
13
- TEST_IS_RUM_ACTIVE,
13
+ setRumTestTags,
14
14
  TEST_CODE_OWNERS,
15
15
  getTestEnvironmentMetadata,
16
16
  getTestLevelsMetadataTags,
@@ -79,14 +79,19 @@ const {
79
79
  getMaxEfdRetryCount,
80
80
  getPullRequestBaseBranch,
81
81
  TEST_FINAL_STATUS,
82
- TEST_FAILURE_SCREENSHOT_UPLOADED,
83
- TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR,
84
82
  getTestOptimizationRequestResults,
85
83
  } = require('../../dd-trace/src/plugins/util/test')
86
84
  const { isMarkedAsUnskippable } = require('../../datadog-plugin-jest/src/util')
87
85
  const { ORIGIN_KEY, COMPONENT } = require('../../dd-trace/src/constants')
88
86
  const { RESOURCE_NAME } = require('../../../ext/tags')
89
87
  const getConfig = require('../../dd-trace/src/config')
88
+ const {
89
+ SCREENSHOT_UPLOAD_RESULT_ERROR,
90
+ SCREENSHOT_UPLOAD_RESULT_UPLOADED,
91
+ getScreenshotCapturedAtMs,
92
+ getScreenshotUploadResult,
93
+ setScreenshotUploadTags,
94
+ } = require('../../dd-trace/src/ci-visibility/test-screenshot')
90
95
  const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
91
96
  const log = require('../../dd-trace/src/log')
92
97
 
@@ -141,8 +146,6 @@ const CYPRESS_STATUS_TO_TEST_STATUS = {
141
146
  }
142
147
 
143
148
  const SCREENSHOT_ATTEMPT_RE = /\(attempt \d+\)/
144
- const SCREENSHOT_UPLOAD_RESULT_UPLOADED = 'uploaded'
145
- const SCREENSHOT_UPLOAD_RESULT_ERROR = 'error'
146
149
 
147
150
  function getScreenshotFilePath (screenshot) {
148
151
  return typeof screenshot === 'string' ? screenshot : screenshot?.path
@@ -155,31 +158,6 @@ function isFailureScreenshotByMetadata (screenshot, screenshotFilePath) {
155
158
  return screenshotFilePath.includes('(failed)')
156
159
  }
157
160
 
158
- /**
159
- * Resolves a screenshot's capture time (epoch ms) for the media upload. Cypress
160
- * screenshot objects carry an ISO `takenAt`; falls back to the file's mtime, then
161
- * to the current time. Stamped once here and reused on retry via the idempotency
162
- * key, so the stored object is overwritten rather than duplicated.
163
- *
164
- * @param {object|string} screenshot - Cypress screenshot object or path
165
- * @param {string} filePath - Resolved screenshot file path
166
- * @returns {number} Capture time in epoch milliseconds
167
- */
168
- function getScreenshotCapturedAtMs (screenshot, filePath) {
169
- const takenAt = screenshot !== null && typeof screenshot === 'object' ? screenshot.takenAt : undefined
170
- if (takenAt) {
171
- const parsedMs = new Date(takenAt).getTime()
172
- if (Number.isInteger(parsedMs) && parsedMs > 0) {
173
- return parsedMs
174
- }
175
- }
176
- try {
177
- return Math.floor(statSync(filePath).mtimeMs)
178
- } catch {
179
- return dateNow()
180
- }
181
- }
182
-
183
161
  function isFailureScreenshotForUpload (screenshot) {
184
162
  const screenshotFilePath = getScreenshotFilePath(screenshot)
185
163
  if (!screenshotFilePath) {
@@ -240,27 +218,6 @@ function getTestScreenshots (cypressTest, attemptIndex, specScreenshots) {
240
218
  return specScreenshots.filter(screenshot => isScreenshotForTestAttempt(screenshot, titleParts, attemptIndex))
241
219
  }
242
220
 
243
- function getScreenshotUploadResult (uploadResults) {
244
- let hasUploaded = false
245
- for (const uploadResult of uploadResults) {
246
- if (uploadResult === SCREENSHOT_UPLOAD_RESULT_ERROR) {
247
- return SCREENSHOT_UPLOAD_RESULT_ERROR
248
- }
249
- if (uploadResult === SCREENSHOT_UPLOAD_RESULT_UPLOADED) {
250
- hasUploaded = true
251
- }
252
- }
253
- return hasUploaded ? SCREENSHOT_UPLOAD_RESULT_UPLOADED : undefined
254
- }
255
-
256
- function setScreenshotUploadTags (testSpan, uploadResult) {
257
- if (uploadResult === SCREENSHOT_UPLOAD_RESULT_ERROR) {
258
- testSpan.setTag(TEST_FAILURE_SCREENSHOT_UPLOAD_ERROR, 'true')
259
- } else if (uploadResult === SCREENSHOT_UPLOAD_RESULT_UPLOADED) {
260
- testSpan.setTag(TEST_FAILURE_SCREENSHOT_UPLOADED, 'true')
261
- }
262
- }
263
-
264
221
  function getSessionStatus (summary) {
265
222
  if (summary.totalFailed !== undefined && summary.totalFailed > 0) {
266
223
  return 'fail'
@@ -901,6 +858,12 @@ class CypressPlugin {
901
858
  if (isFlakyTestRetriesEnabled && this.isTestIsolationEnabled) {
902
859
  this.isFlakyTestRetriesEnabled = true
903
860
  this.flakyTestRetriesCount = flakyTestRetriesCount ?? 0
861
+ if (typeof this.cypressConfig.retries === 'number') {
862
+ this.cypressConfig.retries = {
863
+ openMode: this.cypressConfig.retries,
864
+ runMode: this.cypressConfig.retries,
865
+ }
866
+ }
904
867
  this.cypressConfig.retries.runMode = this.flakyTestRetriesCount
905
868
  } else {
906
869
  this.flakyTestRetriesCount = 0
@@ -1444,13 +1407,13 @@ class CypressPlugin {
1444
1407
  // and create a skipped test span for each of them
1445
1408
  for (const { title } of cypressTests) {
1446
1409
  const cypressTestName = title.join(' ')
1447
- const isTestFinished = finishedTests.find(({ testName }) => cypressTestName === testName)
1410
+ const isTestFinished = finishedTests.some(({ testName }) => cypressTestName === testName)
1448
1411
 
1449
1412
  if (isTestFinished) {
1450
1413
  continue
1451
1414
  }
1452
1415
 
1453
- const isSkippedByItr = this.testsToSkip.find(test =>
1416
+ const isSkippedByItr = this.testsToSkip.some(test =>
1454
1417
  cypressTestName === test.name && spec.relative === test.suite
1455
1418
  )
1456
1419
  const testSourceFile = spec.absolute && this.repositoryRoot
@@ -1721,6 +1684,7 @@ class CypressPlugin {
1721
1684
  repositoryRoot: this.repositoryRoot,
1722
1685
  isTestIsolationEnabled: this.isTestIsolationEnabled,
1723
1686
  rumFlushWaitMillis: this.rumFlushWaitMillis,
1687
+ rumTestExecutionIdCookieName: RUM_TEST_EXECUTION_ID_COOKIE_NAME,
1724
1688
  }
1725
1689
 
1726
1690
  this.testSuiteSpan ||= this.getTestSuiteSpan({ testSuite, testSuiteAbsolutePath })
@@ -1835,21 +1799,19 @@ class CypressPlugin {
1835
1799
  }
1836
1800
  this.activeTestSpan.setTag(TEST_STATUS, testStatus)
1837
1801
 
1838
- // Save the test status to know if it has passed all retries
1839
- if (this.testStatuses[testName]) {
1840
- this.testStatuses[testName].push(testStatus)
1802
+ const testIdentifier = `${testSuite}\0${testName}`
1803
+ let testStatuses = this.testStatuses[testIdentifier]
1804
+ if (testStatuses) {
1805
+ testStatuses.push(testStatus)
1841
1806
  } else {
1842
- this.testStatuses[testName] = [testStatus]
1807
+ testStatuses = this.testStatuses[testIdentifier] = [testStatus]
1843
1808
  }
1844
- const testStatuses = this.testStatuses[testName]
1845
1809
  const activeSpanTags = this.activeTestSpan.context().getTags()
1846
1810
 
1847
1811
  if (error) {
1848
1812
  this.activeTestSpan.setTag('error', error)
1849
1813
  }
1850
- if (isRUMActive) {
1851
- this.activeTestSpan.setTag(TEST_IS_RUM_ACTIVE, 'true')
1852
- }
1814
+ setRumTestTags(this.activeTestSpan, isRUMActive)
1853
1815
  // Source-line resolution strategy:
1854
1816
  // 1. If plain JS and no source map, trust invocationDetails.line directly.
1855
1817
  // 2. Otherwise, try invocationDetails.stack line mapped through source map.
@@ -80,7 +80,12 @@ class CypressPlugin extends Plugin {
80
80
  // Already initialized by manual plugin call — just chain user handlers.
81
81
  // Pass the plugin's afterScreenshot so chaining a user handler doesn't drop the upload
82
82
  // (the chained registration replaces the one plugin.js set, so it must include it).
83
- for (const h of userAfterSpecHandlers) on('after:spec', h)
83
+ if (userAfterSpecHandlers.length > 0) {
84
+ on('after:spec', (spec, results) => userAfterSpecHandlers.reduce(
85
+ (chain, handler) => chain.then(() => handler(spec, results)),
86
+ Promise.resolve()
87
+ ))
88
+ }
84
89
  registerAfterScreenshot(datadogAfterScreenshotHandler)
85
90
  registerAfterRunWithCleanup()
86
91
  payload.registered = true
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
- const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
4
3
  let rumFlushWaitMillis = 500
4
+ let rumTestExecutionIdCookieName
5
5
 
6
6
  let isEarlyFlakeDetectionEnabled = false
7
7
  let isKnownTestsEnabled = false
@@ -129,6 +129,89 @@ function runBeforeEachTask (test) {
129
129
  })
130
130
  }
131
131
 
132
+ /**
133
+ * @param {string} message
134
+ * @param {unknown} error
135
+ * @returns {false}
136
+ */
137
+ function logRumCorrelationCookieError (message, error) {
138
+ try {
139
+ Cypress.log({
140
+ name: 'dd-trace',
141
+ message,
142
+ consoleProps: () => ({ Error: error }),
143
+ })
144
+ } catch (loggingError) {
145
+ // eslint-disable-next-line no-console
146
+ console.error(message, error, loggingError)
147
+ }
148
+ return false
149
+ }
150
+
151
+ /**
152
+ * @param {unknown} error
153
+ * @returns {false}
154
+ */
155
+ function handleRumCorrelationCookieError (error) {
156
+ return logRumCorrelationCookieError('Could not set the RUM correlation cookie', error)
157
+ }
158
+
159
+ /**
160
+ * @param {unknown} error
161
+ * @returns {false}
162
+ */
163
+ function handleRumCorrelationCookieCleanupError (error) {
164
+ return logRumCorrelationCookieError('Could not clear the previous RUM correlation cookie', error)
165
+ }
166
+
167
+ /**
168
+ * @param {string} traceId
169
+ * @returns {Promise<boolean>}
170
+ */
171
+ function setRumCorrelationCookie (traceId) {
172
+ if (typeof cy.now !== 'function') {
173
+ return Cypress.Promise.resolve(handleRumCorrelationCookieError(new Error('Cypress cy.now is not available')))
174
+ }
175
+
176
+ let clearCookiePromise = Cypress.Promise.resolve()
177
+ if (!isTestIsolationEnabled) {
178
+ clearCookiePromise = Cypress.Promise.try(() => {
179
+ return cy.now('clearCookie', rumTestExecutionIdCookieName, { log: false })
180
+ }).then(undefined, handleRumCorrelationCookieCleanupError)
181
+ }
182
+
183
+ return clearCookiePromise.then(() => {
184
+ return Cypress.Promise.try(() => {
185
+ return cy.now('setCookie', rumTestExecutionIdCookieName, traceId, { log: false })
186
+ })
187
+ }).then(() => true, handleRumCorrelationCookieError)
188
+ }
189
+
190
+ /**
191
+ * @param {boolean} isCookieSet
192
+ * @returns {void}
193
+ */
194
+ function restartRumSession (isCookieSet) {
195
+ if (!isCookieSet || isTestIsolationEnabled || !originalWindow) {
196
+ return
197
+ }
198
+
199
+ const rum = safeGetRum(originalWindow)
200
+ if (rum) {
201
+ try {
202
+ const evt = new originalWindow.MouseEvent('click', { bubbles: true, cancelable: true })
203
+ // The browser-sdk addEventListener wrapper filters out untrusted synthetic events
204
+ // unless __ddIsTrusted is set. Set it so the click triggers expandOrRenewSession().
205
+ // See: https://github.com/DataDog/browser-sdk/blob/v6.27.1/packages/core/src/browser/addEventListener.ts#L119
206
+ Object.defineProperty(evt, '__ddIsTrusted', { value: true })
207
+ originalWindow.dispatchEvent(evt)
208
+ } catch {}
209
+ if (rum.startView) {
210
+ rum.startView()
211
+ }
212
+ }
213
+ }
214
+
132
215
  // Catch test failures for quarantined tests and suppress them
133
216
  // By not re-throwing the error, Cypress marks the test as passed
134
217
  // This allows quarantined tests to run but not affect the exit code
@@ -150,6 +233,8 @@ Cypress.on('fail', (err, runnable) => {
150
233
  // If command:end fired for all commands (none in-flight) but the last command
151
234
  // has no error, it means command:end fired before the error was attached to it.
152
235
  if (!hadInFlightCommands && currentTestCommands.length > 0) {
236
+ // We have to support very old cypress versions in v5
237
+ // eslint-disable-next-line unicorn/prefer-at
153
238
  const lastCommand = currentTestCommands[currentTestCommands.length - 1]
154
239
  if (!lastCommand.error) {
155
240
  lastCommand.error = { message: err.message, stack: err.stack, name: err.name }
@@ -316,36 +401,16 @@ beforeEach(function () {
316
401
  if (shouldDiscard) {
317
402
  this.currentTest._ddShouldDiscard = true
318
403
  }
404
+ let rumCookiePromise
319
405
  if (traceId) {
320
- cy.setCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME, traceId).then(() => {
321
- // When testIsolation:false, the page is not reset between tests, so the RUM session
322
- // stopped in afterEach must be explicitly restarted so events in this test are
323
- // associated with the new testExecutionId.
324
- //
325
- // After stopSession(), the RUM SDK creates a new session upon a user interaction
326
- // (click, scroll, keydown, or touchstart). We dispatch a synthetic click on the window
327
- // to trigger session renewal, then call startView() to establish a view boundary.
328
- if (!isTestIsolationEnabled && originalWindow) {
329
- const rum = safeGetRum(originalWindow)
330
- if (rum) {
331
- try {
332
- const evt = new originalWindow.MouseEvent('click', { bubbles: true, cancelable: true })
333
- // The browser-sdk addEventListener wrapper filters out untrusted synthetic events
334
- // unless __ddIsTrusted is set. Set it so the click triggers expandOrRenewSession().
335
- // See: https://github.com/DataDog/browser-sdk/blob/v6.27.1/packages/core/src/browser/addEventListener.ts#L119
336
- Object.defineProperty(evt, '__ddIsTrusted', { value: true })
337
- originalWindow.dispatchEvent(evt)
338
- } catch {}
339
- if (rum.startView) {
340
- rum.startView()
341
- }
342
- }
343
- }
344
- })
406
+ rumCookiePromise = setRumCorrelationCookie(traceId)
345
407
  }
346
408
  if (shouldSkip) {
347
409
  this.skip()
348
410
  }
411
+ if (rumCookiePromise) {
412
+ return rumCookiePromise.then(restartRumSession)
413
+ }
349
414
  }).then(() => {
350
415
  // Clear any commands accumulated during DD-owned setup (e.g. setCookie, RUM restart)
351
416
  // so they are not reported as user test steps.
@@ -370,6 +435,7 @@ before(function () {
370
435
  isImpactedTestsEnabled = suiteConfig.isImpactedTestsEnabled
371
436
  isModifiedTest = suiteConfig.isModifiedTest
372
437
  isTestIsolationEnabled = suiteConfig.isTestIsolationEnabled
438
+ rumTestExecutionIdCookieName = suiteConfig.rumTestExecutionIdCookieName
373
439
  if (Number.isFinite(suiteConfig.rumFlushWaitMillis)) {
374
440
  rumFlushWaitMillis = suiteConfig.rumFlushWaitMillis
375
441
  }
@@ -46,7 +46,7 @@ class ElectronRendererReceivePlugin extends ConsumerPlugin {
46
46
 
47
47
  if (channel?.startsWith('datadog:')) return
48
48
 
49
- const childOf = this._tracer.extract('text_map', args[args.length - 1])
49
+ const childOf = this._tracer.extract('text_map', args.at(-1))
50
50
 
51
51
  if (childOf) {
52
52
  args.pop()