dd-trace 6.13.0 → 6.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (208) hide show
  1. package/LICENSE-3rdparty.csv +3 -1
  2. package/ci/init.js +8 -3
  3. package/ci/test-optimization-validation/ci-package-scripts.js +2 -2
  4. package/ci/test-optimization-validation/ci-remediation.js +6 -6
  5. package/ci/test-optimization-validation/command-blocker.js +7 -5
  6. package/ci/test-optimization-validation/framework-adapters/cucumber.js +1 -1
  7. package/ci/test-optimization-validation/framework-adapters/vitest.js +3 -3
  8. package/ci/test-optimization-validation/package-check.js +1 -1
  9. package/ci/test-optimization-validation/report-writer.js +7 -5
  10. package/ci/vitest-no-worker-init-runner-setup.mjs +8 -0
  11. package/ci/vitest-no-worker-init-setup.mjs +13 -13
  12. package/ci/vitest-no-worker-init-v5-setup.mjs +4 -0
  13. package/index.d.ts +8 -0
  14. package/package.json +13 -10
  15. package/packages/datadog-esbuild/index.js +3 -1
  16. package/packages/datadog-instrumentations/src/cypress-config.js +19 -17
  17. package/packages/datadog-instrumentations/src/fs.js +24 -20
  18. package/packages/datadog-instrumentations/src/google-cloud-vertexai.js +2 -2
  19. package/packages/datadog-instrumentations/src/graphql.js +16 -16
  20. package/packages/datadog-instrumentations/src/helpers/graphql-jit-runtime.js +504 -0
  21. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  22. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +265 -0
  23. package/packages/datadog-instrumentations/src/helpers/rewriter/compiler.js +1 -2
  24. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +95 -30
  25. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +0 -25
  26. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql-jit.js +83 -0
  27. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/graphql.js +85 -1
  28. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  29. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/mercurius.js +30 -12
  30. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/playwright.js +14 -5
  31. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +251 -0
  32. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +11 -1
  33. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +562 -27
  34. package/packages/datadog-instrumentations/src/jest.js +166 -45
  35. package/packages/datadog-instrumentations/src/knex.js +76 -0
  36. package/packages/datadog-instrumentations/src/mariadb.js +6 -0
  37. package/packages/datadog-instrumentations/src/mocha/common.js +59 -2
  38. package/packages/datadog-instrumentations/src/mocha/main.js +66 -20
  39. package/packages/datadog-instrumentations/src/mocha/utils.js +2 -1
  40. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +98 -0
  41. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -8
  42. package/packages/datadog-instrumentations/src/net.js +14 -17
  43. package/packages/datadog-instrumentations/src/next.js +212 -62
  44. package/packages/datadog-instrumentations/src/openai.js +42 -40
  45. package/packages/datadog-instrumentations/src/pg.js +15 -0
  46. package/packages/datadog-instrumentations/src/playwright.js +113 -13
  47. package/packages/datadog-instrumentations/src/rum-browser-scripts.js +40 -0
  48. package/packages/datadog-instrumentations/src/sequelize.js +94 -2
  49. package/packages/datadog-instrumentations/src/undici.js +53 -4
  50. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +37 -3
  51. package/packages/datadog-instrumentations/src/vitest-main.js +92 -9
  52. package/packages/datadog-instrumentations/src/vitest-worker.js +71 -14
  53. package/packages/datadog-instrumentations/src/webdriverio.js +965 -6
  54. package/packages/datadog-plugin-amqp10/src/consumer.js +3 -1
  55. package/packages/datadog-plugin-amqp10/src/producer.js +3 -1
  56. package/packages/datadog-plugin-amqp10/src/util.js +2 -1
  57. package/packages/datadog-plugin-avsc/src/schema_iterator.js +6 -1
  58. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +16 -5
  59. package/packages/datadog-plugin-cassandra-driver/src/index.js +8 -3
  60. package/packages/datadog-plugin-cucumber/src/index.js +0 -2
  61. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +262 -48
  62. package/packages/datadog-plugin-google-cloud-pubsub/src/client.js +4 -1
  63. package/packages/datadog-plugin-graphql/src/execute.js +998 -196
  64. package/packages/datadog-plugin-graphql/src/index.js +27 -7
  65. package/packages/datadog-plugin-graphql/src/jit.js +136 -0
  66. package/packages/datadog-plugin-graphql/src/request.js +18 -27
  67. package/packages/datadog-plugin-graphql/src/resolve-error.js +108 -0
  68. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -2
  69. package/packages/datadog-plugin-graphql/src/tools/transforms.js +45 -3
  70. package/packages/datadog-plugin-graphql/src/utils.js +208 -120
  71. package/packages/datadog-plugin-graphql/src/validate.js +10 -23
  72. package/packages/datadog-plugin-jest/src/index.js +0 -6
  73. package/packages/datadog-plugin-langchain/src/tracing.js +7 -0
  74. package/packages/datadog-plugin-mocha/src/index.js +403 -75
  75. package/packages/datadog-plugin-mongodb-core/src/query.js +6 -0
  76. package/packages/datadog-plugin-mysql/src/index.js +41 -15
  77. package/packages/datadog-plugin-net/src/tcp.js +3 -1
  78. package/packages/datadog-plugin-next/src/index.js +63 -29
  79. package/packages/datadog-plugin-playwright/src/index.js +167 -27
  80. package/packages/datadog-plugin-undici/src/index.js +27 -41
  81. package/packages/datadog-plugin-vitest/src/index.js +6 -12
  82. package/packages/dd-trace/src/aiguard/evaluation.js +2 -1
  83. package/packages/dd-trace/src/aiguard/messages/anthropic.js +8 -1
  84. package/packages/dd-trace/src/aiguard/messages/openai.js +8 -1
  85. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +20 -6
  86. package/packages/dd-trace/src/appsec/api_security/index.js +23 -6
  87. package/packages/dd-trace/src/appsec/api_security/sampler.js +99 -40
  88. package/packages/dd-trace/src/appsec/graphql.js +6 -2
  89. package/packages/dd-trace/src/appsec/iast/taint-tracking/plugin.js +3 -3
  90. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +2 -1
  91. package/packages/dd-trace/src/appsec/iast/telemetry/span-tags.js +8 -2
  92. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/command-sensitive-analyzer.js +3 -1
  93. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/sql-sensitive-analyzer.js +531 -95
  94. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/evidence-redaction/sensitive-analyzers/url-sensitive-analyzer.js +1 -1
  95. package/packages/dd-trace/src/appsec/lambda.js +44 -26
  96. package/packages/dd-trace/src/ci-visibility/encode/json-encoder.js +32 -2
  97. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +8 -8
  98. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +6 -4
  99. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +20 -4
  100. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +5 -173
  101. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +7 -6
  102. package/packages/dd-trace/src/ci-visibility/exporters/agents.js +54 -0
  103. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/writer.js +1 -2
  104. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +394 -225
  105. package/packages/dd-trace/src/ci-visibility/exporters/limits.js +9 -0
  106. package/packages/dd-trace/src/ci-visibility/exporters/request.js +323 -31
  107. package/packages/dd-trace/src/ci-visibility/exporters/settings-cache-key.js +42 -0
  108. package/packages/dd-trace/src/ci-visibility/final-flush.js +2 -1
  109. package/packages/dd-trace/src/ci-visibility/requests/fs-cache.js +57 -18
  110. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +0 -3
  111. package/packages/dd-trace/src/ci-visibility/requests/request.js +11 -0
  112. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +164 -71
  113. package/packages/dd-trace/src/ci-visibility/requests/video-request.js +180 -0
  114. package/packages/dd-trace/src/ci-visibility/telemetry.js +10 -7
  115. package/packages/dd-trace/src/ci-visibility/test-video.js +61 -0
  116. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  117. package/packages/dd-trace/src/config/index.js +73 -13
  118. package/packages/dd-trace/src/config/parsers.js +11 -1
  119. package/packages/dd-trace/src/config/stable.js +3 -3
  120. package/packages/dd-trace/src/config/supported-configurations.json +22 -2
  121. package/packages/dd-trace/src/crashtracking/crashtracker.js +82 -18
  122. package/packages/dd-trace/src/debugger/config.js +12 -1
  123. package/packages/dd-trace/src/debugger/constants.js +1 -0
  124. package/packages/dd-trace/src/debugger/devtools_client/breakpoints.js +2 -1
  125. package/packages/dd-trace/src/debugger/devtools_client/request-options.js +28 -0
  126. package/packages/dd-trace/src/debugger/devtools_client/send.js +7 -40
  127. package/packages/dd-trace/src/debugger/devtools_client/status.js +11 -6
  128. package/packages/dd-trace/src/debugger/devtools_client/tags.js +41 -0
  129. package/packages/dd-trace/src/debugger/index.js +23 -3
  130. package/packages/dd-trace/src/dogstatsd.js +510 -76
  131. package/packages/dd-trace/src/encode/0.4-cross-payload.js +170 -0
  132. package/packages/dd-trace/src/encode/0.4.js +5 -5
  133. package/packages/dd-trace/src/encode/agentless-ci-visibility.js +9 -8
  134. package/packages/dd-trace/src/evp_proxy/direct.js +3 -25
  135. package/packages/dd-trace/src/exporters/agent/index.js +2 -1
  136. package/packages/dd-trace/src/exporters/agent/writer.js +24 -7
  137. package/packages/dd-trace/src/exporters/agentless/index.js +7 -9
  138. package/packages/dd-trace/src/exporters/agentless/writer.js +103 -119
  139. package/packages/dd-trace/src/exporters/common/agents.js +28 -6
  140. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +2 -2
  141. package/packages/dd-trace/src/exporters/common/final-flush-request-tracker.js +191 -0
  142. package/packages/dd-trace/src/exporters/common/proxy.js +52 -0
  143. package/packages/dd-trace/src/exporters/common/request.js +42 -14
  144. package/packages/dd-trace/src/exporters/common/url.js +40 -1
  145. package/packages/dd-trace/src/exporters/common/writer.js +9 -7
  146. package/packages/dd-trace/src/flush.js +6 -3
  147. package/packages/dd-trace/src/id.js +14 -0
  148. package/packages/dd-trace/src/index.js +5 -2
  149. package/packages/dd-trace/src/llmobs/experiments/dataset.js +2 -1
  150. package/packages/dd-trace/src/llmobs/index.js +32 -32
  151. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +22 -71
  152. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +23 -13
  153. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +7 -0
  154. package/packages/dd-trace/src/llmobs/util.js +0 -33
  155. package/packages/dd-trace/src/llmobs/writers/base.js +49 -32
  156. package/packages/dd-trace/src/log/index.js +7 -1
  157. package/packages/dd-trace/src/{ci-visibility/log-submission → log-submission}/log-submission-plugin.js +48 -19
  158. package/packages/dd-trace/src/openfeature/writers/base.js +14 -12
  159. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  160. package/packages/dd-trace/src/opentelemetry/logs/index.js +5 -24
  161. package/packages/dd-trace/src/opentelemetry/logs/otlp_http_log_exporter.js +14 -4
  162. package/packages/dd-trace/src/opentelemetry/metrics/index.js +15 -22
  163. package/packages/dd-trace/src/opentelemetry/metrics/otlp_http_metric_exporter.js +14 -4
  164. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +10 -0
  165. package/packages/dd-trace/src/opentelemetry/otlp/otlp_http_exporter_base.js +5 -0
  166. package/packages/dd-trace/src/opentelemetry/otlp/otlp_transformer_base.js +19 -4
  167. package/packages/dd-trace/src/opentelemetry/resource-attributes.js +55 -0
  168. package/packages/dd-trace/src/opentelemetry/trace/index.js +6 -1
  169. package/packages/dd-trace/src/opentelemetry/trace/otlp_http_trace_exporter.js +10 -0
  170. package/packages/dd-trace/src/opentracing/propagation/text_map.js +101 -20
  171. package/packages/dd-trace/src/payload-tagging/index.js +2 -1
  172. package/packages/dd-trace/src/plugins/ci_plugin.js +1 -6
  173. package/packages/dd-trace/src/plugins/tracing.js +12 -10
  174. package/packages/dd-trace/src/plugins/util/ci.js +3 -1
  175. package/packages/dd-trace/src/plugins/util/inferred_proxy.js +16 -15
  176. package/packages/dd-trace/src/plugins/util/test.js +83 -20
  177. package/packages/dd-trace/src/plugins/util/url.js +2 -2
  178. package/packages/dd-trace/src/plugins/util/web.js +4 -9
  179. package/packages/dd-trace/src/priority_sampler.js +4 -0
  180. package/packages/dd-trace/src/profiling/config.js +1 -0
  181. package/packages/dd-trace/src/profiling/oom.js +7 -2
  182. package/packages/dd-trace/src/profiling/profiler.js +23 -13
  183. package/packages/dd-trace/src/profiling/profilers/space.js +38 -11
  184. package/packages/dd-trace/src/profiling/webspan-utils.js +7 -4
  185. package/packages/dd-trace/src/proxy.js +75 -5
  186. package/packages/dd-trace/src/remote_config/index.js +319 -101
  187. package/packages/dd-trace/src/remote_config/scheduler.js +37 -12
  188. package/packages/dd-trace/src/runtime_metrics/client.js +37 -8
  189. package/packages/dd-trace/src/runtime_metrics/otlp_runtime_metrics.js +5 -1
  190. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +5 -1
  191. package/packages/dd-trace/src/sampling_rule.js +39 -10
  192. package/packages/dd-trace/src/serverless/telemetry-delivery-tracker.js +4 -8
  193. package/packages/dd-trace/src/serverless/vercel.js +53 -20
  194. package/packages/dd-trace/src/serverless.js +10 -3
  195. package/packages/dd-trace/src/service-naming/schemas/util.js +1 -1
  196. package/packages/dd-trace/src/span_processor.js +18 -3
  197. package/packages/dd-trace/src/span_stats.js +24 -28
  198. package/packages/dd-trace/src/startup-log.js +3 -2
  199. package/packages/dd-trace/src/telemetry/agentless-url.js +28 -0
  200. package/packages/dd-trace/src/telemetry/send-data.js +23 -20
  201. package/packages/dd-trace/src/telemetry/session-propagation.js +3 -2
  202. package/packages/dd-trace/src/tracer.js +4 -5
  203. package/packages/dd-trace/src/tracer_metadata.js +11 -3
  204. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  205. package/vendor/dist/protobufjs/index.js +1 -1
  206. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  207. package/packages/datadog-instrumentations/src/playwright-browser-scripts.js +0 -27
  208. package/packages/dd-trace/src/encode/agentless-json.js +0 -210
@@ -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()
@@ -80,9 +81,12 @@ const testsToTestStatuses = new Map()
80
81
  const activeRumPages = new Set()
81
82
 
82
83
  const RUM_FLUSH_WAIT_TIME = getValueFromEnvSources('DD_CIVISIBILITY_RUM_FLUSH_WAIT_MILLIS')
84
+ const isPlaywrightWorker = getValueFromEnvSources('DD_PLAYWRIGHT_WORKER') === '1'
83
85
  const DD_PROPERTIES_TIMEOUT = 5000
84
86
  const isFailureScreenshotUploadEnabled =
85
87
  getValueFromEnvSources('DD_TEST_FAILURE_SCREENSHOTS_ENABLED') === true
88
+ const isFailureVideoUploadEnabled =
89
+ getValueFromEnvSources('DD_TEST_FAILURE_VIDEOS_ENABLED') === true
86
90
 
87
91
  let applyRepeatEachIndex = null
88
92
  let reporterError
@@ -91,7 +95,7 @@ let hasReporterError = false
91
95
  let startedSuites = []
92
96
 
93
97
  // Browser-side callbacks live in a coverage-excluded file so coverage counters can't reach chromium.
94
- const { detectRum, stopRumSession } = require('./playwright-browser-scripts')
98
+ const { detectRum, stopRumSession } = require('./rum-browser-scripts')
95
99
 
96
100
  const STATUS_TO_TEST_STATUS = {
97
101
  passed: 'pass',
@@ -134,18 +138,22 @@ let rootDir = ''
134
138
  let sessionProjects = []
135
139
 
136
140
  const MINIMUM_SUPPORTED_VERSION_RANGE_EFD = '>=1.38.0' // TODO: remove this once we drop support for v5
141
+ const MINIMUM_FAILURE_VIDEO_UPLOAD_VERSION_RANGE = '>=1.38.0'
137
142
  const EFD_RETRY_COUNT_REQUEST = 'ddEfdRetryCountRequest'
138
143
  const EFD_RETRY_COUNT_RESPONSE = 'ddEfdRetryCountResponse'
139
144
  const DD_PROPERTIES_REQUEST = 'ddPropertiesRequest'
140
145
  const DD_PROPERTIES_RESPONSE = 'ddProperties'
141
146
  const kDdPlaywrightDisabledTestIds = Symbol('ddPlaywrightDisabledTestIds')
142
147
  const kDdPlaywrightFailureScreenshots = Symbol('ddPlaywrightFailureScreenshots')
148
+ const kDdPlaywrightFailureVideos = Symbol('ddPlaywrightFailureVideos')
143
149
  const kDdPlaywrightReporterConfigured = Symbol('ddPlaywrightReporterConfigured')
144
150
  const kDdPlaywrightWorkerHostInstrumented = Symbol('ddPlaywrightWorkerHostInstrumented')
145
151
  const kDdPlaywrightWorkerInstrumented = Symbol('ddPlaywrightWorkerInstrumented')
146
152
  const instrumentedPlaywrightReporters = new WeakSet()
147
153
  const PLAYWRIGHT_FAILURE_SCREENSHOT_PATH_RE = /(?:^|[\\/])test-failed-\d+\.png$/
154
+ const PLAYWRIGHT_FAILURE_VIDEO_PATH_RE = /(?:^|[\\/])video(?:-\d+)?\.webm$/
148
155
  const automaticFailureScreenshotPaths = new Set()
156
+ const automaticFailureVideoPaths = new Set()
149
157
 
150
158
  /**
151
159
  * Returns whether Playwright's internal screenshot recorder created an attachment.
@@ -157,6 +165,16 @@ function isAutomaticFailureScreenshotAttachment (attachment) {
157
165
  return typeof attachment?.path === 'string' && automaticFailureScreenshotPaths.delete(attachment.path)
158
166
  }
159
167
 
168
+ /**
169
+ * Returns whether Playwright's internal video recorder created an attachment.
170
+ *
171
+ * @param {object} attachment - Playwright attachment payload
172
+ * @returns {boolean}
173
+ */
174
+ function isAutomaticFailureVideoAttachment (attachment) {
175
+ return typeof attachment?.path === 'string' && automaticFailureVideoPaths.delete(attachment.path)
176
+ }
177
+
160
178
  function isValidKnownTests (receivedKnownTests) {
161
179
  return !!receivedKnownTests.playwright
162
180
  }
@@ -553,6 +571,21 @@ function isFailureScreenshotCaptureEnabled (projects) {
553
571
  return false
554
572
  }
555
573
 
574
+ /**
575
+ * Returns whether at least one Playwright project records videos that can be retained for failures.
576
+ *
577
+ * @param {Array<object>} projects - Playwright projects with resolved use options
578
+ * @returns {boolean} Whether video capture is enabled
579
+ */
580
+ function isFailureVideoCaptureEnabled (projects) {
581
+ for (const project of projects) {
582
+ const video = project.use?.video
583
+ const mode = typeof video === 'object' && video !== null ? video.mode : video
584
+ if (mode === 'on' || mode === 'retain-on-failure') return true
585
+ }
586
+ return false
587
+ }
588
+
556
589
  function getProjectsFromDispatcher (dispatcher) {
557
590
  const bundledConfig = dispatcher._testRun?.config?.config?.projects
558
591
  if (bundledConfig) {
@@ -614,7 +647,15 @@ function getTestSuiteError (testSuiteAbsolutePath) {
614
647
  if (errors.length === 1) {
615
648
  return errors[0]
616
649
  }
617
- return new Error(`${errors.length} errors in this test suite:\n${errors.map(e => e.message).join('\n------\n')}`)
650
+ let errorMessages = ''
651
+ let isFirstError = true
652
+ for (const error of errors) {
653
+ if (!isFirstError) errorMessages += '\n------\n'
654
+ errorMessages += error.message || error.value || ''
655
+ isFirstError = false
656
+ }
657
+
658
+ return new Error(`${errors.length} errors in this test suite:\n${errorMessages}`)
618
659
  }
619
660
 
620
661
  function getTestByTestId (dispatcher, testId) {
@@ -1087,6 +1128,7 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1087
1128
  const projects = getProjectsFromDispatcher(dispatcher)
1088
1129
  sessionProjects = projects
1089
1130
  const automaticFailureScreenshotsByTestId = new Map()
1131
+ const failureVideosByTestId = new Map()
1090
1132
 
1091
1133
  if (disabledTestIds.size && !worker[kDdPlaywrightWorkerHostInstrumented] &&
1092
1134
  typeof worker.runTestGroup === 'function') {
@@ -1112,20 +1154,28 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1112
1154
  if (!test) return
1113
1155
 
1114
1156
  automaticFailureScreenshotsByTestId.clear()
1157
+ failureVideosByTestId.clear()
1115
1158
  const browser = getBrowserNameFromProjects(projects, test)
1116
1159
  const shouldCreateTestSpan = test.expectedStatus === 'skipped'
1117
1160
  testBeginHandler(test, browser, shouldCreateTestSpan)
1118
1161
  })
1119
1162
  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)
1163
+ const { testId, _ddIsAutomaticFailureScreenshot, _ddIsAutomaticFailureVideo } = attachment
1164
+ if (_ddIsAutomaticFailureScreenshot) {
1165
+ let screenshots = automaticFailureScreenshotsByTestId.get(testId)
1166
+ if (!screenshots) {
1167
+ screenshots = []
1168
+ automaticFailureScreenshotsByTestId.set(testId, screenshots)
1169
+ }
1170
+ screenshots.push(attachment)
1171
+ } else if (isFailureVideoUploadEnabled && _ddIsAutomaticFailureVideo) {
1172
+ let videos = failureVideosByTestId.get(testId)
1173
+ if (!videos) {
1174
+ videos = []
1175
+ failureVideosByTestId.set(testId, videos)
1176
+ }
1177
+ videos.push(attachment)
1127
1178
  }
1128
- screenshots.push(attachment)
1129
1179
  })
1130
1180
  worker.on('testEnd', ({ testId, status, errors, annotations }) => {
1131
1181
  const test = getTestByTestId(dispatcher, testId)
@@ -1164,6 +1214,14 @@ function onDispatcherCreateWorker (dispatcher, worker) {
1164
1214
  worker[kDdPlaywrightFailureScreenshots] ??= []
1165
1215
  worker[kDdPlaywrightFailureScreenshots].push(screenshots)
1166
1216
  }
1217
+ if (isFailureVideoUploadEnabled &&
1218
+ !shouldCreateTestSpan &&
1219
+ !test._ddShouldSkipEfdRetry &&
1220
+ !disabledTestIds.has(testId)) {
1221
+ const videos = testStatus === 'fail' ? failureVideosByTestId.get(testId) : undefined
1222
+ worker[kDdPlaywrightFailureVideos] ??= []
1223
+ worker[kDdPlaywrightFailureVideos].push(videos)
1224
+ }
1167
1225
  const isAtrRetry = testResult?.retry > 0 &&
1168
1226
  isFlakyTestRetriesEnabled &&
1169
1227
  !test._ddIsAttemptToFix &&
@@ -1278,6 +1336,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1278
1336
  rootDir = getRootDir(this, config)
1279
1337
  const projects = getProjectsFromRunner(this, config)
1280
1338
  const isFailureScreenshotEnabled = isFailureScreenshotCaptureEnabled(projects)
1339
+ const isFailureVideoEnabled = isFailureVideoCaptureEnabled(projects)
1340
+ const isFailureVideoUploadSupported = satisfies(playwrightVersion, MINIMUM_FAILURE_VIDEO_UPLOAD_VERSION_RANGE)
1281
1341
  const processArgv = process.argv.slice(2).join(' ')
1282
1342
  const command = `playwright ${processArgv}`
1283
1343
  testSessionStartCh.publish({
@@ -1285,6 +1345,8 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1285
1345
  frameworkVersion: playwrightVersion,
1286
1346
  rootDir,
1287
1347
  isFailureScreenshotEnabled,
1348
+ isFailureVideoEnabled,
1349
+ isFailureVideoUploadSupported,
1288
1350
  })
1289
1351
 
1290
1352
  try {
@@ -1311,7 +1373,11 @@ function runAllTestsWrapper (runAllTests, playwrightVersion) {
1311
1373
  log.error('Playwright session start error', e)
1312
1374
  }
1313
1375
 
1314
- testSessionConfigurationCh.publish({ isFailureScreenshotEnabled })
1376
+ testSessionConfigurationCh.publish({
1377
+ isFailureScreenshotEnabled,
1378
+ isFailureVideoEnabled,
1379
+ isFailureVideoUploadSupported,
1380
+ })
1315
1381
 
1316
1382
  const isTestOptimizationSupported = satisfies(playwrightVersion, MINIMUM_SUPPORTED_VERSION_RANGE_EFD)
1317
1383
  const shouldGetKnownTests = isKnownTestsEnabled && isTestOptimizationSupported
@@ -1647,6 +1713,9 @@ createRootSuiteCh.subscribe({
1647
1713
 
1648
1714
  pageGotoCh.subscribe({
1649
1715
  asyncEnd (ctx) {
1716
+ // Playwright library consumers such as Vitest have no Playwright test span and may navigate during page startup.
1717
+ if (!isPlaywrightWorker) return
1718
+
1650
1719
  // The Page.goto rewriter waits for this so tests closing immediately after navigation still get RUM tags.
1651
1720
  const rumDetectionPromise = handlePageGoto(ctx.self)
1652
1721
  ctx.resolveCallback = onDone => rumDetectionPromise.then(onDone, onDone)
@@ -1697,8 +1766,33 @@ function recordAutomaticFailureScreenshotPath (ctx) {
1697
1766
  }
1698
1767
  }
1699
1768
 
1769
+ /**
1770
+ * Records only the destination used by Playwright's automatic video recorder.
1771
+ *
1772
+ * @param {object} ctx - Orchestrion context
1773
+ * @returns {void}
1774
+ */
1775
+ function recordAutomaticFailureVideoPath (ctx) {
1776
+ const video = ctx.arguments?.[0]
1777
+ if (!isFailureVideoUploadEnabled || typeof video?.saveAs !== 'function') return
1778
+
1779
+ const saveAs = video.saveAs
1780
+ ctx.arguments[0] = new Proxy(video, {
1781
+ get (target, property, receiver) {
1782
+ if (property !== 'saveAs') return Reflect.get(target, property, receiver)
1783
+ return function (filePath) {
1784
+ if (typeof filePath === 'string' && PLAYWRIGHT_FAILURE_VIDEO_PATH_RE.test(filePath)) {
1785
+ automaticFailureVideoPaths.add(filePath)
1786
+ }
1787
+ return Reflect.apply(saveAs, target, arguments)
1788
+ }
1789
+ },
1790
+ })
1791
+ }
1792
+
1700
1793
  artifactsRecorderScreenshotPathCh.subscribe({ end: recordAutomaticFailureScreenshotPath })
1701
1794
  snapshotRecorderScreenshotPathCh.subscribe({ end: recordAutomaticFailureScreenshotPath })
1795
+ saveAutomaticVideoCh.subscribe({ start: recordAutomaticFailureVideoPath })
1702
1796
 
1703
1797
  if (DD_MAJOR < 6) { // <1.38.0 is only supported up to version 5
1704
1798
  addHook({
@@ -2061,6 +2155,7 @@ function finishProcessHostStartRunner (processHost) {
2061
2155
  workerReportCh.publish({
2062
2156
  serializedTraces: message[1],
2063
2157
  screenshots: processHost[kDdPlaywrightFailureScreenshots]?.shift(),
2158
+ videos: processHost[kDdPlaywrightFailureVideos]?.shift(),
2064
2159
  })
2065
2160
  } else if (Array.isArray(message) && message[0] === PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE) {
2066
2161
  workerReportTelemetryCh.publish(message[1])
@@ -2409,6 +2504,7 @@ function instrumentWorkerMainMethods (workerMain) {
2409
2504
  shimmer.wrap(workerMain, 'dispatchEvent', dispatchEvent => function (event, payload) {
2410
2505
  if (event === 'testBegin') {
2411
2506
  automaticFailureScreenshotPaths.clear()
2507
+ automaticFailureVideoPaths.clear()
2412
2508
  } else if (event === 'stepBegin') {
2413
2509
  stepInfoByStepId[payload.stepId] = {
2414
2510
  startTime: payload.wallTime,
@@ -2425,8 +2521,12 @@ function instrumentWorkerMainMethods (workerMain) {
2425
2521
  duration: payload.wallTime - stepInfo.startTime,
2426
2522
  error: payload.error,
2427
2523
  })
2428
- } else if (event === 'attach' && isAutomaticFailureScreenshotAttachment(payload)) {
2429
- payload._ddIsAutomaticFailureScreenshot = true
2524
+ } else if (event === 'attach') {
2525
+ if (isAutomaticFailureScreenshotAttachment(payload)) {
2526
+ payload._ddIsAutomaticFailureScreenshot = true
2527
+ } else if (isAutomaticFailureVideoAttachment(payload)) {
2528
+ payload._ddIsAutomaticFailureVideo = true
2529
+ }
2430
2530
  }
2431
2531
  return dispatchEvent.apply(this, arguments)
2432
2532
  })
@@ -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
  *