dd-trace 6.7.0 → 6.9.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 (272) hide show
  1. package/ci/diagnose.js +27 -25
  2. package/ci/init.js +6 -6
  3. package/ci/runbook.md +114 -20
  4. package/ci/test-optimization-validation/approval-artifacts.js +36 -3
  5. package/ci/test-optimization-validation/approval.js +78 -22
  6. package/ci/test-optimization-validation/blocker-category.js +24 -0
  7. package/ci/test-optimization-validation/ci-discovery.js +23 -17
  8. package/ci/test-optimization-validation/ci-package-scripts.js +34 -6
  9. package/ci/test-optimization-validation/ci-remediation.js +1 -1
  10. package/ci/test-optimization-validation/cli.js +280 -52
  11. package/ci/test-optimization-validation/command-blocker.js +159 -20
  12. package/ci/test-optimization-validation/command-output-policy.js +14 -8
  13. package/ci/test-optimization-validation/command-runner.js +9 -7
  14. package/ci/test-optimization-validation/environment.js +4 -2
  15. package/ci/test-optimization-validation/executable.js +15 -13
  16. package/ci/test-optimization-validation/execution-lock.js +70 -0
  17. package/ci/test-optimization-validation/framework-adapters/cucumber-validation.json +3 -0
  18. package/ci/test-optimization-validation/framework-adapters/cucumber.js +657 -5
  19. package/ci/test-optimization-validation/framework-adapters/playwright.js +5 -5
  20. package/ci/test-optimization-validation/framework-adapters/vitest.js +517 -0
  21. package/ci/test-optimization-validation/generated-files.js +47 -37
  22. package/ci/test-optimization-validation/generated-test-contract.js +9 -7
  23. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  24. package/ci/test-optimization-validation/literal-glob.js +52 -0
  25. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  26. package/ci/test-optimization-validation/manifest-scaffold.js +301 -52
  27. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  28. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  29. package/ci/test-optimization-validation/offline-output.js +6 -6
  30. package/ci/test-optimization-validation/package-check.js +94 -0
  31. package/ci/test-optimization-validation/plan-writer.js +183 -10
  32. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  33. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  34. package/ci/test-optimization-validation/redaction.js +18 -27
  35. package/ci/test-optimization-validation/report-writer.js +182 -48
  36. package/ci/test-optimization-validation/result-semantics.js +20 -3
  37. package/ci/test-optimization-validation/runner-command.js +41 -21
  38. package/ci/test-optimization-validation/runner-contract.js +79 -43
  39. package/ci/test-optimization-validation/safe-files.js +4 -4
  40. package/ci/test-optimization-validation/scenarios/basic-reporting.js +60 -6
  41. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  42. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  43. package/ci/test-optimization-validation/scenarios/test-management.js +2 -2
  44. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  45. package/ci/test-optimization-validation/test-output.js +2 -1
  46. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  47. package/ci/vitest-no-worker-init-setup.mjs +377 -84
  48. package/index.d.ts +175 -14
  49. package/init.js +1 -17
  50. package/initialize.mjs +11 -0
  51. package/loader-hook.mjs +6 -4
  52. package/package.json +12 -4
  53. package/packages/datadog-core/src/utils/src/kebabcase.js +8 -2
  54. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  55. package/packages/datadog-esbuild/index.js +4 -2
  56. package/packages/datadog-esbuild/src/utils.js +17 -2
  57. package/packages/datadog-instrumentations/src/ai.js +61 -34
  58. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  59. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +85 -9
  60. package/packages/datadog-instrumentations/src/cucumber.js +131 -51
  61. package/packages/datadog-instrumentations/src/dns.js +21 -2
  62. package/packages/datadog-instrumentations/src/electron.js +1 -0
  63. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  64. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  65. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  66. package/packages/datadog-instrumentations/src/helpers/extract-package-and-module-path.js +10 -0
  67. package/packages/datadog-instrumentations/src/helpers/hook.js +19 -2
  68. package/packages/datadog-instrumentations/src/helpers/hooks.js +4 -0
  69. package/packages/datadog-instrumentations/src/helpers/register.js +9 -3
  70. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +34 -9
  71. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  72. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +175 -0
  73. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  74. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  75. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  76. package/packages/datadog-instrumentations/src/helpers/rewriter/transforms.js +50 -18
  77. package/packages/datadog-instrumentations/src/helpers/router-helper.js +38 -7
  78. package/packages/datadog-instrumentations/src/helpers/shared-utils.js +39 -0
  79. package/packages/datadog-instrumentations/src/http/client.js +1 -3
  80. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  81. package/packages/datadog-instrumentations/src/jest.js +986 -246
  82. package/packages/datadog-instrumentations/src/mocha/common.js +1 -3
  83. package/packages/datadog-instrumentations/src/mocha/main.js +109 -51
  84. package/packages/datadog-instrumentations/src/mocha/utils.js +120 -32
  85. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +44 -0
  86. package/packages/datadog-instrumentations/src/mocha/worker.js +441 -43
  87. package/packages/datadog-instrumentations/src/moleculer/client.js +1 -1
  88. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  89. package/packages/datadog-instrumentations/src/next.js +155 -23
  90. package/packages/datadog-instrumentations/src/openai.js +14 -0
  91. package/packages/datadog-instrumentations/src/pg.js +180 -6
  92. package/packages/datadog-instrumentations/src/playwright.js +176 -81
  93. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  94. package/packages/datadog-instrumentations/src/promise.js +3 -3
  95. package/packages/datadog-instrumentations/src/rhea.js +1 -1
  96. package/packages/datadog-instrumentations/src/router.js +195 -19
  97. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +567 -65
  98. package/packages/datadog-instrumentations/src/vitest-main.js +438 -54
  99. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  100. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  101. package/packages/datadog-instrumentations/src/webdriverio.js +1090 -0
  102. package/packages/datadog-instrumentations/src/when.js +3 -3
  103. package/packages/datadog-instrumentations/src/ws.js +5 -1
  104. package/packages/datadog-plugin-aerospike/src/index.js +1 -3
  105. package/packages/datadog-plugin-amqplib/src/client.js +1 -1
  106. package/packages/datadog-plugin-amqplib/src/producer.js +1 -1
  107. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  108. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +2 -2
  109. package/packages/datadog-plugin-aws-sdk/src/services/stepfunctions.js +2 -2
  110. package/packages/datadog-plugin-azure-event-hubs/src/producer.js +1 -3
  111. package/packages/datadog-plugin-azure-service-bus/src/producer.js +5 -3
  112. package/packages/datadog-plugin-bullmq/src/producer.js +3 -3
  113. package/packages/datadog-plugin-child_process/src/index.js +3 -3
  114. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +6 -11
  115. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  116. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  117. package/packages/datadog-plugin-cypress/src/source-map-utils.js +1 -1
  118. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  119. package/packages/datadog-plugin-electron/src/net.js +10 -4
  120. package/packages/datadog-plugin-fastify/src/tracing.js +1 -1
  121. package/packages/datadog-plugin-fs/src/index.js +0 -4
  122. package/packages/datadog-plugin-google-cloud-pubsub/src/consumer.js +14 -7
  123. package/packages/datadog-plugin-google-cloud-pubsub/src/producer.js +19 -10
  124. package/packages/datadog-plugin-graphql/src/execute.js +1 -0
  125. package/packages/datadog-plugin-graphql/src/tools/signature.js +1 -1
  126. package/packages/datadog-plugin-http/src/client.js +3 -2
  127. package/packages/datadog-plugin-http2/src/client.js +1 -1
  128. package/packages/datadog-plugin-jest/src/index.js +3 -21
  129. package/packages/datadog-plugin-jest/src/util.js +4 -1
  130. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  131. package/packages/datadog-plugin-langchain/src/tokens.js +2 -2
  132. package/packages/datadog-plugin-mocha/src/index.js +342 -6
  133. package/packages/datadog-plugin-next/src/index.js +57 -9
  134. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  135. package/packages/datadog-plugin-pg/src/index.js +65 -1
  136. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  137. package/packages/datadog-plugin-prisma/src/index.js +5 -1
  138. package/packages/datadog-plugin-rhea/src/consumer.js +16 -13
  139. package/packages/datadog-plugin-rhea/src/producer.js +1 -1
  140. package/packages/datadog-plugin-undici/src/index.js +2 -1
  141. package/packages/datadog-plugin-vitest/src/index.js +179 -18
  142. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  143. package/packages/datadog-webpack/index.js +2 -2
  144. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  145. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  146. package/packages/dd-trace/src/appsec/downstream_requests.js +1 -1
  147. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  148. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  149. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  150. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  151. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  152. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-base-analyzer.js +1 -1
  153. package/packages/dd-trace/src/appsec/iast/analyzers/hardcoded-password-analyzer.js +1 -1
  154. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  155. package/packages/dd-trace/src/appsec/iast/analyzers/sql-injection-analyzer.js +1 -1
  156. package/packages/dd-trace/src/appsec/iast/taint-tracking/taint-tracking-impl.js +1 -3
  157. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +2 -2
  158. package/packages/dd-trace/src/appsec/iast/vulnerability-reporter.js +2 -2
  159. package/packages/dd-trace/src/appsec/index.js +48 -476
  160. package/packages/dd-trace/src/appsec/sdk/track_event.js +10 -1
  161. package/packages/dd-trace/src/appsec/waf/waf_manager.js +1 -1
  162. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  163. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  164. package/packages/dd-trace/src/ci-visibility/exporters/ci-validation/sink.js +7 -6
  165. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +29 -15
  166. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +19 -1
  167. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +25 -1
  168. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/webdriverio.js +26 -0
  169. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -1
  170. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  171. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  172. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -3
  173. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  174. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +2 -2
  175. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  176. package/packages/dd-trace/src/config/defaults.js +14 -6
  177. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -2
  178. package/packages/dd-trace/src/config/helper.js +2 -0
  179. package/packages/dd-trace/src/config/index.js +31 -5
  180. package/packages/dd-trace/src/config/parsers.js +19 -1
  181. package/packages/dd-trace/src/config/remote_config.js +1 -1
  182. package/packages/dd-trace/src/config/supported-configurations.json +13 -6
  183. package/packages/dd-trace/src/datastreams/encoding.js +3 -4
  184. package/packages/dd-trace/src/debugger/devtools_client/log.js +2 -1
  185. package/packages/dd-trace/src/debugger/devtools_client/probe_sampler.js +1 -4
  186. package/packages/dd-trace/src/debugger/devtools_client/send.js +1 -2
  187. package/packages/dd-trace/src/debugger/devtools_client/snapshot/index.js +2 -2
  188. package/packages/dd-trace/src/debugger/devtools_client/snapshot/redaction.js +16 -2
  189. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  190. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  191. package/packages/dd-trace/src/encode/tags-processors.js +2 -2
  192. package/packages/dd-trace/src/exporters/common/url.js +3 -5
  193. package/packages/dd-trace/src/exporters/electron/index.js +4 -1
  194. package/packages/dd-trace/src/external-logger/src/index.js +1 -2
  195. package/packages/dd-trace/src/flare/index.js +2 -2
  196. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  197. package/packages/dd-trace/src/guardrails/log.js +1 -10
  198. package/packages/dd-trace/src/id.js +1 -1
  199. package/packages/dd-trace/src/llmobs/constants/tags.js +1 -1
  200. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  201. package/packages/dd-trace/src/llmobs/experiments/client.js +4 -2
  202. package/packages/dd-trace/src/llmobs/experiments/dataset.js +66 -14
  203. package/packages/dd-trace/src/llmobs/experiments/experiment.js +289 -95
  204. package/packages/dd-trace/src/llmobs/experiments/index.js +66 -19
  205. package/packages/dd-trace/src/llmobs/experiments/noop.js +95 -10
  206. package/packages/dd-trace/src/llmobs/experiments/result.js +13 -2
  207. package/packages/dd-trace/src/llmobs/experiments/util.js +220 -0
  208. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  209. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  210. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  211. package/packages/dd-trace/src/llmobs/plugins/anthropic/index.js +1 -1
  212. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  213. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +1 -1
  214. package/packages/dd-trace/src/llmobs/plugins/genai/index.js +1 -1
  215. package/packages/dd-trace/src/llmobs/plugins/langchain/handlers/index.js +1 -1
  216. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  217. package/packages/dd-trace/src/llmobs/sdk.js +152 -78
  218. package/packages/dd-trace/src/llmobs/span_processor.js +9 -7
  219. package/packages/dd-trace/src/llmobs/tagger.js +1 -2
  220. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  221. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  222. package/packages/dd-trace/src/llmobs/writers/spans.js +12 -6
  223. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  224. package/packages/dd-trace/src/log/channels.js +1 -9
  225. package/packages/dd-trace/src/log/levels.js +12 -0
  226. package/packages/dd-trace/src/noop/proxy.js +2 -2
  227. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  228. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  229. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  230. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  231. package/packages/dd-trace/src/opentelemetry/metrics/otlp_transformer.js +1 -1
  232. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +3 -2
  233. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  234. package/packages/dd-trace/src/opentracing/span.js +1 -1
  235. package/packages/dd-trace/src/opentracing/span_context.js +1 -1
  236. package/packages/dd-trace/src/plugins/ci_plugin.js +124 -23
  237. package/packages/dd-trace/src/plugins/index.js +2 -0
  238. package/packages/dd-trace/src/plugins/plugin.js +1 -1
  239. package/packages/dd-trace/src/plugins/tracing.js +1 -0
  240. package/packages/dd-trace/src/plugins/util/ci.js +13 -6
  241. package/packages/dd-trace/src/plugins/util/git.js +7 -5
  242. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +3 -3
  243. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  244. package/packages/dd-trace/src/plugins/util/llm.js +2 -2
  245. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  246. package/packages/dd-trace/src/plugins/util/tags.js +2 -0
  247. package/packages/dd-trace/src/plugins/util/test.js +356 -86
  248. package/packages/dd-trace/src/plugins/util/url.js +5 -5
  249. package/packages/dd-trace/src/plugins/util/user-provided-git.js +1 -1
  250. package/packages/dd-trace/src/plugins/util/web.js +24 -2
  251. package/packages/dd-trace/src/priority_sampler.js +6 -3
  252. package/packages/dd-trace/src/profiler.js +1 -1
  253. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  254. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  255. package/packages/dd-trace/src/profiling/profilers/poisson.js +1 -1
  256. package/packages/dd-trace/src/profiling/profilers/shared.js +2 -2
  257. package/packages/dd-trace/src/proxy.js +1 -0
  258. package/packages/dd-trace/src/ritm.js +10 -20
  259. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  260. package/packages/dd-trace/src/sampling_rule.js +4 -2
  261. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  262. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  263. package/packages/dd-trace/src/startup-log.js +3 -0
  264. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  265. package/packages/dd-trace/src/telemetry/logs/log-collector.js +3 -1
  266. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  267. package/packages/dd-trace/src/util.js +16 -0
  268. package/vendor/dist/@apm-js-collab/code-transformer/index.js +12 -12
  269. package/vendor/dist/pprof-format/index.js +1 -1
  270. package/vendor/dist/source-map/index.js +1 -1
  271. package/version.js +10 -8
  272. /package/packages/dd-trace/src/appsec/{blocked_templates.js → blocking/templates.js} +0 -0
@@ -1,28 +1,25 @@
1
- import { realpathSync } from 'node:fs'
2
- import { relative } from 'node:path'
3
- import { performance } from 'node:perf_hooks'
1
+ import { afterEach, beforeAll, beforeEach, inject } from 'vitest'
4
2
 
5
- import { afterEach, beforeAll, beforeEach } from 'vitest'
6
-
7
- // Instrumentation-less worker setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
8
- // It applies Test Optimization execution changes without initializing dd-trace in Vitest workers.
3
+ // Instrumentation-less setup for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
4
+ // It applies Test Optimization execution changes without initializing dd-trace and also supports Browser Mode.
9
5
  const VITEST_NO_WORKER_INIT_ACTIVE_ENV = 'DD_TEST_OPT_VITEST_NO_WORKER_INIT_ACTIVE'
10
- const isNoWorkerInitActive = getIsNoWorkerInitActive()
11
- const providedContext = isNoWorkerInitActive ? getProvidedContext() : {}
6
+ const providedContext = getProvidedContext()
7
+ const isNoWorkerInitActive = providedContext.isActive ?? getIsNoWorkerInitActive()
12
8
  const attemptToFixTests = providedContext.attemptToFixTests || {}
13
9
  const attemptToFixRetries = providedContext.attemptToFixRetries || 0
14
10
  const disabledTests = providedContext.disabledTests || {}
15
- const earlyFlakeDetectionRetries = providedContext.earlyFlakeDetectionRetries || 0
16
- const earlyFlakeDetectionRetryThresholds = Array.isArray(providedContext.earlyFlakeDetectionRetryThresholds)
17
- ? providedContext.earlyFlakeDetectionRetryThresholds
18
- : []
19
- const earlyFlakeDetectionSlowRetries = providedContext.earlyFlakeDetectionSlowRetries || {}
20
- const hasEarlyFlakeDetectionSlowRetries = Object.keys(earlyFlakeDetectionSlowRetries).length > 0
11
+ const earlyFlakeDetectionRetryPolicy = providedContext.earlyFlakeDetectionRetryPolicy || {
12
+ durationRetryCounts: [],
13
+ schedulingRetryCount: 0,
14
+ }
15
+ const earlyFlakeDetectionRetries = earlyFlakeDetectionRetryPolicy.schedulingRetryCount
21
16
  const isEarlyFlakeDetectionEnabled = providedContext.isEarlyFlakeDetectionEnabled === true
22
17
  const knownTests = providedContext.knownTests || {}
23
18
  const modifiedFiles = providedContext.modifiedFiles || {}
24
19
  const quarantinedTests = providedContext.quarantinedTests || {}
25
- const repositoryRoot = realpath(providedContext.repositoryRoot || process.cwd())
20
+ const isRumCorrelationEnabled = providedContext.isRumCorrelationEnabled !== false
21
+ const rumTestExecutionIdCookieName = providedContext.rumTestExecutionIdCookieName
22
+ const testPropertiesByFilepath = providedContext.testPropertiesByFilepath || {}
26
23
  let setVitestTaskFn
27
24
  if (isNoWorkerInitActive) {
28
25
  try {
@@ -35,7 +32,21 @@ const earlyFlakeDetectionRetriesByTask = new WeakMap()
35
32
  const earlyFlakeDetectionSkippedResults = new WeakMap()
36
33
  const earlyFlakeDetectionStartByTask = new WeakMap()
37
34
  const nextAttemptIndexByTask = new WeakMap()
38
- const realpaths = new Map()
35
+ const retryAttemptIndexByTask = new WeakMap()
36
+ const usedRumTestExecutionIds = new Set()
37
+ let now
38
+ let timeOrigin
39
+ // Use an unfaked monotonic clock in Node and Vitest's parent frame in Browser Mode.
40
+ if (typeof globalThis.process?.uptime === 'function') {
41
+ now = () => globalThis.process.uptime() * 1000
42
+ timeOrigin = Date.now() - now()
43
+ } else {
44
+ const clock = globalThis.window?.parent && globalThis.window.parent !== globalThis.window
45
+ ? globalThis.window.parent.performance
46
+ : globalThis.performance
47
+ now = clock ? clock.now.bind(clock) : Date.now
48
+ timeOrigin = Number.isFinite(clock?.timeOrigin) ? clock.timeOrigin : Date.now() - now()
49
+ }
39
50
 
40
51
  if (isNoWorkerInitActive) {
41
52
  // eslint-disable-next-line no-empty-pattern
@@ -51,11 +62,12 @@ if (isNoWorkerInitActive) {
51
62
  const isEarlyFlakeDetectionTestAttempt = isEarlyFlakeDetectionTest(testSuite, testName)
52
63
  const isQuarantinedTest = quarantinedTests[testSuite]?.[testName] && !isAttemptToFixTest
53
64
  const attemptIndex = getNextAttemptIndex(task)
65
+ const attemptStart = now()
54
66
  if (attemptIndex > 0) {
55
67
  recordTestOptimizationStatus(task, attemptIndex - 1)
56
68
  }
57
69
 
58
- if (disabledTests[testSuite]?.[testName] && !isAttemptToFixTest) {
70
+ if (!isAttemptToFixTest && disabledTests[testSuite]?.[testName]) {
59
71
  skip('Skipped by Datadog Test Optimization')
60
72
  } else if (isAttemptToFixTest && attemptIndex > 0) {
61
73
  task.result.state = 'run'
@@ -66,49 +78,59 @@ if (isNoWorkerInitActive) {
66
78
  }
67
79
  }
68
80
 
69
- if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt || isQuarantinedTest) {
70
- onTestFinished(() => {
71
- if (attemptIndex === getFinalAttemptIndex(task)) {
72
- if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt) {
73
- recordTestOptimizationStatus(task, attemptIndex, true)
74
- }
75
- switchQuarantinedFinalFailure(task, attemptIndex)
81
+ prepareRumCorrelation(task, attemptIndex)
82
+
83
+ onTestFinished(() => {
84
+ recordTestAttemptTiming(task, attemptIndex, attemptStart)
85
+ recordRetryErrorCount(task)
86
+ if (
87
+ (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt || isQuarantinedTest) &&
88
+ attemptIndex === getFinalAttemptIndex(task)
89
+ ) {
90
+ if (isAttemptToFixTest || isEarlyFlakeDetectionTestAttempt) {
91
+ recordTestOptimizationStatus(task, attemptIndex, true)
76
92
  }
77
- })
78
- }
93
+ switchQuarantinedFinalFailure(task, attemptIndex)
94
+ }
95
+ finishRumCorrelation(task, attemptIndex)
96
+ })
79
97
  })
80
98
 
81
99
  afterEach(function ({ task }) {
82
100
  const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
83
- if (restoreEarlyFlakeDetectionSkippedResult(task)) {
84
- return
85
- }
86
- if (attemptIndex === getFinalAttemptIndex(task)) {
101
+ const restoredEarlyFlakeDetectionResult = restoreEarlyFlakeDetectionSkippedResult(task)
102
+ if (!restoredEarlyFlakeDetectionResult && attemptIndex === getFinalAttemptIndex(task)) {
87
103
  recordTestOptimizationStatus(task, attemptIndex)
88
104
  }
89
- switchQuarantinedFinalFailure(task, attemptIndex)
105
+ if (!restoredEarlyFlakeDetectionResult) {
106
+ switchQuarantinedFinalFailure(task, attemptIndex)
107
+ }
90
108
  })
91
109
  }
92
110
 
93
111
  function applyExecutionChanges (suite) {
94
- for (const task of suite?.tasks || []) {
95
- if (task.type === 'suite') {
96
- applyExecutionChanges(task)
97
- continue
98
- }
112
+ const tasks = suite?.tasks
113
+ if (tasks) {
114
+ for (const task of tasks) {
115
+ if (task.type === 'suite') {
116
+ applyExecutionChanges(task)
117
+ continue
118
+ }
99
119
 
100
- const testSuite = getTestSuite(task)
101
- const testName = getTestName(task)
102
- if (attemptToFixTests[testSuite]?.[testName]) {
103
- task.retry = 0
104
- task.repeats = attemptToFixRetries
105
- task.meta.__ddTestOptAtfRetries = attemptToFixRetries
106
- } else if (disabledTests[testSuite]?.[testName]) {
107
- task.mode = 'skip'
108
- } else if (isEarlyFlakeDetectionTest(testSuite, testName)) {
109
- task.retry = 0
110
- task.repeats = earlyFlakeDetectionRetries
111
- task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries
120
+ const testSuite = getTestSuite(task)
121
+ const testName = getTestName(task)
122
+ if (attemptToFixTests[testSuite]?.[testName]) {
123
+ task.retry = 0
124
+ task.repeats = attemptToFixRetries
125
+ task.meta.__ddTestOptAtfRetries = attemptToFixRetries
126
+ } else if (disabledTests[testSuite]?.[testName]) {
127
+ task.mode = 'skip'
128
+ } else if (isEarlyFlakeDetectionTest(testSuite, testName)) {
129
+ task.retry = 0
130
+ task.repeats = earlyFlakeDetectionRetries
131
+ task.meta.__ddTestOptEfdRetries = earlyFlakeDetectionRetries
132
+ }
133
+ wrapRetryCondition(task)
112
134
  }
113
135
  }
114
136
  }
@@ -117,9 +139,194 @@ function getNextAttemptIndex (task) {
117
139
  const attemptIndex = nextAttemptIndexByTask.get(task) || 0
118
140
  nextAttemptIndexByTask.set(task, attemptIndex + 1)
119
141
  task.meta.__ddTestOptCurrentAttemptIndex = attemptIndex
142
+
143
+ const repeatCount = task.result?.repeatCount || 0
144
+ const retryAttempt = retryAttemptIndexByTask.get(task)
145
+ const retryAttemptIndex = retryAttempt?.repeatCount === repeatCount ? retryAttempt.index + 1 : 0
146
+ retryAttemptIndexByTask.set(task, {
147
+ index: retryAttemptIndex,
148
+ repeatCount,
149
+ })
150
+
120
151
  return attemptIndex
121
152
  }
122
153
 
154
+ /**
155
+ * Sets the RUM correlation cookie for a sequential browser test attempt.
156
+ *
157
+ * @param {object} task
158
+ * @param {number} attemptIndex
159
+ * @returns {void}
160
+ */
161
+ function prepareRumCorrelation (task, attemptIndex) {
162
+ // A per-origin cookie cannot identify overlapping attempts without racing.
163
+ if (!isRumCorrelationEnabled || isConcurrentTask(task)) return
164
+
165
+ if (
166
+ typeof window === 'undefined' ||
167
+ typeof document === 'undefined' ||
168
+ typeof rumTestExecutionIdCookieName !== 'string'
169
+ ) {
170
+ return
171
+ }
172
+
173
+ const testExecutionId = generateTestExecutionId()
174
+ if (
175
+ !testExecutionId ||
176
+ usedRumTestExecutionIds.has(testExecutionId) ||
177
+ !setRumCorrelationCookie(testExecutionId)
178
+ ) {
179
+ return
180
+ }
181
+
182
+ usedRumTestExecutionIds.add(testExecutionId)
183
+ task.meta.__ddTestOptRumTestExecutionIds ||= []
184
+ task.meta.__ddTestOptRumTestExecutionIds[attemptIndex] = testExecutionId
185
+ recordRumActivity(task, attemptIndex)
186
+ }
187
+
188
+ /**
189
+ * Records whether RUM was active and clears the attempt cookie.
190
+ *
191
+ * @param {object} task
192
+ * @param {number} attemptIndex
193
+ * @returns {void}
194
+ */
195
+ function finishRumCorrelation (task, attemptIndex) {
196
+ const testExecutionId = task.meta.__ddTestOptRumTestExecutionIds?.[attemptIndex]
197
+ if (!testExecutionId) return
198
+
199
+ recordRumActivity(task, attemptIndex)
200
+ clearRumCorrelationCookie(testExecutionId)
201
+ }
202
+
203
+ /**
204
+ * Retains evidence that RUM was active at any point observed during the attempt.
205
+ *
206
+ * @param {object} task
207
+ * @param {number} attemptIndex
208
+ * @returns {void}
209
+ */
210
+ function recordRumActivity (task, attemptIndex) {
211
+ if (!getIsRumActive(getRum())) return
212
+
213
+ task.meta.__ddTestOptRumActive ||= []
214
+ task.meta.__ddTestOptRumActive[attemptIndex] = true
215
+ }
216
+
217
+ /**
218
+ * Returns the RUM public API without letting application-defined accessors fail a test.
219
+ *
220
+ * @returns {object|undefined}
221
+ */
222
+ function getRum () {
223
+ try {
224
+ return window.DD_RUM
225
+ } catch {}
226
+ }
227
+
228
+ /**
229
+ * Returns whether the current RUM session is active.
230
+ *
231
+ * @param {object|undefined} rum
232
+ * @returns {boolean}
233
+ */
234
+ function getIsRumActive (rum) {
235
+ if (!rum) return false
236
+ if (typeof rum.getInternalContext !== 'function') return false
237
+
238
+ try {
239
+ return !!rum.getInternalContext()
240
+ } catch {
241
+ return false
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Returns whether a task or any containing suite is concurrent.
247
+ *
248
+ * @param {object} task
249
+ * @returns {boolean}
250
+ */
251
+ function isConcurrentTask (task) {
252
+ let currentTask = task
253
+ while (currentTask) {
254
+ if (currentTask.concurrent === true) return true
255
+ currentTask = currentTask.suite
256
+ }
257
+ return false
258
+ }
259
+
260
+ /**
261
+ * Generates a positive 63-bit decimal test execution ID.
262
+ *
263
+ * @returns {string|undefined}
264
+ */
265
+ function generateTestExecutionId () {
266
+ // This setup file executes in a browser even though lint validates it against the package's Node.js target.
267
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
268
+ if (typeof globalThis.crypto?.getRandomValues !== 'function') return
269
+
270
+ const words = new Uint32Array(2)
271
+ try {
272
+ // eslint-disable-next-line n/no-unsupported-features/node-builtins
273
+ globalThis.crypto.getRandomValues(words)
274
+ } catch {
275
+ return
276
+ }
277
+
278
+ words[0] &= 0x7F_FF_FF_FF
279
+ if (words[0] === 0 && words[1] === 0) return
280
+
281
+ return ((BigInt(words[0]) << 32n) | BigInt(words[1])).toString(10)
282
+ }
283
+
284
+ /**
285
+ * Sets and verifies the RUM correlation cookie for the current origin.
286
+ *
287
+ * @param {string} testExecutionId
288
+ * @returns {boolean}
289
+ */
290
+ function setRumCorrelationCookie (testExecutionId) {
291
+ try {
292
+ // eslint-disable-next-line unicorn/no-document-cookie
293
+ document.cookie = `${rumTestExecutionIdCookieName}=${testExecutionId}; path=/`
294
+ return getRumCorrelationCookie() === testExecutionId
295
+ } catch {
296
+ return false
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Clears the RUM correlation cookie if it still belongs to this attempt.
302
+ *
303
+ * @param {string} testExecutionId
304
+ * @returns {void}
305
+ */
306
+ function clearRumCorrelationCookie (testExecutionId) {
307
+ try {
308
+ if (getRumCorrelationCookie() === testExecutionId) {
309
+ // eslint-disable-next-line unicorn/no-document-cookie
310
+ document.cookie = `${rumTestExecutionIdCookieName}=; path=/; max-age=0`
311
+ }
312
+ } catch {}
313
+ }
314
+
315
+ /**
316
+ * Returns the current RUM correlation cookie value.
317
+ *
318
+ * @returns {string|undefined}
319
+ */
320
+ function getRumCorrelationCookie () {
321
+ const prefix = `${rumTestExecutionIdCookieName}=`
322
+ for (const cookie of document.cookie.split(';')) {
323
+ const normalizedCookie = cookie.trim()
324
+ if (normalizedCookie.startsWith(prefix)) {
325
+ return normalizedCookie.slice(prefix.length)
326
+ }
327
+ }
328
+ }
329
+
123
330
  function recordTestOptimizationStatus (task, attemptIndex = task.result?.repeatCount || 0, onlyIfNewErrors = false) {
124
331
  const testSuite = getTestSuite(task)
125
332
  const testName = getTestName(task)
@@ -175,7 +382,7 @@ function recordEarlyFlakeDetectionStatus (task, attemptIndex, onlyIfNewErrors) {
175
382
  earlyFlakeDetectionRetriesByTask.set(task, retryCount)
176
383
  task.repeats = retryCount
177
384
  task.meta.__ddTestOptEfdRetries = retryCount
178
- if (retryCount === 0 && hasEarlyFlakeDetectionSlowRetries) {
385
+ if (retryCount === 0) {
179
386
  task.meta.__ddTestOptEfdAbortReason = 'slow'
180
387
  }
181
388
  }
@@ -206,6 +413,76 @@ function recordManualRepeatStatus (task, attemptIndex) {
206
413
  task.meta.__ddTestOptRepeatErrorCounts[attemptIndex] = task.result?.errors?.length || 0
207
414
  }
208
415
 
416
+ /**
417
+ * Records cumulative errors at the end of each configured retry attempt.
418
+ *
419
+ * @param {object} task
420
+ * @returns {void}
421
+ */
422
+ function recordRetryErrorCount (task) {
423
+ const retryLimit = getRetryLimit(task)
424
+ if (retryLimit <= 0) return
425
+
426
+ const retryCount = task.result?.retryCount || 0
427
+ task.meta.__ddTestOptRetryErrorCounts ||= []
428
+ task.meta.__ddTestOptRetryErrorCounts[retryCount] = task.result?.errors?.length || 0
429
+ }
430
+
431
+ /**
432
+ * Records the wall-clock start and elapsed time for one retry or repeat execution.
433
+ *
434
+ * @param {object} task
435
+ * @param {number} attemptIndex
436
+ * @param {number} attemptStart
437
+ * @returns {void}
438
+ */
439
+ function recordTestAttemptTiming (task, attemptIndex, attemptStart) {
440
+ task.meta.__ddTestOptAttemptStartTimes ||= []
441
+ task.meta.__ddTestOptAttemptDurations ||= []
442
+ task.meta.__ddTestOptAttemptStartTimes[attemptIndex] = timeOrigin + attemptStart
443
+ task.meta.__ddTestOptAttemptDurations[attemptIndex] = now() - attemptStart
444
+ }
445
+
446
+ /**
447
+ * Returns the configured retry count for numeric and object-form retry options.
448
+ *
449
+ * @param {object} task
450
+ * @returns {number}
451
+ */
452
+ function getRetryLimit (task) {
453
+ return typeof task.retry === 'number' ? task.retry : task.retry?.count || 0
454
+ }
455
+
456
+ /**
457
+ * Wraps Vitest's retry condition so final-attempt handling follows the condition's actual result.
458
+ *
459
+ * @param {object} task
460
+ * @returns {void}
461
+ */
462
+ function wrapRetryCondition (task) {
463
+ if (typeof task.retry !== 'object' || !task.retry?.condition || getRetryLimit(task) === 0) return
464
+
465
+ const condition = task.retry.condition
466
+ task.retry = {
467
+ ...task.retry,
468
+ condition (error) {
469
+ let shouldRetry = false
470
+ if (condition instanceof RegExp) {
471
+ shouldRetry = condition.test(error?.message || '')
472
+ } else if (typeof condition === 'function') {
473
+ shouldRetry = condition(error)
474
+ }
475
+
476
+ if (!shouldRetry && (task.result?.repeatCount || 0) >= (task.repeats || 0)) {
477
+ const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
478
+ recordTestOptimizationStatus(task, attemptIndex)
479
+ markQuarantinedFailure(task)
480
+ }
481
+ return shouldRetry
482
+ },
483
+ }
484
+ }
485
+
209
486
  function restoreEarlyFlakeDetectionSkippedResult (task) {
210
487
  if (!task.meta.__ddTestOptEfdSkipCurrentAttempt) {
211
488
  return false
@@ -236,7 +513,18 @@ function getFinalAttemptIndex (task) {
236
513
  if (isEarlyFlakeDetectionTest(testSuite, testName)) {
237
514
  return getEarlyFlakeDetectionRetryCountForTask(task)
238
515
  }
239
- return task.repeats
516
+
517
+ const attemptIndex = task.meta.__ddTestOptCurrentAttemptIndex
518
+ const repeatCount = task.result?.repeatCount || 0
519
+ const repeatLimit = task.repeats || 0
520
+ const retryAttemptIndex = retryAttemptIndexByTask.get(task)?.index || 0
521
+ const retryLimit = getRetryLimit(task)
522
+ const retriesRemaining = task.result?.state === 'fail' || task.result?.state === 'run'
523
+ ? Math.max(retryLimit - retryAttemptIndex, 0)
524
+ : 0
525
+ const repeatsRemaining = Math.max(repeatLimit - repeatCount, 0)
526
+
527
+ return attemptIndex + retriesRemaining + (repeatsRemaining * (retryLimit + 1))
240
528
  }
241
529
 
242
530
  function switchQuarantinedFinalFailure (task, attemptIndex) {
@@ -250,9 +538,27 @@ function switchQuarantinedFinalFailure (task, attemptIndex) {
250
538
  return
251
539
  }
252
540
 
253
- const retryCount = task.result?.retryCount || 0
254
- const retryLimit = task.retry || 0
255
- if (retryCount < retryLimit || attemptIndex < getFinalAttemptIndex(task)) {
541
+ if (attemptIndex < getFinalAttemptIndex(task)) {
542
+ return
543
+ }
544
+
545
+ markQuarantinedFailure(task)
546
+ }
547
+
548
+ /**
549
+ * Converts a quarantined failure into the passing runner state expected by Vitest.
550
+ *
551
+ * @param {object} task
552
+ * @returns {void}
553
+ */
554
+ function markQuarantinedFailure (task) {
555
+ const testSuite = getTestSuite(task)
556
+ const testName = getTestName(task)
557
+ if (
558
+ !quarantinedTests[testSuite]?.[testName] ||
559
+ attemptToFixTests[testSuite]?.[testName] ||
560
+ task.result?.state !== 'fail'
561
+ ) {
256
562
  return
257
563
  }
258
564
 
@@ -292,7 +598,7 @@ function getPreviousErrorCount (errorCounts, repeatCount) {
292
598
 
293
599
  function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
294
600
  if (attemptIndex === 0) {
295
- earlyFlakeDetectionStartByTask.set(task, performance.now())
601
+ earlyFlakeDetectionStartByTask.set(task, now())
296
602
  return false
297
603
  }
298
604
 
@@ -302,18 +608,18 @@ function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
302
608
  earlyFlakeDetectionRetriesByTask.set(task, retryCount)
303
609
  task.repeats = retryCount
304
610
  task.meta.__ddTestOptEfdRetries = retryCount
305
- if (retryCount === 0 && hasEarlyFlakeDetectionSlowRetries) {
611
+ if (retryCount === 0) {
306
612
  task.meta.__ddTestOptEfdAbortReason = 'slow'
307
613
  }
308
614
  }
309
615
 
310
616
  if (attemptIndex <= retryCount) {
311
- earlyFlakeDetectionStartByTask.set(task, performance.now())
617
+ earlyFlakeDetectionStartByTask.set(task, now())
312
618
  return false
313
619
  }
314
620
 
315
621
  if (!canReplaceVitestTaskFn()) {
316
- earlyFlakeDetectionStartByTask.set(task, performance.now())
622
+ earlyFlakeDetectionStartByTask.set(task, now())
317
623
  return false
318
624
  }
319
625
 
@@ -329,16 +635,10 @@ function prepareEarlyFlakeDetectionAttempt (task, attemptIndex) {
329
635
  }
330
636
 
331
637
  function getEarlyFlakeDetectionRetryCount (task) {
332
- if (!hasEarlyFlakeDetectionSlowRetries) {
333
- return earlyFlakeDetectionRetries
334
- }
335
-
336
638
  const executionStart = earlyFlakeDetectionStartByTask.get(task)
337
- const duration = executionStart === undefined ? task.result?.duration ?? 0 : performance.now() - executionStart
338
- for (const { key, limitMs } of earlyFlakeDetectionRetryThresholds) {
339
- if (duration < limitMs) {
340
- return earlyFlakeDetectionSlowRetries[key] ?? 0
341
- }
639
+ const duration = executionStart === undefined ? task.result?.duration ?? 0 : now() - executionStart
640
+ for (const { durationLimitMs, retryCount } of earlyFlakeDetectionRetryPolicy.durationRetryCounts) {
641
+ if (duration < durationLimitMs) return retryCount
342
642
  }
343
643
  return 0
344
644
  }
@@ -376,21 +676,10 @@ function isModifiedTest (testSuite) {
376
676
  }
377
677
 
378
678
  function getTestSuite (task) {
379
- let filepath = realpaths.get(task.file.filepath)
380
- if (filepath === undefined) {
381
- filepath = realpath(task.file.filepath)
382
- realpaths.set(task.file.filepath, filepath)
383
- }
384
-
385
- return normalizePath(relative(repositoryRoot, filepath))
386
- }
387
-
388
- function realpath (filepath) {
389
- try {
390
- return realpathSync(filepath)
391
- } catch {
392
- return filepath
393
- }
679
+ const filepath = task.file.filepath
680
+ return testPropertiesByFilepath[filepath]?.testSuite ||
681
+ testPropertiesByFilepath[normalizePath(filepath)]?.testSuite ||
682
+ normalizePath(filepath)
394
683
  }
395
684
 
396
685
  function getTestName (task) {
@@ -423,8 +712,12 @@ function getIsNoWorkerInitActive () {
423
712
 
424
713
  function getProvidedContext () {
425
714
  try {
426
- return globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup || {}
715
+ return inject('_ddVitestWorkerSetup') || {}
427
716
  } catch {
428
- return {}
717
+ try {
718
+ return globalThis.__vitest_worker__.providedContext._ddVitestWorkerSetup || {}
719
+ } catch {
720
+ return {}
721
+ }
429
722
  }
430
723
  }