dd-trace 6.20.1 → 6.22.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 (225) hide show
  1. package/ci/diagnose.js +9 -14
  2. package/ci/init.js +1 -2
  3. package/ci/test-optimization-validation/ci-discovery.js +9 -4
  4. package/ci/test-optimization-validation/executable.js +1 -5
  5. package/ci/test-optimization-validation/framework-adapters/cypress.js +1 -4
  6. package/ci/test-optimization-validation/framework-adapters/vitest.js +1 -1
  7. package/ci/test-optimization-validation/generated-files.js +6 -4
  8. package/ci/test-optimization-validation/generated-test-contract.js +7 -3
  9. package/ci/test-optimization-validation/manifest-scaffold.js +7 -2
  10. package/ci/test-optimization-validation/manifest-schema.js +6 -7
  11. package/ci/test-optimization-validation/report-writer.js +6 -2
  12. package/ci/test-optimization-validation/runner-command.js +2 -4
  13. package/ci/test-optimization-validation/runner-contract.js +1 -2
  14. package/ci/test-optimization-validation/scenarios/auto-test-retries.js +1 -15
  15. package/ci/vitest-no-worker-init-setup.mjs +10 -7
  16. package/index.d.ts +116 -17
  17. package/initialize.mjs +1 -3
  18. package/openfeature/rules-based.d.ts +11 -0
  19. package/openfeature/rules-based.js +7 -0
  20. package/package.json +13 -11
  21. package/packages/datadog-core/src/storage.js +16 -0
  22. package/packages/datadog-esbuild/index.js +18 -12
  23. package/packages/datadog-instrumentations/src/aerospike.js +3 -3
  24. package/packages/datadog-instrumentations/src/ai.js +3 -3
  25. package/packages/datadog-instrumentations/src/aws-durable-execution-sdk-js.js +0 -6
  26. package/packages/datadog-instrumentations/src/aws-sdk.js +21 -13
  27. package/packages/datadog-instrumentations/src/cassandra-driver.js +25 -20
  28. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +9 -23
  29. package/packages/datadog-instrumentations/src/cucumber.js +48 -33
  30. package/packages/datadog-instrumentations/src/elasticsearch.js +1 -5
  31. package/packages/datadog-instrumentations/src/electron.js +6 -6
  32. package/packages/datadog-instrumentations/src/fastify.js +13 -4
  33. package/packages/datadog-instrumentations/src/fs.js +2 -2
  34. package/packages/datadog-instrumentations/src/google-cloud-pubsub.js +9 -5
  35. package/packages/datadog-instrumentations/src/grpc/client.js +2 -0
  36. package/packages/datadog-instrumentations/src/helpers/bundler-register.js +2 -2
  37. package/packages/datadog-instrumentations/src/helpers/check-require-cache.js +1 -2
  38. package/packages/datadog-instrumentations/src/helpers/instrument.js +1 -1
  39. package/packages/datadog-instrumentations/src/helpers/register.js +36 -1
  40. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +2 -0
  41. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentation-registry.js +87 -6
  42. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +5 -6
  43. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/postgres.js +48 -31
  44. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +64 -11
  45. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +1 -0
  46. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms/postgres.js +42 -4
  47. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +7 -10
  48. package/packages/datadog-instrumentations/src/ioredis.js +7 -5
  49. package/packages/datadog-instrumentations/src/iovalkey.js +7 -5
  50. package/packages/datadog-instrumentations/src/jest.js +122 -11
  51. package/packages/datadog-instrumentations/src/knex.js +9 -5
  52. package/packages/datadog-instrumentations/src/mariadb-bundle.js +3 -3
  53. package/packages/datadog-instrumentations/src/mocha/common.js +20 -8
  54. package/packages/datadog-instrumentations/src/mocha/main.js +140 -43
  55. package/packages/datadog-instrumentations/src/mocha/utils.js +127 -38
  56. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +61 -34
  57. package/packages/datadog-instrumentations/src/mocha/worker.js +7 -5
  58. package/packages/datadog-instrumentations/src/mongodb-core.js +3 -3
  59. package/packages/datadog-instrumentations/src/mongoose.js +3 -3
  60. package/packages/datadog-instrumentations/src/nova-sonic/audio.js +173 -0
  61. package/packages/datadog-instrumentations/src/nova-sonic/index.js +209 -0
  62. package/packages/datadog-instrumentations/src/nova-sonic/session.js +483 -0
  63. package/packages/datadog-instrumentations/src/openai-realtime/index.js +18 -6
  64. package/packages/datadog-instrumentations/src/openai-realtime/session.js +1 -1
  65. package/packages/datadog-instrumentations/src/openai.js +7 -0
  66. package/packages/datadog-instrumentations/src/pino.js +5 -6
  67. package/packages/datadog-instrumentations/src/playwright.js +3 -0
  68. package/packages/datadog-instrumentations/src/redis.js +27 -13
  69. package/packages/datadog-instrumentations/src/router.js +30 -0
  70. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +2 -6
  71. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +7 -5
  72. package/packages/datadog-instrumentations/src/vitest-main.js +9 -17
  73. package/packages/datadog-instrumentations/src/vitest-util.js +2 -2
  74. package/packages/datadog-instrumentations/src/vitest-worker.js +1 -2
  75. package/packages/datadog-instrumentations/src/vitest.js +2 -2
  76. package/packages/datadog-instrumentations/src/webdriverio.js +118 -21
  77. package/packages/datadog-instrumentations/src/ws.js +1 -4
  78. package/packages/datadog-plugin-amqplib/src/client.js +1 -2
  79. package/packages/datadog-plugin-avsc/src/schema_iterator.js +1 -5
  80. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -2
  81. package/packages/datadog-plugin-aws-sdk/src/base.js +5 -8
  82. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/index.js +18 -0
  83. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/sonic.js +45 -0
  84. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +9 -3
  85. package/packages/datadog-plugin-aws-sdk/src/services/sqs.js +2 -5
  86. package/packages/datadog-plugin-azure-durable-functions/src/index.js +5 -4
  87. package/packages/datadog-plugin-azure-functions/src/index.js +5 -3
  88. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +0 -2
  89. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +18 -5
  90. package/packages/datadog-plugin-cypress/src/index.js +6 -6
  91. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -2
  92. package/packages/datadog-plugin-cypress/src/support.js +1 -4
  93. package/packages/datadog-plugin-express/src/code_origin.js +1 -2
  94. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +2 -2
  95. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +1 -2
  96. package/packages/datadog-plugin-graphql/src/execute.js +5 -5
  97. package/packages/datadog-plugin-grpc/src/util.js +1 -2
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +1 -2
  99. package/packages/datadog-plugin-mocha/src/index.js +223 -39
  100. package/packages/datadog-plugin-mocha/src/webdriverio-video.js +453 -0
  101. package/packages/datadog-plugin-mongodb-core/src/query.js +8 -5
  102. package/packages/datadog-plugin-next/src/index.js +1 -2
  103. package/packages/datadog-plugin-openai/src/tracing.js +5 -2
  104. package/packages/datadog-plugin-openai-agents/src/integration.js +6 -2
  105. package/packages/datadog-plugin-openai-agents/src/processor.js +2 -4
  106. package/packages/datadog-plugin-postgres/src/index.js +24 -0
  107. package/packages/datadog-plugin-protobufjs/src/schema_iterator.js +1 -5
  108. package/packages/datadog-plugin-rhea/src/consumer.js +1 -4
  109. package/packages/datadog-plugin-vitest/src/index.js +5 -2
  110. package/packages/datadog-shimmer/src/shimmer.js +5 -4
  111. package/packages/datadog-webpack/index.js +55 -43
  112. package/packages/datadog-webpack/src/loader.js +56 -8
  113. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -4
  114. package/packages/dd-trace/src/aiguard/messages/utils.js +1 -2
  115. package/packages/dd-trace/src/aiguard/redaction.js +8 -2
  116. package/packages/dd-trace/src/appsec/api_security/normalized-route.js +3 -2
  117. package/packages/dd-trace/src/appsec/api_security/sampler.js +2 -6
  118. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -9
  119. package/packages/dd-trace/src/appsec/handlers/http-request.js +3 -9
  120. package/packages/dd-trace/src/appsec/handlers/http-response.js +7 -2
  121. package/packages/dd-trace/src/appsec/iast/analyzers/path-traversal-analyzer.js +4 -3
  122. package/packages/dd-trace/src/appsec/iast/analyzers/set-cookies-header-interceptor.js +1 -4
  123. package/packages/dd-trace/src/appsec/iast/taint-tracking/operations-taint-object.js +7 -1
  124. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +1 -3
  125. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter-esm.mjs +1 -3
  126. package/packages/dd-trace/src/appsec/iast/telemetry/iast-metric.js +1 -6
  127. package/packages/dd-trace/src/appsec/lambda.js +1 -3
  128. package/packages/dd-trace/src/appsec/rasp/fs-plugin.js +37 -27
  129. package/packages/dd-trace/src/appsec/rasp/lfi.js +9 -5
  130. package/packages/dd-trace/src/appsec/reporter.js +21 -1
  131. package/packages/dd-trace/src/appsec/stack_trace.js +1 -2
  132. package/packages/dd-trace/src/ci-visibility/dynamic-atr-retries.js +3 -6
  133. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/msgpack-to-json.js +1 -1
  134. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +13 -7
  135. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +1 -2
  136. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +2 -8
  137. package/packages/dd-trace/src/ci-visibility/resolve-runfile-path.js +54 -0
  138. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +5 -8
  139. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +2 -53
  140. package/packages/dd-trace/src/config/config-types.d.ts +2 -2
  141. package/packages/dd-trace/src/config/defaults.js +5 -5
  142. package/packages/dd-trace/src/config/generated-config-types.d.ts +8 -0
  143. package/packages/dd-trace/src/config/helper.js +34 -3
  144. package/packages/dd-trace/src/config/index.js +10 -9
  145. package/packages/dd-trace/src/config/normalize-service.js +1 -1
  146. package/packages/dd-trace/src/config/parsers.js +2 -2
  147. package/packages/dd-trace/src/config/supported-configurations.json +35 -0
  148. package/packages/dd-trace/src/datastreams/processor.js +3 -2
  149. package/packages/dd-trace/src/datastreams/schemas/schema_builder.js +1 -4
  150. package/packages/dd-trace/src/debugger/constants.js +1 -0
  151. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -3
  152. package/packages/dd-trace/src/debugger/devtools_client/remote_config.js +17 -12
  153. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -2
  154. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -0
  155. package/packages/dd-trace/src/debugger/devtools_client/worker-error.js +21 -0
  156. package/packages/dd-trace/src/debugger/guardrail-metrics.js +2 -2
  157. package/packages/dd-trace/src/debugger/index.js +24 -2
  158. package/packages/dd-trace/src/debugger/probe_sampler.js +108 -11
  159. package/packages/dd-trace/src/dogstatsd.js +1 -1
  160. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  161. package/packages/dd-trace/src/exporters/common/request.js +2 -5
  162. package/packages/dd-trace/src/exporters/common/url.js +1 -1
  163. package/packages/dd-trace/src/id.js +15 -5
  164. package/packages/dd-trace/src/lambda/handler.js +179 -19
  165. package/packages/dd-trace/src/lambda/index.js +15 -1
  166. package/packages/dd-trace/src/lambda/runtime/patch.js +34 -7
  167. package/packages/dd-trace/src/llmobs/agent-manifest.js +296 -0
  168. package/packages/dd-trace/src/llmobs/audio-utils.js +3 -2
  169. package/packages/dd-trace/src/llmobs/constants/tags.js +13 -0
  170. package/packages/dd-trace/src/llmobs/experiments/experiment.js +1 -1
  171. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +15 -0
  172. package/packages/dd-trace/src/llmobs/plugins/genai/util.js +1 -2
  173. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/chat_model.js +1 -3
  174. package/packages/dd-trace/src/llmobs/plugins/langgraph/index.js +1 -1
  175. package/packages/dd-trace/src/llmobs/plugins/nova-sonic.js +92 -0
  176. package/packages/dd-trace/src/llmobs/plugins/openai/index.js +56 -2
  177. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +38 -0
  178. package/packages/dd-trace/src/llmobs/prompts/manager.js +5 -7
  179. package/packages/dd-trace/src/llmobs/prompts/prompt.js +1 -1
  180. package/packages/dd-trace/src/llmobs/sdk.js +23 -6
  181. package/packages/dd-trace/src/llmobs/span_processor.js +18 -8
  182. package/packages/dd-trace/src/llmobs/tagger.js +56 -3
  183. package/packages/dd-trace/src/llmobs/util.js +1 -2
  184. package/packages/dd-trace/src/llmobs/writers/base.js +7 -6
  185. package/packages/dd-trace/src/llmobs/writers/util.js +3 -1
  186. package/packages/dd-trace/src/log/log.js +1 -5
  187. package/packages/dd-trace/src/openfeature/agentless_configuration_source.js +30 -1
  188. package/packages/dd-trace/src/openfeature/configuration_source.js +17 -2
  189. package/packages/dd-trace/src/openfeature/debug-logging-hook.js +25 -0
  190. package/packages/dd-trace/src/openfeature/evaluation-telemetry-opt-out-hook.js +51 -0
  191. package/packages/dd-trace/src/openfeature/flagging_provider.js +59 -3
  192. package/packages/dd-trace/src/openfeature/remote_config.js +9 -0
  193. package/packages/dd-trace/src/openfeature/writers/util.js +43 -4
  194. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +1 -1
  195. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  196. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -2
  197. package/packages/dd-trace/src/opentelemetry/otlp/common.proto +1 -1
  198. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +1 -1
  199. package/packages/dd-trace/src/opentelemetry/trace/otlp_transformer.js +6 -23
  200. package/packages/dd-trace/src/opentracing/propagation/text_map.js +53 -13
  201. package/packages/dd-trace/src/otel-sampling.js +4 -5
  202. package/packages/dd-trace/src/otel-thread-ctx.js +81 -15
  203. package/packages/dd-trace/src/plugins/database.js +7 -16
  204. package/packages/dd-trace/src/plugins/util/git.js +9 -25
  205. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +1 -5
  206. package/packages/dd-trace/src/plugins/util/test.js +48 -4
  207. package/packages/dd-trace/src/priority_sampler.js +1 -2
  208. package/packages/dd-trace/src/profiling/profiler.js +4 -6
  209. package/packages/dd-trace/src/profiling/profilers/wall.js +4 -10
  210. package/packages/dd-trace/src/proxy.js +3 -2
  211. package/packages/dd-trace/src/ritm.js +4 -2
  212. package/packages/dd-trace/src/serverless.js +10 -0
  213. package/packages/dd-trace/src/span_processor.js +13 -17
  214. package/packages/dd-trace/src/storage-channels.js +112 -46
  215. package/packages/dd-trace/src/telemetry/endpoints.js +1 -3
  216. package/packages/dd-trace/src/util.js +29 -0
  217. package/packages/dd-trace/src/web-tags-cache.js +6 -2
  218. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  219. package/vendor/dist/@datadog/openfeature-node-server/rules-based/LICENSE +201 -0
  220. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.d.ts +925 -0
  221. package/vendor/dist/@datadog/openfeature-node-server/rules-based/index.js +1 -0
  222. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  223. package/vendor/dist/protobufjs/index.js +1 -1
  224. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  225. package/vendor/dist/shell-quote/index.js +1 -1
@@ -1,13 +1,13 @@
1
1
  'use strict'
2
2
 
3
3
  const { performance } = require('node:perf_hooks')
4
- const { fileURLToPath } = require('node:url')
4
+ const { clearTimeout, setTimeout } = require('node:timers')
5
5
 
6
6
  const { channel } = require('dc-polyfill')
7
7
 
8
8
  const CiPlugin = require('../../dd-trace/src/plugins/ci_plugin')
9
9
  const { storage } = require('../../datadog-core')
10
- const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
10
+ const { getEnvironmentVariable, getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
11
11
  const {
12
12
  getEfdRetryCountForDuration,
13
13
  hasEfdRetries,
@@ -15,16 +15,20 @@ const {
15
15
  const {
16
16
  getDynamicAtrRetryCount,
17
17
  } = require('../../dd-trace/src/ci-visibility/dynamic-atr-retries')
18
+ const { FINAL_FLUSH_TIMEOUT } = require('../../dd-trace/src/ci-visibility/final-flush')
18
19
  const {
19
20
  SCREENSHOT_UPLOAD_RESULT_ERROR,
20
21
  SCREENSHOT_UPLOAD_RESULT_UPLOADED,
21
22
  setScreenshotUploadTags,
22
23
  } = require('../../dd-trace/src/ci-visibility/test-screenshot')
24
+ const { setVideoUploadTags } = require('../../dd-trace/src/ci-visibility/test-video')
23
25
  const log = require('../../dd-trace/src/log')
24
26
  const {
25
27
  requestWebdriverioScreenshotUpload,
28
+ requestWebdriverioVideoUpload,
26
29
  sendWebdriverioWorkerMessage,
27
30
  SUITE_FINISH,
31
+ VIDEO_UPLOAD_FLUSH,
28
32
  WEBDRIVERIO_WORKER_ENV,
29
33
  } = require('../../datadog-instrumentations/src/mocha/webdriverio-protocol')
30
34
 
@@ -83,6 +87,11 @@ const {
83
87
  TELEMETRY_TEST_SESSION,
84
88
  } = require('../../dd-trace/src/ci-visibility/telemetry')
85
89
 
90
+ // Load the recorder and capture clocks before test code can replace globals with fake timers.
91
+ const createWebdriverioVideo = require('./webdriverio-video')
92
+
93
+ const dateNow = Date.now
94
+
86
95
  const jasmineAdapterRunAsyncEndCh = 'tracing:orchestrion:@wdio/jasmine-framework:JasmineAdapter_run:asyncEnd'
87
96
  const jasmineDoneCh = 'ci:webdriverio:jasmine:done'
88
97
  const jasmineExecuteAsyncEndCh = 'tracing:orchestrion:@wdio/utils:executeAsync:asyncEnd'
@@ -106,6 +115,7 @@ const WEBDRIVERIO_JASMINE_FAILED_EXPECTATION_COUNT = Symbol('webdriverioJasmineF
106
115
  const WEBDRIVERIO_JASMINE_FUNCTION_TYPE = Symbol('webdriverioJasmineFunctionType')
107
116
  const WEBDRIVERIO_JASMINE_TEST = Symbol('webdriverioJasmineTest')
108
117
  const isWebdriverioWorker = !!getEnvironmentVariable(WEBDRIVERIO_WORKER_ENV)
118
+ const shouldRecordWebdriverioVideos = getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED', true) === true
109
119
  const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])
110
120
  const BASE64_RE = /^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$/
111
121
 
@@ -121,6 +131,20 @@ const BASE64_RE = /^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$
121
131
  * @property {string|undefined} status
122
132
  */
123
133
 
134
+ /**
135
+ * Waits for an attempt's final capture, bounded by its existing deadline, without waiting for encoding or upload.
136
+ *
137
+ * @param {ReturnType<typeof createWebdriverioVideo>} video
138
+ * @param {Promise<unknown>} [finishPromise]
139
+ */
140
+ function waitForWebdriverioCapture (video, finishPromise) {
141
+ if (!video) return finishPromise
142
+ // WDIO awaits its async framework hook, so bridge the recorder's callback before it can start the next attempt.
143
+ /** @type {Promise<void>} */
144
+ const captureWait = new Promise(resolve => video.waitForCapture(resolve))
145
+ return finishPromise ? Promise.all([finishPromise, captureWait]) : captureWait
146
+ }
147
+
124
148
  /**
125
149
  * Normalizes a WebdriverIO Jasmine spec identifier to a filesystem path.
126
150
  *
@@ -128,7 +152,20 @@ const BASE64_RE = /^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$
128
152
  * @returns {string|undefined}
129
153
  */
130
154
  function normalizeJasmineFile (file) {
131
- return file?.startsWith('file://') ? fileURLToPath(file) : file
155
+ return file?.startsWith('file://') ? require('node:url').fileURLToPath(file) : file
156
+ }
157
+
158
+ function getWebdriverioJasmineSpecResult (spec) {
159
+ if (spec?.result) return spec.result
160
+ if (typeof spec?.startedEvent === 'function') return spec.startedEvent()
161
+ if (!spec?.id) return
162
+ return {
163
+ description: spec.description,
164
+ filename: spec.filename,
165
+ fullName: typeof spec.getFullName === 'function' ? spec.getFullName() : spec.description,
166
+ id: spec.id,
167
+ parentSuiteId: spec.parentSuiteId,
168
+ }
132
169
  }
133
170
 
134
171
  /**
@@ -245,14 +282,19 @@ function getJasmineFailureFile (result, specs) {
245
282
  class MochaPlugin extends CiPlugin {
246
283
  static id = 'mocha'
247
284
 
285
+ #pendingWebdriverioVideos = new Set()
286
+ #webdriverioCapture
287
+ #webdriverioVideoFlushTimer
288
+
248
289
  constructor (...args) {
249
290
  super(...args)
250
291
 
251
292
  this._testTitleToParams = {}
252
293
  this.sourceRoot = process.cwd()
253
294
  this._webdriverioScreenshotUploads = new WeakMap()
254
- this._pendingWebdriverioScreenshotUploads = 0
255
- this._webdriverioScreenshotUploadCallbacks = []
295
+ this._webdriverioVideos = new Map()
296
+ this._pendingWebdriverioMediaUploads = 0
297
+ this._webdriverioMediaUploadCallbacks = []
256
298
 
257
299
  this.addSub('ci:webdriverio:screenshot:capabilities', (ctx) => {
258
300
  ctx.enabled = Boolean(
@@ -283,6 +325,36 @@ class MochaPlugin extends CiPlugin {
283
325
  exporter.uploadTestScreenshot({ content, traceId, idempotencyKey, capturedAtMs }, onDone)
284
326
  })
285
327
 
328
+ this.addSub('ci:webdriverio:video:capabilities',
329
+ /** @type {import('node:diagnostics_channel').ChannelListener} */ (
330
+ /** @param {{enabled?: boolean}} ctx */
331
+ (ctx) => {
332
+ ctx.enabled = Boolean(
333
+ shouldRecordWebdriverioVideos &&
334
+ this._tracerConfig.testOptimization?.DD_TEST_FAILURE_VIDEOS_ENABLED &&
335
+ (isWebdriverioWorker
336
+ ? this.libraryConfig?.isTestFailureVideosEnabled && createWebdriverioVideo.isFfmpegAvailable()
337
+ : this.tracer._exporter?.canUploadTestVideos?.())
338
+ )
339
+ }
340
+ ))
341
+
342
+ this.addSub('ci:webdriverio:video:upload',
343
+ /** @type {import('node:diagnostics_channel').ChannelListener} */ (
344
+ /**
345
+ * @param {{filePath: string, traceId: string, idempotencyKey: string, capturedAtMs: number,
346
+ * signal?: AbortSignal, onDone: (error?: Error) => void}} options
347
+ */
348
+ ({ filePath, traceId, idempotencyKey, capturedAtMs, signal, onDone }) => {
349
+ const exporter = this.tracer?._exporter
350
+ if (!exporter?.canUploadTestVideos?.() || !exporter.uploadTestVideo) {
351
+ onDone(new Error('WebdriverIO video upload is not supported by the active Test Optimization transport'))
352
+ return
353
+ }
354
+ exporter.uploadTestVideo({ filePath, traceId, idempotencyKey, capturedAtMs, signal }, onDone)
355
+ }
356
+ ))
357
+
286
358
  this.addSub('ci:mocha:worker:configuration', ({
287
359
  libraryConfig,
288
360
  repositoryRoot,
@@ -306,6 +378,19 @@ class MochaPlugin extends CiPlugin {
306
378
  'active Test Optimization transport.'
307
379
  )
308
380
  }
381
+ if (
382
+ testFramework === WEBDRIVERIO_FRAMEWORK &&
383
+ shouldRecordWebdriverioVideos &&
384
+ this._tracerConfig.testOptimization.DD_TEST_FAILURE_VIDEOS_ENABLED &&
385
+ !(isWebdriverioWorker
386
+ ? libraryConfig.isTestFailureVideosEnabled
387
+ : this.tracer._exporter?.canUploadTestVideos?.())
388
+ ) {
389
+ log.warn(
390
+ 'DD_TEST_FAILURE_VIDEOS_ENABLED is true, but WebdriverIO video upload is not supported by the %s',
391
+ 'active Test Optimization transport.'
392
+ )
393
+ }
309
394
  if (testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
310
395
  this._webdriverioJasmineState = {
311
396
  completedTestStatuses: new Map(),
@@ -481,7 +566,14 @@ class MochaPlugin extends CiPlugin {
481
566
  this.addSub(jasmineReporterSpecDoneEndCh, (ctx) => {
482
567
  if (this.testFrameworkAdapter === WEBDRIVERIO_JASMINE_ADAPTER) {
483
568
  const result = ctx.result
484
- const finishPromise = this.#finishWebdriverioJasmineTest(ctx.arguments?.[0], ctx.self?._specs)
569
+ const testResult = ctx.arguments?.[0]
570
+ const test = this._webdriverioJasmineState?.tests.get(testResult?.id)
571
+ const video = this._webdriverioVideos.get(test?.span)
572
+ const finishTestPromise = this.#finishWebdriverioJasmineTest(testResult, ctx.self?._specs)
573
+ // Replay can defer video.finish(), which must start the final capture before we wait for it.
574
+ const finishPromise = finishTestPromise
575
+ ? finishTestPromise.then(() => waitForWebdriverioCapture(video))
576
+ : waitForWebdriverioCapture(video)
485
577
  if (finishPromise) {
486
578
  ctx.result = finishPromise.then(() => result)
487
579
  }
@@ -559,9 +651,9 @@ class MochaPlugin extends CiPlugin {
559
651
  testSuiteMetadata[TEST_ITR_FORCED_RUN] = 'true'
560
652
  this.telemetry.count(TELEMETRY_ITR_FORCED_TO_RUN, { testLevel: 'suite' })
561
653
  }
562
- testSuiteMetadata[TEST_SOURCE_FILE] = this.repositoryRoot !== this.sourceRoot && !!this.repositoryRoot
563
- ? getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
564
- : testSuite
654
+ testSuiteMetadata[TEST_SOURCE_FILE] = this.repositoryRoot === this.sourceRoot || !this.repositoryRoot
655
+ ? testSuite
656
+ : getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
565
657
  if (testSuiteMetadata[TEST_SOURCE_FILE]) {
566
658
  testSuiteMetadata[TEST_SOURCE_START] = 1
567
659
  }
@@ -641,6 +733,8 @@ class MochaPlugin extends CiPlugin {
641
733
  this.addBind('ci:mocha:test:start', (ctx) => {
642
734
  const store = storage('legacy').getStore()
643
735
  const span = this.startTestSpan(ctx)
736
+ const { isDisabled, isAttemptToFix } = /** @type {{isDisabled?: boolean, isAttemptToFix?: boolean}} */ (ctx)
737
+ if (!isDisabled || isAttemptToFix) this.#startWebdriverioVideo(span)
644
738
 
645
739
  ctx.parentStore = store
646
740
  ctx.currentStore = { ...store, span }
@@ -650,13 +744,42 @@ class MochaPlugin extends CiPlugin {
650
744
  return ctx.currentStore
651
745
  })
652
746
 
747
+ this.addSub('ci:mocha:after-each:finish', (ctx) => {
748
+ const context = /** @type {{isWaiting: boolean, onDone: () => void}} */ (ctx)
749
+ const video = this.#webdriverioCapture
750
+ this.#webdriverioCapture = undefined
751
+ if (!video) return
752
+ context.isWaiting = true
753
+ video.waitForCapture(context.onDone)
754
+ })
755
+
653
756
  this.addSub('ci:mocha:worker:finish', ({ onDone } = {}) => {
654
- const flush = () => this.tracer._exporter.flush(onDone)
655
- if (this._pendingWebdriverioScreenshotUploads === 0) {
757
+ // An interrupted runner may exit without reporting the active attempt.
758
+ for (const span of this._webdriverioVideos.keys()) {
759
+ this.#finishWebdriverioMedia(span, 'skip', () => {})
760
+ }
761
+ const flush = () => {
762
+ clearTimeout(this.#webdriverioVideoFlushTimer)
763
+ this.#webdriverioVideoFlushTimer = undefined
764
+ if (isWebdriverioWorker && this.libraryConfig?.isTestFailureVideosEnabled) {
765
+ sendWebdriverioWorkerMessage({ name: VIDEO_UPLOAD_FLUSH, content: { complete: true } })
766
+ }
767
+ this.tracer._exporter.flush(onDone)
768
+ }
769
+ if (this._pendingWebdriverioMediaUploads === 0) {
656
770
  flush()
657
771
  return
658
772
  }
659
- this._webdriverioScreenshotUploadCallbacks.push(flush)
773
+ if (isWebdriverioWorker && this.libraryConfig?.isTestFailureVideosEnabled) {
774
+ sendWebdriverioWorkerMessage({ name: VIDEO_UPLOAD_FLUSH })
775
+ }
776
+ this._webdriverioMediaUploadCallbacks.push(flush)
777
+ if (this.#pendingWebdriverioVideos.size) {
778
+ this.#webdriverioVideoFlushTimer ??= setTimeout(() => {
779
+ for (const video of this.#pendingWebdriverioVideos) video.cancel()
780
+ }, FINAL_FLUSH_TIMEOUT)
781
+ this.#webdriverioVideoFlushTimer.unref?.()
782
+ }
660
783
  })
661
784
 
662
785
  this.addSub('ci:mocha:test:finish', ({
@@ -720,7 +843,7 @@ class MochaPlugin extends CiPlugin {
720
843
  this.activeTestSpan = null
721
844
  }
722
845
  }
723
- if (status !== 'fail' || !this.#startWebdriverioScreenshotUpload(span, finishSpan)) {
846
+ if (!this.#finishWebdriverioMedia(span, status, finishSpan)) {
724
847
  finishSpan()
725
848
  }
726
849
  }
@@ -830,23 +953,24 @@ class MochaPlugin extends CiPlugin {
830
953
  this.runningTestProbe) {
831
954
  finishWait = this.waitForInFlightDiBreakpointHits()
832
955
  }
833
- let screenshotFinished = false
834
- let canFinishScreenshot = false
956
+ let mediaFinished = false
957
+ let canFinishMedia = false
835
958
  let replayFinished = !finishWait
836
959
  let spanFinished = false
837
960
  const finishWhenReady = () => {
838
- if (!spanFinished && replayFinished && (screenshotStarted === false || screenshotFinished)) {
961
+ if (!spanFinished && replayFinished && (mediaStarted === false || mediaFinished)) {
839
962
  spanFinished = true
840
963
  finishSpan()
841
964
  }
842
965
  }
843
- const screenshotStarted = this.#startWebdriverioScreenshotUpload(span, () => {
844
- screenshotFinished = true
845
- if (canFinishScreenshot) {
966
+ const video = this._webdriverioVideos.get(span)
967
+ const mediaStarted = this.#finishWebdriverioMedia(span, 'fail', () => {
968
+ mediaFinished = true
969
+ if (canFinishMedia) {
846
970
  finishWhenReady()
847
971
  }
848
972
  })
849
- canFinishScreenshot = true
973
+ canFinishMedia = true
850
974
  if (finishWait) {
851
975
  const finishReplay = () => {
852
976
  replayFinished = true
@@ -857,6 +981,9 @@ class MochaPlugin extends CiPlugin {
857
981
  promises.finishTestPromise = finishTestPromise
858
982
  }
859
983
  }
984
+ if (video && promises) {
985
+ promises.finishTestPromise = waitForWebdriverioCapture(video, promises.finishTestPromise)
986
+ }
860
987
  finishWhenReady()
861
988
  }
862
989
  })
@@ -986,10 +1113,7 @@ class MochaPlugin extends CiPlugin {
986
1113
  #startWebdriverioJasmineTest (result, specs, currentSuite) {
987
1114
  const state = this._webdriverioJasmineState
988
1115
  const currentStore = storage('legacy').getStore()
989
- if (!state || !result?.id) {
990
- return currentStore
991
- }
992
- if (state.completedTestStatuses.has(result.id)) {
1116
+ if (!state || !result?.id || state.completedTestStatuses.has(result.id)) {
993
1117
  return currentStore
994
1118
  }
995
1119
 
@@ -1131,6 +1255,7 @@ class MochaPlugin extends CiPlugin {
1131
1255
  title: test.title,
1132
1256
  })
1133
1257
  test.span = span
1258
+ if (!test.isDisabled || test.isAttemptToFix) this.#startWebdriverioVideo(span)
1134
1259
  test.currentStore = {
1135
1260
  ...parentStore,
1136
1261
  span,
@@ -1153,8 +1278,9 @@ class MochaPlugin extends CiPlugin {
1153
1278
  return
1154
1279
  }
1155
1280
 
1281
+ const specResult = getWebdriverioJasmineSpecResult(spec)
1156
1282
  if (this.libraryConfig?.isTestManagementTestsEnabled) {
1157
- this.#startWebdriverioJasmineTest(spec.result)
1283
+ this.#startWebdriverioJasmineTest(specResult)
1158
1284
  const test = this._webdriverioJasmineState?.tests.get(spec.id)
1159
1285
  if (test?.isDisabled && !test.isAttemptToFix) {
1160
1286
  spec.pend('Skipped by Datadog Test Management')
@@ -1486,6 +1612,71 @@ class MochaPlugin extends CiPlugin {
1486
1612
  }
1487
1613
  }
1488
1614
 
1615
+ /** @param {object} span - Span owning one WebdriverIO attempt */
1616
+ #startWebdriverioVideo (span) {
1617
+ if (this.testFramework !== WEBDRIVERIO_FRAMEWORK ||
1618
+ !shouldRecordWebdriverioVideos ||
1619
+ !this._tracerConfig.testOptimization?.DD_TEST_FAILURE_VIDEOS_ENABLED) return
1620
+ const canUpload = isWebdriverioWorker
1621
+ ? this.libraryConfig?.isTestFailureVideosEnabled
1622
+ : this.tracer._exporter?.canUploadTestVideos?.()
1623
+ if (!canUpload) return
1624
+
1625
+ const browser = globalThis._wdioGlobals?.get?.('browser') || globalThis.browser
1626
+ const video = createWebdriverioVideo(browser)
1627
+ this.#webdriverioCapture = video
1628
+ if (video) this._webdriverioVideos.set(span, video)
1629
+ }
1630
+
1631
+ /**
1632
+ * Finishes a span only after its independent screenshot and video uploads settle.
1633
+ *
1634
+ * @param {object} span
1635
+ * @param {string} status
1636
+ * @param {() => void} onDone
1637
+ */
1638
+ #finishWebdriverioMedia (span, status, onDone) {
1639
+ const video = this._webdriverioVideos.get(span)
1640
+ if (!video) return status === 'fail' && this.#startWebdriverioScreenshotUpload(span, onDone)
1641
+
1642
+ this._webdriverioVideos.delete(span)
1643
+ this.#pendingWebdriverioVideos.add(video)
1644
+ this._pendingWebdriverioMediaUploads++
1645
+ const traceId = span.context().toTraceId()
1646
+ const capturedAtMs = dateNow()
1647
+ let pending = 2
1648
+ const complete = () => {
1649
+ if (--pending !== 0) return
1650
+ onDone()
1651
+ this.#completeWebdriverioMediaUpload()
1652
+ }
1653
+ if (status !== 'fail' || !this.#startWebdriverioScreenshotUpload(span, complete)) complete()
1654
+ video.finish(status === 'fail', (filePath, index, callback) => {
1655
+ const options = {
1656
+ filePath,
1657
+ traceId,
1658
+ idempotencyKey: `${traceId}:webdriverio-failure-${index}.webm`,
1659
+ capturedAtMs,
1660
+ }
1661
+ if (isWebdriverioWorker) requestWebdriverioVideoUpload(options, callback)
1662
+ else this.tracer._exporter.uploadTestVideo(options, callback)
1663
+ }, result => {
1664
+ this.#pendingWebdriverioVideos.delete(video)
1665
+ setVideoUploadTags(span, result)
1666
+ complete()
1667
+ })
1668
+ return true
1669
+ }
1670
+
1671
+ /** Releases worker shutdown after all media-owned spans have finished. */
1672
+ #completeWebdriverioMediaUpload () {
1673
+ this._pendingWebdriverioMediaUploads--
1674
+ if (this._pendingWebdriverioMediaUploads !== 0) return
1675
+ const callbacks = this._webdriverioMediaUploadCallbacks
1676
+ this._webdriverioMediaUploadCallbacks = []
1677
+ for (const callback of callbacks) callback()
1678
+ }
1679
+
1489
1680
  /**
1490
1681
  * Captures and uploads the current WebdriverIO browser state once for a failed test attempt.
1491
1682
  *
@@ -1493,6 +1684,7 @@ class MochaPlugin extends CiPlugin {
1493
1684
  * @param {() => void} [onDone] - Called after the screenshot upload finishes
1494
1685
  */
1495
1686
  #startWebdriverioScreenshotUpload (span, onDone) {
1687
+ this._webdriverioVideos.get(span)?.capture()
1496
1688
  if (
1497
1689
  !span ||
1498
1690
  this.testFramework !== WEBDRIVERIO_FRAMEWORK ||
@@ -1527,7 +1719,7 @@ class MochaPlugin extends CiPlugin {
1527
1719
  finished: false,
1528
1720
  }
1529
1721
  this._webdriverioScreenshotUploads.set(span, upload)
1530
- this._pendingWebdriverioScreenshotUploads++
1722
+ this._pendingWebdriverioMediaUploads++
1531
1723
  const finishUpload = (result) => this.#finishWebdriverioScreenshotUpload(span, upload, result)
1532
1724
  let captureTimeout
1533
1725
  const failCapture = (error) => {
@@ -1586,18 +1778,10 @@ class MochaPlugin extends CiPlugin {
1586
1778
  const uploadCallbacks = upload.callbacks
1587
1779
  upload.callbacks = []
1588
1780
 
1589
- this._pendingWebdriverioScreenshotUploads--
1590
- let workerCallbacks = []
1591
- if (this._pendingWebdriverioScreenshotUploads === 0) {
1592
- workerCallbacks = this._webdriverioScreenshotUploadCallbacks
1593
- this._webdriverioScreenshotUploadCallbacks = []
1594
- }
1595
1781
  for (const callback of uploadCallbacks) {
1596
1782
  callback()
1597
1783
  }
1598
- for (const callback of workerCallbacks) {
1599
- callback()
1600
- }
1784
+ this.#completeWebdriverioMediaUpload()
1601
1785
  }
1602
1786
 
1603
1787
  /**
@@ -1616,7 +1800,7 @@ class MochaPlugin extends CiPlugin {
1616
1800
  }
1617
1801
  const exporter = this.tracer?._exporter
1618
1802
  const traceId = span.context().toTraceId()
1619
- const capturedAtMs = Date.now()
1803
+ const capturedAtMs = dateNow()
1620
1804
  let pendingUploads = screenshotList.length
1621
1805
  let hasUploadError = false
1622
1806
  const finishOne = (error) => {
@@ -1775,9 +1959,9 @@ class MochaPlugin extends CiPlugin {
1775
1959
  const testSuite = getTestSuitePath(testSuiteAbsolutePath, this.sourceRoot)
1776
1960
  const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuite)
1777
1961
 
1778
- extraTags[TEST_SOURCE_FILE] = this.repositoryRoot !== this.sourceRoot && !!this.repositoryRoot
1779
- ? getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
1780
- : testSuite
1962
+ extraTags[TEST_SOURCE_FILE] = this.repositoryRoot === this.sourceRoot || !this.repositoryRoot
1963
+ ? testSuite
1964
+ : getTestSuitePath(testSuiteAbsolutePath, this.repositoryRoot)
1781
1965
 
1782
1966
  if (isNew) {
1783
1967
  extraTags[TEST_IS_NEW] = 'true'