dd-trace 6.13.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 (201) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/package.json +13 -10
  14. package/packages/datadog-esbuild/index.js +3 -1
  15. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  16. package/packages/datadog-instrumentations/src/fs.js +24 -20
  17. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  18. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  19. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  20. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  21. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  22. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +13 -0
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  33. package/packages/datadog-instrumentations/src/jest.js +25 -20
  34. package/packages/datadog-instrumentations/src/knex.js +76 -0
  35. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  36. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  37. package/packages/datadog-instrumentations/src/mocha/main.js +25 -15
  38. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  39. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  40. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  41. package/packages/datadog-instrumentations/src/net.js +14 -17
  42. package/packages/datadog-instrumentations/src/next.js +212 -62
  43. package/packages/datadog-instrumentations/src/openai.js +42 -40
  44. package/packages/datadog-instrumentations/src/pg.js +15 -0
  45. package/packages/datadog-instrumentations/src/playwright.js +109 -13
  46. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  47. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  48. package/packages/datadog-instrumentations/src/undici.js +53 -4
  49. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  50. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  51. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  52. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  53. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  54. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  56. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  57. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  58. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  59. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  60. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  61. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  62. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  63. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  64. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  65. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  66. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  67. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  68. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  69. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  70. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  71. package/packages/datadog-plugin-jest/src/index.js +0 -6
  72. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  73. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  74. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  75. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  76. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  77. package/packages/datadog-plugin-next/src/index.js +63 -29
  78. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  79. package/packages/datadog-plugin-undici/src/index.js +27 -41
  80. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  81. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  82. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  83. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  85. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  86. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  87. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  88. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  90. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  91. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +11 -36
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  93. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  94. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  95. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  96. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  97. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  100. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  101. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  102. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  103. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  104. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  105. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  106. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  107. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  108. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  109. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  110. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +176 -0
  111. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  112. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  113. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  114. package/packages/dd-trace/src/config/index.js +73 -13
  115. package/packages/dd-trace/src/config/parsers.js +11 -1
  116. package/packages/dd-trace/src/config/stable.js +3 -3
  117. package/packages/dd-trace/src/config/supported-configurations.json +20 -2
  118. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  119. package/packages/dd-trace/src/debugger/config.js +12 -1
  120. package/packages/dd-trace/src/debugger/constants.js +1 -0
  121. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  122. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +33 -0
  123. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  124. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  125. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  126. package/packages/dd-trace/src/debugger/index.js +23 -3
  127. package/packages/dd-trace/src/dogstatsd.js +510 -76
  128. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  129. package/packages/dd-trace/src/encode/0.4.js +5 -5
  130. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  131. package/packages/dd-trace/src/evp_proxy/direct.js +27 -23
  132. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  133. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  134. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  135. package/packages/dd-trace/src/exporters/agentless/writer.js +100 -118
  136. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  137. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  138. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  139. package/packages/dd-trace/src/exporters/common/request.js +31 -11
  140. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  141. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  142. package/packages/dd-trace/src/flush.js +6 -3
  143. package/packages/dd-trace/src/id.js +14 -0
  144. package/packages/dd-trace/src/index.js +5 -2
  145. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  146. package/packages/dd-trace/src/llmobs/index.js +32 -32
  147. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  148. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  149. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  150. package/packages/dd-trace/src/llmobs/util.js +0 -33
  151. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  152. package/packages/dd-trace/src/log/index.js +7 -1
  153. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  154. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  155. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  156. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  157. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  158. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  159. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  160. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  161. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  162. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  163. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  164. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentracing/propagation/text_map.js +93 -18
  166. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  167. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  168. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  169. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  170. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  171. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  172. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  173. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  174. package/packages/dd-trace/src/profiling/config.js +1 -0
  175. package/packages/dd-trace/src/profiling/oom.js +7 -2
  176. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  177. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  178. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  179. package/packages/dd-trace/src/proxy.js +75 -5
  180. package/packages/dd-trace/src/remote_config/index.js +319 -101
  181. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  182. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  183. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  184. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  185. package/packages/dd-trace/src/sampling_rule.js +36 -9
  186. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  187. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  188. package/packages/dd-trace/src/serverless.js +10 -3
  189. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  190. package/packages/dd-trace/src/span_stats.js +24 -28
  191. package/packages/dd-trace/src/startup-log.js +3 -2
  192. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  193. package/packages/dd-trace/src/telemetry/send-data.js +18 -16
  194. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  195. package/packages/dd-trace/src/tracer.js +4 -5
  196. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  197. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  198. package/vendor/dist/protobufjs/index.js +1 -1
  199. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  200. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  201. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -70,6 +70,7 @@ const createRootSuiteCh = tracingChannel('orchestrion:playwright:createRootSuite
70
70
  const artifactsRecorderScreenshotPathCh =
71
71
  tracingChannel('orchestrion:playwright:ArtifactsRecorder_createScreenshotAttachmentPath')
72
72
  const snapshotRecorderScreenshotPathCh = tracingChannel('orchestrion:playwright:SnapshotRecorder_createAttachmentPath')
73
+ const saveAutomaticVideoCh = tracingChannel('orchestrion:playwright:saveAutomaticVideo')
73
74
  const pageGotoCh = tracingChannel('orchestrion:playwright-core:Page_goto')
74
75
 
75
76
  const testToCtx = new WeakMap()
@@ -83,6 +84,8 @@ const RUM_FLUSH_WAIT_TIME = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WA
83
84
  const DD_PROPERTIES_TIMEOUT = 5000
84
85
  const isFailureScreenshotUploadEnabled =
85
86
  getValueFromEnvSources('DD_TEST_FAILURE_SCREENSHOTS_ENABLED') === true
87
+ const isFailureVideoUploadEnabled =
88
+ getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED') === true
86
89
 
87
90
  let applyRepeatEachIndex = null
88
91
  let reporterError
@@ -91,7 +94,7 @@ let hasReporterError = false
91
94
  let startedSuites = []
92
95
 
93
96
  // Browser-side callbacks live in a coverage-excluded file so coverage counters can't reach chromium.
94
- const { detectRum, stopRumSession } = require('./playwright-browser-scripts')
97
+ const { detectRum, stopRumSession } = require('./rum-browser-scripts')
95
98
 
96
99
  const STATUS_TO_TEST_STATUS = {
97
100
  passed: 'pass',
@@ -134,18 +137,22 @@ let rootDir = ''
134
137
  let sessionProjects = []
135
138
 
136
139
  const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0' // TODO: remove this once we drop support for v5
140
+ const MINIMUM_FAILURE_VIDEO_UPLOAD_VERSION_RANGE = '>=1.38.0'
137
141
  const EFD_RETRY_COUNT_REQUEST = 'ddEfdRetryCountRequest'
138
142
  const EFD_RETRY_COUNT_RESPONSE = 'ddEfdRetryCountResponse'
139
143
  const DD_PROPERTIES_REQUEST = 'ddPropertiesRequest'
140
144
  const DD_PROPERTIES_RESPONSE = 'ddProperties'
141
145
  const kDdPlaywrightDisabledTestIds = Symbol('ddPlaywrightDisabledTestIds')
142
146
  const kDdPlaywrightFailureScreenshots = Symbol('ddPlaywrightFailureScreenshots')
147
+ const kDdPlaywrightFailureVideos = Symbol('ddPlaywrightFailureVideos')
143
148
  const kDdPlaywrightReporterConfigured = Symbol('ddPlaywrightReporterConfigured')
144
149
  const kDdPlaywrightWorkerHostInstrumented = Symbol('ddPlaywrightWorkerHostInstrumented')
145
150
  const kDdPlaywrightWorkerInstrumented = Symbol('ddPlaywrightWorkerInstrumented')
146
151
  const instrumentedPlaywrightReporters = new WeakSet()
147
152
  const PLAYWRIGHT_FAILURE_SCREENSHOT_PATH_RE = /(?:^|[\\/])test-failed-\d+\.png$/
153
+ const PLAYWRIGHT_FAILURE_VIDEO_PATH_RE = /(?:^|[\\/])video(?:-\d+)?\.webm$/
148
154
  const automaticFailureScreenshotPaths = new Set()
155
+ const automaticFailureVideoPaths = new Set()
149
156
 
150
157
  /**
151
158
  * Returns whether Playwright's internal screenshot recorder created an attachment.
@@ -157,6 +164,16 @@ function isAutomaticFailureScreenshotAttachment (attachment) {
157
164
  return typeof attachment?.path === 'string' && automaticFailureScreenshotPaths.delete(attachment.path)
158
165
  }
159
166
 
167
+ /**
168
+ * Returns whether Playwright's internal video recorder created an attachment.
169
+ *
170
+ * @param {object} attachment - Playwright attachment payload
171
+ * @returns {boolean}
172
+ */
173
+ function isAutomaticFailureVideoAttachment (attachment) {
174
+ return typeof attachment?.path === 'string' && automaticFailureVideoPaths.delete(attachment.path)
175
+ }
176
+
160
177
  function isValidKnownTests (receivedKnownTests) {
161
178
  return !!receivedKnownTests.playwright
162
179
  }
@@ -553,6 +570,21 @@ function isFailureScreenshotCaptureEnabled (projects) {
553
570
  return false
554
571
  }
555
572
 
573
+ /**
574
+ * Returns whether at least one Playwright project records videos that can be retained for failures.
575
+ *
576
+ * @param {Array<object>} projects - Playwright projects with resolved use options
577
+ * @returns {boolean} Whether video capture is enabled
578
+ */
579
+ function isFailureVideoCaptureEnabled (projects) {
580
+ for (const project of projects) {
581
+ const video = project.use?.video
582
+ const mode = typeof video === 'object' && video !== null ? video.mode : video
583
+ if (mode === 'on' || mode === 'retain-on-failure') return true
584
+ }
585
+ return false
586
+ }
587
+
556
588
  function getProjectsFromDispatcher (dispatcher) {
557
589
  const bundledConfig = dispatcher._testRun?.config?.config?.projects
558
590
  if (bundledConfig) {
@@ -614,7 +646,15 @@ function getTestSuiteError (testSuiteAbsolutePath) {
614
646
  if (errors.length === 1) {
615
647
  return errors[0]
616
648
  }
617
- return new Error(`${errors.length} errors in this test suite:\n${errors.map(e => e.message).join('\n------\n')}`)
649
+ let errorMessages = ''
650
+ let isFirstError = true
651
+ for (const error of errors) {
652
+ if (!isFirstError) errorMessages += '\n------\n'
653
+ errorMessages += error.message || error.value || ''
654
+ isFirstError = false
655
+ }
656
+
657
+ return new Error(`${errors.length} errors in this test suite:\n${errorMessages}`)
618
658
  }
619
659
 
620
660
  function getTestByTestId (dispatcher, testId) {
@@ -1087,6 +1127,7 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1087
1127
  const projects = getProjectsFromDispatcher(dispatcher)
1088
1128
  sessionProjects = projects
1089
1129
  const automaticFailureScreenshotsByTestId = new Map()
1130
+ const failureVideosByTestId = new Map()
1090
1131
 
1091
1132
  if (disabledTestIds.size && !worker[kDdPlaywrightWorkerHostInstrumented] &&
1092
1133
  typeof worker.runTestGroup === 'function') {
@@ -1112,20 +1153,28 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1112
1153
  if (!test) return
1113
1154
 
1114
1155
  automaticFailureScreenshotsByTestId.clear()
1156
+ failureVideosByTestId.clear()
1115
1157
  const browser = getBrowserNameFromProjects(projects, test)
1116
1158
  const shouldCreateTestSpan = test.expectedStatus === 'skipped'
1117
1159
  testBeginHandler(test, browser, shouldCreateTestSpan)
1118
1160
  })
1119
1161
  worker.on('attach', (attachment) => {
1120
- const { testId, _ddIsAutomaticFailureScreenshot } = attachment
1121
- if (!_ddIsAutomaticFailureScreenshot) return
1122
-
1123
- let screenshots = automaticFailureScreenshotsByTestId.get(testId)
1124
- if (!screenshots) {
1125
- screenshots = []
1126
- automaticFailureScreenshotsByTestId.set(testId, screenshots)
1162
+ const { testId, _ddIsAutomaticFailureScreenshot, _ddIsAutomaticFailureVideo } = attachment
1163
+ if (_ddIsAutomaticFailureScreenshot) {
1164
+ let screenshots = automaticFailureScreenshotsByTestId.get(testId)
1165
+ if (!screenshots) {
1166
+ screenshots = []
1167
+ automaticFailureScreenshotsByTestId.set(testId, screenshots)
1168
+ }
1169
+ screenshots.push(attachment)
1170
+ } else if (isFailureVideoUploadEnabled && _ddIsAutomaticFailureVideo) {
1171
+ let videos = failureVideosByTestId.get(testId)
1172
+ if (!videos) {
1173
+ videos = []
1174
+ failureVideosByTestId.set(testId, videos)
1175
+ }
1176
+ videos.push(attachment)
1127
1177
  }
1128
- screenshots.push(attachment)
1129
1178
  })
1130
1179
  worker.on('testEnd', ({ testId, status, errors, annotations }) => {
1131
1180
  const test = getTestByTestId(dispatcher, testId)
@@ -1164,6 +1213,14 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1164
1213
  worker[kDdPlaywrightFailureScreenshots] ??= []
1165
1214
  worker[kDdPlaywrightFailureScreenshots].push(screenshots)
1166
1215
  }
1216
+ if (isFailureVideoUploadEnabled &&
1217
+ !shouldCreateTestSpan &&
1218
+ !test._ddShouldSkipEfdRetry &&
1219
+ !disabledTestIds.has(testId)) {
1220
+ const videos = testStatus === 'fail' ? failureVideosByTestId.get(testId) : undefined
1221
+ worker[kDdPlaywrightFailureVideos] ??= []
1222
+ worker[kDdPlaywrightFailureVideos].push(videos)
1223
+ }
1167
1224
  const isAtrRetry = testResult?.retry > 0 &&
1168
1225
  isFlakyTestRetriesEnabled &&
1169
1226
  !test._ddIsAttemptToFix &&
@@ -1278,6 +1335,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1278
1335
  rootDir = getRootDir(this, config)
1279
1336
  const projects = getProjectsFromRunner(this, config)
1280
1337
  const isFailureScreenshotEnabled = isFailureScreenshotCaptureEnabled(projects)
1338
+ const isFailureVideoEnabled = isFailureVideoCaptureEnabled(projects)
1339
+ const isFailureVideoUploadSupported = satisfies(playwrightVersion, MINIMUM_FAILURE_VIDEO_UPLOAD_VERSION_RANGE)
1281
1340
  const processArgv = process.argv.slice(2).join(' ')
1282
1341
  const command = `playwright ${processArgv}`
1283
1342
  testSessionStartCh.publish({
@@ -1285,6 +1344,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1285
1344
  frameworkVersion: playwrightVersion,
1286
1345
  rootDir,
1287
1346
  isFailureScreenshotEnabled,
1347
+ isFailureVideoEnabled,
1348
+ isFailureVideoUploadSupported,
1288
1349
  })
1289
1350
 
1290
1351
  try {
@@ -1311,7 +1372,11 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1311
1372
  log.error('Playwright session start error', e)
1312
1373
  }
1313
1374
 
1314
- testSessionConfigurationCh.publish({ isFailureScreenshotEnabled })
1375
+ testSessionConfigurationCh.publish({
1376
+ isFailureScreenshotEnabled,
1377
+ isFailureVideoEnabled,
1378
+ isFailureVideoUploadSupported,
1379
+ })
1315
1380
 
1316
1381
  const isTestOptimizationSupported = satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)
1317
1382
  const shouldGetKnownTests = isKnownTestsEnabled && isTestOptimizationSupported
@@ -1697,8 +1762,33 @@ function recordAutomaticFailureScreenshotPath (ctx) {
1697
1762
  }
1698
1763
  }
1699
1764
 
1765
+ /**
1766
+ * Records only the destination used by Playwright's automatic video recorder.
1767
+ *
1768
+ * @param {object} ctx - Orchestrion context
1769
+ * @returns {void}
1770
+ */
1771
+ function recordAutomaticFailureVideoPath (ctx) {
1772
+ const video = ctx.arguments?.[0]
1773
+ if (!isFailureVideoUploadEnabled || typeof video?.saveAs !== 'function') return
1774
+
1775
+ const saveAs = video.saveAs
1776
+ ctx.arguments[0] = new Proxy(video, {
1777
+ get (target, property, receiver) {
1778
+ if (property !== 'saveAs') return Reflect.get(target, property, receiver)
1779
+ return function (filePath) {
1780
+ if (typeof filePath === 'string' && PLAYWRIGHT_FAILURE_VIDEO_PATH_RE.test(filePath)) {
1781
+ automaticFailureVideoPaths.add(filePath)
1782
+ }
1783
+ return Reflect.apply(saveAs, target, arguments)
1784
+ }
1785
+ },
1786
+ })
1787
+ }
1788
+
1700
1789
  artifactsRecorderScreenshotPathCh.subscribe({ end: recordAutomaticFailureScreenshotPath })
1701
1790
  snapshotRecorderScreenshotPathCh.subscribe({ end: recordAutomaticFailureScreenshotPath })
1791
+ saveAutomaticVideoCh.subscribe({ start: recordAutomaticFailureVideoPath })
1702
1792
 
1703
1793
  if (DD_MAJOR < 6) { // <1.38.0 is only supported up to version 5
1704
1794
  addHook({
@@ -2061,6 +2151,7 @@ function finishProcessHostStartRunner (processHost) {
2061
2151
  workerReportCh.publish({
2062
2152
  serializedTraces: message[1],
2063
2153
  screenshots: processHost[kDdPlaywrightFailureScreenshots]?.shift(),
2154
+ videos: processHost[kDdPlaywrightFailureVideos]?.shift(),
2064
2155
  })
2065
2156
  } else if (Array.isArray(message) && message[0] === PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE) {
2066
2157
  workerReportTelemetryCh.publish(message[1])
@@ -2409,6 +2500,7 @@ function instrumentWorkerMainMethods (workerMain) {
2409
2500
  shimmer.wrap(workerMain, 'dispatchEvent', dispatchEvent => function (event, payload) {
2410
2501
  if (event === 'testBegin') {
2411
2502
  automaticFailureScreenshotPaths.clear()
2503
+ automaticFailureVideoPaths.clear()
2412
2504
  } else if (event === 'stepBegin') {
2413
2505
  stepInfoByStepId[payload.stepId] = {
2414
2506
  startTime: payload.wallTime,
@@ -2425,8 +2517,12 @@ function instrumentWorkerMainMethods (workerMain) {
2425
2517
  duration: payload.wallTime - stepInfo.startTime,
2426
2518
  error: payload.error,
2427
2519
  })
2428
- } else if (event === 'attach' && isAutomaticFailureScreenshotAttachment(payload)) {
2429
- payload._ddIsAutomaticFailureScreenshot = true
2520
+ } else if (event === 'attach') {
2521
+ if (isAutomaticFailureScreenshotAttachment(payload)) {
2522
+ payload._ddIsAutomaticFailureScreenshot = true
2523
+ } else if (isAutomaticFailureVideoAttachment(payload)) {
2524
+ payload._ddIsAutomaticFailureVideo = true
2525
+ }
2430
2526
  }
2431
2527
  return dispatchEvent.apply(this, arguments)
2432
2528
  })
@@ -0,0 +1,40 @@
1
+ 'use strict'
2
+
3
+ // Serialized into browsers by browser-test integrations. Excluded from coverage by filename.
4
+
5
+ /** @returns {{ isRumInstrumented: boolean, isRumActive: boolean, rumSamplingRate: number | null }} */
6
+ function detectRum () {
7
+ const isRumInstrumented = !!window.DD_RUM
8
+ const isRumActive = window.DD_RUM && window.DD_RUM.getInternalContext
9
+ ? !!window.DD_RUM.getInternalContext()
10
+ : false
11
+ const rumInitConfiguration = window.DD_RUM && window.DD_RUM.getInitConfiguration
12
+ ? window.DD_RUM.getInitConfiguration()
13
+ : undefined
14
+ const rumSamplingRate = rumInitConfiguration ? rumInitConfiguration.sessionSampleRate : null
15
+
16
+ return { isRumInstrumented, isRumActive, rumSamplingRate }
17
+ }
18
+
19
+ /** @returns {boolean} */
20
+ function stopRumSession () {
21
+ if (window.DD_RUM && window.DD_RUM.stopSession) {
22
+ window.DD_RUM.stopSession()
23
+ return true
24
+ }
25
+ return false
26
+ }
27
+
28
+ /** @returns {boolean} */
29
+ function stopRumSessionAndReportActivity () {
30
+ if (window.DD_RUM && window.DD_RUM.stopSession) {
31
+ const isRumActive = window.DD_RUM.getInternalContext
32
+ ? !!window.DD_RUM.getInternalContext()
33
+ : false
34
+ window.DD_RUM.stopSession()
35
+ return isRumActive
36
+ }
37
+ return false
38
+ }
39
+
40
+ module.exports = { detectRum, stopRumSession, stopRumSessionAndReportActivity }
@@ -5,15 +5,45 @@ const {
5
5
  channel,
6
6
  addHook,
7
7
  } = require('./helpers/instrument')
8
+ const {
9
+ wrapPoolRelease,
10
+ wrapPromisePoolAcquire,
11
+ wrapPromisePoolQueryMethod,
12
+ } = require('./helpers/pool-acquire')
13
+
14
+ addHook({
15
+ name: 'sequelize',
16
+ versions: ['>=4'],
17
+ file: 'lib/dialects/abstract/connection-manager.js',
18
+ }, ConnectionManager => {
19
+ shimmer.wrap(ConnectionManager.prototype, 'getConnection', getConnection => wrapPromisePoolAcquire(
20
+ getConnection,
21
+ resolveSequelizeDriver,
22
+ resolveSequelizeConfig,
23
+ sequelizeHasIdleConnection
24
+ ))
25
+ shimmer.wrap(ConnectionManager.prototype, 'releaseConnection', wrapPoolRelease)
26
+ if (typeof ConnectionManager.prototype.destroyConnection === 'function') {
27
+ shimmer.wrap(ConnectionManager.prototype, 'destroyConnection', wrapPoolRelease)
28
+ }
29
+
30
+ return ConnectionManager
31
+ })
8
32
 
9
33
  addHook({ name: 'sequelize', versions: ['>=4'], file: 'lib/sequelize.js' }, Sequelize => {
10
34
  const startCh = channel('datadog:sequelize:query:start')
11
35
  const finishCh = channel('datadog:sequelize:query:finish')
12
36
 
13
37
  shimmer.wrap(Sequelize.prototype, 'query', query => {
38
+ const queryWithPool = wrapPromisePoolQueryMethod(
39
+ query,
40
+ (sequelize, args) => args[1]?.transaction ? undefined : sequelize.connectionManager,
41
+ sequelize => resolveSequelizeDriver(sequelize.connectionManager)
42
+ )
43
+
14
44
  return function (sql, options) {
15
45
  if (!startCh.hasSubscribers) {
16
- return query.apply(this, arguments)
46
+ return queryWithPool.apply(this, arguments)
17
47
  }
18
48
 
19
49
  let dialect
@@ -33,7 +63,7 @@ addHook({ name: 'sequelize', versions: ['>=4'], file: 'lib/sequelize.js' }, Sequ
33
63
  }
34
64
 
35
65
  return startCh.runStores({ sql, dialect }, () => {
36
- const promise = query.apply(this, arguments)
66
+ const promise = queryWithPool.apply(this, arguments)
37
67
  promise.then(onFinish, () => { onFinish() })
38
68
 
39
69
  return promise
@@ -43,3 +73,65 @@ addHook({ name: 'sequelize', versions: ['>=4'], file: 'lib/sequelize.js' }, Sequ
43
73
 
44
74
  return Sequelize
45
75
  })
76
+
77
+ addHook({ name: 'sequelize', versions: ['>=4'], file: 'lib/transaction.js' }, Transaction => {
78
+ shimmer.wrap(Transaction.prototype, 'prepareEnvironment', prepareEnvironment => wrapPromisePoolQueryMethod(
79
+ prepareEnvironment,
80
+ transaction => transaction.parent ? undefined : transaction.sequelize.connectionManager,
81
+ transaction => resolveSequelizeDriver(transaction.sequelize.connectionManager)
82
+ ))
83
+
84
+ return Transaction
85
+ })
86
+
87
+ /**
88
+ * @typedef {{
89
+ * available: number,
90
+ * pending?: number,
91
+ * waiting?: number,
92
+ * }} SequelizePool
93
+ */
94
+
95
+ /**
96
+ * @param {{ pool: SequelizePool & { read?: SequelizePool, write?: SequelizePool } }} manager
97
+ * @param {[{ type?: string, useMaster?: boolean }?]} args
98
+ * @returns {boolean}
99
+ */
100
+ function sequelizeHasIdleConnection (manager, args) {
101
+ const pool = manager.pool
102
+ const options = args[0]
103
+ const selected = pool?.read === undefined
104
+ ? pool
105
+ : options?.type === 'SELECT' && !options?.useMaster ? pool.read : pool.write
106
+ const waiting = selected.waiting ?? selected.pending
107
+ return selected.available > waiting
108
+ }
109
+
110
+ /**
111
+ * @param {{ dialectName?: string }|undefined} manager
112
+ * @returns {'mariadb'|'mysql2'|'pg'|undefined}
113
+ */
114
+ function resolveSequelizeDriver (manager) {
115
+ switch (manager?.dialectName) {
116
+ case 'mariadb':
117
+ return 'mariadb'
118
+ case 'mysql':
119
+ return 'mysql2'
120
+ case 'postgres':
121
+ return 'pg'
122
+ }
123
+ }
124
+
125
+ /**
126
+ * @param {{ config: Record<string, unknown> & { username?: string } }} manager
127
+ * @returns {Record<string, unknown>}
128
+ */
129
+ function resolveSequelizeConfig (manager) {
130
+ const config = manager.config
131
+ return {
132
+ database: config.database,
133
+ host: config.host,
134
+ port: config.port,
135
+ user: config.username,
136
+ }
137
+ }
@@ -1,20 +1,25 @@
1
1
  'use strict'
2
2
 
3
- const tracingChannel = require('dc-polyfill').tracingChannel
3
+ const { channel, tracingChannel } = require('dc-polyfill')
4
4
 
5
5
  const shimmer = require('../../datadog-shimmer')
6
6
  const satisfies = require('../../../vendor/dist/semifies')
7
- const {
8
- addHook,
9
- } = require('./helpers/instrument')
7
+ const { addHook } = require('./helpers/instrument')
10
8
  const { createWrapFetch } = require('./helpers/fetch')
11
9
 
12
10
  const ch = tracingChannel('apm:undici:fetch')
11
+ const upgradeCh = channel('apm:undici:request:upgrade')
13
12
 
14
13
  // Undici 5.0.x has a bug where fetch doesn't preserve AggregateError in the error cause chain
15
14
  // Use native DC only for versions where error handling works correctly
16
15
  const NATIVE_DC_VERSION = '>=4.7.0 <5.0.0 || >=5.1.0'
17
16
 
17
+ addHook({
18
+ name: 'undici',
19
+ versions: ['>=4.4.1 <8.0.0', '>=8.0.0'],
20
+ file: 'lib/core/request.js',
21
+ }, wrapRequestUpgrade)
22
+
18
23
  addHook({
19
24
  name: 'undici',
20
25
  versions: ['^4.4.1', '5', '>=6.0.0'],
@@ -27,3 +32,47 @@ addHook({
27
32
  // For older versions or those with buggy error handling, wrap fetch
28
33
  return shimmer.wrap(undici, 'fetch', createWrapFetch(undici.Request, ch))
29
34
  })
35
+
36
+ /**
37
+ * @param {Function & { prototype: Record<string, Function> }} Request
38
+ * @returns {Function}
39
+ */
40
+ function wrapRequestUpgrade (Request) {
41
+ const requestSource = Function.prototype.toString.call(Request)
42
+ const methodName = typeof Request.prototype.onRequestUpgrade === 'function'
43
+ ? 'onRequestUpgrade'
44
+ : 'onUpgrade'
45
+ const method = Request.prototype[methodName]
46
+
47
+ // Orchestrion cannot leave fixed source byte-for-byte untouched after a conditional AST match.
48
+ if (typeof method !== 'function' ||
49
+ !requestSource.includes('channels.trailers.publish') ||
50
+ Function.prototype.toString.call(method).includes('channels.trailers.publish')) {
51
+ return Request
52
+ }
53
+
54
+ shimmer.wrap(Request.prototype, methodName, createWrapUpgrade)
55
+ return Request
56
+ }
57
+
58
+ /**
59
+ * @param {Function} upgrade
60
+ * @returns {Function}
61
+ */
62
+ function createWrapUpgrade (upgrade) {
63
+ return function (statusCode, headers, socket) {
64
+ if (!upgradeCh.hasSubscribers) return upgrade.apply(this, arguments)
65
+
66
+ let result
67
+ try {
68
+ result = upgrade.apply(this, arguments)
69
+ } catch (error) {
70
+ upgradeCh.publish({ error, headers, request: this, statusCode })
71
+ throw error
72
+ }
73
+ if (!this.aborted) {
74
+ upgradeCh.publish({ headers, request: this, statusCode })
75
+ }
76
+ return result
77
+ }
78
+ }
@@ -56,6 +56,22 @@ const VITEST_NO_WORKER_INIT_SETUP_FILE = path.join(
56
56
  'ci',
57
57
  'vitest-no-worker-init-setup.mjs'
58
58
  )
59
+ const VITEST_NO_WORKER_INIT_RUNNER_SETUP_FILE = path.join(
60
+ __dirname,
61
+ '..',
62
+ '..',
63
+ '..',
64
+ 'ci',
65
+ 'vitest-no-worker-init-runner-setup.mjs'
66
+ )
67
+ const VITEST_NO_WORKER_INIT_V5_SETUP_FILE = path.join(
68
+ __dirname,
69
+ '..',
70
+ '..',
71
+ '..',
72
+ 'ci',
73
+ 'vitest-no-worker-init-v5-setup.mjs'
74
+ )
59
75
  const VITEST_BROWSER_SETUP_FILE_PLUGIN = {
60
76
  name: 'datadog:vitest-browser-setup-file',
61
77
  config: configureVitestBrowserSetupFile,
@@ -325,7 +341,10 @@ function isEarlyFlakeDetectionActive (state) {
325
341
  function configure (ctx, frameworkVersion, testSpecifications, setupData, options) {
326
342
  const { shouldReportTestModule, state } = options
327
343
  reserveEarlyFlakeDetectionSuite = options.reserveEarlyFlakeDetectionSuite
328
- addSetupFileToVitestConfigs(ctx, VITEST_NO_WORKER_INIT_SETUP_FILE, testSpecifications)
344
+ const setupFile = satisfies(frameworkVersion, '>=5.0.0')
345
+ ? VITEST_NO_WORKER_INIT_V5_SETUP_FILE
346
+ : VITEST_NO_WORKER_INIT_RUNNER_SETUP_FILE
347
+ addSetupFileToVitestConfigs(ctx, setupFile, testSpecifications)
329
348
  addVitestBrowserSetupFileAccess(testSpecifications)
330
349
 
331
350
  const {
@@ -470,8 +489,15 @@ function allowVitestBrowserSetupFile (viteConfig) {
470
489
  const allow = viteConfig.server?.fs?.allow
471
490
  if (!allow) return
472
491
 
473
- if (!allow.includes(VITEST_NO_WORKER_INIT_SETUP_FILE)) {
474
- allow.push(VITEST_NO_WORKER_INIT_SETUP_FILE)
492
+ const setupFiles = [
493
+ VITEST_NO_WORKER_INIT_SETUP_FILE,
494
+ VITEST_NO_WORKER_INIT_RUNNER_SETUP_FILE,
495
+ VITEST_NO_WORKER_INIT_V5_SETUP_FILE,
496
+ ]
497
+ for (const setupFile of setupFiles) {
498
+ if (!allow.includes(setupFile)) {
499
+ allow.push(setupFile)
500
+ }
475
501
  }
476
502
  }
477
503
 
@@ -493,6 +519,7 @@ function addVitestBrowserSetupFileAccess (testSpecifications) {
493
519
  if (!browserServerProject) continue
494
520
 
495
521
  addVitestBrowserCommand(safeConfig(project))
522
+ addVitestBrowserRuntimeCommand(project)
496
523
  if (configuredProjects.has(browserServerProject)) continue
497
524
 
498
525
  configuredProjects.add(browserServerProject)
@@ -507,6 +534,13 @@ function addVitestBrowserSetupFileAccess (testSpecifications) {
507
534
  }
508
535
  }
509
536
 
537
+ function addVitestBrowserRuntimeCommand (project) {
538
+ const parentBrowser = project?._parentBrowser || project?._parent?._parentBrowser
539
+ if (parentBrowser?.commands) {
540
+ parentBrowser.commands[VITEST_BROWSER_EFD_SUITE_ADMISSION_COMMAND] = handleBrowserEfdSuiteAdmission
541
+ }
542
+ }
543
+
510
544
  /**
511
545
  * Adds the private EFD admission command to a Vitest Browser Mode configuration.
512
546
  *