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
@@ -25,7 +25,7 @@ function wrapAddCommand (addCommand) {
25
25
  return startCh.runStores(ctx, () => {
26
26
  const res = addCommand.apply(this, arguments)
27
27
 
28
- res.then(() => finish(finishCh, errorCh, ctx), err => finish(finishCh, errorCh, ctx, err))
28
+ res.then(() => finish(ctx), err => finish(ctx, err))
29
29
 
30
30
  return res
31
31
  })
@@ -85,13 +85,11 @@ addHook({ name: '@redis/client', file: 'dist/lib/client/commands-queue.js', vers
85
85
 
86
86
  addHook({ name: 'redis', versions: ['>=2.6 <4'] }, redis => {
87
87
  shimmer.wrap(redis.RedisClient.prototype, 'internal_send_command', internalSendCommand => function (options) {
88
- if (!startCh.hasSubscribers) return internalSendCommand.apply(this, arguments)
89
-
90
- if (!options.callback) return internalSendCommand.apply(this, arguments)
88
+ if (!startCh.hasSubscribers || !options.callback) return internalSendCommand.apply(this, arguments)
91
89
 
92
90
  const ctx = getStartCtx(this, options.command, options.args)
93
91
  return startCh.runStores(ctx, () => {
94
- options.callback = wrapCallback(finishCh, errorCh, ctx, options.callback)
92
+ options.callback = wrapCallback(ctx, options.callback)
95
93
 
96
94
  try {
97
95
  return internalSendCommand.apply(this, arguments)
@@ -114,11 +112,11 @@ addHook({ name: 'redis', versions: ['>=0.12 <2.6'] }, redis => {
114
112
  const ctx = getStartCtx(this, command, args)
115
113
  return startCh.runStores(ctx, () => {
116
114
  if (typeof callback === 'function') {
117
- arguments[2] = wrapCallback(finishCh, errorCh, ctx, callback)
115
+ arguments[2] = wrapCallback(ctx, callback)
118
116
  } else if (Array.isArray(args) && typeof args.at(-1) === 'function') {
119
- args[args.length - 1] = wrapCallback(finishCh, errorCh, ctx, args.at(-1))
117
+ args[args.length - 1] = wrapCallback(ctx, args.at(-1))
120
118
  } else {
121
- arguments[2] = wrapCallback(finishCh, errorCh, ctx)
119
+ arguments[2] = wrapCallback(ctx)
122
120
  }
123
121
 
124
122
  try {
@@ -155,20 +153,36 @@ function getStartCtx (client, command, args, argsStartIndex) {
155
153
  }
156
154
  }
157
155
 
158
- function wrapCallback (finishCh, errorCh, ctx, callback) {
156
+ /**
157
+ * @param {Record<string, unknown> & { error?: Error }} ctx
158
+ * @param {Function} [callback]
159
+ */
160
+ function wrapCallback (ctx, callback) {
159
161
  if (typeof callback !== 'function') return callback
160
162
  return function (err) {
161
- return finish(finishCh, errorCh, ctx, err, callback, this, arguments)
163
+ return finish(ctx, err, callback, this, arguments)
162
164
  }
163
165
  }
164
166
 
165
- function finish (finishCh, errorCh, ctx, error, callback, thisArg, args) {
167
+ /**
168
+ * @param {Record<string, unknown> & { error?: Error }} ctx
169
+ * @param {Error} [error]
170
+ * @param {Function} [callback]
171
+ * @param {object} [thisArg]
172
+ * @param {Iterable<unknown>} [args]
173
+ */
174
+ function finish (ctx, error, callback, thisArg, args) {
166
175
  if (error) {
167
176
  ctx.error = error
168
177
  errorCh.publish(ctx)
169
178
  }
170
179
  if (callback) {
171
- return finishCh.runStores(ctx, callback, thisArg, ...args)
180
+ return finishCh.runStores(
181
+ ctx,
182
+ /** @type {Parameters<typeof finishCh.runStores>[1]} */ (callback),
183
+ thisArg,
184
+ ...(/** @type {Iterable<unknown>} */ (args))
185
+ )
172
186
  }
173
- finishCh.publish(ctx)
187
+ return finishCh.publish(ctx)
174
188
  }
@@ -91,6 +91,28 @@ function resolveLayerRouteOrForwardError (meta, layer, next) {
91
91
  }
92
92
  }
93
93
 
94
+ // Wrapped `next` -> the host continuation it calls.
95
+ const hostNexts = new WeakMap()
96
+
97
+ /**
98
+ * Routers store their continuation on `req.next` once per `handle()`, and host
99
+ * APIs call it directly (express `res.format`, `res.sendFile` and `res.render`
100
+ * errors). Point it at the wrapper the current handler receives when that
101
+ * wrapper wraps the same continuation: `req.next` is the host continuation
102
+ * itself, or an earlier layer's wrapper of it. Route handlers receive a
103
+ * route-local continuation from `Route.dispatch`, so `req.next` keeps the
104
+ * router-level one there. A nested router restores its parent's `req.next`
105
+ * when it finishes.
106
+ *
107
+ * @param {import('node:http').IncomingMessage | undefined} req
108
+ * @param {Function} hostNext
109
+ * @param {Function} wrappedNext
110
+ */
111
+ function adoptRequestNext (req, hostNext, wrappedNext) {
112
+ hostNexts.set(wrappedNext, hostNext)
113
+ if (req && (req.next === hostNext || hostNexts.get(req.next) === hostNext)) req.next = wrappedNext
114
+ }
115
+
94
116
  /**
95
117
  * Build the request/error dispatch wrappers for one host (`express` / `router`).
96
118
  * They wrap the layer's prototype dispatch and read the side-table metadata, so
@@ -171,6 +193,7 @@ function createLayerDispatchWrappers (name) {
171
193
  }
172
194
 
173
195
  const wrappedNext = typeof next === 'function' ? wrapNext(req, meta.name, next) : next
196
+ if (wrappedNext !== next) adoptRequestNext(req, next, wrappedNext)
174
197
  enterChannel.publish({ name: meta.name, req, route, layer: this })
175
198
 
176
199
  try {
@@ -199,6 +222,7 @@ function createLayerDispatchWrappers (name) {
199
222
  }
200
223
 
201
224
  const wrappedNext = typeof next === 'function' ? wrapNext(req, meta.name, next) : next
225
+ if (wrappedNext !== next) adoptRequestNext(req, next, wrappedNext)
202
226
  enterChannel.publish({ name: meta.name, req, route, layer: this })
203
227
 
204
228
  try {
@@ -223,6 +247,7 @@ function createLayerDispatchWrappers (name) {
223
247
 
224
248
  let calls = 0
225
249
  if (typeof next === 'function') {
250
+ const hostNext = next
226
251
  next = shimmer.wrapCallback(next, originalNext => function next (error) {
227
252
  calls++
228
253
  if (calls === 1) {
@@ -238,6 +263,7 @@ function createLayerDispatchWrappers (name) {
238
263
 
239
264
  originalNext.apply(this, arguments)
240
265
  })
266
+ adoptRequestNext(req, hostNext, next)
241
267
  }
242
268
 
243
269
  const route = needMultiMatch ? resolveLayerRoute(meta, layer) : captureRoute
@@ -273,6 +299,7 @@ function createLayerDispatchWrappers (name) {
273
299
 
274
300
  let calls = 0
275
301
  if (typeof next === 'function') {
302
+ const hostNext = next
276
303
  next = shimmer.wrapCallback(next, originalNext => function next (nextError) {
277
304
  calls++
278
305
  if (calls === 1) {
@@ -288,6 +315,7 @@ function createLayerDispatchWrappers (name) {
288
315
 
289
316
  originalNext.apply(this, arguments)
290
317
  })
318
+ adoptRequestNext(req, hostNext, next)
291
319
  }
292
320
 
293
321
  const route = needMultiMatch ? resolveLayerRoute(meta, layer) : captureRoute
@@ -336,6 +364,7 @@ function createLayerDispatchWrappers (name) {
336
364
  const nextIndex = isErrorHandler ? 3 : 2
337
365
  let calls = 0
338
366
  if (typeof args[nextIndex] === 'function') {
367
+ const hostNext = args[nextIndex]
339
368
  args[nextIndex] = shimmer.wrapCallback(args[nextIndex], originalNext => function next (error) {
340
369
  calls++
341
370
  if (calls === 1) {
@@ -351,6 +380,7 @@ function createLayerDispatchWrappers (name) {
351
380
 
352
381
  originalNext.apply(this, arguments)
353
382
  })
383
+ adoptRequestNext(req, hostNext, args[nextIndex])
354
384
  }
355
385
 
356
386
  const route = needMultiMatch ? resolveLayerRoute(meta, layer) : captureRoute
@@ -5,9 +5,7 @@
5
5
  /** @returns {{ isRumInstrumented: boolean, isRumActive: boolean, rumSamplingRate: number | null }} */
6
6
  function detectRum () {
7
7
  const isRumInstrumented = !!window.DD_RUM
8
- const isRumActive = window.DD_RUM && window.DD_RUM.getInternalContext
9
- ? !!window.DD_RUM.getInternalContext()
10
- : false
8
+ const isRumActive = !!(window.DD_RUM && window.DD_RUM.getInternalContext && window.DD_RUM.getInternalContext())
11
9
  const rumInitConfiguration = window.DD_RUM && window.DD_RUM.getInitConfiguration
12
10
  ? window.DD_RUM.getInitConfiguration()
13
11
  : undefined
@@ -26,9 +24,7 @@ function stopRumSession () {
26
24
 
27
25
  function stopRumSessionAndReportActivity () {
28
26
  if (window.DD_RUM && window.DD_RUM.stopSession) {
29
- const isRumActive = window.DD_RUM.getInternalContext
30
- ? !!window.DD_RUM.getInternalContext()
31
- : false
27
+ const isRumActive = !!(window.DD_RUM.getInternalContext && window.DD_RUM.getInternalContext())
32
28
  window.DD_RUM.stopSession()
33
29
  return isRumActive
34
30
  }
@@ -659,8 +659,7 @@ function createMainProcessReporter (reporterState) {
659
659
  },
660
660
 
661
661
  onTaskUpdate (packs, events) {
662
- if (!isReporterActive()) return
663
- if (!events) return
662
+ if (!isReporterActive() || !events) return
664
663
 
665
664
  for (const event of events) {
666
665
  if (event[1] === 'test-retried') {
@@ -685,8 +684,7 @@ function createMainProcessReporter (reporterState) {
685
684
  },
686
685
 
687
686
  onFinished (files) {
688
- if (!isReporterActive()) return
689
- if (!files) return
687
+ if (!isReporterActive() || !files) return
690
688
 
691
689
  for (const file of files) {
692
690
  const testModule = createTestModuleFromFile(file)
@@ -1276,10 +1274,12 @@ function reportFinalTestAttempt (testReport) {
1276
1274
  isQuarantined: testProperties.isQuarantined,
1277
1275
  })
1278
1276
  }
1277
+ const attemptIndex = task.meta?.__ddTestOptCurrentAttemptIndex ?? 0
1278
+ const attemptStartTime = task.meta?.__ddTestOptAttemptStartTimes?.[attemptIndex]
1279
1279
  const {
1280
1280
  isRumActive,
1281
1281
  testExecutionId,
1282
- } = getRumCorrelation(task)
1282
+ } = getRumCorrelation(task, attemptIndex)
1283
1283
  testSkipCh.publish({
1284
1284
  ...browserEnvironment,
1285
1285
  testName,
@@ -1291,6 +1291,8 @@ function reportFinalTestAttempt (testReport) {
1291
1291
  isRumActive,
1292
1292
  isTestFrameworkWorker: true,
1293
1293
  requestErrorTags: state.requestErrorTags,
1294
+ startTime: Number.isFinite(attemptStartTime) ? attemptStartTime : undefined,
1295
+ duration: task.meta?.__ddTestOptAttemptDurations?.[attemptIndex],
1294
1296
  testStartLine: task.location?.line,
1295
1297
  testExecutionId,
1296
1298
  ...testSuiteStore,
@@ -300,8 +300,7 @@ function configureEfdSuiteTracker (testFilepaths, repositoryRoot) {
300
300
  * @param {boolean} hasNewTest
301
301
  */
302
302
  function reserveEarlyFlakeDetectionSuite (testSuite, hasNewTest) {
303
- if (!isEfdSuiteAdmissionEnabled || typeof testSuite !== 'string') return false
304
- if (isEarlyFlakeDetectionFaulty) return false
303
+ if (!isEfdSuiteAdmissionEnabled || typeof testSuite !== 'string' || isEarlyFlakeDetectionFaulty) return false
305
304
 
306
305
  if (hasNewTest) {
307
306
  suitesWithNewTests.add(testSuite)
@@ -1201,11 +1200,7 @@ function getSortWrapper (sort, frameworkVersion) {
1201
1200
 
1202
1201
  function getFinishWrapper (exitOrClose) {
1203
1202
  return async function () {
1204
- if (finishedSessionContexts.has(this)) {
1205
- return exitOrClose.apply(this, arguments)
1206
- }
1207
-
1208
- if (!testSessionFinishCh.hasSubscribers) {
1203
+ if (finishedSessionContexts.has(this) || !testSessionFinishCh.hasSubscribers) {
1209
1204
  return exitOrClose.apply(this, arguments)
1210
1205
  }
1211
1206
  finishedSessionContexts.add(this)
@@ -1230,15 +1225,15 @@ function getFinishWrapper (exitOrClose) {
1230
1225
  const tests = getTypeTasks(this.state.getFiles())
1231
1226
  const hasExecutedTests = tests.some(test =>
1232
1227
  test.mode !== 'skip' && test.mode !== 'todo' &&
1233
- test.result?.state !== 'skip' && test.result?.state !== 'todo'
1228
+ (test.result?.state === 'pass' || test.result?.state === 'fail')
1234
1229
  )
1235
1230
  const hasErrors = hasRunError || error ||
1236
1231
  this.state.getCountOfFailedTests() > 0 || this.state.getUnhandledErrors().length > 0
1237
- const testSessionEmptyReason = !hasErrors && !hasExecutedTests
1238
- ? (isEmptyShard
1239
- ? 'zero_test_shard'
1240
- : tests.length > 0 || skippedSuites.length > 0 ? 'all_tests_skipped' : 'zero_tests')
1241
- : undefined
1232
+ const testSessionEmptyReason = hasErrors || hasExecutedTests
1233
+ ? undefined
1234
+ : isEmptyShard
1235
+ ? 'zero_test_shard'
1236
+ : tests.length > 0 || skippedSuites.length > 0 ? 'all_tests_skipped' : 'zero_tests'
1242
1237
  const status = hasErrors ? 'fail' : (testSessionEmptyReason ? 'skip' : 'pass')
1243
1238
  const flushPromise = getChannelPromise(testSessionFinishCh, {
1244
1239
  status,
@@ -1758,8 +1753,7 @@ async function reportTypecheckFile (file, sessionConfiguration, frameworkVersion
1758
1753
  }
1759
1754
 
1760
1755
  async function reportTypecheckResults (result, frameworkVersion, ctx, typechecker, files = result?.files) {
1761
- if (!testSuiteFinishCh.hasSubscribers) return
1762
- if (!Array.isArray(result?.files) || !Array.isArray(files)) return
1756
+ if (!testSuiteFinishCh.hasSubscribers || !Array.isArray(result?.files) || !Array.isArray(files)) return
1763
1757
 
1764
1758
  const setupState = ctx && mainProcessSetupStates.get(ctx)
1765
1759
  if (
@@ -2239,8 +2233,6 @@ addHook({
2239
2233
  versions: ['>=5.0.0'],
2240
2234
  filePattern: 'dist/chunks/index.*',
2241
2235
  }, (vitestPackage, frameworkVersion) => {
2242
- if (!getVitestExport(vitestPackage)) return vitestPackage
2243
-
2244
2236
  wrapVitestInternals(vitestPackage, frameworkVersion)
2245
2237
 
2246
2238
  const typecheckPoolWorker = getTypecheckPoolWorkerExport(vitestPackage)
@@ -33,7 +33,7 @@ const testManagementTestsCh = channel('ci:vitest:test-management-tests')
33
33
  const modifiedFilesCh = channel('ci:vitest:modified-files')
34
34
 
35
35
  const CLOSING_SCRIPT_TAG_RE = /<\/script/i
36
- const SERIALIZED_CONTEXT_PREFIX = '\u0000dd-vitest-context:'
36
+ const SERIALIZED_CONTEXT_PREFIX = '\0dd-vitest-context:'
37
37
 
38
38
  const workerReportTraceCh = channel('ci:vitest:worker-report:trace')
39
39
  const workerReportCoverageCh = channel('ci:vitest:worker-report:coverage')
@@ -299,7 +299,7 @@ function getVitestTestProperties (providedContext, testSuiteAbsolutePath, testNa
299
299
 
300
300
  return {
301
301
  testSuite: testProperties?.testSuite,
302
- isNew: Array.isArray(knownTests) ? !knownTests.includes(testName) : false,
302
+ isNew: Array.isArray(knownTests) && !knownTests.includes(testName),
303
303
  isModified: testProperties?.isModified === true,
304
304
  isAttemptToFix: testManagementProperties.isAttemptToFix,
305
305
  isDisabled: testManagementProperties.isDisabled,
@@ -354,8 +354,7 @@ function hasConcurrentTask (tasks) {
354
354
  if (!tasks) return false
355
355
 
356
356
  for (const task of tasks) {
357
- if (task.concurrent === true) return true
358
- if (hasConcurrentTask(task.tasks)) return true
357
+ if (task.concurrent === true || hasConcurrentTask(task.tasks)) return true
359
358
  }
360
359
 
361
360
  return false
@@ -3,9 +3,9 @@
3
3
  const { getEnvironmentVariable } = require('../../dd-trace/src/config/helper')
4
4
  const { isTrue } = require('../../dd-trace/src/util')
5
5
 
6
- const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
7
-
8
6
  if (getEnvironmentVariable('DD_VITEST_WORKER') === '1') {
7
+ const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
8
+
9
9
  // In no-worker-init mode the main process reports Vitest events from reporter hooks.
10
10
  // Loading worker instrumentation here would reintroduce the worker tracer path.
11
11
  // eslint-disable-next-line eslint-rules/eslint-process-env
@@ -6,6 +6,7 @@ const realSetTimeout = setTimeout
6
6
  const { AsyncResource } = require('node:async_hooks')
7
7
  const { fileURLToPath } = require('node:url')
8
8
 
9
+ const { createFinalFlushTimeoutError, FINAL_FLUSH_TIMEOUT } = require('../../dd-trace/src/ci-visibility/final-flush')
9
10
  const { EMPTY_EFD_RETRY_POLICY } = require('../../dd-trace/src/ci-visibility/efd-retry-policy')
10
11
  const { RUM_TEST_EXECUTION_ID_COOKIE_NAME } = require('../../dd-trace/src/ci-visibility/rum')
11
12
  const { getEnvironmentVariable, getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
@@ -30,6 +31,9 @@ const {
30
31
  SCREENSHOT_UPLOAD_RESPONSE,
31
32
  sendWebdriverioWorkerMessage,
32
33
  SUITE_FINISH,
34
+ VIDEO_UPLOAD,
35
+ VIDEO_UPLOAD_FLUSH,
36
+ VIDEO_UPLOAD_RESPONSE,
33
37
  WEBDRIVERIO_WORKER_ENV,
34
38
  WEBDRIVERIO_WORKER_EVENT,
35
39
  WEBDRIVERIO_WORKER_ORIGIN,
@@ -52,6 +56,8 @@ const logSubmissionFlushCh = channel('ci:log-submission:flush')
52
56
  const modifiedFilesCh = channel('ci:mocha:modified-files')
53
57
  const screenshotCapabilitiesCh = channel('ci:webdriverio:screenshot:capabilities')
54
58
  const screenshotUploadCh = channel('ci:webdriverio:screenshot:upload')
59
+ const videoCapabilitiesCh = channel('ci:webdriverio:video:capabilities')
60
+ const videoUploadCh = channel('ci:webdriverio:video:upload')
55
61
  const testManagementTestsCh = channel('ci:mocha:test-management-tests')
56
62
  const workerConfigurationCh = channel('ci:mocha:worker:configuration')
57
63
  const workerReportLogsCh = channel('ci:mocha:worker-report:logs')
@@ -59,8 +65,10 @@ const workerReportTelemetryCh = channel('ci:mocha:worker-report:telemetry')
59
65
  const workerReportTraceCh = channel('ci:mocha:worker-report:trace')
60
66
 
61
67
  const jasmineAdapterInitCh = tracingChannel('orchestrion:@wdio/jasmine-framework:JasmineAdapter_init')
68
+ const mochaAdapterInitCh = tracingChannel('orchestrion:@wdio/mocha-framework:MochaAdapter_init')
62
69
  const baseReporterWaitForSyncCh = tracingChannel('orchestrion:@wdio/runner:BaseReporter_waitForSync')
63
70
  const runnerRunCh = tracingChannel('orchestrion:@wdio/runner:Runner_run')
71
+ const runnerStartSessionCh = tracingChannel('orchestrion:@wdio/runner:Runner_startSession')
64
72
  const executeAsyncCh = tracingChannel('orchestrion:@wdio/utils:executeAsync')
65
73
  const launcherStartInstanceCh = tracingChannel('orchestrion:@wdio/cli:Launcher_startInstance')
66
74
  const configParserShardCh = tracingChannel('orchestrion:@wdio/config:ConfigParser_shard')
@@ -80,7 +88,7 @@ const TEST_FRAMEWORK = 'webdriverio'
80
88
  const SET_RUM_COOKIE_SCRIPT = 'cookie => { globalThis.document.cookie = cookie }'
81
89
  const RUM_FLUSH_WAIT_TIME = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
82
90
  const isWebdriverioWorker = !!getEnvironmentVariable(WEBDRIVERIO_WORKER_ENV)
83
- let jasmineWorkerRequestId = 0
91
+ let workerRequestId = 0
84
92
 
85
93
  const loadCh = channel('dd-trace:instrumentation:load')
86
94
  if (loadCh.hasSubscribers) {
@@ -289,8 +297,7 @@ async function preloadRumNavigation () {
289
297
  try {
290
298
  const browser = this
291
299
  const testExecutionId = getRumTestExecutionId(browser)
292
- if (!canCorrelateRumBrowser(browser)) return
293
- if (!rumRunnerBrowsers.has(browser)) return
300
+ if (!canCorrelateRumBrowser(browser) || !rumRunnerBrowsers.has(browser)) return
294
301
 
295
302
  retainRumBrowser(browser)
296
303
  if (!testExecutionId) return
@@ -363,9 +370,7 @@ async function handleRumNavigation (context) {
363
370
  log.debug("RUM was detected on the page, but it isn't active because the sampling rate is below 100%")
364
371
  }
365
372
  const testExecutionId = getRumTestExecutionId(browser, isRumActive)
366
- if (sampledOut) return
367
-
368
- if (!testExecutionId) {
373
+ if (sampledOut || !testExecutionId) {
369
374
  return
370
375
  }
371
376
  if (!isRumActive && browser.isBidi) return
@@ -957,6 +962,8 @@ function waitForRumTestStart (context) {
957
962
  * @property {boolean|undefined} hasTests
958
963
  * @property {number|undefined} exitCode
959
964
  * @property {number|undefined} retries
965
+ * @property {AbortController|undefined} videoController
966
+ * @property {ReturnType<typeof setTimeout>|undefined} videoFlushTimer
960
967
  */
961
968
 
962
969
  /**
@@ -1019,6 +1026,7 @@ function createWorkerConfiguration () {
1019
1026
  isItrEnabled: false,
1020
1027
  isKnownTestsEnabled: false,
1021
1028
  isTestFailureScreenshotsEnabled: false,
1029
+ isTestFailureVideosEnabled: false,
1022
1030
  isSuitesSkippingEnabled: false,
1023
1031
  isTestDynamicInstrumentationEnabled: false,
1024
1032
  isTestManagementTestsEnabled: false,
@@ -1032,22 +1040,23 @@ function createWorkerConfiguration () {
1032
1040
  }
1033
1041
 
1034
1042
  /**
1035
- * Configures a Jasmine worker and waits for its coordinator-owned optimization settings.
1043
+ * Configures a framework worker before browser session creation using its coordinator-owned settings.
1036
1044
  *
1037
1045
  * @param {{_jrunner?: {env?: {addReporter?: (reporter: object) => void}}, _specs?: string[]}} adapter
1038
1046
  * @param {{
1039
1047
  * resolveCallback?: (onDone: () => void) => void,
1040
1048
  * rejectCallback?: (onDone: () => void) => void
1041
1049
  * }} context
1050
+ * @param {'mocha'|'jasmine'} testFrameworkAdapter
1042
1051
  */
1043
- function initializeJasmineWorker (adapter, context) {
1052
+ function initializeFrameworkWorker (adapter, context, testFrameworkAdapter) {
1044
1053
  if (!isWebdriverioWorker) {
1045
1054
  return
1046
1055
  }
1047
1056
 
1048
1057
  const specs = adapter?._specs || []
1049
1058
 
1050
- if (jasmineDoneCh.hasSubscribers) {
1059
+ if (testFrameworkAdapter === JASMINE_FRAMEWORK_ADAPTER && jasmineDoneCh.hasSubscribers) {
1051
1060
  adapter?._jrunner?.env?.addReporter?.({
1052
1061
  /**
1053
1062
  * Publishes run-level failures that are absent from suiteDone.
@@ -1066,11 +1075,11 @@ function initializeJasmineWorker (adapter, context) {
1066
1075
  * @param {() => void} onDone
1067
1076
  */
1068
1077
  const waitForCoordinator = onDone => {
1069
- const requestId = `${process.pid}-${++jasmineWorkerRequestId}`
1078
+ const requestId = `${process.pid}-${++workerRequestId}`
1070
1079
  let finished = false
1071
1080
 
1072
1081
  /**
1073
- * Releases Jasmine initialization exactly once.
1082
+ * Releases framework initialization exactly once.
1074
1083
  *
1075
1084
  * @param {object} [configuration]
1076
1085
  */
@@ -1087,7 +1096,7 @@ function initializeJasmineWorker (adapter, context) {
1087
1096
  repositoryRoot: configuration.repositoryRoot,
1088
1097
  specs,
1089
1098
  testFramework: TEST_FRAMEWORK,
1090
- testFrameworkAdapter: JASMINE_FRAMEWORK_ADAPTER,
1099
+ testFrameworkAdapter,
1091
1100
  })
1092
1101
  onDone()
1093
1102
  }
@@ -1111,7 +1120,7 @@ function initializeJasmineWorker (adapter, context) {
1111
1120
  name: WORKER_READY,
1112
1121
  content: {
1113
1122
  requestId,
1114
- testFrameworkAdapter: JASMINE_FRAMEWORK_ADAPTER,
1123
+ testFrameworkAdapter,
1115
1124
  },
1116
1125
  }, error => {
1117
1126
  if (error) {
@@ -1327,6 +1336,9 @@ function configureCoordinator (state, response) {
1327
1336
  const screenshotCapabilities = {}
1328
1337
  screenshotCapabilitiesCh.publish(screenshotCapabilities)
1329
1338
  configuration.isTestFailureScreenshotsEnabled = screenshotCapabilities.enabled === true
1339
+ const videoCapabilities = {}
1340
+ videoCapabilitiesCh.publish(videoCapabilities)
1341
+ configuration.isTestFailureVideosEnabled = videoCapabilities.enabled === true
1330
1342
 
1331
1343
  configuration.repositoryRoot = repositoryRoot
1332
1344
  if (err || !libraryConfig) {
@@ -1667,6 +1679,13 @@ function handleWorkerMessage (state, workerRecord, message) {
1667
1679
  return
1668
1680
  }
1669
1681
 
1682
+ if (message.name === WORKER_READY || message.name === CONFIGURATION_REQUEST) {
1683
+ // Watch mode reuses the worker. Keep old requests cancelled while the new run gets a fresh signal.
1684
+ clearTimeout(workerRecord.videoFlushTimer)
1685
+ workerRecord.videoFlushTimer = undefined
1686
+ workerRecord.videoController?.abort(new Error('WebdriverIO video run completed'))
1687
+ workerRecord.videoController = undefined
1688
+ }
1670
1689
  if (message.name === WORKER_READY) {
1671
1690
  initializeCoordinator(state, configuration => {
1672
1691
  if (state.testFrameworkAdapter === JASMINE_FRAMEWORK_ADAPTER) {
@@ -1687,25 +1706,51 @@ function handleWorkerMessage (state, workerRecord, message) {
1687
1706
  handleConfigurationRequest(state, workerRecord, message)
1688
1707
  return
1689
1708
  }
1690
- if (message.name === SCREENSHOT_UPLOAD) {
1709
+ if (message.name === VIDEO_UPLOAD_FLUSH) {
1710
+ if (message.content?.complete) {
1711
+ clearTimeout(workerRecord.videoFlushTimer)
1712
+ workerRecord.videoFlushTimer = undefined
1713
+ // The worker may have reached its own deadline before the coordinator's timer fires.
1714
+ workerRecord.videoController?.abort(new Error('WebdriverIO video flush completed'))
1715
+ return
1716
+ }
1717
+ const controller = workerRecord.videoController ??= new AbortController()
1718
+ workerRecord.videoFlushTimer ??= setTimeout(() => {
1719
+ controller.abort(createFinalFlushTimeoutError())
1720
+ }, FINAL_FLUSH_TIMEOUT)
1721
+ workerRecord.videoFlushTimer.unref?.()
1722
+ return
1723
+ }
1724
+ if (message.name === SCREENSHOT_UPLOAD || message.name === VIDEO_UPLOAD) {
1725
+ const isVideo = message.name === VIDEO_UPLOAD
1726
+ const uploadCh = isVideo ? videoUploadCh : screenshotUploadCh
1691
1727
  const { requestId, ...content } = message.content || {}
1692
1728
  const respond = (error) => sendWorkerMessage(workerRecord, {
1693
1729
  origin: 'datadog',
1694
- name: SCREENSHOT_UPLOAD_RESPONSE,
1730
+ name: isVideo ? VIDEO_UPLOAD_RESPONSE : SCREENSHOT_UPLOAD_RESPONSE,
1695
1731
  content: {
1696
1732
  error: error?.message,
1697
1733
  requestId,
1698
1734
  },
1699
1735
  })
1700
- if (!requestId || !screenshotUploadCh.hasSubscribers) {
1701
- respond(new Error('WebdriverIO screenshot upload is not available'))
1702
- } else {
1736
+ if (requestId && uploadCh.hasSubscribers) {
1703
1737
  try {
1704
- screenshotUploadCh.publish({ ...content, onDone: respond })
1738
+ let signal
1739
+ if (isVideo) {
1740
+ workerRecord.videoController ??= new AbortController()
1741
+ signal = workerRecord.videoController.signal
1742
+ }
1743
+ uploadCh.publish({
1744
+ ...content,
1745
+ signal,
1746
+ onDone: respond,
1747
+ })
1705
1748
  } catch (error) {
1706
- log.error('WebdriverIO screenshot upload error: %s', error?.message || String(error))
1749
+ log.error('WebdriverIO media upload error: %s', error?.message || String(error))
1707
1750
  respond(error)
1708
1751
  }
1752
+ } else {
1753
+ respond(new Error('WebdriverIO media upload is not available'))
1709
1754
  }
1710
1755
  return
1711
1756
  }
@@ -1732,6 +1777,8 @@ function handleWorkerMessage (state, workerRecord, message) {
1732
1777
  * @param {object} exit
1733
1778
  */
1734
1779
  function handleWorkerExit (state, workerRecord, exit) {
1780
+ clearTimeout(workerRecord.videoFlushTimer)
1781
+ workerRecord.videoController?.abort(new Error('WebdriverIO worker exited during video upload'))
1735
1782
  state.activeWorkers--
1736
1783
  workerRecord.exitCode = exit.exitCode
1737
1784
  workerRecord.retries = exit.retries
@@ -1768,6 +1815,8 @@ function registerWorker (state, worker, specs) {
1768
1815
  hasTests: undefined,
1769
1816
  exitCode: undefined,
1770
1817
  retries: undefined,
1818
+ videoController: undefined,
1819
+ videoFlushTimer: undefined,
1771
1820
  }
1772
1821
  state.activeWorkers++
1773
1822
  if (state.activeWorkers > state.maxActiveWorkers) {
@@ -1917,6 +1966,44 @@ function waitForRumCleanupBeforeSessionEnd (context) {
1917
1966
  context.rumCleanupCallback = cleanupAllRumBrowsers
1918
1967
  }
1919
1968
 
1969
+ /**
1970
+ * Opted-in background screenshots must not dismiss dialogs. Preserve explicit customer policies.
1971
+ *
1972
+ * @param {{
1973
+ * arguments: [{framework?: string, sessionId?: string, automationProtocol?: string}, object],
1974
+ * self: {_isMultiremote?: boolean, _isMultiRemote?: boolean}
1975
+ * }} context
1976
+ */
1977
+ function configureVideoPromptBehavior (context) {
1978
+ if (!testFinishCh.hasSubscribers || getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED', true) !== true) return
1979
+ const [config, capabilities] = context.arguments
1980
+ if (!SUPPORTED_FRAMEWORK_ADAPTERS.has(config?.framework) || 'sessionId' in config ||
1981
+ config.automationProtocol === './protocol-stub.js') return
1982
+ try {
1983
+ const videoCapabilities = {}
1984
+ videoCapabilitiesCh.publish(videoCapabilities)
1985
+ if (!videoCapabilities.enabled) return
1986
+ /** @param {{unhandledPromptBehavior?: unknown}} caps - Capabilities for one browser session. */
1987
+ const withDefault = caps => caps && caps.unhandledPromptBehavior === undefined
1988
+ ? { ...caps, unhandledPromptBehavior: 'ignore' }
1989
+ : caps
1990
+ // Prepare every session before replacing the argument, so a read failure cannot leave partial defaults.
1991
+ context.arguments[1] = (context.self._isMultiremote || context.self._isMultiRemote)
1992
+ ? Object.fromEntries(Object.entries(capabilities).map(([name, options]) => {
1993
+ const session = { ...options }
1994
+ session.capabilities = withDefault(session.capabilities)
1995
+ return [name, session]
1996
+ }))
1997
+ : withDefault(capabilities)
1998
+ } catch (error) {
1999
+ log.error('Error configuring WebdriverIO video prompt handling: %s', error?.message || String(error))
2000
+ }
2001
+ }
2002
+
2003
+ runnerStartSessionCh.start.subscribe(
2004
+ /** @type {import('node:diagnostics_channel').ChannelListener} */ (configureVideoPromptBehavior)
2005
+ )
2006
+
1920
2007
  baseReporterWaitForSyncCh.asyncEnd.subscribe(
1921
2008
  /** @type {import('node:diagnostics_channel').ChannelListener} */ (waitForLogSubmissionAtWorkerExit)
1922
2009
  )
@@ -1974,7 +2061,17 @@ executeAsyncCh.subscribe({
1974
2061
  // @ts-expect-error See the partial tracing-channel subscriber above.
1975
2062
  jasmineAdapterInitCh.subscribe({
1976
2063
  asyncEnd (context) {
1977
- initializeJasmineWorker(context.result || context.self, context)
2064
+ initializeFrameworkWorker(context.result || context.self, context, JASMINE_FRAMEWORK_ADAPTER)
2065
+ },
2066
+ })
2067
+
2068
+ // @ts-expect-error See the partial tracing-channel subscriber above.
2069
+ mochaAdapterInitCh.subscribe({
2070
+ asyncEnd (context) {
2071
+ // Mocha normally receives worker settings in run(), after WebdriverIO creates its browser session.
2072
+ if (getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED', true) === true) {
2073
+ initializeFrameworkWorker(context.result || context.self, context, MOCHA_FRAMEWORK_ADAPTER)
2074
+ }
1978
2075
  },
1979
2076
  })
1980
2077
 
@@ -291,10 +291,7 @@ function dataLength (data) {
291
291
  if (data instanceof Blob) {
292
292
  return data.size
293
293
  }
294
- if (ArrayBuffer.isView(data)) {
295
- return data.byteLength
296
- }
297
- if (data instanceof ArrayBuffer) {
294
+ if (ArrayBuffer.isView(data) || data instanceof ArrayBuffer) {
298
295
  return data.byteLength
299
296
  }
300
297
  let total = 0
@@ -13,8 +13,7 @@ class AmqplibClientPlugin extends ClientPlugin {
13
13
  bindStart (ctx) {
14
14
  const { channel = {}, method, fields } = ctx
15
15
 
16
- if (method === 'basic.deliver' || method === 'basic.get') return
17
- if (method === 'basic.publish') return
16
+ if (method === 'basic.deliver' || method === 'basic.get' || method === 'basic.publish') return
18
17
 
19
18
  const stream = (channel.connection && channel.connection.stream) || {}
20
19
  const span = this.startSpan(this.operationName(), {