dd-trace 5.124.0 → 5.126.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 (208) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/index.d.ts +8 -0
  14. package/package.json +13 -10
  15. package/packages/datadog-esbuild/index.js +3 -1
  16. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  17. package/packages/datadog-instrumentations/src/fs.js +24 -20
  18. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  19. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  20. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  21. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  22. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +14 -5
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  34. package/packages/datadog-instrumentations/src/jest.js +166 -45
  35. package/packages/datadog-instrumentations/src/knex.js +76 -0
  36. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  37. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  38. package/packages/datadog-instrumentations/src/mocha/main.js +66 -20
  39. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  40. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  41. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  42. package/packages/datadog-instrumentations/src/net.js +14 -17
  43. package/packages/datadog-instrumentations/src/next.js +212 -62
  44. package/packages/datadog-instrumentations/src/openai.js +42 -40
  45. package/packages/datadog-instrumentations/src/pg.js +15 -0
  46. package/packages/datadog-instrumentations/src/playwright.js +113 -13
  47. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  48. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  49. package/packages/datadog-instrumentations/src/undici.js +53 -4
  50. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  51. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  52. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  53. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  54. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  56. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  58. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  59. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  60. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  61. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  62. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  63. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  64. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  65. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  66. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  67. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  68. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  69. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  70. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  71. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  72. package/packages/datadog-plugin-jest/src/index.js +0 -6
  73. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  74. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  75. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  76. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  77. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  78. package/packages/datadog-plugin-next/src/index.js +63 -29
  79. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  80. package/packages/datadog-plugin-undici/src/index.js +27 -41
  81. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  82. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  83. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  85. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  86. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  87. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  88. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  90. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  91. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  93. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -95
  94. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  95. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  96. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  97. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  100. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  101. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  102. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  103. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  104. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  105. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  106. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  107. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  108. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  109. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  110. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  111. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  112. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  113. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +180 -0
  114. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  115. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  116. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  117. package/packages/dd-trace/src/config/index.js +73 -13
  118. package/packages/dd-trace/src/config/parsers.js +11 -1
  119. package/packages/dd-trace/src/config/stable.js +3 -3
  120. package/packages/dd-trace/src/config/supported-configurations.json +22 -2
  121. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  122. package/packages/dd-trace/src/debugger/config.js +12 -1
  123. package/packages/dd-trace/src/debugger/constants.js +1 -0
  124. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  125. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +28 -0
  126. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  127. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  128. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  129. package/packages/dd-trace/src/debugger/index.js +23 -3
  130. package/packages/dd-trace/src/dogstatsd.js +510 -76
  131. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  132. package/packages/dd-trace/src/encode/0.4.js +5 -5
  133. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  134. package/packages/dd-trace/src/evp_proxy/direct.js +3 -25
  135. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  136. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  137. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  138. package/packages/dd-trace/src/exporters/agentless/writer.js +103 -119
  139. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  140. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  141. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  142. package/packages/dd-trace/src/exporters/common/proxy.js +52 -0
  143. package/packages/dd-trace/src/exporters/common/request.js +42 -14
  144. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  145. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  146. package/packages/dd-trace/src/flush.js +6 -3
  147. package/packages/dd-trace/src/id.js +14 -0
  148. package/packages/dd-trace/src/index.js +5 -2
  149. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  150. package/packages/dd-trace/src/llmobs/index.js +32 -32
  151. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  152. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  153. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  154. package/packages/dd-trace/src/llmobs/util.js +0 -33
  155. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  156. package/packages/dd-trace/src/log/index.js +7 -1
  157. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  158. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  159. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  160. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  161. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  162. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  163. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  164. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +5 -0
  166. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  167. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  168. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  169. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  170. package/packages/dd-trace/src/opentracing/propagation/text_map.js +101 -20
  171. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  172. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  173. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  174. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  175. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  176. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  177. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  178. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  179. package/packages/dd-trace/src/priority_sampler.js +4 -0
  180. package/packages/dd-trace/src/profiling/config.js +1 -0
  181. package/packages/dd-trace/src/profiling/oom.js +7 -2
  182. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  183. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  184. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  185. package/packages/dd-trace/src/proxy.js +75 -5
  186. package/packages/dd-trace/src/remote_config/index.js +319 -101
  187. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  188. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  189. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  190. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  191. package/packages/dd-trace/src/sampling_rule.js +39 -10
  192. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  193. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  194. package/packages/dd-trace/src/serverless.js +10 -3
  195. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  196. package/packages/dd-trace/src/span_processor.js +18 -3
  197. package/packages/dd-trace/src/span_stats.js +24 -28
  198. package/packages/dd-trace/src/startup-log.js +3 -2
  199. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  200. package/packages/dd-trace/src/telemetry/send-data.js +23 -20
  201. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  202. package/packages/dd-trace/src/tracer.js +4 -5
  203. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  204. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  205. package/vendor/dist/protobufjs/index.js +1 -1
  206. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  207. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  208. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -1,5 +1,7 @@
1
1
  'use strict'
2
2
 
3
+ const { AsyncResource } = require('node:async_hooks')
4
+
3
5
  const { createCoverageMap } = require('../../../../vendor/dist/istanbul-lib-coverage')
4
6
  const satisfies = require('../../../../vendor/dist/semifies')
5
7
  const { DD_MAJOR } = require('../../../../version')
@@ -35,6 +37,7 @@ const {
35
37
  isMarkedAsUnskippable,
36
38
  } = require('../../../dd-trace/src/plugins/util/test')
37
39
 
40
+ const { addMochaRunHooks } = require('./common')
38
41
  const {
39
42
  isNewTest,
40
43
  getTestProperties,
@@ -60,8 +63,6 @@ const {
60
63
  adjustRunnerFailuresForTestOptimization,
61
64
  } = require('./utils')
62
65
 
63
- require('./common')
64
-
65
66
  const MINIMUM_MOCHA_VERSION = DD_MAJOR >= 6 ? '>=8.0.0' : '>=5.2.0'
66
67
 
67
68
  /**
@@ -81,6 +82,8 @@ const runnerTestEndHandlers = new WeakMap()
81
82
  const runnerFailuresAdjusted = new WeakSet()
82
83
  const runnerFrameworkErrors = new WeakMap()
83
84
  const runnerStarted = new WeakSet()
85
+ const readyRunners = new WeakSet()
86
+ const pendingRunnerStarts = new WeakMap()
84
87
  const runnerRecoveryStates = new WeakMap()
85
88
  const runnersWithPendingCoverageReset = new WeakSet()
86
89
  const parallelRunners = new WeakSet()
@@ -536,7 +539,10 @@ function stopCurrentHook (runner, hook) {
536
539
  if (test) {
537
540
  if (hook.parent?._afterEach?.includes(hook) || hook.parent?._afterAll?.includes(hook)) {
538
541
  markTestTerminal(runner, test)
539
- if (!test._ddTestFinishStarted) runnerTestEndHandlers.get(runner)?.(test)
542
+ if (!test._ddTestFinishStarted) {
543
+ const onTestEnd = runnerTestEndHandlers.get(runner)
544
+ onTestEnd?.(test)
545
+ }
540
546
  } else {
541
547
  markTestPending(runner, test)
542
548
  }
@@ -763,14 +769,18 @@ function wrapRunnerEmit (Runner) {
763
769
  const test = hook.ctx?.currentTest
764
770
  if (test && hook.parent?._afterEach?.includes(hook)) {
765
771
  markTestTerminal(this, test)
766
- if (!test._ddTestFinishStarted) runnerTestEndHandlers.get(this)?.(test)
772
+ if (!test._ddTestFinishStarted) {
773
+ const onTestEnd = runnerTestEndHandlers.get(this)
774
+ onTestEnd?.(test)
775
+ }
767
776
  }
768
777
  } else if (event === 'pending' || event === 'pass' || event === 'fail' || event === 'retry' ||
769
778
  event === 'test end') {
770
779
  const test = arguments[1]
771
780
  stopAfterEachHooks(this, test)
772
781
  if (event === 'test end' && !test._ddTestFinishStarted) {
773
- runnerTestEndHandlers.get(this)?.(test)
782
+ const onTestEnd = runnerTestEndHandlers.get(this)
783
+ onTestEnd?.(test)
774
784
  }
775
785
  }
776
786
  }
@@ -1060,16 +1070,50 @@ function getExecutionConfiguration (runner, isParallel, frameworkVersion, onFini
1060
1070
  runStoresWithCompletion(libraryConfigurationCh, ctx, onReceivedConfiguration)
1061
1071
  }
1062
1072
 
1073
+ /**
1074
+ * @param {import('mocha').Runner} runner
1075
+ * @returns {void}
1076
+ */
1077
+ function startMochaRunner (runner) {
1078
+ if (readyRunners.has(runner)) {
1079
+ runner.suite.run()
1080
+ } else {
1081
+ // Global setup can finish after configuration. Preserve the configuration
1082
+ // context until Runner#run has installed its delayed-start listener.
1083
+ pendingRunnerStarts.set(runner, AsyncResource.bind(() => runner.suite.run()))
1084
+ }
1085
+ }
1086
+
1087
+ /**
1088
+ * @param {import('mocha').Runner['run']} run
1089
+ * @param {import('mocha').Runner} runner
1090
+ * @param {Parameters<import('mocha').Runner['run']>} args
1091
+ * @returns {import('mocha').Runner}
1092
+ */
1093
+ function runMochaRunner (run, runner, args) {
1094
+ const result = run.apply(runner, args)
1095
+ // Once delay mode is enabled, startup must complete even if the plugin is disabled during global setup.
1096
+ readyRunners.add(runner)
1097
+ const start = pendingRunnerStarts.get(runner)
1098
+ if (start) {
1099
+ pendingRunnerStarts.delete(runner)
1100
+ start()
1101
+ }
1102
+ return result
1103
+ }
1104
+
1063
1105
  // In this hook we delay the execution with options.delay to grab library configuration,
1064
1106
  // skippable and known tests.
1065
1107
  // It is called but skipped in parallel mode.
1066
- addHook({
1067
- name: 'mocha',
1068
- versions: [MINIMUM_MOCHA_VERSION],
1069
- file: 'lib/mocha.js',
1070
- }, (Mocha, frameworkVersion) => {
1108
+ /**
1109
+ * @param {Function} Mocha
1110
+ * @param {string} frameworkVersion
1111
+ * @returns {Function}
1112
+ */
1113
+ function wrapMochaRun (Mocha, frameworkVersion) {
1071
1114
  warnDeprecatedMochaVersion(frameworkVersion)
1072
1115
 
1116
+ // Shimmer is required because run must return its Runner while execution is paused and resumed after configuration.
1073
1117
  shimmer.wrap(Mocha.prototype, 'run', run => function (...args) {
1074
1118
  // Workers do not need to request any data, just run the tests
1075
1119
  if (!testFinishCh.hasSubscribers || getEnvironmentVariable('MOCHA_WORKER_ID') || this.options.parallel) {
@@ -1110,23 +1154,25 @@ addHook({
1110
1154
  getCodeCoverageCh.publish({
1111
1155
  onDone: (receivedCodeCoverage) => {
1112
1156
  untestedCoverage = receivedCodeCoverage
1113
- global.run()
1157
+ startMochaRunner(runner)
1114
1158
  },
1115
1159
  })
1116
1160
  } else {
1117
- global.run()
1161
+ startMochaRunner(runner)
1118
1162
  }
1119
1163
  })
1120
1164
 
1121
1165
  return runner
1122
1166
  })
1123
1167
  return Mocha
1124
- })
1168
+ }
1169
+
1170
+ addMochaRunHooks([MINIMUM_MOCHA_VERSION], wrapMochaRun)
1125
1171
 
1126
1172
  addHook({
1127
1173
  name: 'mocha',
1128
1174
  versions: [MINIMUM_MOCHA_VERSION],
1129
- file: 'lib/cli/run-helpers.js',
1175
+ filePattern: String.raw`lib/cli/run-helpers\.(?:c?js)$`,
1130
1176
  }, (run) => {
1131
1177
  // `runMocha` is an async function
1132
1178
  shimmer.wrap(run, 'runMocha', runMocha => function (...args) {
@@ -1136,7 +1182,7 @@ addHook({
1136
1182
  const mocha = args[0]
1137
1183
 
1138
1184
  /**
1139
- * This attaches `run` to the global context, which we'll call after
1185
+ * This enables the delayed root suite, which we'll release after
1140
1186
  * our configuration and skippable suites requests.
1141
1187
  * You need this both here and in Mocha#run hook: the programmatic API
1142
1188
  * does not call `runMocha`, so it needs to be in Mocha#run. When using
@@ -1157,7 +1203,7 @@ addHook({
1157
1203
  addHook({
1158
1204
  name: 'mocha',
1159
1205
  versions: [MINIMUM_MOCHA_VERSION],
1160
- file: 'lib/runner.js',
1206
+ filePattern: String.raw`lib/runner\.(?:c?js)$`,
1161
1207
  }, function (Runner, frameworkVersion) {
1162
1208
  if (patched.has(Runner)) return Runner
1163
1209
 
@@ -1168,7 +1214,7 @@ addHook({
1168
1214
 
1169
1215
  shimmer.wrap(Runner.prototype, 'run', run => function (...args) {
1170
1216
  if (!testFinishCh.hasSubscribers) {
1171
- return run.apply(this, args)
1217
+ return runMochaRunner(run, this, args)
1172
1218
  }
1173
1219
 
1174
1220
  const { onRunDone, onFlushDone } = getRunCompletionCallbacks(args[0])
@@ -1501,7 +1547,7 @@ addHook({
1501
1547
  }
1502
1548
  })
1503
1549
 
1504
- return run.apply(this, args)
1550
+ return runMochaRunner(run, this, args)
1505
1551
  })
1506
1552
 
1507
1553
  return Runner
@@ -1588,7 +1634,7 @@ addHook({
1588
1634
  addHook({
1589
1635
  name: 'mocha',
1590
1636
  versions: ['>=8.0.0'],
1591
- file: 'lib/nodejs/parallel-buffered-runner.js',
1637
+ filePattern: String.raw`lib/nodejs/parallel-buffered-runner\.(?:c?js)$`,
1592
1638
  }, (ParallelBufferedRunner, frameworkVersion) => {
1593
1639
  shimmer.wrap(ParallelBufferedRunner.prototype, 'run', run => function (cb, { files, options = {} }) {
1594
1640
  if (!testFinishCh.hasSubscribers) {
@@ -1667,7 +1713,7 @@ addHook({
1667
1713
  addHook({
1668
1714
  name: 'mocha',
1669
1715
  versions: ['>=8.0.0'],
1670
- file: 'lib/nodejs/buffered-worker-pool.js',
1716
+ filePattern: String.raw`lib/nodejs/buffered-worker-pool\.(?:c?js)$`,
1671
1717
  }, (BufferedWorkerPoolPackage, frameworkVersion) => {
1672
1718
  const { BufferedWorkerPool } = BufferedWorkerPoolPackage
1673
1719
 
@@ -507,7 +507,8 @@ function inheritDatadogPropertiesFromRetriedTest (test) {
507
507
  }
508
508
 
509
509
  function runnableWrapper (RunnablePackage, libraryConfig) {
510
- shimmer.wrap(RunnablePackage.prototype, 'run', run => function (...args) {
510
+ const Runnable = RunnablePackage.Runnable ?? RunnablePackage
511
+ shimmer.wrap(Runnable.prototype, 'run', run => function (...args) {
511
512
  if (!testFinishCh.hasSubscribers) {
512
513
  return run.apply(this, args)
513
514
  }
@@ -6,6 +6,11 @@ const {
6
6
  WEBDRIVERIO_WORKER_EVENT,
7
7
  WEBDRIVERIO_WORKER_ORIGIN,
8
8
  } = require('../../../dd-trace/src/ci-visibility/exporters/test-worker/webdriverio')
9
+ const { FINAL_FLUSH_TIMEOUT } = require('../../../dd-trace/src/ci-visibility/final-flush')
10
+
11
+ const SCREENSHOT_UPLOAD = 'dd:test-optimization:webdriverio:screenshot:upload'
12
+ const SCREENSHOT_UPLOAD_RESPONSE = 'dd:test-optimization:webdriverio:screenshot:upload:response'
13
+ const SCREENSHOT_UPLOAD_TIMEOUT_MS = FINAL_FLUSH_TIMEOUT + 5000
9
14
 
10
15
  /**
11
16
  * Sends a message over WebdriverIO's worker IPC envelope.
@@ -30,10 +35,103 @@ function sendWebdriverioWorkerMessage (message, onError, onDone) {
30
35
  })
31
36
  }
32
37
 
38
+ let screenshotUploadRequestId = 0
39
+ const screenshotUploadRequests = new Map()
40
+
41
+ /**
42
+ * Removes shared screenshot response listeners when there are no pending requests.
43
+ *
44
+ * @returns {void}
45
+ */
46
+ function removeScreenshotUploadListeners () {
47
+ if (screenshotUploadRequests.size !== 0) return
48
+
49
+ process.off('message', onScreenshotUploadResponse)
50
+ process.off('disconnect', onScreenshotUploadDisconnect)
51
+ }
52
+
53
+ /**
54
+ * Completes one pending screenshot upload request.
55
+ *
56
+ * @param {string} requestId
57
+ * @param {Error} [error]
58
+ * @returns {void}
59
+ */
60
+ function finishScreenshotUploadRequest (requestId, error) {
61
+ const request = screenshotUploadRequests.get(requestId)
62
+ if (!request) return
63
+
64
+ screenshotUploadRequests.delete(requestId)
65
+ clearTimeout(request.timeout)
66
+ removeScreenshotUploadListeners()
67
+ request.onDone(error)
68
+ }
69
+
70
+ /**
71
+ * Dispatches one coordinator screenshot response to its pending request.
72
+ *
73
+ * @param {object} message
74
+ * @returns {void}
75
+ */
76
+ function onScreenshotUploadResponse (message) {
77
+ if (message?.name !== SCREENSHOT_UPLOAD_RESPONSE) return
78
+
79
+ const { error: errorMessage, requestId } = message.content || {}
80
+ if (!requestId) return
81
+
82
+ finishScreenshotUploadRequest(requestId, errorMessage ? new Error(errorMessage) : undefined)
83
+ }
84
+
85
+ /**
86
+ * Fails every pending screenshot upload after coordinator disconnect.
87
+ *
88
+ * @returns {void}
89
+ */
90
+ function onScreenshotUploadDisconnect () {
91
+ for (const requestId of screenshotUploadRequests.keys()) {
92
+ finishScreenshotUploadRequest(
93
+ requestId,
94
+ new Error('WebdriverIO coordinator disconnected during screenshot upload')
95
+ )
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Requests one screenshot upload from the WebdriverIO coordinator.
101
+ *
102
+ * @param {object} content - Screenshot upload metadata
103
+ * @param {(error?: Error) => void} onDone - Upload completion callback
104
+ * @returns {void}
105
+ */
106
+ function requestWebdriverioScreenshotUpload (content, onDone) {
107
+ const requestId = `${process.pid}-${++screenshotUploadRequestId}`
108
+ const timeout = setTimeout(() => {
109
+ finishScreenshotUploadRequest(requestId, new Error('WebdriverIO screenshot upload timed out'))
110
+ }, SCREENSHOT_UPLOAD_TIMEOUT_MS)
111
+ timeout.unref?.()
112
+ if (screenshotUploadRequests.size === 0) {
113
+ process.on('message', onScreenshotUploadResponse)
114
+ process.once('disconnect', onScreenshotUploadDisconnect)
115
+ }
116
+ screenshotUploadRequests.set(requestId, { onDone, timeout })
117
+ sendWebdriverioWorkerMessage({
118
+ origin: 'datadog',
119
+ name: SCREENSHOT_UPLOAD,
120
+ content: { ...content, requestId },
121
+ }, error => finishScreenshotUploadRequest(
122
+ requestId,
123
+ error || new Error('WebdriverIO screenshot upload IPC failed')
124
+ ))
125
+ }
126
+
33
127
  module.exports = {
34
128
  CONFIGURATION_REQUEST: 'dd:test-optimization:webdriverio:configuration:request',
35
129
  CONFIGURATION_RESPONSE: 'dd:test-optimization:webdriverio:configuration:response',
36
130
  createWebdriverioWorkerMessage,
131
+ requestWebdriverioScreenshotUpload,
132
+ SCREENSHOT_UPLOAD,
133
+ SCREENSHOT_UPLOAD_RESPONSE,
134
+ SCREENSHOT_UPLOAD_TIMEOUT_MS,
37
135
  sendWebdriverioWorkerMessage,
38
136
  SUITE_FINISH: 'dd:test-optimization:webdriverio:test-suite:finish',
39
137
  WORKER_READY: 'dd:test-optimization:webdriverio:worker:ready',
@@ -7,6 +7,7 @@ const { getEnvironmentVariable } = require('../../../dd-trace/src/config/helper'
7
7
  const log = require('../../../dd-trace/src/log')
8
8
  const { DD_MAJOR } = require('../../../../version')
9
9
 
10
+ const { addMochaRunHooks } = require('./common')
10
11
  const {
11
12
  runnableWrapper,
12
13
  getOnTestHandler,
@@ -29,7 +30,6 @@ const {
29
30
  WEBDRIVERIO_WORKER_ENV,
30
31
  WORKER_READY,
31
32
  } = require('./webdriverio-protocol')
32
- require('./common')
33
33
 
34
34
  const MINIMUM_MOCHA_VERSION = DD_MAJOR >= 6 ? '>=8.0.0' : '>=5.2.0'
35
35
 
@@ -360,13 +360,15 @@ function isFailedTestReplayEnabled () {
360
360
  return config.isTestDynamicInstrumentationEnabled && config.isDiEnabled
361
361
  }
362
362
 
363
- addHook({
364
- name: 'mocha',
365
- versions: ['>=8.0.0'],
366
- file: 'lib/mocha.js',
367
- }, (Mocha, frameworkVersion) => {
363
+ /**
364
+ * @param {Function} Mocha
365
+ * @param {string} frameworkVersion
366
+ * @returns {Function}
367
+ */
368
+ function wrapMochaRun (Mocha, frameworkVersion) {
368
369
  reportWebdriverioWorkerReady(frameworkVersion)
369
370
 
371
+ // Shimmer is required because run must return its Runner while execution is paused and resumed after configuration.
370
372
  shimmer.wrap(Mocha.prototype, 'run', run => function (...args) {
371
373
  applyMochaOptions(this.options)
372
374
  if (!isWebdriverioWorker || !workerFinishCh.hasSubscribers) {
@@ -438,13 +440,15 @@ addHook({
438
440
  })
439
441
 
440
442
  return Mocha
441
- })
443
+ }
444
+
445
+ addMochaRunHooks(['>=8.0.0'], wrapMochaRun)
442
446
 
443
447
  // Runner is also hooked in mocha/main.js, but in here we only generate test events.
444
448
  addHook({
445
449
  name: 'mocha',
446
450
  versions: [MINIMUM_MOCHA_VERSION],
447
- file: 'lib/runner.js',
451
+ filePattern: String.raw`lib/runner\.(?:c?js)$`,
448
452
  }, function (Runner) {
449
453
  shimmer.wrap(Runner.prototype, 'runTests', runTests => getRunTestsWrapper(runTests, config))
450
454
 
@@ -27,8 +27,6 @@ addHook({ name: 'net' }, (net) => {
27
27
  }
28
28
 
29
29
  const options = getOptions(args)
30
- const lastIndex = args.length - 1
31
- const callback = args[lastIndex]
32
30
 
33
31
  if (!options) return connect.apply(this, args)
34
32
 
@@ -38,42 +36,39 @@ addHook({ name: 'net' }, (net) => {
38
36
  const errorCh = protocol === 'ipc' ? errorICPCh : errorTCPCh
39
37
  const ctx = { options }
40
38
 
41
- if (typeof callback === 'function') {
42
- args[lastIndex] = function (...args) {
43
- return finishCh.runStores(ctx, callback, this, ...args)
44
- }
45
- }
46
-
47
39
  return startCh.runStores(ctx, () => {
48
- setupListeners(this, protocol, ctx, finishCh, errorCh)
40
+ const cleanup = setupListeners(this, protocol, ctx, finishCh, errorCh)
49
41
 
50
- const emit = this.emit
42
+ const originalEmit = this.emit
51
43
  let pendingReadyEvents = 2
52
44
  // Named `emit`/arity-1 mirrors the socket method so the per-socket wrap
53
45
  // skips its name/length rewrite.
54
- this.emit = shimmer.wrapFunction(emit, originalEmit => function emit (eventName) {
46
+ this.emit = shimmer.wrapFunction(originalEmit, wrappedEmit => function emit (eventName) {
55
47
  switch (eventName) {
56
48
  case 'ready':
57
49
  case 'connect':
58
50
  if (--pendingReadyEvents === 0) this.emit = originalEmit
59
51
  return readyCh.runStores(ctx, () => {
60
- return Reflect.apply(originalEmit, this, arguments)
52
+ return Reflect.apply(wrappedEmit, this, arguments)
61
53
  })
62
54
  case 'error':
63
55
  case 'close':
64
56
  this.emit = originalEmit
65
- return Reflect.apply(originalEmit, this, arguments)
57
+ return Reflect.apply(wrappedEmit, this, arguments)
66
58
  default:
67
- return Reflect.apply(originalEmit, this, arguments)
59
+ return Reflect.apply(wrappedEmit, this, arguments)
68
60
  }
69
61
  })
70
62
 
71
63
  try {
72
64
  return connect.apply(this, args)
73
- } catch (err) {
74
- errorCh.publish(err)
65
+ } catch (error) {
66
+ cleanup()
67
+ this.emit = originalEmit
68
+ errorCh.publish(error)
69
+ finishCh.runStores(ctx, () => {})
75
70
 
76
- throw err
71
+ throw error
77
72
  }
78
73
  })
79
74
  })
@@ -142,4 +137,6 @@ function setupListeners (socket, protocol, ctx, finishCh, errorCh) {
142
137
  for (const event of events) {
143
138
  socket.once(event, wrapListener)
144
139
  }
140
+
141
+ return cleanupOtherListeners
145
142
  }