dd-trace 6.4.0 → 6.6.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 (185) 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 +11 -8
  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 +31 -19
  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/channel.js +74 -0
  65. package/packages/datadog-instrumentations/src/helpers/hook.js +27 -6
  66. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +8 -8
  67. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +28 -2
  68. package/packages/datadog-instrumentations/src/jest.js +44 -17
  69. package/packages/datadog-instrumentations/src/mariadb.js +1 -1
  70. package/packages/datadog-instrumentations/src/mocha/main.js +79 -32
  71. package/packages/datadog-instrumentations/src/mocha/utils.js +0 -4
  72. package/packages/datadog-instrumentations/src/mongodb.js +3 -3
  73. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  74. package/packages/datadog-instrumentations/src/mquery.js +2 -2
  75. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  76. package/packages/datadog-instrumentations/src/next.js +24 -0
  77. package/packages/datadog-instrumentations/src/nyc.js +0 -2
  78. package/packages/datadog-instrumentations/src/oracledb.js +2 -2
  79. package/packages/datadog-instrumentations/src/pg.js +2 -2
  80. package/packages/datadog-instrumentations/src/playwright.js +162 -46
  81. package/packages/datadog-instrumentations/src/process.js +3 -0
  82. package/packages/datadog-instrumentations/src/router.js +18 -8
  83. package/packages/datadog-instrumentations/src/selenium.js +52 -26
  84. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +3 -3
  85. package/packages/datadog-instrumentations/src/vitest-main.js +14 -28
  86. package/packages/datadog-instrumentations/src/vitest-util.js +0 -7
  87. package/packages/datadog-instrumentations/src/vitest-worker.js +5 -8
  88. package/packages/datadog-instrumentations/src/ws.js +2 -1
  89. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +2 -2
  90. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/util.js +2 -1
  91. package/packages/datadog-plugin-aws-sdk/src/util.js +2 -2
  92. package/packages/datadog-plugin-cucumber/src/index.js +3 -2
  93. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +19 -57
  94. package/packages/datadog-plugin-cypress/src/index.js +6 -1
  95. package/packages/datadog-plugin-cypress/src/support.js +92 -26
  96. package/packages/datadog-plugin-electron/src/ipc.js +1 -1
  97. package/packages/datadog-plugin-graphql/src/execute.js +87 -45
  98. package/packages/datadog-plugin-graphql/src/validate.js +2 -2
  99. package/packages/datadog-plugin-http2/src/client.js +1 -1
  100. package/packages/datadog-plugin-langchain/src/handlers/embedding.js +2 -1
  101. package/packages/datadog-plugin-langchain/src/handlers/language_models.js +2 -1
  102. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  103. package/packages/datadog-plugin-mocha/src/index.js +2 -1
  104. package/packages/datadog-plugin-playwright/src/index.js +181 -65
  105. package/packages/datadog-plugin-router/src/index.js +11 -2
  106. package/packages/datadog-plugin-selenium/src/index.js +5 -36
  107. package/packages/datadog-plugin-vitest/src/index.js +5 -5
  108. package/packages/datadog-plugin-ws/src/close.js +2 -1
  109. package/packages/datadog-plugin-ws/src/producer.js +2 -1
  110. package/packages/datadog-plugin-ws/src/receiver.js +2 -1
  111. package/packages/datadog-plugin-ws/src/server.js +2 -1
  112. package/packages/dd-trace/src/aiguard/sdk.js +1 -1
  113. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +2 -2
  114. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +3 -1
  115. package/packages/dd-trace/src/azure_metadata.js +2 -1
  116. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +93 -28
  117. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +1 -1
  118. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/index.js +160 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +288 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/payload-projection.js +84 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +369 -0
  122. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +60 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +41 -3
  124. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -2
  125. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +6 -2
  127. package/packages/dd-trace/src/ci-visibility/rum.js +7 -0
  128. package/packages/dd-trace/src/ci-visibility/test-api-manual/test-api-manual-plugin.js +4 -0
  129. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +1 -1
  130. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +137 -9
  131. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +168 -8
  132. package/packages/dd-trace/src/ci-visibility/test-screenshot.js +90 -0
  133. package/packages/dd-trace/src/config/generated-config-types.d.ts +16 -0
  134. package/packages/dd-trace/src/config/helper.js +1 -0
  135. package/packages/dd-trace/src/config/index.js +10 -0
  136. package/packages/dd-trace/src/config/supported-configurations.json +66 -0
  137. package/packages/dd-trace/src/exporter.js +2 -0
  138. package/packages/dd-trace/src/exporters/common/client-library-headers.js +21 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +59 -30
  140. package/packages/dd-trace/src/exporters/common/url.js +15 -1
  141. package/packages/dd-trace/src/feature-registry.js +10 -3
  142. package/packages/dd-trace/src/llmobs/experiments/client.js +113 -0
  143. package/packages/dd-trace/src/llmobs/experiments/dataset.js +154 -0
  144. package/packages/dd-trace/src/llmobs/experiments/experiment.js +283 -0
  145. package/packages/dd-trace/src/llmobs/experiments/index.js +152 -0
  146. package/packages/dd-trace/src/llmobs/experiments/noop.js +30 -0
  147. package/packages/dd-trace/src/llmobs/experiments/result.js +34 -0
  148. package/packages/dd-trace/src/llmobs/noop.js +6 -0
  149. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +20 -3
  150. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +2 -2
  151. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  152. package/packages/dd-trace/src/llmobs/plugins/openai/constants.js +8 -0
  153. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +48 -14
  154. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +43 -0
  155. package/packages/dd-trace/src/llmobs/sdk.js +17 -0
  156. package/packages/dd-trace/src/llmobs/tagger.js +68 -0
  157. package/packages/dd-trace/src/llmobs/telemetry.js +2 -1
  158. package/packages/dd-trace/src/llmobs/util.js +32 -0
  159. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +322 -0
  160. package/packages/dd-trace/src/openfeature/configuration_source.js +90 -0
  161. package/packages/dd-trace/src/openfeature/flagging_provider.js +14 -23
  162. package/packages/dd-trace/src/openfeature/index.js +0 -2
  163. package/packages/dd-trace/src/openfeature/noop.js +1 -28
  164. package/packages/dd-trace/src/openfeature/register.js +16 -27
  165. package/packages/dd-trace/src/openfeature/remote_config.js +15 -18
  166. package/packages/dd-trace/src/openfeature/require-provider.js +18 -0
  167. package/packages/dd-trace/src/opentelemetry/span-ending-hook.js +10 -0
  168. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  169. package/packages/dd-trace/src/plugins/ci_plugin.js +20 -6
  170. package/packages/dd-trace/src/plugins/util/git.js +3 -2
  171. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +29 -5
  172. package/packages/dd-trace/src/plugins/util/test.js +56 -4
  173. package/packages/dd-trace/src/profiling/profilers/space.js +1 -1
  174. package/packages/dd-trace/src/profiling/profilers/wall.js +1 -0
  175. package/packages/dd-trace/src/proxy.js +84 -7
  176. package/packages/dd-trace/src/span_processor.js +5 -0
  177. package/packages/dd-trace/src/standalone/index.js +0 -14
  178. package/packages/dd-trace/src/telemetry/send-data.js +2 -2
  179. package/packages/dd-trace/src/util.js +26 -0
  180. package/vendor/dist/@apm-js-collab/code-transformer/index.js +1 -1
  181. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  182. package/vendor/dist/protobufjs/index.js +1 -1
  183. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  184. package/vendor/dist/shell-quote/index.js +1 -1
  185. package/packages/datadog-instrumentations/src/helpers/require-optional-peer.js +0 -17
@@ -419,7 +419,7 @@ function createMainProcessReporter (reporterState) {
419
419
  testSuiteFinishCh.publish({
420
420
  status: getDatadogStatus(testSuiteResult),
421
421
  deferFlush: true,
422
- onFinish: noop,
422
+ onDone: noop,
423
423
  ...testSuiteCtx.currentStore,
424
424
  })
425
425
  testSuiteContexts.delete(testModuleId)
@@ -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,
@@ -16,6 +16,7 @@ const {
16
16
  getTestSuitePath,
17
17
  isModifiedTest,
18
18
  } = require('../../dd-trace/src/plugins/util/test')
19
+ const { getChannelPromise } = require('./helpers/channel')
19
20
  const { addHook } = require('./helpers/instrument')
20
21
  const noWorkerInit = require('./vitest-main-no-worker-init')
21
22
  const {
@@ -38,7 +39,6 @@ const {
38
39
  workerReportLogsCh,
39
40
  codeCoverageReportCh,
40
41
  findExportByName,
41
- getChannelPromise,
42
42
  getTypeTasks,
43
43
  getWorkspaceProject,
44
44
  setProvidedContext,
@@ -433,7 +433,8 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, s
433
433
  err,
434
434
  libraryConfig,
435
435
  requestErrorTags: receivedRequestErrorTags = {},
436
- } = await getChannelPromise(libraryConfigurationCh, frameworkVersion, {
436
+ } = await getChannelPromise(libraryConfigurationCh, {
437
+ frameworkVersion,
437
438
  isVitestNoWorkerInitActive: shouldInstallNoWorkerInit,
438
439
  })
439
440
  requestErrorTags = receivedRequestErrorTags
@@ -450,7 +451,7 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, s
450
451
  resetMainProcessProvidedContext(ctx)
451
452
 
452
453
  if (testSessionConfigurationCh.hasSubscribers) {
453
- testSessionConfiguration = await getChannelPromise(testSessionConfigurationCh, frameworkVersion)
454
+ testSessionConfiguration = await getChannelPromise(testSessionConfigurationCh, { frameworkVersion }) || {}
454
455
  const {
455
456
  testSessionId,
456
457
  testModuleId,
@@ -494,7 +495,7 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, s
494
495
 
495
496
  if (isKnownTestsEnabled) {
496
497
  const currentKnownTestsResponse = knownTestsResponse || await getChannelPromise(knownTestsCh)
497
- if (currentKnownTestsResponse.err) {
498
+ if (!currentKnownTestsResponse || currentKnownTestsResponse.err) {
498
499
  isEarlyFlakeDetectionEnabled = false
499
500
  } else {
500
501
  knownTests = currentKnownTestsResponse.knownTests
@@ -538,12 +539,13 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, s
538
539
  }
539
540
 
540
541
  if (isTestManagementTestsEnabled) {
541
- const { err, testManagementTests: receivedTestManagementTests } =
542
+ const testManagementResponse =
542
543
  testManagementTestsResponse || await getChannelPromise(testManagementTestsCh)
543
- if (err) {
544
+ if (!testManagementResponse || testManagementResponse.err) {
544
545
  isTestManagementTestsEnabled = false
545
546
  log.error('Could not get test management tests.')
546
547
  } else {
548
+ const { testManagementTests: receivedTestManagementTests } = testManagementResponse
547
549
  testManagementTests = receivedTestManagementTests
548
550
  testManagementTestsBySuite = getTestManagementTestsBySuite(receivedTestManagementTests)
549
551
  shouldSendTestProperties = true
@@ -558,8 +560,7 @@ async function runMainProcessSetup (ctx, frameworkVersion, testSpecifications, s
558
560
 
559
561
  if (isImpactedTestsEnabled) {
560
562
  const modifiedFilesResponse = await getChannelPromise(modifiedFilesCh)
561
- const { err } = modifiedFilesResponse
562
- if (err) {
563
+ if (!modifiedFilesResponse || modifiedFilesResponse.err) {
563
564
  log.error('Could not get modified tests.')
564
565
  } else {
565
566
  modifiedFiles = modifiedFilesResponse.modifiedFiles
@@ -812,18 +813,13 @@ function getFinishWrapper (exitOrClose) {
812
813
  return exitOrClose.apply(this, arguments)
813
814
  }
814
815
 
815
- let onFinish
816
-
817
- const flushPromise = new Promise(resolve => {
818
- onFinish = resolve
819
- })
820
816
  const failedSuites = this.state.getFailedFilepaths()
821
817
  let error
822
818
  if (failedSuites.length) {
823
819
  error = new Error(`Test suites failed: ${failedSuites.length}.`)
824
820
  }
825
821
 
826
- testSessionFinishCh.publish({
822
+ const flushPromise = getChannelPromise(testSessionFinishCh, {
827
823
  status: getSessionStatus(this.state),
828
824
  testCodeCoverageLinesTotal,
829
825
  error,
@@ -833,7 +829,6 @@ function getFinishWrapper (exitOrClose) {
833
829
  requestErrorTags,
834
830
  vitestPool,
835
831
  isVitestNoWorkerInitActive,
836
- onFinish,
837
832
  })
838
833
 
839
834
  logTestOptimizationSummary({ attemptToFixExecutions, newTestsWithDynamicNames })
@@ -842,9 +837,7 @@ function getFinishWrapper (exitOrClose) {
842
837
 
843
838
  // If coverage was generated, publish coverage report channel for upload
844
839
  if (coverageRootDir && codeCoverageReportCh.hasSubscribers) {
845
- await new Promise((resolve) => {
846
- codeCoverageReportCh.publish({ rootDir: coverageRootDir, onDone: resolve })
847
- })
840
+ await getChannelPromise(codeCoverageReportCh, { rootDir: coverageRootDir })
848
841
  }
849
842
 
850
843
  return exitOrClose.apply(this, arguments)
@@ -1213,16 +1206,11 @@ async function reportTypecheckFile (file, sessionConfiguration, frameworkVersion
1213
1206
  testSuiteErrorCh.runStores(testSuiteCtx, () => {})
1214
1207
  }
1215
1208
 
1216
- let onFinish
1217
- const onFinishPromise = new Promise(resolve => {
1218
- onFinish = resolve
1219
- })
1220
- testSuiteFinishCh.publish({
1209
+ await getChannelPromise(testSuiteFinishCh, {
1210
+ frameworkVersion,
1221
1211
  status: getTypecheckTaskStatus(file),
1222
- onFinish,
1223
1212
  ...testSuiteCtx.currentStore,
1224
1213
  })
1225
- await onFinishPromise
1226
1214
  }
1227
1215
 
1228
1216
  async function reportTypecheckResults (result, frameworkVersion, ctx, typechecker) {
@@ -1234,7 +1222,7 @@ async function reportTypecheckResults (result, frameworkVersion, ctx, typechecke
1234
1222
  }
1235
1223
  const providedContext = getMainProcessProvidedContext(ctx)
1236
1224
  const sessionConfiguration = testSessionConfigurationCh.hasSubscribers
1237
- ? await getChannelPromise(testSessionConfigurationCh, frameworkVersion)
1225
+ ? await getChannelPromise(testSessionConfigurationCh, { frameworkVersion }) || {}
1238
1226
  : {}
1239
1227
 
1240
1228
  await Promise.all(result.files.map(file => reportTypecheckFile(
@@ -1343,10 +1331,8 @@ function wrapTinyPoolRun (TinyPool) {
1343
1331
 
1344
1332
  shimmer.wrap(TinyPool.prototype, 'run', run => async function () {
1345
1333
  // 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
1334
  this.threads.forEach(threadHandler)
1348
1335
  const runResult = await run.apply(this, arguments)
1349
- // eslint-disable-next-line unicorn/no-array-for-each
1350
1336
  this.threads.forEach(threadHandler)
1351
1337
  return runResult
1352
1338
  })
@@ -39,12 +39,6 @@ function findExportByName (pkg, name) {
39
39
  }
40
40
  }
41
41
 
42
- function getChannelPromise (channelToPublishTo, frameworkVersion, payload) {
43
- return new Promise(resolve => {
44
- channelToPublishTo.publish({ ...payload, onDone: resolve, frameworkVersion })
45
- })
46
- }
47
-
48
42
  function getTestRunnerExport (testPackage) {
49
43
  return findExportByName(testPackage, 'VitestTestRunner') || findExportByName(testPackage, 'TestRunner')
50
44
  }
@@ -239,7 +233,6 @@ module.exports = {
239
233
  workerReportLogsCh,
240
234
  codeCoverageReportCh,
241
235
  findExportByName,
242
- getChannelPromise,
243
236
  getTestRunnerExport,
244
237
  getTypeTasks,
245
238
  getTestName,
@@ -9,6 +9,7 @@ const {
9
9
  recordAttemptToFixExecution,
10
10
  logAttemptToFixTestExecution,
11
11
  } = require('../../dd-trace/src/plugins/util/test')
12
+ const { getChannelPromise } = require('./helpers/channel')
12
13
  const { addHook } = require('./helpers/instrument')
13
14
  const {
14
15
  testStartCh,
@@ -661,11 +662,6 @@ addHook({
661
662
  testSuiteStartCh.runStores(testSuiteCtx, () => {})
662
663
  const startTestsResponse = await startTests.apply(this, arguments)
663
664
 
664
- let onFinish = null
665
- const onFinishPromise = new Promise(resolve => {
666
- onFinish = resolve
667
- })
668
-
669
665
  const testTasks = getTypeTasks(startTestsResponse[0].tasks)
670
666
  const testEventPromises = []
671
667
 
@@ -811,9 +807,10 @@ addHook({
811
807
  testSuiteErrorCh.runStores(testSuiteCtx, () => {})
812
808
  }
813
809
 
814
- testSuiteFinishCh.publish({ status: testSuiteResult.state, onFinish, ...testSuiteCtx.currentStore })
815
-
816
- await onFinishPromise
810
+ await getChannelPromise(testSuiteFinishCh, {
811
+ status: testSuiteResult.state,
812
+ ...testSuiteCtx.currentStore,
813
+ })
817
814
 
818
815
  return startTestsResponse
819
816
  })
@@ -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
@@ -72,6 +72,7 @@ class CucumberPlugin extends CiPlugin {
72
72
  isEarlyFlakeDetectionFaulty,
73
73
  isTestManagementTestsEnabled,
74
74
  isParallel,
75
+ onDone,
75
76
  }) => {
76
77
  this._exportPendingWorkerTraces()
77
78
  const {
@@ -127,7 +128,7 @@ class CucumberPlugin extends CiPlugin {
127
128
  })
128
129
 
129
130
  this.libraryConfig = null
130
- this.tracer._exporter.flush()
131
+ this.tracer._exporter.flush(onDone)
131
132
  })
132
133
 
133
134
  this.addSub('ci:cucumber:test-suite:start', ({
@@ -438,7 +439,7 @@ class CucumberPlugin extends CiPlugin {
438
439
  const isModified = isModifiedTest(
439
440
  testScenarioPath,
440
441
  scenario.location.line,
441
- scenario.steps[scenario.steps.length - 1].location.line,
442
+ scenario.steps.at(-1).location.line,
442
443
  modifiedFiles,
443
444
  'cucumber'
444
445
  )
@@ -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()