dd-trace 6.4.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 (178) 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/init.js +18 -0
  52. package/initialize.mjs +1 -1
  53. package/loader-hook.mjs +28 -18
  54. package/openfeature.d.ts +1 -0
  55. package/openfeature.js +4 -0
  56. package/package.json +10 -7
  57. package/packages/datadog-instrumentations/src/ai.js +8 -2
  58. package/packages/datadog-instrumentations/src/child_process.js +1 -1
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +1 -1
  60. package/packages/datadog-instrumentations/src/cucumber-worker-threads.js +9 -3
  61. package/packages/datadog-instrumentations/src/cucumber.js +20 -7
  62. package/packages/datadog-instrumentations/src/cypress-config.js +398 -52
  63. package/packages/datadog-instrumentations/src/fastify.js +7 -11
  64. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  65. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  66. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  67. package/packages/datadog-instrumentations/src/jest.js +26 -0
  68. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  69. package/packages/datadog-instrumentations/src/mocha/main.js +0 -2
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  71. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  72. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  73. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  74. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  75. package/packages/datadog-instrumentations/src/next.js +24 -0
  76. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  77. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  78. package/packages/datadog-instrumentations/src/pg.js +2 -2
  79. package/packages/datadog-instrumentations/src/playwright.js +159 -22
  80. package/packages/datadog-instrumentations/src/process.js +3 -0
  81. package/packages/datadog-instrumentations/src/router.js +18 -8
  82. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  83. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +2 -2
  84. package/packages/datadog-instrumentations/src/vitest-main.js +0 -2
  85. package/packages/datadog-instrumentations/src/ws.js +2 -1
  86. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  87. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  88. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  89. package/packages/datadog-plugin-cucumber/src/index.js +1 -1
  90. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  91. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  92. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  93. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  94. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  95. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  96. package/packages/datadog-plugin-http2/src/client.js +1 -1
  97. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  98. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  99. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  100. package/packages/datadog-plugin-playwright/src/index.js +177 -64
  101. package/packages/datadog-plugin-router/src/index.js +11 -2
  102. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  103. package/packages/datadog-plugin-ws/src/close.js +2 -1
  104. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  105. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  106. package/packages/datadog-plugin-ws/src/server.js +2 -1
  107. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  108. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  109. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  110. package/packages/dd-trace/src/azure_metadata.js +2 -1
  111. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  112. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  113. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  114. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  115. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  116. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  117. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  119. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  120. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  121. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  122. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  123. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  124. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  127. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  128. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  129. package/packages/dd-trace/src/config/helper.js +1 -0
  130. package/packages/dd-trace/src/config/index.js +10 -0
  131. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  132. package/packages/dd-trace/src/exporter.js +2 -0
  133. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  134. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  135. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  136. package/packages/dd-trace/src/feature-registry.js +10 -3
  137. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  138. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  139. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  140. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  141. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  142. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  143. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  144. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  145. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  146. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  147. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  148. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  149. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  150. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  151. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  152. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  153. package/packages/dd-trace/src/llmobs/util.js +32 -0
  154. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  155. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  156. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  157. package/packages/dd-trace/src/openfeature/index.js +0 -2
  158. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  159. package/packages/dd-trace/src/openfeature/register.js +16 -27
  160. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  161. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  162. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  163. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  164. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  165. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  166. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  167. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  168. package/packages/dd-trace/src/proxy.js +84 -7
  169. package/packages/dd-trace/src/span_processor.js +5 -0
  170. package/packages/dd-trace/src/standalone/index.js +0 -14
  171. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  172. package/packages/dd-trace/src/util.js +26 -0
  173. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  174. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  175. package/vendor/dist/protobufjs/index.js +1 -1
  176. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  177. package/vendor/dist/shell-quote/index.js +1 -1
  178. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -86,26 +86,36 @@ function createLayerDispatchWrappers (name) {
86
86
  const finishChannel = channel(`apm:${name}:middleware:finish`)
87
87
  const errorChannel = channel(`apm:${name}:middleware:error`)
88
88
  const nextChannel = channel(`apm:${name}:middleware:next`)
89
+ const repeatChannel = channel(`apm:${name}:middleware:repeat`)
89
90
  // Bound per name so express and a bare router keep independent guards.
90
91
  const publishError = createErrorPublisher(errorChannel)
91
92
 
92
- function wrapNext (req, originalNext) {
93
+ /**
94
+ * @param {import('node:http').IncomingMessage} req
95
+ * @param {string | undefined} layerName
96
+ * @param {(error?: unknown) => void} originalNext
97
+ */
98
+ function wrapNext (req, layerName, originalNext) {
93
99
  // Per layer dispatch, N per request. Named `next`/arity-1 mirrors the
94
100
  // router continuation so wrapCallback skips its name/length rewrite.
95
- let published = false
101
+ let calls = 0
96
102
  return shimmer.wrapCallback(originalNext, original => function next (error) {
97
103
  // A handler that calls `next()` and then rejects (`next(); await bg()`)
98
104
  // makes the host call this continuation twice. Publish once so the second
99
105
  // pass cannot tag the already-finished span's parent with a late error.
100
- if (!published) {
101
- published = true
102
-
106
+ calls++
107
+ if (calls === 1) {
103
108
  if (error && error !== 'route' && error !== 'router') {
104
109
  publishError({ req, error })
105
110
  }
106
111
 
107
112
  nextChannel.publish({ req })
108
113
  finishChannel.publish({ req })
114
+ } else if (calls === 2) {
115
+ // Surface the repeat as a diagnostic on the still-live request span. The
116
+ // host cannot tell a legitimate `next(); await bg()` from a buggy double
117
+ // `next()`, so this only records that it happened, not that it is wrong.
118
+ repeatChannel.publish({ req, name: layerName, error })
109
119
  }
110
120
 
111
121
  original.apply(this, arguments)
@@ -125,7 +135,7 @@ function createLayerDispatchWrappers (name) {
125
135
  const meta = getLayerMeta(this)
126
136
  if (meta === undefined || this.handle.length > 3) return originalRequest.call(this, req, res, next)
127
137
 
128
- const wrappedNext = typeof next === 'function' ? wrapNext(req, next) : next
138
+ const wrappedNext = typeof next === 'function' ? wrapNext(req, meta.name, next) : next
129
139
  enterChannel.publish({ name: meta.name, req, route: resolveLayerRoute(meta, this), layer: this })
130
140
 
131
141
  try {
@@ -143,7 +153,7 @@ function createLayerDispatchWrappers (name) {
143
153
  const meta = getLayerMeta(this)
144
154
  if (meta === undefined || this.handle.length !== 4) return originalError.call(this, error, req, res, next)
145
155
 
146
- const wrappedNext = typeof next === 'function' ? wrapNext(req, next) : next
156
+ const wrappedNext = typeof next === 'function' ? wrapNext(req, meta.name, next) : next
147
157
  enterChannel.publish({ name: meta.name, req, route: resolveLayerRoute(meta, this), layer: this })
148
158
 
149
159
  try {
@@ -170,7 +180,7 @@ function createLayerDispatchWrappers (name) {
170
180
  const isErrorHandler = original.length === 4
171
181
  const req = args[isErrorHandler ? 1 : 0]
172
182
  const nextIndex = isErrorHandler ? 3 : 2
173
- if (typeof args[nextIndex] === 'function') args[nextIndex] = wrapNext(req, args[nextIndex])
183
+ if (typeof args[nextIndex] === 'function') args[nextIndex] = wrapNext(req, meta.name, args[nextIndex])
174
184
 
175
185
  enterChannel.publish({ name: meta.name, req, route: resolveLayerRoute(meta, layer), layer })
176
186
 
@@ -5,6 +5,10 @@ const realSetTimeout = setTimeout
5
5
 
6
6
  const shimmer = require('../../datadog-shimmer')
7
7
  const { getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
8
+ const {
9
+ RUM_TEST_EXECUTION_ID_COOKIE_NAME: DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME,
10
+ } = require('../../dd-trace/src/ci-visibility/rum')
11
+ const log = require('../../dd-trace/src/log')
8
12
  const { addHook, channel } = require('./helpers/instrument')
9
13
 
10
14
  const ciSeleniumDriverGetStartCh = channel('ci:selenium:driver:get')
@@ -20,7 +24,6 @@ if (window.DD_RUM && window.DD_RUM.stopSession) {
20
24
  const IS_RUM_ACTIVE_SCRIPT = 'return !!window.DD_RUM'
21
25
 
22
26
  const DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
23
- const DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME = 'datadog-ci-visibility-test-execution-id'
24
27
 
25
28
  // TODO: can we increase the supported version range?
26
29
  addHook({
@@ -32,28 +35,47 @@ addHook({
32
35
  if (!ciSeleniumDriverGetStartCh.hasSubscribers) {
33
36
  return get.apply(this, arguments)
34
37
  }
35
- let traceId
36
- const setTraceId = (inputTraceId) => {
37
- traceId = inputTraceId
38
- }
39
38
  const getResult = await get.apply(this, arguments)
40
39
 
41
- const isRumActive = await this.executeScript(IS_RUM_ACTIVE_SCRIPT)
42
- const capabilities = await this.getCapabilities()
40
+ let isRumActive
41
+ try {
42
+ isRumActive = await this.executeScript(IS_RUM_ACTIVE_SCRIPT)
43
+ } catch (error) {
44
+ log.error('Selenium RUM detection error', error)
45
+ }
46
+
47
+ let browserName
48
+ let browserVersion
49
+ try {
50
+ const capabilities = await this.getCapabilities()
51
+ browserName = capabilities.getBrowserName()
52
+ browserVersion = capabilities.getBrowserVersion()
53
+ } catch (error) {
54
+ log.error('Selenium browser metadata error', error)
55
+ }
43
56
 
44
- ciSeleniumDriverGetStartCh.publish({
45
- setTraceId,
57
+ const context = {
46
58
  seleniumVersion,
47
- browserName: capabilities.getBrowserName(),
48
- browserVersion: capabilities.getBrowserVersion(),
59
+ browserName,
60
+ browserVersion,
49
61
  isRumActive,
50
- })
62
+ testExecutionId: undefined,
63
+ }
64
+ try {
65
+ ciSeleniumDriverGetStartCh.publish(context)
66
+ } catch (error) {
67
+ log.error('Selenium RUM correlation channel error', error)
68
+ }
51
69
 
52
- if (traceId && isRumActive) {
53
- await this.manage().addCookie({
54
- name: DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME,
55
- value: traceId,
56
- })
70
+ if (context.testExecutionId && isRumActive) {
71
+ try {
72
+ await this.manage().addCookie({
73
+ name: DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME,
74
+ value: context.testExecutionId,
75
+ })
76
+ } catch (error) {
77
+ log.error('Selenium RUM correlation cookie error', error)
78
+ }
57
79
  }
58
80
 
59
81
  return getResult
@@ -63,16 +85,20 @@ addHook({
63
85
  if (!ciSeleniumDriverGetStartCh.hasSubscribers) {
64
86
  return quit.apply(this, arguments)
65
87
  }
66
- const isRumActive = await this.executeScript(RUM_STOP_SESSION_SCRIPT)
88
+ try {
89
+ const isRumActive = await this.executeScript(RUM_STOP_SESSION_SCRIPT)
67
90
 
68
- if (isRumActive) {
69
- // We'll have time for RUM to flush the events (there's no callback to know when it's done)
70
- await new Promise(resolve => {
71
- realSetTimeout(() => {
72
- resolve()
73
- }, DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS)
74
- })
75
- await this.manage().deleteCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME)
91
+ if (isRumActive) {
92
+ // We'll have time for RUM to flush the events (there's no callback to know when it's done)
93
+ await new Promise(resolve => {
94
+ realSetTimeout(() => {
95
+ resolve()
96
+ }, DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS)
97
+ })
98
+ await this.manage().deleteCookie(DD_CIVISIBILITY_TEST_EXECUTION_ID_COOKIE_NAME)
99
+ }
100
+ } catch (error) {
101
+ log.error('Selenium RUM cleanup error', error)
76
102
  }
77
103
 
78
104
  return quit.apply(this, arguments)
@@ -784,7 +784,7 @@ function getRepeatedTestReport (task, testName, testSuiteAbsolutePath, testPrope
784
784
 
785
785
  return {
786
786
  errors,
787
- finalAttempt: attempts[attempts.length - 1],
787
+ finalAttempt: attempts.at(-1),
788
788
  nonFinalAttempts: attempts.slice(0, -1),
789
789
  state,
790
790
  status,
@@ -880,7 +880,7 @@ function reportFinalTestAttempt (testReport) {
880
880
  const finalAttempt = testReport.finalAttempt
881
881
 
882
882
  if (status === 'fail') {
883
- const error = errors[errors.length - 1] || errors[0]
883
+ const error = errors.at(-1) || errors[0]
884
884
  reportTestAttempt(testReport, finalAttempt || {
885
885
  error,
886
886
  finalStatus,
@@ -1343,10 +1343,8 @@ function wrapTinyPoolRun (TinyPool) {
1343
1343
 
1344
1344
  shimmer.wrap(TinyPool.prototype, 'run', run => async function () {
1345
1345
  // We have to do this before and after because the threads list gets recycled, that is, the processes are re-created
1346
- // eslint-disable-next-line unicorn/no-array-for-each
1347
1346
  this.threads.forEach(threadHandler)
1348
1347
  const runResult = await run.apply(this, arguments)
1349
- // eslint-disable-next-line unicorn/no-array-for-each
1350
1348
  this.threads.forEach(threadHandler)
1351
1349
  return runResult
1352
1350
  })
@@ -3,6 +3,7 @@
3
3
  const { tracingChannel } = /** @type {import('node:diagnostics_channel')} */ (require('dc-polyfill'))
4
4
 
5
5
  const shimmer = require('../../datadog-shimmer')
6
+ const { getSegment } = require('../../dd-trace/src/util')
6
7
  const {
7
8
  addHook,
8
9
  channel,
@@ -106,7 +107,7 @@ function createWrapEmit (emit) {
106
107
  }
107
108
 
108
109
  const ctx = { req }
109
- ctx.req.resStatus = headers[0].split(' ')[1]
110
+ ctx.req.resStatus = getSegment(headers[0], ' ', 1)
110
111
 
111
112
  emitCh.runStores(ctx, () => {
112
113
  try {
@@ -94,7 +94,7 @@ class RunInChildContextPlugin extends BaseContextPlugin {
94
94
  static retryable = false
95
95
 
96
96
  bindStart (ctx) {
97
- if (SUPPRESSED_CHILD_CONTEXT_SUBTYPES.has(getRunInChildContextSubType(ctx))) {
97
+ if (SUPPRESSED_CHILD_CONTEXT_SUBTYPES.has(getRunInChildContextSubtype(ctx))) {
98
98
  // Pass the active store through unchanged so any nested spans
99
99
  // remain parented to the surrounding map/parallel span
100
100
  ctx.suppressed = true
@@ -105,7 +105,7 @@ class RunInChildContextPlugin extends BaseContextPlugin {
105
105
  }
106
106
 
107
107
  // runInChildContext has two overloads: `(name, fn, options)` and `(fn, options)`.
108
- function getRunInChildContextSubType (ctx) {
108
+ function getRunInChildContextSubtype (ctx) {
109
109
  const args = ctx.arguments || []
110
110
  const opts = typeof args[0] === 'function' ? args[1] : args[2]
111
111
  return opts?.subType
@@ -16,7 +16,8 @@ const REPLAYED_STATUSES = new Set(['SUCCEEDED', 'FAILED'])
16
16
  * @returns {{ stepId: string | undefined, stepData: object | undefined }}
17
17
  */
18
18
  function getStepDataForNext (ctxImpl) {
19
- const stepId = ctxImpl?.getNextStepId?.()
19
+ // @aws/durable-execution-sdk-js@2.2.0 renames .getNextStepId() to .peekStepId()
20
+ const stepId = ctxImpl?.peekStepId?.() ?? ctxImpl?.getNextStepId?.()
20
21
  const stepData = stepId ? ctxImpl?._executionContext?.getStepData?.(stepId) : undefined
21
22
  return { stepId, stepData }
22
23
  }
@@ -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
@@ -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 })
@@ -1847,9 +1811,7 @@ class CypressPlugin {
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()