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
@@ -4,13 +4,15 @@ const path = require('node:path')
4
4
 
5
5
  const satisfies = require('../../../vendor/dist/semifies')
6
6
 
7
+ const { hasEfdRetries } = require('../../dd-trace/src/ci-visibility/efd-retry-policy')
8
+ const { RUM_TEST_EXECUTION_ID_COOKIE_NAME } = require('../../dd-trace/src/ci-visibility/rum')
7
9
  const { getValueFromEnvSources } = require('../../dd-trace/src/config/helper')
8
10
  const log = require('../../dd-trace/src/log')
9
11
  const {
10
12
  DYNAMIC_NAME_RE,
11
- EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS,
12
13
  getTestSuitePath,
13
14
  logAttemptToFixTestExecution,
15
+ recordTestManagementExecution,
14
16
  recordAttemptToFixExecution,
15
17
  } = require('../../dd-trace/src/plugins/util/test')
16
18
  const {
@@ -29,9 +31,9 @@ const {
29
31
  testSuiteStartCh,
30
32
  } = require('./vitest-util')
31
33
 
32
- // No-worker-init instrumentation for DD_EXPERIMENTAL_TEST_OPT_VITEST_NO_WORKER_INIT.
33
- // When enabled, Vitest workers do not initialize dd-trace, so this main-process instrumentation
34
- // takes over some worker responsibilities, including test span creation and lifecycle reporting.
34
+ // Main-process Vitest reporting used by no-worker-init and Browser Mode.
35
+ // Execution changes are applied by an instrumentation-less setup file in the test environment,
36
+ // while this module creates test spans from Vitest reporter events.
35
37
  const mainProcessReporterStates = new WeakMap()
36
38
  const loggedAttemptToFixTests = new Set()
37
39
 
@@ -53,16 +55,181 @@ const VITEST_NO_WORKER_INIT_SETUP_FILE = path.join(
53
55
  'ci',
54
56
  'vitest-no-worker-init-setup.mjs'
55
57
  )
58
+ const VITEST_BROWSER_SETUP_FILE_PLUGIN = {
59
+ name: 'datadog:vitest-browser-setup-file',
60
+ config: configureVitestBrowserSetupFile,
61
+ configResolved: allowVitestBrowserSetupFile,
62
+ }
56
63
  const VITEST_NO_WORKER_INIT_ISOLATE_WARNING =
57
64
  `${VITEST_NO_WORKER_INIT_REQUEST_ENV} is ignored because Vitest isolate is disabled. ` +
58
65
  'The lighter Vitest worker path only works when each test file runs in an isolated worker.'
59
66
  const NODE_OPTIONS_QUOTE_RE = /[\s"\\]/
67
+ const VITEST_BROWSER_STACK_LOCATION_RE = /:\d+:\d+$/
68
+ const VITEST_BROWSER_URL_RE = /https?:\/\/[^\s)"'>\]]+/g
60
69
  let hasWarnedDisabledIsolate = false
61
70
  let hasWarnedUnsupportedVersion = false
62
71
  let nodeOptionsBeforeNoWorkerInit
63
72
 
64
73
  function noop () {}
65
74
 
75
+ /**
76
+ * Removes Vite and Vitest runtime query parameters from browser error URLs.
77
+ *
78
+ * @param {string} url
79
+ * @returns {string}
80
+ */
81
+ function removeVitestBrowserUrlMetadata (url) {
82
+ const locationMatch = url.match(VITEST_BROWSER_STACK_LOCATION_RE)
83
+ const location = locationMatch?.[0] || ''
84
+ const urlWithoutLocation = location ? url.slice(0, -location.length) : url
85
+ const queryStart = urlWithoutLocation.indexOf('?')
86
+ if (queryStart === -1) return url
87
+
88
+ const fragmentStart = urlWithoutLocation.indexOf('#', queryStart)
89
+ const queryEnd = fragmentStart === -1 ? urlWithoutLocation.length : fragmentStart
90
+ const parameters = urlWithoutLocation.slice(queryStart + 1, queryEnd).split('&')
91
+ const keptParameters = []
92
+ let removedParameter = false
93
+
94
+ for (const parameter of parameters) {
95
+ const valueSeparator = parameter.indexOf('=')
96
+ const name = valueSeparator === -1 ? parameter : parameter.slice(0, valueSeparator)
97
+ if (name === 'import' || name === 'browserv') {
98
+ removedParameter = true
99
+ } else {
100
+ keptParameters.push(parameter)
101
+ }
102
+ }
103
+
104
+ if (!removedParameter) return url
105
+
106
+ const query = keptParameters.length ? `?${keptParameters.join('&')}` : ''
107
+ return urlWithoutLocation.slice(0, queryStart) + query + urlWithoutLocation.slice(queryEnd) + location
108
+ }
109
+
110
+ /**
111
+ * Removes Vite and Vitest runtime URL metadata from browser error text.
112
+ *
113
+ * @param {string} value
114
+ * @returns {string}
115
+ */
116
+ function normalizeVitestBrowserErrorText (value) {
117
+ return value.replaceAll(VITEST_BROWSER_URL_RE, removeVitestBrowserUrlMetadata)
118
+ }
119
+
120
+ /**
121
+ * Removes the ephemeral Vite server origin in addition to its runtime query parameters.
122
+ *
123
+ * @param {string} url
124
+ * @returns {string}
125
+ */
126
+ function normalizeVitestBrowserStackUrl (url) {
127
+ const normalizedUrl = removeVitestBrowserUrlMetadata(url)
128
+ const protocolEnd = normalizedUrl.indexOf('://')
129
+ const pathStart = normalizedUrl.indexOf('/', protocolEnd + 3)
130
+ return pathStart === -1 ? normalizedUrl : normalizedUrl.slice(pathStart)
131
+ }
132
+
133
+ /**
134
+ * Normalizes browser URLs in a stack when Vitest does not provide parsed frames.
135
+ *
136
+ * @param {string} value
137
+ * @returns {string}
138
+ */
139
+ function normalizeVitestBrowserStackText (value) {
140
+ return value.replaceAll(VITEST_BROWSER_URL_RE, normalizeVitestBrowserStackUrl)
141
+ }
142
+
143
+ /**
144
+ * Rebuilds an error stack from the source-mapped frames already parsed by Vitest.
145
+ *
146
+ * @param {{
147
+ * stack?: string,
148
+ * stacks?: Array<{ column: number, file: string, line: number, method?: string }>
149
+ * }} error
150
+ * @returns {string|undefined}
151
+ */
152
+ function normalizeVitestBrowserErrorStack (error) {
153
+ if (typeof error.stack !== 'string') return error.stack
154
+ if (!Array.isArray(error.stacks)) {
155
+ return normalizeVitestBrowserStackText(error.stack)
156
+ }
157
+
158
+ const firstLineEnd = error.stack.indexOf('\n')
159
+ const stackLines = new Array(error.stacks.length + 1)
160
+ stackLines[0] = normalizeVitestBrowserErrorText(
161
+ firstLineEnd === -1 ? error.stack : error.stack.slice(0, firstLineEnd)
162
+ )
163
+
164
+ for (let index = 0; index < error.stacks.length; index++) {
165
+ const frame = error.stacks[index]
166
+ const location = `${frame.file}:${frame.line}:${frame.column}`
167
+ stackLines[index + 1] = frame.method
168
+ ? ` at ${frame.method} (${location})`
169
+ : ` at ${location}`
170
+ }
171
+
172
+ return stackLines.join('\n')
173
+ }
174
+
175
+ /**
176
+ * Returns an error copy with browser-only URL metadata removed.
177
+ *
178
+ * @param {{
179
+ * message?: string,
180
+ * name?: string,
181
+ * stack?: string,
182
+ * stacks?: Array<{ column: number, file: string, line: number, method?: string }>
183
+ * }|undefined} error
184
+ * @returns {{
185
+ * message?: string,
186
+ * name?: string,
187
+ * stack?: string,
188
+ * stacks?: Array<{ column: number, file: string, line: number, method?: string }>
189
+ * }|undefined}
190
+ */
191
+ function normalizeVitestBrowserError (error) {
192
+ if (!error) return error
193
+
194
+ const message = typeof error.message === 'string'
195
+ ? normalizeVitestBrowserErrorText(error.message)
196
+ : error.message
197
+ const stack = normalizeVitestBrowserErrorStack(error)
198
+ if (message === error.message && stack === error.stack) return error
199
+
200
+ const normalizedError = { ...error }
201
+ if (error.name !== undefined) normalizedError.name = error.name
202
+ if (message !== undefined) normalizedError.message = message
203
+ if (stack !== undefined) normalizedError.stack = stack
204
+ return normalizedError
205
+ }
206
+
207
+ /**
208
+ * Normalizes browser errors without mutating Vitest's task result.
209
+ *
210
+ * @param {Array<{
211
+ * message?: string,
212
+ * name?: string,
213
+ * stack?: string,
214
+ * stacks?: Array<{ column: number, file: string, line: number, method?: string }>
215
+ * }>|undefined} errors
216
+ * @returns {Array<{
217
+ * message?: string,
218
+ * name?: string,
219
+ * stack?: string,
220
+ * stacks?: Array<{ column: number, file: string, line: number, method?: string }>
221
+ * }>}
222
+ */
223
+ function normalizeVitestBrowserErrors (errors) {
224
+ if (!errors?.length) return []
225
+
226
+ const normalizedErrors = new Array(errors.length)
227
+ for (let index = 0; index < errors.length; index++) {
228
+ normalizedErrors[index] = normalizeVitestBrowserError(errors[index])
229
+ }
230
+ return normalizedErrors
231
+ }
232
+
66
233
  function isRequested () {
67
234
  return getValueFromEnvSources(VITEST_NO_WORKER_INIT_REQUEST_ENV) === true
68
235
  }
@@ -143,38 +310,51 @@ function isNoWorkerInitPool (pool, isVitestWorkerPool) {
143
310
  return pool === undefined || isVitestWorkerPool(pool)
144
311
  }
145
312
 
313
+ /**
314
+ * @param {object} state
315
+ * @returns {boolean}
316
+ */
317
+ function isEarlyFlakeDetectionActive (state) {
318
+ return state.isEarlyFlakeDetectionEnabled &&
319
+ !state.isEarlyFlakeDetectionFaulty &&
320
+ hasEfdRetries(state.earlyFlakeDetectionRetryPolicy)
321
+ }
322
+
146
323
  function configure (ctx, frameworkVersion, testSpecifications, setupData, options) {
147
- const { getConfiguredEfdRetryCount, state } = options
324
+ const { shouldReportTestModule, state } = options
148
325
  addSetupFileToVitestConfigs(ctx, VITEST_NO_WORKER_INIT_SETUP_FILE, testSpecifications)
326
+ addVitestBrowserSetupFileAccess(testSpecifications)
149
327
 
150
328
  const {
151
329
  knownTestsBySuite,
152
330
  modifiedFiles,
153
331
  repositoryRoot,
154
332
  testManagementTestsBySuite,
333
+ testPropertiesByFilepath,
155
334
  testSessionConfiguration,
156
335
  } = setupData
157
336
 
158
337
  setProvidedContext(ctx, {
159
338
  _ddVitestWorkerSetup: {
339
+ isActive: true,
160
340
  attemptToFixRetries: state.testManagementAttemptToFixRetries,
161
341
  attemptToFixTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isAttemptToFix'),
162
342
  disabledTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isDisabled'),
163
- earlyFlakeDetectionRetries: getConfiguredEfdRetryCount(
164
- state.earlyFlakeDetectionSlowTestRetries,
165
- state.earlyFlakeDetectionNumRetries
166
- ),
167
- earlyFlakeDetectionRetryThresholds: EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS,
168
- earlyFlakeDetectionSlowRetries: state.earlyFlakeDetectionSlowTestRetries,
169
- isEarlyFlakeDetectionEnabled: state.isEarlyFlakeDetectionEnabled && !state.isEarlyFlakeDetectionFaulty,
343
+ earlyFlakeDetectionRetryPolicy: state.earlyFlakeDetectionRetryPolicy,
344
+ isEarlyFlakeDetectionEnabled: isEarlyFlakeDetectionActive(state),
345
+ isRumCorrelationEnabled: !canRaceRumCorrelation(ctx, testSpecifications),
170
346
  knownTests: knownTestsBySuite || {},
171
347
  modifiedFiles: modifiedFiles || {},
172
348
  quarantinedTests: getSelectedTestManagementTests(testManagementTestsBySuite, 'isQuarantined'),
173
349
  repositoryRoot: repositoryRoot || process.cwd(),
350
+ rumTestExecutionIdCookieName: RUM_TEST_EXECUTION_ID_COOKIE_NAME,
351
+ testPropertiesByFilepath: testPropertiesByFilepath || {},
174
352
  },
175
- }, 'Could not send Vitest worker setup context, so no-worker execution changes will not work.')
353
+ }, 'Could not send Vitest setup context, so main-process execution changes will not work.')
176
354
 
177
- installMainProcessReporter(ctx, frameworkVersion, testSessionConfiguration || {}, setupData, state)
355
+ installMainProcessReporter(ctx, frameworkVersion, testSessionConfiguration || {}, setupData, state, {
356
+ shouldReportTestModule,
357
+ })
178
358
  }
179
359
 
180
360
  function deactivate (ctx) {
@@ -182,6 +362,11 @@ function deactivate (ctx) {
182
362
  if (installedReporterState) {
183
363
  installedReporterState.isActive = false
184
364
  }
365
+ setProvidedContext(ctx, {
366
+ _ddVitestWorkerSetup: {
367
+ isActive: false,
368
+ },
369
+ }, 'Could not deactivate Vitest main-process execution changes.')
185
370
  }
186
371
 
187
372
  function configureWorkerEnv (workerEnv, shouldSkipWorkerInit = false) {
@@ -224,14 +409,75 @@ function addSetupFileToVitestConfigs (ctx, setupFile, testSpecifications) {
224
409
  for (const config of configs) {
225
410
  if (!config) continue
226
411
 
412
+ config.includeTaskLocation = true
227
413
  if (config.setupFiles === undefined) {
228
414
  config.setupFiles = []
229
415
  } else if (typeof config.setupFiles === 'string') {
230
416
  config.setupFiles = [config.setupFiles]
231
417
  }
232
418
 
233
- if (!config.setupFiles.includes(setupFile)) {
234
- config.setupFiles.push(setupFile)
419
+ const setupFileIndex = config.setupFiles.indexOf(setupFile)
420
+ if (setupFileIndex !== 0) {
421
+ if (setupFileIndex > 0) {
422
+ config.setupFiles.splice(setupFileIndex, 1)
423
+ }
424
+ config.setupFiles.unshift(setupFile)
425
+ }
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Resolves the Vitest runner from the browser project's dependency tree.
431
+ *
432
+ * @param {{
433
+ * resolve?: { dedupe?: string[] }
434
+ * }} viteConfig
435
+ * @returns {void}
436
+ */
437
+ function configureVitestBrowserSetupFile (viteConfig) {
438
+ viteConfig.resolve ||= {}
439
+ viteConfig.resolve.dedupe ||= []
440
+
441
+ if (!viteConfig.resolve.dedupe.includes('@vitest/runner')) {
442
+ viteConfig.resolve.dedupe.push('@vitest/runner')
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Allows Vite to serve the Datadog setup file after its default workspace detection has run.
448
+ *
449
+ * @param {{ server?: { fs?: { allow?: string[] } } }} viteConfig
450
+ * @returns {void}
451
+ */
452
+ function allowVitestBrowserSetupFile (viteConfig) {
453
+ const allow = viteConfig.server?.fs?.allow
454
+ if (allow && !allow.includes(VITEST_NO_WORKER_INIT_SETUP_FILE)) {
455
+ allow.push(VITEST_NO_WORKER_INIT_SETUP_FILE)
456
+ }
457
+ }
458
+
459
+ /**
460
+ * Installs the Vite access plugin on each parent project that owns a Browser Mode server.
461
+ *
462
+ * @param {object[]|undefined} testSpecifications
463
+ * @returns {void}
464
+ */
465
+ function addVitestBrowserSetupFileAccess (testSpecifications) {
466
+ if (!Array.isArray(testSpecifications)) return
467
+
468
+ const configuredProjects = new Set()
469
+ for (const testSpecification of testSpecifications) {
470
+ const project = getTestSpecificationProject(testSpecification)
471
+ if (!isBrowserTestSpecification(testSpecification)) continue
472
+
473
+ const browserServerProject = project?._parent || project
474
+ if (!browserServerProject || configuredProjects.has(browserServerProject)) continue
475
+
476
+ configuredProjects.add(browserServerProject)
477
+ browserServerProject.options ||= {}
478
+ browserServerProject.options.plugins ||= []
479
+ if (!browserServerProject.options.plugins.includes(VITEST_BROWSER_SETUP_FILE_PLUGIN)) {
480
+ browserServerProject.options.plugins.push(VITEST_BROWSER_SETUP_FILE_PLUGIN)
235
481
  }
236
482
  }
237
483
  }
@@ -279,12 +525,20 @@ function getSelectedTestManagementTests (testManagementTestsBySuite, propertyNam
279
525
  return selectedTests
280
526
  }
281
527
 
282
- function installMainProcessReporter (ctx, frameworkVersion, testSessionConfiguration, testOptimizationData, state) {
528
+ function installMainProcessReporter (
529
+ ctx,
530
+ frameworkVersion,
531
+ testSessionConfiguration,
532
+ testOptimizationData,
533
+ state,
534
+ options
535
+ ) {
283
536
  if (!ctx?.reporters) return
284
537
 
285
538
  const reporterState = {
286
539
  frameworkVersion,
287
540
  isActive: true,
541
+ shouldReportTestModule: options.shouldReportTestModule,
288
542
  state,
289
543
  testSessionConfiguration,
290
544
  testOptimizationData,
@@ -307,26 +561,26 @@ function createMainProcessReporter (reporterState) {
307
561
 
308
562
  return {
309
563
  onTestModuleStart (testModule) {
310
- if (!isActive()) return
564
+ if (!shouldReport(testModule)) return
311
565
 
312
566
  startTestSuite(testModule)
313
567
  },
314
568
 
315
569
  onTestModuleEnd (testModule) {
316
- if (!isActive()) return
570
+ if (!shouldReport(testModule)) return
317
571
 
318
572
  return reportTestModule(testModule)
319
573
  },
320
574
 
321
575
  onTestCaseResult (testCase) {
322
- if (!isActive()) return
576
+ if (!shouldReport(testCase.module || testCase.task?.file)) return
323
577
 
324
578
  const task = getTestCaseTask(testCase)
325
579
  recordFinalTaskAttemptResult(task)
326
580
  },
327
581
 
328
582
  onTaskUpdate (packs, events) {
329
- if (!isActive()) return
583
+ if (!isReporterActive()) return
330
584
  if (!events) return
331
585
 
332
586
  for (const event of events) {
@@ -336,23 +590,43 @@ function createMainProcessReporter (reporterState) {
336
590
  }
337
591
  },
338
592
 
593
+ onWatcherRerun () {
594
+ for (const testSuiteCtx of testSuiteContexts.values()) {
595
+ testSuiteFinishCh.publish({
596
+ status: 'skip',
597
+ deferFlush: true,
598
+ onDone: noop,
599
+ ...testSuiteCtx.currentStore,
600
+ })
601
+ }
602
+ testSuiteContexts.clear()
603
+ finishedTestModules.clear()
604
+ taskAttemptStatuses.clear()
605
+ tasksWithRecordedFinalAttempt.clear()
606
+ },
607
+
339
608
  onFinished (files) {
340
- if (!isActive()) return
609
+ if (!isReporterActive()) return
341
610
  if (!files) return
342
611
 
343
612
  for (const file of files) {
344
613
  const testModule = createTestModuleFromFile(file)
345
- if (!finishedTestModules.has(file.id)) {
614
+ if (shouldReport(testModule) && !finishedTestModules.has(file.id)) {
346
615
  reportTestModule(testModule)
347
616
  }
348
617
  }
349
618
  },
350
619
  }
351
620
 
352
- function isActive () {
621
+ function isReporterActive () {
353
622
  return reporterState.isActive === true
354
623
  }
355
624
 
625
+ function shouldReport (testModule) {
626
+ return isReporterActive() &&
627
+ (!reporterState.shouldReportTestModule || reporterState.shouldReportTestModule(testModule))
628
+ }
629
+
356
630
  function recordTaskAttemptStatus (taskId, status, attemptCount) {
357
631
  let statuses = taskAttemptStatuses.get(taskId)
358
632
  if (!statuses) {
@@ -389,7 +663,7 @@ function createMainProcessReporter (reporterState) {
389
663
  let testSuiteError
390
664
 
391
665
  for (const task of testTasks) {
392
- const testReport = getTestReport(task, testSuiteCtx.currentStore)
666
+ const testReport = getTestReport(task, testSuiteCtx.currentStore, testSuiteCtx.browserEnvironment)
393
667
  testReports.push(testReport)
394
668
 
395
669
  for (const attempt of testReport.nonFinalAttempts) {
@@ -412,7 +686,9 @@ function createMainProcessReporter (reporterState) {
412
686
  }
413
687
 
414
688
  if (testSuiteError) {
415
- testSuiteCtx.error = testSuiteError
689
+ testSuiteCtx.error = testSuiteCtx.browserEnvironment
690
+ ? normalizeVitestBrowserError(testSuiteError)
691
+ : testSuiteError
416
692
  testSuiteErrorCh.runStores(testSuiteCtx, () => {})
417
693
  }
418
694
 
@@ -428,16 +704,22 @@ function createMainProcessReporter (reporterState) {
428
704
  function startTestSuite (testModule) {
429
705
  const { frameworkVersion, testSessionConfiguration } = reporterState
430
706
  const testModuleId = getTestModuleId(testModule)
707
+ const browserEnvironment = getTestModuleBrowserEnvironment(testModule)
431
708
  const testSuiteCtx = {
709
+ browserEnvironment,
710
+ browserDriver: browserEnvironment?.browserDriver,
711
+ browserName: browserEnvironment?.browserName,
712
+ browserProjectName: browserEnvironment?.browserProjectName,
432
713
  testSuiteAbsolutePath: getTestModuleFilepath(testModule),
433
714
  frameworkVersion,
434
715
  testSessionId: testSessionConfiguration.testSessionId,
435
716
  testModuleId: testSessionConfiguration.testModuleId,
436
717
  testCommand: testSessionConfiguration.testCommand,
437
718
  repositoryRoot: testSessionConfiguration.repositoryRoot,
438
- codeOwnersEntries: testSessionConfiguration.codeOwnersEntries,
719
+ codeOwnersEntries: testSessionConfiguration.codeOwnersEntries ?? undefined,
439
720
  requestErrorTags: reporterState.state.requestErrorTags,
440
721
  isTestFrameworkWorker: true,
722
+ isBrowserMode: browserEnvironment !== undefined,
441
723
  isVitestNoWorkerInitActive: true,
442
724
  }
443
725
  testSuiteStartCh.runStores(testSuiteCtx, () => {})
@@ -445,7 +727,7 @@ function createMainProcessReporter (reporterState) {
445
727
  return testSuiteCtx
446
728
  }
447
729
 
448
- function getTestReport (task, testSuiteStore) {
730
+ function getTestReport (task, testSuiteStore, browserEnvironment) {
449
731
  const result = task.result
450
732
  const testSuiteAbsolutePath = task.file?.filepath
451
733
  const testName = getTestName(task)
@@ -459,6 +741,7 @@ function createMainProcessReporter (reporterState) {
459
741
 
460
742
  if (testProperties.isAttemptToFix && task.meta?.__ddTestOptAtfStatuses?.length) {
461
743
  return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
744
+ browserEnvironment,
462
745
  errorCounts: task.meta.__ddTestOptAtfErrorCounts,
463
746
  finalStatus: getAttemptToFixFinalStatus,
464
747
  state,
@@ -470,6 +753,7 @@ function createMainProcessReporter (reporterState) {
470
753
 
471
754
  if (testProperties.isEarlyFlakeDetection && task.meta?.__ddTestOptEfdStatuses?.length) {
472
755
  return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
756
+ browserEnvironment,
473
757
  errorCounts: task.meta.__ddTestOptEfdErrorCounts,
474
758
  finalStatus: getEarlyFlakeDetectionFinalStatus,
475
759
  state,
@@ -481,8 +765,9 @@ function createMainProcessReporter (reporterState) {
481
765
 
482
766
  if (!testProperties.isAttemptToFix && task.meta?.__ddTestOptRepeatStatuses?.length) {
483
767
  return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
768
+ browserEnvironment,
484
769
  errorCounts: task.meta.__ddTestOptRepeatErrorCounts,
485
- finalStatus: () => status,
770
+ finalStatus: () => getExternalFinalStatus(status, testProperties),
486
771
  state,
487
772
  statuses: task.meta.__ddTestOptRepeatStatuses,
488
773
  testSuiteStore,
@@ -491,11 +776,18 @@ function createMainProcessReporter (reporterState) {
491
776
  }
492
777
 
493
778
  const attemptStatuses = taskAttemptStatuses.get(task.id)
494
- if (!testProperties.isAttemptToFix && !testProperties.isEarlyFlakeDetection && attemptStatuses?.length > 1) {
779
+ const retryCount = task.result?.retryCount || 0
780
+ if (
781
+ !testProperties.isAttemptToFix &&
782
+ !testProperties.isEarlyFlakeDetection &&
783
+ (attemptStatuses?.length > 1 || retryCount > 0)
784
+ ) {
495
785
  return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
496
- finalStatus: () => status,
786
+ browserEnvironment,
787
+ errorCounts: task.meta?.__ddTestOptRetryErrorCounts,
788
+ finalStatus: () => getExternalFinalStatus(status, testProperties),
497
789
  state,
498
- statuses: attemptStatuses,
790
+ statuses: getRetriedTaskStatuses(attemptStatuses, retryCount, status),
499
791
  testSuiteStore,
500
792
  type: 'external',
501
793
  })
@@ -503,6 +795,7 @@ function createMainProcessReporter (reporterState) {
503
795
 
504
796
  if (!testProperties.isAttemptToFix && task.result?.repeatCount > 0) {
505
797
  return getRepeatedTestReport(task, testName, testSuiteAbsolutePath, testProperties, status, {
798
+ browserEnvironment,
506
799
  finalStatus: () => status,
507
800
  state,
508
801
  statuses: getRepeatedTaskStatuses(task, status),
@@ -522,24 +815,14 @@ function createMainProcessReporter (reporterState) {
522
815
  result.state = 'pass'
523
816
  }
524
817
 
525
- const errors = result?.errors || []
526
- const finalErrorIndex = status === 'fail' ? errors.length - 1 : -1
527
- const nonFinalAttempts = []
528
-
529
- if (status !== 'skip') {
530
- for (let index = 0; index < errors.length; index++) {
531
- if (index === finalErrorIndex) continue
532
- nonFinalAttempts.push({
533
- error: errors[index],
534
- isRetry: index > 0,
535
- status: 'fail',
536
- })
537
- }
538
- }
818
+ const errors = browserEnvironment
819
+ ? normalizeVitestBrowserErrors(result?.errors)
820
+ : result?.errors || []
539
821
 
540
822
  return {
823
+ browserEnvironment,
541
824
  errors,
542
- nonFinalAttempts,
825
+ nonFinalAttempts: [],
543
826
  state,
544
827
  status,
545
828
  task,
@@ -578,7 +861,7 @@ function createMainProcessReporter (reporterState) {
578
861
  return {
579
862
  isAttemptToFix,
580
863
  isDisabled: testManagementProperties.isDisabled,
581
- isEarlyFlakeDetection: (isNew || isModified) && state.isEarlyFlakeDetectionEnabled,
864
+ isEarlyFlakeDetection: (isNew || isModified) && isEarlyFlakeDetectionActive(state),
582
865
  isFlakyTestRetries,
583
866
  isQuarantined: testManagementProperties.isQuarantined,
584
867
  isModified,
@@ -643,6 +926,7 @@ function createTaskFromReportedTask (reportedTask, file, suite) {
643
926
  id: reportedTask.id,
644
927
  type: reportedTask.type,
645
928
  name: reportedTask.name || reportedTask.relativeModuleId || reportedTask.moduleId,
929
+ location: reportedTask.location,
646
930
  mode: reportedTask.options?.mode,
647
931
  meta: getReportedTaskMeta(reportedTask),
648
932
  result: getReportedTaskResult(reportedTask),
@@ -731,8 +1015,10 @@ function getTestModuleProjectName (testModule) {
731
1015
 
732
1016
  function getRepeatedTestReport (task, testName, testSuiteAbsolutePath, testProperties, status, options) {
733
1017
  const result = task.result
734
- const errors = result?.errors || []
735
- const { errorCounts, finalStatus, state, statuses, testSuiteStore, type } = options
1018
+ const { browserEnvironment, errorCounts, finalStatus, state, statuses, testSuiteStore, type } = options
1019
+ const errors = browserEnvironment
1020
+ ? normalizeVitestBrowserErrors(result?.errors)
1021
+ : result?.errors || []
736
1022
  const finalAttemptStatus = finalStatus(statuses)
737
1023
  const hasFailure = finalAttemptStatus === 'fail'
738
1024
  const attempts = []
@@ -757,7 +1043,9 @@ function getRepeatedTestReport (task, testName, testSuiteAbsolutePath, testPrope
757
1043
  errorIndex++
758
1044
  }
759
1045
  const attempt = {
1046
+ index,
760
1047
  attemptToFixFailed: type === 'attempt_to_fix' && isFinalAttempt && hasFailure,
1048
+ duration: task.meta?.__ddTestOptAttemptDurations?.[index],
761
1049
  earlyFlakeAbortReason: type === 'early_flake_detection' && isFinalAttempt
762
1050
  ? task.meta?.__ddTestOptEfdAbortReason
763
1051
  : undefined,
@@ -783,6 +1071,7 @@ function getRepeatedTestReport (task, testName, testSuiteAbsolutePath, testPrope
783
1071
  }
784
1072
 
785
1073
  return {
1074
+ browserEnvironment,
786
1075
  errors,
787
1076
  finalAttempt: attempts.at(-1),
788
1077
  nonFinalAttempts: attempts.slice(0, -1),
@@ -841,6 +1130,17 @@ function getEarlyFlakeDetectionFinalStatus (statuses) {
841
1130
  return statuses.includes('pass') ? 'pass' : 'fail'
842
1131
  }
843
1132
 
1133
+ /**
1134
+ * Returns the final status for user-configured retries and repeats.
1135
+ *
1136
+ * @param {string} status
1137
+ * @param {{ isDisabled?: boolean, isQuarantined?: boolean }} testProperties
1138
+ * @returns {string}
1139
+ */
1140
+ function getExternalFinalStatus (status, testProperties) {
1141
+ return testProperties.isDisabled || testProperties.isQuarantined ? 'skip' : status
1142
+ }
1143
+
844
1144
  function getRepeatedTaskStatuses (task, status) {
845
1145
  const repeatedStatuses = []
846
1146
  const repeatCount = task.result?.repeatCount || 0
@@ -850,8 +1150,27 @@ function getRepeatedTaskStatuses (task, status) {
850
1150
  return repeatedStatuses
851
1151
  }
852
1152
 
1153
+ /**
1154
+ * Completes reporter retry events with Vitest's final retry count and status.
1155
+ *
1156
+ * @param {string[]} recordedStatuses
1157
+ * @param {number} retryCount
1158
+ * @param {string} finalStatus
1159
+ * @returns {string[]}
1160
+ */
1161
+ function getRetriedTaskStatuses (recordedStatuses = [], retryCount, finalStatus) {
1162
+ const statuses = [...recordedStatuses]
1163
+ const attemptCount = Math.max(statuses.length, retryCount + 1)
1164
+ while (statuses.length < attemptCount - 1) {
1165
+ statuses.push('fail')
1166
+ }
1167
+ statuses[attemptCount - 1] = finalStatus
1168
+ return statuses
1169
+ }
1170
+
853
1171
  function reportFinalTestAttempt (testReport) {
854
1172
  const {
1173
+ browserEnvironment,
855
1174
  errors,
856
1175
  state,
857
1176
  status,
@@ -863,13 +1182,40 @@ function reportFinalTestAttempt (testReport) {
863
1182
  } = testReport
864
1183
 
865
1184
  if (status === 'skip') {
1185
+ recordTestManagementExecution({
1186
+ testSuite: testProperties.testSuite,
1187
+ testName,
1188
+ status,
1189
+ isAttemptToFix: testProperties.isAttemptToFix,
1190
+ isDisabled: testProperties.isDisabled,
1191
+ isQuarantined: testProperties.isQuarantined,
1192
+ })
1193
+ if (testProperties.isAttemptToFix) {
1194
+ recordAttemptToFixExecution(state.attemptToFixExecutions, {
1195
+ testSuite: testProperties.testSuite,
1196
+ testName,
1197
+ status,
1198
+ isDisabled: testProperties.isDisabled,
1199
+ isQuarantined: testProperties.isQuarantined,
1200
+ })
1201
+ }
1202
+ const {
1203
+ isRumActive,
1204
+ testExecutionId,
1205
+ } = getRumCorrelation(task)
866
1206
  testSkipCh.publish({
1207
+ ...browserEnvironment,
867
1208
  testName,
868
1209
  testSuiteAbsolutePath,
869
1210
  isNew: testProperties.isNew,
1211
+ isAttemptToFix: testProperties.isAttemptToFix,
870
1212
  isDisabled: testProperties.isDisabled,
1213
+ isQuarantined: testProperties.isQuarantined,
1214
+ isRumActive,
871
1215
  isTestFrameworkWorker: true,
872
1216
  requestErrorTags: state.requestErrorTags,
1217
+ testStartLine: task.location?.line,
1218
+ testExecutionId,
873
1219
  ...testSuiteStore,
874
1220
  })
875
1221
  return
@@ -880,7 +1226,7 @@ function reportFinalTestAttempt (testReport) {
880
1226
  const finalAttempt = testReport.finalAttempt
881
1227
 
882
1228
  if (status === 'fail') {
883
- const error = errors.at(-1) || errors[0]
1229
+ const error = finalAttempt?.error || errors[0]
884
1230
  reportTestAttempt(testReport, finalAttempt || {
885
1231
  error,
886
1232
  finalStatus,
@@ -892,7 +1238,7 @@ function reportFinalTestAttempt (testReport) {
892
1238
  true,
893
1239
  true
894
1240
  ),
895
- isRetry: errors.length > 1 || (result?.retryCount || 0) > 0 || (result?.repeatCount || 0) > 0,
1241
+ isRetry: (result?.retryCount || 0) > 0 || (result?.repeatCount || 0) > 0,
896
1242
  status: 'fail',
897
1243
  })
898
1244
  return finalStatus === 'skip' ? undefined : error
@@ -900,13 +1246,14 @@ function reportFinalTestAttempt (testReport) {
900
1246
 
901
1247
  reportTestAttempt(testReport, finalAttempt || {
902
1248
  finalStatus,
903
- isRetry: errors.length > 0 || (result?.retryCount || 0) > 0 || (result?.repeatCount || 0) > 0,
1249
+ isRetry: (result?.retryCount || 0) > 0 || (result?.repeatCount || 0) > 0,
904
1250
  status: 'pass',
905
1251
  })
906
1252
  }
907
1253
 
908
1254
  function reportTestAttempt (testReport, attempt) {
909
1255
  const {
1256
+ browserEnvironment,
910
1257
  state,
911
1258
  task,
912
1259
  testName,
@@ -916,7 +1263,27 @@ function reportTestAttempt (testReport, attempt) {
916
1263
  } = testReport
917
1264
  const result = task.result
918
1265
  const status = attempt.status
1266
+ const {
1267
+ isRumActive,
1268
+ testExecutionId,
1269
+ } = getRumCorrelation(task, attempt.index)
1270
+ const attemptStartTimes = task.meta?.__ddTestOptAttemptStartTimes
1271
+ const attemptStartIndex = attempt.index ?? (attemptStartTimes?.length || 1) - 1
1272
+ const attemptStartTime = attemptStartTimes?.[attemptStartIndex]
1273
+ let duration
1274
+ if (status === 'pass') {
1275
+ if (
1276
+ attempt.duration !== undefined ||
1277
+ (!attempt.isRetry && isFinalTestAttempt(testReport, attempt) && result?.duration !== undefined)
1278
+ ) {
1279
+ duration = attempt.duration ?? result.duration
1280
+ }
1281
+ } else {
1282
+ duration = attempt.duration ??
1283
+ (shouldUseTaskDurationForFailure(testReport, attempt) ? result?.duration : undefined)
1284
+ }
919
1285
  const testCtx = {
1286
+ ...browserEnvironment,
920
1287
  currentStore: testSuiteStore,
921
1288
  testName,
922
1289
  testSuiteAbsolutePath,
@@ -931,9 +1298,23 @@ function reportTestAttempt (testReport, attempt) {
931
1298
  isRetryReasonAttemptToFix: testProperties.isAttemptToFix && attempt.isRetry,
932
1299
  isRetryReasonAtr: !testProperties.isAttemptToFix && !testProperties.isEarlyFlakeDetection &&
933
1300
  testProperties.isFlakyTestRetries,
1301
+ isRumActive,
934
1302
  isTestFrameworkWorker: true,
935
1303
  requestErrorTags: state.requestErrorTags,
1304
+ startTime: Number.isFinite(attemptStartTime)
1305
+ ? attemptStartTime
1306
+ : (Number.isFinite(duration) && duration >= 0 ? Date.now() - duration : undefined),
1307
+ testStartLine: task.location?.line,
1308
+ testExecutionId,
936
1309
  }
1310
+ recordTestManagementExecution({
1311
+ testSuite: testProperties.testSuite,
1312
+ testName,
1313
+ status,
1314
+ isAttemptToFix: testProperties.isAttemptToFix,
1315
+ isDisabled: testProperties.isDisabled,
1316
+ isQuarantined: testProperties.isQuarantined,
1317
+ })
937
1318
  if (testProperties.isAttemptToFix) {
938
1319
  recordAttemptToFixExecution(state.attemptToFixExecutions, {
939
1320
  testSuite: testProperties.testSuite,
@@ -954,13 +1335,8 @@ function reportTestAttempt (testReport, attempt) {
954
1335
  testStartCh.runStores(testCtx, () => {})
955
1336
  testCtx.status = status
956
1337
  testCtx.task = task
957
- if (
958
- status === 'pass' &&
959
- !attempt.isRetry &&
960
- isFinalTestAttempt(testReport, attempt) &&
961
- result?.duration !== undefined
962
- ) {
963
- testCtx.duration = result.duration
1338
+ if (status === 'pass' && duration !== undefined) {
1339
+ testCtx.duration = duration
964
1340
  }
965
1341
  testFinishTimeCh.runStores(testCtx, () => {})
966
1342
 
@@ -975,7 +1351,7 @@ function reportTestAttempt (testReport, attempt) {
975
1351
  }
976
1352
 
977
1353
  testErrorCh.publish({
978
- duration: shouldUseTaskDurationForFailure(testReport, attempt) ? result?.duration : undefined,
1354
+ duration,
979
1355
  error: attempt.error,
980
1356
  earlyFlakeAbortReason: attempt.earlyFlakeAbortReason,
981
1357
  finalStatus: attempt.finalStatus,
@@ -985,6 +1361,24 @@ function reportTestAttempt (testReport, attempt) {
985
1361
  })
986
1362
  }
987
1363
 
1364
+ /**
1365
+ * Returns the browser RUM correlation metadata for a test attempt.
1366
+ *
1367
+ * @param {object} task
1368
+ * @param {number|undefined} attemptIndex
1369
+ * @returns {{ isRumActive?: boolean, testExecutionId?: string }}
1370
+ */
1371
+ function getRumCorrelation (task, attemptIndex) {
1372
+ const testExecutionIds = task.meta?.__ddTestOptRumTestExecutionIds
1373
+ if (!Array.isArray(testExecutionIds) || testExecutionIds.length === 0) return {}
1374
+
1375
+ attemptIndex ??= testExecutionIds.length - 1
1376
+ return {
1377
+ isRumActive: task.meta?.__ddTestOptRumActive?.[attemptIndex] === true,
1378
+ testExecutionId: testExecutionIds[attemptIndex],
1379
+ }
1380
+ }
1381
+
988
1382
  function isFinalTestAttempt (testReport, attempt) {
989
1383
  return attempt.finalStatus !== undefined || testReport.finalAttempt === undefined
990
1384
  }
@@ -1207,10 +1601,11 @@ function getTestSpecificationPool (testSpecification) {
1207
1601
  const options = getTestSpecificationOptions(testSpecification)
1208
1602
  const file = getTestSpecificationFile(testSpecification)
1209
1603
  const project = getTestSpecificationProject(testSpecification)
1604
+ const projectConfig = safeConfig(project)
1210
1605
  return options?.pool ||
1211
1606
  file?.pool ||
1212
1607
  testSpecification?.pool ||
1213
- project?.config?.pool ||
1608
+ projectConfig?.pool ||
1214
1609
  project?.serializedConfig?.pool ||
1215
1610
  project?.pool
1216
1611
  }
@@ -1243,15 +1638,16 @@ function getTestSpecificationIsolate (testSpecification, pool) {
1243
1638
  const options = getTestSpecificationOptions(testSpecification)
1244
1639
  const file = getTestSpecificationFile(testSpecification)
1245
1640
  const project = getTestSpecificationProject(testSpecification)
1641
+ const projectConfig = safeConfig(project)
1246
1642
  return getPoolOptionsIsolate(options, pool) ??
1247
1643
  getPoolOptionsIsolate(file, pool) ??
1248
- getPoolOptionsIsolate(project?.config, pool) ??
1644
+ getPoolOptionsIsolate(projectConfig, pool) ??
1249
1645
  getPoolOptionsIsolate(project?.serializedConfig, pool) ??
1250
1646
  getPoolOptionsIsolate(project, pool) ??
1251
1647
  getPoolOptionsIsolate(testSpecification, pool) ??
1252
1648
  options?.isolate ??
1253
1649
  file?.isolate ??
1254
- project?.config?.isolate ??
1650
+ projectConfig?.isolate ??
1255
1651
  project?.serializedConfig?.isolate ??
1256
1652
  project?.isolate ??
1257
1653
  testSpecification?.isolate
@@ -1263,7 +1659,7 @@ function getEffectiveTestSpecificationIsolate (testSpecification, pool, defaultI
1263
1659
  }
1264
1660
 
1265
1661
  function getProjectName (project) {
1266
- return normalizeProjectName(project?.name || project?.config?.name || project?.test?.name)
1662
+ return normalizeProjectName(project?.name || safeConfig(project)?.name || project?.test?.name)
1267
1663
  }
1268
1664
 
1269
1665
  function normalizeProjectName (name) {
@@ -1273,6 +1669,111 @@ function normalizeProjectName (name) {
1273
1669
  return typeof label === 'string' ? label : undefined
1274
1670
  }
1275
1671
 
1672
+ /**
1673
+ * Returns whether a Vitest specification runs in Browser Mode.
1674
+ *
1675
+ * @param {object} testSpecification
1676
+ * @returns {boolean}
1677
+ */
1678
+ function isBrowserTestSpecification (testSpecification) {
1679
+ return getTestSpecificationPool(testSpecification) === 'browser' ||
1680
+ isBrowserProject(getTestSpecificationProject(testSpecification))
1681
+ }
1682
+
1683
+ /**
1684
+ * Returns whether a Vitest project has Browser Mode enabled.
1685
+ *
1686
+ * @param {object|undefined} project
1687
+ * @returns {boolean}
1688
+ */
1689
+ function isBrowserProject (project) {
1690
+ try {
1691
+ if (project?.isBrowserEnabled?.() === true) return true
1692
+ } catch {}
1693
+ return getProjectReportingConfig(project)?.browser?.enabled === true
1694
+ }
1695
+
1696
+ /**
1697
+ * Returns whether Vitest can overlap tests or their setup and teardown.
1698
+ *
1699
+ * @param {object|undefined} config
1700
+ * @returns {boolean}
1701
+ */
1702
+ function hasConcurrentTestExecution (config) {
1703
+ const sequence = config?.sequence
1704
+ return sequence?.concurrent === true ||
1705
+ sequence?.hooks === 'parallel' ||
1706
+ sequence?.setupFiles === 'parallel'
1707
+ }
1708
+
1709
+ /**
1710
+ * Returns whether Vitest can overlap browser execution that shares the RUM correlation cookie origin.
1711
+ *
1712
+ * @param {object} ctx
1713
+ * @param {object[]|undefined} testSpecifications
1714
+ * @returns {boolean}
1715
+ */
1716
+ function canRaceRumCorrelation (ctx, testSpecifications) {
1717
+ if (!Array.isArray(testSpecifications)) {
1718
+ return hasConcurrentTestExecution(safeConfig(ctx))
1719
+ }
1720
+
1721
+ let browserFileCount = 0
1722
+ let hasBrowserProject = false
1723
+ let project
1724
+ for (const testSpecification of testSpecifications) {
1725
+ const testProject = getTestSpecificationProject(testSpecification)
1726
+ if (!isBrowserTestSpecification(testSpecification)) continue
1727
+
1728
+ const config = getProjectReportingConfig(testProject) || safeConfig(ctx)
1729
+ if (hasConcurrentTestExecution(config)) return true
1730
+
1731
+ browserFileCount++
1732
+ if (hasBrowserProject && testProject !== project) return true
1733
+ project = testProject
1734
+ hasBrowserProject = true
1735
+ }
1736
+ if (browserFileCount < 2) return false
1737
+
1738
+ const config = getProjectReportingConfig(project) || safeConfig(ctx)
1739
+ const fileParallelism = config?.browser?.fileParallelism ?? config?.fileParallelism
1740
+ return fileParallelism !== false && config?.maxWorkers !== 1
1741
+ }
1742
+
1743
+ function getTestModuleBrowserEnvironment (testModule) {
1744
+ const project = testModule?.project
1745
+ const config = getProjectReportingConfig(project)
1746
+ const isBrowserMode = testModule?.task?.pool === 'browser' ||
1747
+ testModule?.pool === 'browser' ||
1748
+ isBrowserProject(project)
1749
+ if (!isBrowserMode) return
1750
+
1751
+ const provider = config?.browser?.provider
1752
+ const browserDriver = typeof provider === 'string' ? provider : provider?.name
1753
+
1754
+ return {
1755
+ browserDriver,
1756
+ browserName: config?.browser?.name,
1757
+ browserProjectName: getTestModuleProjectName(testModule),
1758
+ isBrowserMode: true,
1759
+ }
1760
+ }
1761
+
1762
+ /**
1763
+ * Returns the runtime or serialized project configuration used for read-only reporting metadata.
1764
+ *
1765
+ * @param {object|undefined} project
1766
+ * @returns {object|undefined}
1767
+ */
1768
+ function getProjectReportingConfig (project) {
1769
+ const config = safeConfig(project)
1770
+ if (config) return config
1771
+
1772
+ try {
1773
+ return project?.serializedConfig
1774
+ } catch {}
1775
+ }
1776
+
1276
1777
  function safeConfig (project) {
1277
1778
  let config
1278
1779
  try {
@@ -1293,5 +1794,6 @@ module.exports = {
1293
1794
  configure,
1294
1795
  deactivate,
1295
1796
  configureWorkerEnv,
1797
+ isSupportedVersion,
1296
1798
  shouldUse,
1297
1799
  }