dd-trace 6.12.0 → 6.14.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 (251) 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/cypress.js +9 -11
  8. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  9. package/ci/test-optimization-validation/package-check.js +1 -1
  10. package/ci/test-optimization-validation/report-writer.js +7 -5
  11. package/ci/test-optimization-validation/scenarios/ci-wiring.js +5 -4
  12. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  13. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  14. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  15. package/index.d.ts +127 -26
  16. package/package.json +22 -19
  17. package/packages/datadog-esbuild/index.js +3 -1
  18. package/packages/datadog-instrumentations/src/bunyan.js +4 -2
  19. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  20. package/packages/datadog-instrumentations/src/fs.js +24 -20
  21. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  22. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  23. package/packages/datadog-instrumentations/src/helpers/bunyan.js +12 -3
  24. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  25. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  26. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +266 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  34. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  35. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +279 -0
  36. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +12 -1
  37. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  38. package/packages/datadog-instrumentations/src/jest.js +165 -38
  39. package/packages/datadog-instrumentations/src/knex.js +76 -0
  40. package/packages/datadog-instrumentations/src/mariadb-bundle.js +1597 -0
  41. package/packages/datadog-instrumentations/src/mariadb.js +13 -0
  42. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  43. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  44. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  45. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  46. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  47. package/packages/datadog-instrumentations/src/net.js +14 -17
  48. package/packages/datadog-instrumentations/src/next.js +212 -62
  49. package/packages/datadog-instrumentations/src/openai.js +42 -40
  50. package/packages/datadog-instrumentations/src/pg.js +15 -0
  51. package/packages/datadog-instrumentations/src/pino.js +15 -4
  52. package/packages/datadog-instrumentations/src/playwright.js +137 -27
  53. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  54. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  55. package/packages/datadog-instrumentations/src/undici.js +53 -4
  56. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +44 -7
  57. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  58. package/packages/datadog-instrumentations/src/vitest-worker.js +82 -14
  59. package/packages/datadog-instrumentations/src/webdriverio.js +986 -1
  60. package/packages/datadog-instrumentations/src/winston.js +6 -3
  61. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  62. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  63. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  64. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  65. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  66. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  67. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  68. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  69. package/packages/datadog-plugin-cypress/src/support.js +1 -0
  70. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  71. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  72. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  73. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  74. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  75. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  76. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  77. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  78. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  79. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  80. package/packages/datadog-plugin-http/src/client.js +2 -15
  81. package/packages/datadog-plugin-http2/src/client.js +2 -15
  82. package/packages/datadog-plugin-jest/src/index.js +0 -6
  83. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  84. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  85. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  86. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  87. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  88. package/packages/datadog-plugin-next/src/index.js +63 -29
  89. package/packages/datadog-plugin-openai-agents/src/integration.js +15 -11
  90. package/packages/datadog-plugin-playwright/src/index.js +176 -28
  91. package/packages/datadog-plugin-undici/src/index.js +29 -56
  92. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  93. package/packages/dd-trace/src/aiguard/evaluation.js +96 -21
  94. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +2 -3
  95. package/packages/dd-trace/src/aiguard/messages/anthropic.js +29 -10
  96. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  97. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  98. package/packages/dd-trace/src/aiguard/noop.js +9 -1
  99. package/packages/dd-trace/src/aiguard/redaction.js +183 -0
  100. package/packages/dd-trace/src/aiguard/sdk.js +7 -2
  101. package/packages/dd-trace/src/aiguard/tags.js +2 -0
  102. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  103. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  104. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  105. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  106. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  107. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  108. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  109. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  110. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +10 -9
  111. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  112. package/packages/dd-trace/src/appsec/rule_manager.js +2 -0
  113. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  114. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  115. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  116. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  117. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +7 -150
  118. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  119. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  120. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  121. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +397 -234
  122. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  123. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  124. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  125. package/packages/dd-trace/src/ci-visibility/final-flush.js +21 -0
  126. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  127. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  128. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  129. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  130. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  131. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  132. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  133. package/packages/dd-trace/src/config/index.js +75 -14
  134. package/packages/dd-trace/src/config/parsers.js +11 -1
  135. package/packages/dd-trace/src/config/stable.js +3 -3
  136. package/packages/dd-trace/src/config/supported-configurations.json +47 -2
  137. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  138. package/packages/dd-trace/src/debugger/config.js +12 -1
  139. package/packages/dd-trace/src/debugger/constants.js +1 -0
  140. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  141. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  142. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -39
  143. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  144. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  145. package/packages/dd-trace/src/debugger/index.js +23 -3
  146. package/packages/dd-trace/src/dogstatsd.js +532 -71
  147. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  148. package/packages/dd-trace/src/encode/0.4.js +5 -5
  149. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  150. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  151. package/packages/dd-trace/src/exporters/agent/index.js +24 -3
  152. package/packages/dd-trace/src/exporters/agent/writer.js +28 -11
  153. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  154. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  155. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  156. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  157. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  158. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  159. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  160. package/packages/dd-trace/src/exporters/common/writer.js +31 -7
  161. package/packages/dd-trace/src/exporters/span-stats/index.js +23 -4
  162. package/packages/dd-trace/src/external-logger/src/index.js +4 -3
  163. package/packages/dd-trace/src/flush.js +105 -0
  164. package/packages/dd-trace/src/id.js +14 -0
  165. package/packages/dd-trace/src/index.js +5 -2
  166. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -0
  167. package/packages/dd-trace/src/llmobs/experiments/dataset.js +52 -2
  168. package/packages/dd-trace/src/llmobs/experiments/experiment.js +420 -105
  169. package/packages/dd-trace/src/llmobs/experiments/index.js +54 -42
  170. package/packages/dd-trace/src/llmobs/experiments/noop.js +39 -8
  171. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  172. package/packages/dd-trace/src/llmobs/experiments/util.js +21 -0
  173. package/packages/dd-trace/src/llmobs/index.js +111 -48
  174. package/packages/dd-trace/src/llmobs/noop.js +2 -1
  175. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  176. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  177. package/packages/dd-trace/src/llmobs/plugins/anthropic/util.js +8 -4
  178. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +6 -3
  179. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  180. package/packages/dd-trace/src/llmobs/plugins/openai/utils.js +13 -6
  181. package/packages/dd-trace/src/llmobs/plugins/openai-agents/utils.js +5 -7
  182. package/packages/dd-trace/src/llmobs/util.js +0 -33
  183. package/packages/dd-trace/src/llmobs/writers/base.js +106 -53
  184. package/packages/dd-trace/src/log/index.js +7 -1
  185. package/packages/dd-trace/src/log-submission/log-submission-plugin.js +249 -0
  186. package/packages/dd-trace/src/noop/span.js +5 -0
  187. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  188. package/packages/dd-trace/src/openfeature/writers/exposures.js +8 -1
  189. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  190. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +45 -3
  191. package/packages/dd-trace/src/opentelemetry/logs/index.js +9 -25
  192. package/packages/dd-trace/src/opentelemetry/logs/logger_provider.js +7 -4
  193. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  194. package/packages/dd-trace/src/opentelemetry/metrics/index.js +18 -23
  195. package/packages/dd-trace/src/opentelemetry/metrics/meter_provider.js +8 -0
  196. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +18 -6
  197. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +14 -2
  198. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +25 -5
  199. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +61 -25
  200. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  201. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  202. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  203. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  204. package/packages/dd-trace/src/opentelemetry/tracer.js +26 -2
  205. package/packages/dd-trace/src/opentracing/propagation/text_map.js +494 -351
  206. package/packages/dd-trace/src/opentracing/span.js +28 -0
  207. package/packages/dd-trace/src/otel-thread-ctx.js +23 -5
  208. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  209. package/packages/dd-trace/src/plugin_manager.js +2 -0
  210. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  211. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  212. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  213. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  214. package/packages/dd-trace/src/plugins/util/status-validator.js +98 -0
  215. package/packages/dd-trace/src/plugins/util/test.js +157 -41
  216. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  217. package/packages/dd-trace/src/plugins/util/web.js +6 -74
  218. package/packages/dd-trace/src/profiling/config.js +1 -0
  219. package/packages/dd-trace/src/profiling/oom.js +11 -5
  220. package/packages/dd-trace/src/profiling/profiler.js +30 -13
  221. package/packages/dd-trace/src/profiling/profilers/events.js +3 -3
  222. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  223. package/packages/dd-trace/src/profiling/profilers/wall.js +14 -14
  224. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  225. package/packages/dd-trace/src/proxy.js +108 -10
  226. package/packages/dd-trace/src/remote_config/index.js +319 -101
  227. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  228. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  229. package/packages/dd-trace/src/runtime_metrics/index.js +5 -0
  230. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +10 -1
  231. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +22 -7
  232. package/packages/dd-trace/src/sampling_rule.js +36 -9
  233. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +51 -0
  234. package/packages/dd-trace/src/serverless/vercel.js +155 -0
  235. package/packages/dd-trace/src/serverless.js +45 -21
  236. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  237. package/packages/dd-trace/src/span_stats.js +64 -31
  238. package/packages/dd-trace/src/startup-log.js +3 -2
  239. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  240. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  241. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  242. package/packages/dd-trace/src/tracer.js +26 -5
  243. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  244. package/packages/dd-trace/src/web-tags-cache.js +67 -13
  245. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -1
  246. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  247. package/vendor/dist/protobufjs/index.js +1 -1
  248. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  249. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  250. package/packages/dd-trace/src/ci-visibility/log-submission/log-submission-plugin.js +0 -53
  251. package/packages/dd-trace/src/encode/agentless-json.js +0 -209
@@ -12,12 +12,15 @@ const patched = new WeakSet()
12
12
  const configureCh = channel('ci:log-submission:winston:configure')
13
13
  const addTransport = channel('ci:log-submission:winston:add-transport')
14
14
 
15
- addHook({ name: 'winston', file: 'lib/winston/transports/index.js', versions: ['>=3'] }, transportsPackage => {
15
+ addHook({ name: 'winston', versions: ['>=3'] }, winston => {
16
16
  if (configureCh.hasSubscribers) {
17
- configureCh.publish(transportsPackage.Http)
17
+ configureCh.publish({
18
+ createJsonFormat: winston.format.json,
19
+ StreamTransport: winston.transports.Stream,
20
+ })
18
21
  }
19
22
 
20
- return transportsPackage
23
+ return winston
21
24
  })
22
25
 
23
26
  addHook({ name: 'winston', file: 'lib/winston/logger.js', versions: ['>=3'] }, Logger => {
@@ -12,9 +12,11 @@ class Amqp10ConsumerPlugin extends ConsumerPlugin {
12
12
 
13
13
  const source = getShortName(link)
14
14
  const address = getAddress(link)
15
+ let resource = 'receive'
16
+ if (source) resource += ` ${source}`
15
17
 
16
18
  this.startSpan({
17
- resource: ['receive', source].filter(Boolean).join(' '),
19
+ resource,
18
20
  type: 'worker',
19
21
  meta: {
20
22
  'amqp.link.source.address': source,
@@ -14,9 +14,11 @@ class Amqp10ProducerPlugin extends ProducerPlugin {
14
14
 
15
15
  const address = getAddress(link)
16
16
  const target = getShortName(link)
17
+ let resource = 'send'
18
+ if (target) resource += ` ${target}`
17
19
 
18
20
  this.startSpan({
19
- resource: ['send', target].filter(Boolean).join(' '),
21
+ resource,
20
22
  meta: {
21
23
  'amqp.link.target.address': target,
22
24
  'amqp.link.role': 'sender',
@@ -9,7 +9,8 @@ function getAddress (link) {
9
9
  function getShortName (link) {
10
10
  if (!link || !link.name) return null
11
11
 
12
- return link.name.split('_').slice(0, -1).join('_')
12
+ const lastUnderscorePosition = link.name.lastIndexOf('_')
13
+ return lastUnderscorePosition === -1 ? link.name : link.name.slice(0, lastUnderscorePosition)
13
14
  }
14
15
 
15
16
  module.exports = { getAddress, getShortName }
@@ -50,7 +50,12 @@ class SchemaExtractor {
50
50
 
51
51
  if (Array.isArray(fieldType)) {
52
52
  // Union Type
53
- type = 'union[' + fieldType.map(t => SchemaExtractor.getType(t.type || t)).join(',') + ']'
53
+ type = 'union['
54
+ for (const typeDefinition of fieldType) {
55
+ if (type !== 'union[') type += ','
56
+ type += SchemaExtractor.getType(typeDefinition.type || typeDefinition)
57
+ }
58
+ type += ']'
54
59
  } else if (fieldType === 'array') {
55
60
  // Array Type
56
61
  array = true
@@ -265,8 +265,11 @@ function extractRequestParams (params, provider) {
265
265
  for (const message of requestBody.messages) {
266
266
  const textBlocks = message.content?.filter(block => block.text) || []
267
267
  if (textBlocks.length > 0) {
268
+ let content = ''
269
+ for (const block of textBlocks) content += block.text
270
+
268
271
  messages.push({
269
- content: textBlocks.map(block => block.text).join(''),
272
+ content,
270
273
  role: message.role,
271
274
  })
272
275
  }
@@ -288,9 +291,14 @@ function extractRequestParams (params, provider) {
288
291
  for (let idx = requestBody.messages.length - 1; idx >= 0; idx--) {
289
292
  const message = requestBody.messages[idx]
290
293
  if (message.role === 'user') {
291
- prompt = message.content?.filter(block => block.type === 'text')
292
- .map(block => block.text)
293
- .join('')
294
+ const content = message.content
295
+ prompt = undefined
296
+ if (content) {
297
+ prompt = ''
298
+ for (const block of content) {
299
+ if (block.type === 'text') prompt += block.text
300
+ }
301
+ }
294
302
  break
295
303
  }
296
304
  }
@@ -546,7 +554,10 @@ function parseToolInput (inputStr) {
546
554
  }
547
555
 
548
556
  function buildToolResult ({ toolUseId, content }) {
549
- const result = (content || []).map(resolveToolResultItem).join('')
557
+ let result = ''
558
+ if (content) {
559
+ for (const item of content) result += resolveToolResultItem(item)
560
+ }
550
561
  return { name: '', result, toolId: toolUseId ?? '', type: 'tool_result' }
551
562
  }
552
563
 
@@ -33,9 +33,14 @@ class CassandraDriverPlugin extends DatabasePlugin {
33
33
  }
34
34
 
35
35
  function combine (queries) {
36
- return queries
37
- .map(query => (query.query || query).replace(/;?$/, ';'))
38
- .join(' ')
36
+ let combined = ''
37
+ let isFirstQuery = true
38
+ for (const query of queries) {
39
+ if (!isFirstQuery) combined += ' '
40
+ combined += (query.query || query).replace(/;?$/, ';')
41
+ isFirstQuery = false
42
+ }
43
+ return combined
39
44
  }
40
45
 
41
46
  function trim (str, size) {
@@ -124,7 +124,6 @@ class CucumberPlugin extends CiPlugin {
124
124
  this.testSessionSpan.setTag('error', error)
125
125
  this.testModuleSpan.setTag('error', error)
126
126
  }
127
- this.tracer._exporter.exportDeferredTestSuiteSpans?.()
128
127
  this.testModuleSpan.finish()
129
128
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'module')
130
129
  this.testSessionSpan.finish()
@@ -202,7 +201,6 @@ class CucumberPlugin extends CiPlugin {
202
201
  this.addSub('ci:cucumber:test-suite:finish', ({ status, testSuitePath }) => {
203
202
  const testSuiteSpan = this._testSuiteSpansByTestSuite.get(testSuitePath)
204
203
  testSuiteSpan.setTag(TEST_STATUS, status)
205
- this.tracer._exporter.deferTestSuiteSpan?.(testSuiteSpan)
206
204
  testSuiteSpan.finish()
207
205
  this.telemetry.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
208
206
  })
@@ -97,6 +97,12 @@ const {
97
97
  getScreenshotUploadResult,
98
98
  setScreenshotUploadTags,
99
99
  } = require('../../dd-trace/src/ci-visibility/test-screenshot')
100
+ const {
101
+ VIDEO_UPLOAD_RESULT_ERROR,
102
+ VIDEO_UPLOAD_RESULT_UPLOADED,
103
+ VIDEO_UPLOAD_SCOPE_TEST_SUITE,
104
+ setVideoUploadTags,
105
+ } = require('../../dd-trace/src/ci-visibility/test-video')
100
106
  const { appClosing: appClosingTelemetry } = require('../../dd-trace/src/telemetry')
101
107
  const log = require('../../dd-trace/src/log')
102
108
 
@@ -491,6 +497,8 @@ class CypressPlugin {
491
497
  attemptToFixExecutions = new Map()
492
498
  loggedAttemptToFixTests = new Set()
493
499
  uploadedScreenshotPaths = new Set()
500
+ uploadedVideoPaths = new Set()
501
+ pendingVideoUploads = []
494
502
  screenshotUploadPromisesByTraceId = new Map()
495
503
  screenshotUploadAbortControllers = new Set()
496
504
  afterScreenshotHandler = undefined
@@ -581,13 +589,14 @@ class CypressPlugin {
581
589
  this.attemptToFixExecutions = new Map()
582
590
  this.loggedAttemptToFixTests = new Set()
583
591
  this.uploadedScreenshotPaths = new Set()
592
+ this.uploadedVideoPaths = new Set()
593
+ this.pendingVideoUploads = []
584
594
  this.screenshotUploadPromisesByTraceId = new Map()
585
595
  this.screenshotUploadAbortControllers = new Set()
586
596
  this.lastFinishedTest = null
587
597
  this.pendingScreenshotUploads = []
588
598
  this.activeTestSpan = null
589
599
  this.testSuiteSpan = null
590
- this.finishedTestSuiteSpans = []
591
600
  this.testModuleSpan = null
592
601
  this.testSessionSpan = null
593
602
  this.command = undefined
@@ -801,6 +810,35 @@ class CypressPlugin {
801
810
  }
802
811
  }
803
812
 
813
+ /**
814
+ * Warns when video upload is enabled but Cypress cannot produce or send videos.
815
+ *
816
+ * @param {object} cypressConfig - Cypress resolved config
817
+ * @param {object} tracer - dd-trace proxy tracer
818
+ * @param {object} testOptimizationConfig - Test Optimization config
819
+ * @returns {void}
820
+ */
821
+ warnIfMisconfiguredTestFailureVideos (cypressConfig, tracer, testOptimizationConfig) {
822
+ if (!testOptimizationConfig.DD_TEST_FAILURE_VIDEOS_ENABLED) return
823
+
824
+ if (cypressConfig.video === false) {
825
+ log.warn(
826
+ '%s %s',
827
+ 'DD_TEST_FAILURE_VIDEOS_ENABLED is true, but Cypress video capture is disabled.',
828
+ 'Datadog cannot upload failure videos unless Cypress is configured to record videos.'
829
+ )
830
+ return
831
+ }
832
+
833
+ if (!tracer?._tracer?._exporter?.canUploadTestVideos?.()) {
834
+ log.warn(
835
+ '%s %s',
836
+ 'DD_TEST_FAILURE_VIDEOS_ENABLED is true, but Cypress failure video upload is not supported',
837
+ 'by the active Test Optimization transport.'
838
+ )
839
+ }
840
+ }
841
+
804
842
  // Init function returns a promise that resolves with the Cypress configuration
805
843
  // Depending on the received configuration, the Cypress configuration can be modified:
806
844
  // for example, to enable retries for failed tests.
@@ -827,6 +865,7 @@ class CypressPlugin {
827
865
  const testOptimizationConfig = getConfig().testOptimization
828
866
  this.rumFlushWaitMillis = testOptimizationConfig.DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS
829
867
  this.warnIfMisconfiguredTestFailureScreenshots(cypressConfig, tracer, testOptimizationConfig)
868
+ this.warnIfMisconfiguredTestFailureVideos(cypressConfig, tracer, testOptimizationConfig)
830
869
 
831
870
  if (!this.isTestIsolationEnabled) {
832
871
  log.warn('Test isolation is disabled, retries will not be enabled')
@@ -1272,11 +1311,34 @@ class CypressPlugin {
1272
1311
  return details
1273
1312
  }
1274
1313
 
1275
- afterRun (suiteStats, error, shouldFailFinishedSuites = true) {
1314
+ afterRun (suiteStats, error) {
1315
+ const hasPendingVideoSpans = this.pendingVideoUploads.length > 0
1316
+ const videoUploadsPromise = this.uploadPendingTestSuiteVideos()
1276
1317
  if (!this._isInit) {
1277
1318
  log.warn('Attemping to call afterRun without initializating the plugin first')
1319
+ if (videoUploadsPromise) {
1320
+ return Promise.all([videoUploadsPromise, this.#flushExporter(false)])
1321
+ .then(() => this.#flushExporter(false))
1322
+ }
1278
1323
  return
1279
1324
  }
1325
+ const finalizationPromise = this.#finalizeRun(suiteStats, error, hasPendingVideoSpans)
1326
+ if (videoUploadsPromise) {
1327
+ return Promise.all([videoUploadsPromise, finalizationPromise])
1328
+ .then(() => this.#flushExporter(false))
1329
+ }
1330
+ return finalizationPromise
1331
+ }
1332
+
1333
+ /**
1334
+ * Finalizes Cypress run state without starting queued video uploads.
1335
+ *
1336
+ * @param {object|undefined} suiteStats - Cypress run statistics
1337
+ * @param {Error|undefined} error - Run finalization error
1338
+ * @param {boolean} [hasPendingVideoSpans] - Whether video-owned spans will finish during finalization
1339
+ * @returns {Promise<null>}
1340
+ */
1341
+ #finalizeRun (suiteStats, error, hasPendingVideoSpans = false) {
1280
1342
  if (this.testSessionSpan && this.testModuleSpan) {
1281
1343
  const testStatus = error ? 'fail' : getSessionStatus(suiteStats)
1282
1344
  const hasBackfilledCoverage = this.applySkippedCoverageToTestSessionCoverage()
@@ -1284,13 +1346,6 @@ class CypressPlugin {
1284
1346
 
1285
1347
  this.testModuleSpan.setTag(TEST_STATUS, testStatus)
1286
1348
  this.testSessionSpan.setTag(TEST_STATUS, testStatus)
1287
- for (const span of this.finishedTestSuiteSpans) {
1288
- if (error && shouldFailFinishedSuites) {
1289
- span.setTag(TEST_STATUS, 'fail')
1290
- span.setTag('error', error)
1291
- }
1292
- }
1293
- this.finishedTestSuiteSpans = []
1294
1349
  if (error) {
1295
1350
  this.testModuleSpan.setTag('error', error)
1296
1351
  this.testSessionSpan.setTag('error', error)
@@ -1331,14 +1386,28 @@ class CypressPlugin {
1331
1386
  autoInjected: !!getConfig().testOptimization.DD_CIVISIBILITY_AUTO_INSTRUMENTATION_PROVIDER,
1332
1387
  })
1333
1388
 
1334
- finishAllTraceSpans(this.testSessionSpan)
1335
- this.tracer._tracer._exporter?.exportDeferredTestSuiteSpans?.()
1389
+ // Cypress finishes suite videos in after:run, so their spans must remain open until the upload result is known.
1390
+ if (!hasPendingVideoSpans) {
1391
+ finishAllTraceSpans(this.testSessionSpan)
1392
+ }
1336
1393
  }
1337
1394
 
1395
+ return this.#flushExporter(true)
1396
+ }
1397
+
1398
+ /**
1399
+ * Flushes Test Optimization data after Cypress finalization work.
1400
+ *
1401
+ * @param {boolean} closeRun - Whether to close the Cypress run and telemetry lifecycle
1402
+ * @returns {Promise<null>}
1403
+ */
1404
+ #flushExporter (closeRun) {
1338
1405
  return new Promise(resolve => {
1339
1406
  const finishAfterRun = () => {
1340
- this._isInit = false
1341
- appClosingTelemetry()
1407
+ if (closeRun) {
1408
+ this._isInit = false
1409
+ appClosingTelemetry()
1410
+ }
1342
1411
  resolve(null)
1343
1412
  }
1344
1413
 
@@ -1391,12 +1460,12 @@ class CypressPlugin {
1391
1460
  }
1392
1461
 
1393
1462
  afterSpec (spec, results, error) {
1394
- const { tests, stats, screenshots } = results || {}
1463
+ const { tests, stats, screenshots, video } = results || {}
1395
1464
  const cypressTests = tests || []
1396
1465
  const specScreenshots = screenshots || []
1397
1466
  const finishedTests = this.finishedTestsByFile[spec.relative] || []
1398
1467
  const screenshotUploadPromises = []
1399
- const testSpanFinishPromises = []
1468
+ const testSpanFinishes = []
1400
1469
 
1401
1470
  if (!this.testSuiteSpan) {
1402
1471
  // dd:testSuiteStart hasn't been triggered for whatever reason
@@ -1463,7 +1532,7 @@ class CypressPlugin {
1463
1532
  })
1464
1533
  }
1465
1534
 
1466
- skippedTestSpan.finish()
1535
+ testSpanFinishes.push({ testSpan: skippedTestSpan, finishTime: this._now() })
1467
1536
  }
1468
1537
 
1469
1538
  // Make sure that reported test statuses are the same as Cypress reports.
@@ -1604,36 +1673,46 @@ class CypressPlugin {
1604
1673
  const screenshotUploadResultPromise = failedTestTraceId
1605
1674
  ? this.getScreenshotUploadResultPromise(failedTestTraceId)
1606
1675
  : undefined
1607
- if (screenshotUploadResultPromise && !error) {
1608
- testSpanFinishPromises.push(screenshotUploadResultPromise.then((uploadResult) => {
1609
- setScreenshotUploadTags(finishedTest.testSpan, uploadResult)
1610
- this.screenshotUploadPromisesByTraceId.delete(failedTestTraceId)
1611
- finishedTest.testSpan.finish(finishedTest.finishTime)
1612
- }))
1613
- } else {
1614
- if (screenshotUploadResultPromise) {
1615
- this.screenshotUploadPromisesByTraceId.delete(failedTestTraceId)
1616
- }
1617
- finishedTest.testSpan.finish(finishedTest.finishTime)
1618
- }
1676
+ testSpanFinishes.push({
1677
+ testSpan: finishedTest.testSpan,
1678
+ finishTime: finishedTest.finishTime,
1679
+ screenshotUploadResultPromise: error ? undefined : screenshotUploadResultPromise,
1680
+ failedTestTraceId,
1681
+ })
1619
1682
  }
1620
1683
  }
1621
1684
 
1622
- const finishSuite = () => {
1623
- if (this.testSuiteSpan) {
1624
- const status = error ? 'fail' : getSuiteStatus(stats)
1625
- this.testSuiteSpan.setTag(TEST_STATUS, status)
1626
-
1627
- if (error || latestError) {
1628
- this.testSuiteSpan.setTag('error', error || latestError)
1629
- }
1630
- const canRunAfterRun = this.cypressConfig.isTextTerminal ||
1631
- this.cypressConfig.experimentalInteractiveRunEvents
1632
- const exporter = this.tracer._tracer._exporter
1633
- if (canRunAfterRun && exporter?.deferTestSuiteSpan) exporter.deferTestSuiteSpan(this.testSuiteSpan)
1634
- this.testSuiteSpan.finish()
1635
- if (canRunAfterRun) this.finishedTestSuiteSpans.push(this.testSuiteSpan)
1636
- this.testSuiteSpan = null
1685
+ const testSuiteFinishTime = this._now()
1686
+ const suiteFailed = error || latestError || getSuiteStatus(stats) === 'fail'
1687
+ const testSuiteSpan = this.testSuiteSpan
1688
+ const uploadOptions = {
1689
+ filePath: video,
1690
+ testSessionId: typeof this.testSessionSpan?.context === 'function'
1691
+ ? this.testSessionSpan.context().toTraceId()
1692
+ : undefined,
1693
+ testSuiteId: typeof testSuiteSpan?.context === 'function'
1694
+ ? testSuiteSpan.context().toSpanId()
1695
+ : undefined,
1696
+ }
1697
+ const shouldUploadVideo = suiteFailed && this.#canUploadTestSuiteVideo(uploadOptions)
1698
+ if (testSuiteSpan) {
1699
+ testSuiteSpan.setTag(TEST_STATUS, error ? 'fail' : getSuiteStatus(stats))
1700
+ if (error || latestError) testSuiteSpan.setTag('error', error || latestError)
1701
+ this.testSuiteSpan = null
1702
+ }
1703
+
1704
+ let testSpansPromise
1705
+ if (shouldUploadVideo) {
1706
+ this.pendingVideoUploads.push({
1707
+ ...uploadOptions,
1708
+ testSpanFinishes,
1709
+ testSuiteSpan,
1710
+ testSuiteFinishTime,
1711
+ })
1712
+ } else {
1713
+ testSpansPromise = this.finishTestSpans(testSpanFinishes)
1714
+ if (testSuiteSpan) {
1715
+ testSuiteSpan.finish(testSuiteFinishTime)
1637
1716
  this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
1638
1717
  }
1639
1718
  }
@@ -1646,17 +1725,19 @@ class CypressPlugin {
1646
1725
  }
1647
1726
  }
1648
1727
 
1649
- finishSuite()
1650
-
1651
1728
  if (error) {
1652
1729
  this.abortPendingScreenshotUploads(error)
1653
- return this.afterRun(undefined, error, false)
1730
+ const videoSpansPromise = this.#finishPendingTestSuiteVideos(VIDEO_UPLOAD_RESULT_ERROR)
1731
+ const finalizationPromise = this.#finalizeRun(undefined, error)
1732
+ if (videoSpansPromise) {
1733
+ return Promise.all([videoSpansPromise, finalizationPromise]).then(() => null)
1734
+ }
1735
+ return finalizationPromise
1654
1736
  }
1655
1737
 
1656
1738
  const screenshotUploadsPromise = waitForScreenshotUploads()
1657
1739
  let afterSpecPromise = screenshotUploadsPromise
1658
- if (testSpanFinishPromises.length > 0) {
1659
- const testSpansPromise = Promise.all(testSpanFinishPromises).then(() => null)
1740
+ if (testSpansPromise) {
1660
1741
  if (screenshotUploadsPromise) {
1661
1742
  afterSpecPromise = Promise.all([testSpansPromise, screenshotUploadsPromise]).then(() => null)
1662
1743
  } else {
@@ -1667,6 +1748,36 @@ class CypressPlugin {
1667
1748
  return afterSpecPromise
1668
1749
  }
1669
1750
 
1751
+ /**
1752
+ * Applies media outcome tags and finishes test spans from one Cypress suite.
1753
+ *
1754
+ * @param {Array<object>} testSpanFinishes - Test spans and deferred screenshot outcomes
1755
+ * @param {string|undefined} videoUploadResult - Test suite video upload outcome
1756
+ * @returns {Promise<null>|undefined}
1757
+ */
1758
+ finishTestSpans (testSpanFinishes, videoUploadResult) {
1759
+ const finishPromises = []
1760
+ for (const {
1761
+ testSpan,
1762
+ finishTime,
1763
+ screenshotUploadResultPromise,
1764
+ failedTestTraceId,
1765
+ } of testSpanFinishes) {
1766
+ const finishTestSpan = (screenshotUploadResult) => {
1767
+ setScreenshotUploadTags(testSpan, screenshotUploadResult)
1768
+ setVideoUploadTags(testSpan, videoUploadResult, VIDEO_UPLOAD_SCOPE_TEST_SUITE)
1769
+ if (failedTestTraceId) this.screenshotUploadPromisesByTraceId.delete(failedTestTraceId)
1770
+ testSpan.finish(finishTime)
1771
+ }
1772
+ if (screenshotUploadResultPromise) {
1773
+ finishPromises.push(screenshotUploadResultPromise.then(finishTestSpan))
1774
+ } else {
1775
+ finishTestSpan()
1776
+ }
1777
+ }
1778
+ if (finishPromises.length > 0) return Promise.all(finishPromises).then(() => null)
1779
+ }
1780
+
1670
1781
  /**
1671
1782
  * Uploads failure screenshots for a Cypress test attempt.
1672
1783
  *
@@ -1720,6 +1831,109 @@ class CypressPlugin {
1720
1831
  }
1721
1832
  }
1722
1833
 
1834
+ /**
1835
+ * Returns whether a Cypress test suite video can be uploaded.
1836
+ *
1837
+ * @param {object} options - Upload options
1838
+ * @param {string|undefined} options.filePath - Cypress video path
1839
+ * @param {string|undefined} options.testSessionId - Test session id
1840
+ * @param {string|undefined} options.testSuiteId - Test suite id
1841
+ * @returns {boolean}
1842
+ */
1843
+ #canUploadTestSuiteVideo ({ filePath, testSessionId, testSuiteId }) {
1844
+ const exporter = this.tracer?._tracer?._exporter
1845
+ return Boolean(filePath && testSessionId && testSuiteId && !this.uploadedVideoPaths.has(filePath) &&
1846
+ exporter?.canUploadTestVideos?.() && exporter.uploadTestSuiteVideo)
1847
+ }
1848
+
1849
+ /**
1850
+ * Uploads the Cypress spec video for a failed test suite.
1851
+ *
1852
+ * @param {object} options - Upload options
1853
+ * @param {string|undefined} options.filePath - Cypress video path
1854
+ * @param {string|undefined} options.testSessionId - Test session id
1855
+ * @param {string|undefined} options.testSuiteId - Test suite id
1856
+ * @returns {Promise<string>|undefined} Promise resolving to the upload outcome
1857
+ */
1858
+ uploadTestSuiteVideo ({ filePath, testSessionId, testSuiteId }) {
1859
+ const exporter = this.tracer?._tracer?._exporter
1860
+ if (!this.#canUploadTestSuiteVideo({ filePath, testSessionId, testSuiteId })) return
1861
+
1862
+ this.uploadedVideoPaths.add(filePath)
1863
+
1864
+ return new Promise(resolve => {
1865
+ exporter.uploadTestSuiteVideo({
1866
+ filePath,
1867
+ testSessionId,
1868
+ testSuiteId,
1869
+ idempotencyKey: `${testSessionId}:${testSuiteId}:${basename(filePath)}`,
1870
+ capturedAtMs: Date.now(),
1871
+ }, (error) => {
1872
+ resolve(error ? VIDEO_UPLOAD_RESULT_ERROR : VIDEO_UPLOAD_RESULT_UPLOADED)
1873
+ })
1874
+ })
1875
+ }
1876
+
1877
+ /**
1878
+ * Tags and finishes every event that references one Cypress test suite video.
1879
+ *
1880
+ * @param {object} pendingVideoUpload - Pending video upload and owning spans
1881
+ * @param {Array<object>} pendingVideoUpload.testSpanFinishes - Deferred test span finishes
1882
+ * @param {object|undefined} pendingVideoUpload.testSuiteSpan - Owning test suite span
1883
+ * @param {number} pendingVideoUpload.testSuiteFinishTime - Suite completion time captured by after:spec
1884
+ * @param {string|undefined} uploadResult - Video upload outcome
1885
+ * @returns {Promise<null>|undefined}
1886
+ */
1887
+ #finishPendingTestSuiteVideo ({ testSpanFinishes, testSuiteSpan, testSuiteFinishTime }, uploadResult) {
1888
+ const testSpansPromise = this.finishTestSpans(testSpanFinishes, uploadResult)
1889
+ if (testSuiteSpan) {
1890
+ setVideoUploadTags(testSuiteSpan, uploadResult, VIDEO_UPLOAD_SCOPE_TEST_SUITE)
1891
+ testSuiteSpan.finish(testSuiteFinishTime)
1892
+ this.ciVisEvent(TELEMETRY_EVENT_FINISHED, 'suite')
1893
+ }
1894
+ return testSpansPromise
1895
+ }
1896
+
1897
+ /**
1898
+ * Finishes queued video-owned spans when Cypress will not reach after:run.
1899
+ *
1900
+ * @param {string} uploadResult - Video upload outcome applied to every queued span
1901
+ * @returns {Promise<null>|undefined}
1902
+ */
1903
+ #finishPendingTestSuiteVideos (uploadResult) {
1904
+ const pendingVideoUploads = this.pendingVideoUploads
1905
+ this.pendingVideoUploads = []
1906
+ const finishPromises = []
1907
+ for (const pendingVideoUpload of pendingVideoUploads) {
1908
+ const finishPromise = this.#finishPendingTestSuiteVideo(pendingVideoUpload, uploadResult)
1909
+ if (finishPromise) finishPromises.push(finishPromise)
1910
+ }
1911
+ if (finishPromises.length > 0) return Promise.all(finishPromises).then(() => null)
1912
+ }
1913
+
1914
+ /**
1915
+ * Starts queued Cypress video uploads after Cypress has compressed the source files.
1916
+ *
1917
+ * @returns {Promise<null>|undefined}
1918
+ */
1919
+ uploadPendingTestSuiteVideos () {
1920
+ const pendingVideoUploads = this.pendingVideoUploads
1921
+ this.pendingVideoUploads = []
1922
+ const finishPromises = []
1923
+ for (const pendingVideoUpload of pendingVideoUploads) {
1924
+ const { filePath, testSessionId, testSuiteId } = pendingVideoUpload
1925
+ const uploadPromise = this.uploadTestSuiteVideo({ filePath, testSessionId, testSuiteId })
1926
+ if (uploadPromise) {
1927
+ finishPromises.push(uploadPromise.then(uploadResult =>
1928
+ this.#finishPendingTestSuiteVideo(pendingVideoUpload, uploadResult)))
1929
+ } else {
1930
+ const finishPromise = this.#finishPendingTestSuiteVideo(pendingVideoUpload)
1931
+ if (finishPromise) finishPromises.push(finishPromise)
1932
+ }
1933
+ }
1934
+ if (finishPromises.length > 0) return Promise.all(finishPromises).then(() => null)
1935
+ }
1936
+
1723
1937
  getTasks () {
1724
1938
  return {
1725
1939
  'dd:testSuiteStart': ({ testSuite, testSuiteAbsolutePath }) => {
@@ -409,6 +409,7 @@ beforeEach(function () {
409
409
  rumCookiePromise = setRumCorrelationCookie(traceId)
410
410
  }
411
411
  if (shouldSkip) {
412
+ // Test Optimization requested this runtime skip through the Cypress support hook.
412
413
  this.skip()
413
414
  }
414
415
  if (rumCookiePromise) {
@@ -12,9 +12,12 @@ class GoogleCloudPubsubClientPlugin extends ClientPlugin {
12
12
 
13
13
  if (api === 'publish') return
14
14
 
15
+ let resource = api
16
+ if (request.name) resource += ` ${request.name}`
17
+
15
18
  const spanOptions = {
16
19
  service: this.config.service || this.serviceName(),
17
- resource: [api, request.name].filter(Boolean).join(' '),
20
+ resource,
18
21
  kind: this.constructor.kind,
19
22
  meta: {
20
23
  'pubsub.method': api,