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
@@ -3,9 +3,10 @@
3
3
  const path = require('path')
4
4
  const fs = require('fs')
5
5
  const { URL } = require('url')
6
+ const { stripVTControlCharacters } = require('node:util')
6
7
  const { getLageTestSessionName } = require('../../ci-visibility/lage')
7
8
  const log = require('../../log')
8
- const { getEnvironmentVariable } = require('../../config/helper')
9
+ const { getEnvironmentVariable, getValueFromEnvSources } = require('../../config/helper')
9
10
  const { getSegment } = require('../../util')
10
11
  const satisfies = require('../../../../../vendor/dist/semifies')
11
12
 
@@ -45,6 +46,7 @@ const {
45
46
  CI_WORKSPACE_PATH,
46
47
  CI_PIPELINE_ID,
47
48
  CI_PIPELINE_NAME,
49
+ CI_PIPELINE_DISPLAY_NAME,
48
50
  CI_PIPELINE_NUMBER,
49
51
  CI_PIPELINE_URL,
50
52
  CI_JOB_NAME,
@@ -89,10 +91,12 @@ const {
89
91
  const TEST_SESSION_NAME = 'test_session.name'
90
92
 
91
93
  const TEST_FRAMEWORK = 'test.framework'
94
+ const TEST_FRAMEWORK_ADAPTER = 'test.framework_adapter'
92
95
  const TEST_FRAMEWORK_VERSION = 'test.framework_version'
93
96
  const TEST_TYPE = 'test.type'
94
97
  const TEST_NAME = 'test.name'
95
98
  const TEST_SUITE = 'test.suite'
99
+ const TEST_SUITE_EXECUTION_ID = '_dd.test_suite_execution_id'
96
100
  const TEST_STATUS = 'test.status'
97
101
  const TEST_FINAL_STATUS = 'test.final_status'
98
102
  const TEST_PARAMETERS = 'test.parameters'
@@ -120,13 +124,10 @@ const TEST_RETRY_REASON = 'test.retry_reason'
120
124
  const TEST_HAS_FAILED_ALL_RETRIES = 'test.has_failed_all_retries'
121
125
  const TEST_IS_MODIFIED = 'test.is_modified'
122
126
  const TEST_HAS_DYNAMIC_NAME = '_dd.has_dynamic_name'
123
- const EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS = [
124
- { limitMs: 5 * 1000, key: '5s' },
125
- { limitMs: 10 * 1000, key: '10s' },
126
- { limitMs: 30 * 1000, key: '30s' },
127
- { limitMs: 5 * 60 * 1000, key: '5m' },
128
- ]
129
127
  const CI_APP_ORIGIN = 'ciapp-test'
128
+ // eslint-disable-next-line no-control-regex
129
+ const TEST_OPTIMIZATION_NAME_CONTROL_RE = /[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g
130
+ const TEST_OPTIMIZATION_NAME_WHITESPACE_RE = /\s+/g
130
131
 
131
132
  // Matches patterns that are almost certainly runtime-generated values in test names:
132
133
  // - Unix timestamps in ms (13 digits, years ~2020-2090) or s (10 digits)
@@ -159,6 +160,7 @@ const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
159
160
  const TEST_ITR_UNSKIPPABLE = 'test.itr.unskippable'
160
161
  const TEST_ITR_FORCED_RUN = 'test.itr.forced_run'
161
162
  const ITR_CORRELATION_ID = 'itr_correlation_id'
163
+ const TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE = 'Test Impact Analysis skipped all tests.'
162
164
 
163
165
  const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
164
166
 
@@ -177,16 +179,22 @@ const JEST_WORKER_QUARANTINE_PAYLOAD_CODE = 64
177
179
 
178
180
  // cucumber worker variables
179
181
  const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
182
+ const CUCUMBER_WORKER_TELEMETRY_PAYLOAD_CODE = 71
180
183
 
181
184
  // mocha worker variables
182
185
  const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
186
+ const MOCHA_WORKER_LOGS_PAYLOAD_CODE = 81
187
+ const MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE = 82
183
188
 
184
189
  // playwright worker variables
185
190
  const PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE = 90
191
+ const PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE = 91
186
192
 
187
193
  // vitest worker variables
188
194
  const VITEST_WORKER_TRACE_PAYLOAD_CODE = 100
195
+ const VITEST_WORKER_COVERAGE_PAYLOAD_CODE = 101
189
196
  const VITEST_WORKER_LOGS_PAYLOAD_CODE = 102
197
+ const VITEST_WORKER_TELEMETRY_PAYLOAD_CODE = 103
190
198
 
191
199
  const TEST_IS_TEST_FRAMEWORK_WORKER = 'test.is_test_framework_worker'
192
200
 
@@ -207,7 +215,7 @@ const DD_CI_LIBRARY_CONFIGURATION_ERROR_KNOWN_TESTS = '_dd.ci.library_configurat
207
215
  const DD_CI_LIBRARY_CONFIGURATION_ERROR_TEST_MANAGEMENT_TESTS =
208
216
  '_dd.ci.library_configuration_error.test_management_tests'
209
217
 
210
- const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'vitest'])
218
+ const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'webdriverio'])
211
219
  const MINIMUM_FRAMEWORK_VERSION_FOR_EFD = {
212
220
  playwright: '>=1.38.0',
213
221
  }
@@ -244,6 +252,7 @@ const TEST_LEVELS_METADATA_TAGS = [
244
252
  CI_NODE_NAME,
245
253
  CI_PIPELINE_ID,
246
254
  CI_PIPELINE_NAME,
255
+ CI_PIPELINE_DISPLAY_NAME,
247
256
  CI_PIPELINE_NUMBER,
248
257
  CI_PIPELINE_URL,
249
258
  CI_PROVIDER_NAME,
@@ -295,7 +304,10 @@ const TEST_MANAGEMENT_IS_QUARANTINED = 'test.test_management.is_quarantined'
295
304
  const TEST_MANAGEMENT_ENABLED = 'test.test_management.enabled'
296
305
  const TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED = 'test.test_management.attempt_to_fix_passed'
297
306
 
307
+ const MAX_TEST_MANAGEMENT_REPORT_ITEMS = 5
308
+ const MAX_TEST_OPTIMIZATION_NAME_LENGTH = 160
298
309
  const MAX_TEST_OPTIMIZATION_SUMMARY_ITEMS = 10
310
+ const testManagementExecutions = new Map()
299
311
 
300
312
  // Impacted tests
301
313
  const POSSIBLE_BASE_BRANCHES = ['main', 'master', 'preprod', 'prod', 'dev', 'development', 'trunk']
@@ -422,10 +434,22 @@ function addTestOptimizationRequest (requestPromises, responseNames, responseNam
422
434
  }
423
435
  }
424
436
 
437
+ /**
438
+ * Builds the internal key used to correlate a worker test with one suite execution.
439
+ *
440
+ * @param {string} testSuite
441
+ * @param {string|undefined} testSuiteExecutionId
442
+ * @returns {string}
443
+ */
444
+ function getTestSuiteExecutionKey (testSuite, testSuiteExecutionId) {
445
+ return testSuiteExecutionId ? `${testSuite}\0${testSuiteExecutionId}` : testSuite
446
+ }
447
+
425
448
  module.exports = {
426
449
  TEST_CODE_OWNERS,
427
450
  TEST_SESSION_NAME,
428
451
  TEST_FRAMEWORK,
452
+ TEST_FRAMEWORK_ADAPTER,
429
453
  TEST_FRAMEWORK_VERSION,
430
454
  JEST_TEST_RUNNER,
431
455
  JEST_DISPLAY_NAME,
@@ -435,6 +459,8 @@ module.exports = {
435
459
  TEST_TYPE,
436
460
  TEST_NAME,
437
461
  TEST_SUITE,
462
+ TEST_SUITE_EXECUTION_ID,
463
+ getTestSuiteExecutionKey,
438
464
  TEST_STATUS,
439
465
  TEST_FINAL_STATUS,
440
466
  TEST_PARAMETERS,
@@ -453,10 +479,16 @@ module.exports = {
453
479
  JEST_WORKER_TELEMETRY_PAYLOAD_CODE,
454
480
  JEST_WORKER_QUARANTINE_PAYLOAD_CODE,
455
481
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
482
+ CUCUMBER_WORKER_TELEMETRY_PAYLOAD_CODE,
456
483
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
484
+ MOCHA_WORKER_LOGS_PAYLOAD_CODE,
485
+ MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE,
457
486
  PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
487
+ PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE,
458
488
  VITEST_WORKER_TRACE_PAYLOAD_CODE,
489
+ VITEST_WORKER_COVERAGE_PAYLOAD_CODE,
459
490
  VITEST_WORKER_LOGS_PAYLOAD_CODE,
491
+ VITEST_WORKER_TELEMETRY_PAYLOAD_CODE,
460
492
  TEST_IS_TEST_FRAMEWORK_WORKER,
461
493
  TEST_SOURCE_START,
462
494
  TEST_SKIPPED_BY_ITR,
@@ -495,6 +527,7 @@ module.exports = {
495
527
  TEST_ITR_UNSKIPPABLE,
496
528
  TEST_ITR_FORCED_RUN,
497
529
  ITR_CORRELATION_ID,
530
+ TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE,
498
531
  addIntelligentTestRunnerSpanTags,
499
532
  getCoveredFilenamesFromCoverage,
500
533
  getCoveredFilesFromCoverage,
@@ -511,9 +544,6 @@ module.exports = {
511
544
  removeInvalidMetadata,
512
545
  parseAnnotations,
513
546
  getIsFaultyEarlyFlakeDetection,
514
- EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS,
515
- getEfdRetryCount,
516
- getMaxEfdRetryCount,
517
547
  TEST_BROWSER_DRIVER,
518
548
  TEST_BROWSER_DRIVER_VERSION,
519
549
  TEST_BROWSER_NAME,
@@ -564,8 +594,10 @@ module.exports = {
564
594
  collectDynamicNamesFromTraces,
565
595
  collectTestOptimizationSummariesFromTraces,
566
596
  logDynamicNamesWarning,
597
+ recordTestManagementExecution,
567
598
  recordAttemptToFixExecution,
568
599
  collectAttemptToFixExecutionsFromTraces,
600
+ formatTestManagementSummary,
569
601
  formatAttemptToFixSummary,
570
602
  logAttemptToFixTestExecution,
571
603
  formatDynamicNamesSummary,
@@ -712,6 +744,7 @@ function checkShaDiscrepancies (ciMetadata, userProvidedGitMetadata) {
712
744
  *
713
745
  * @param {string=} testFramework
714
746
  * @param {TestEnvironmentConfig=} config
747
+ * @param {boolean=} shouldSkipGitMetadataExtraction
715
748
  * @returns {TestEnvironmentMetadata}
716
749
  */
717
750
  function getTestEnvironmentMetadata (testFramework, config, shouldSkipGitMetadataExtraction = false) {
@@ -800,7 +833,7 @@ function getTestLevelsMetadataTags (testEnvironmentMetadata) {
800
833
  }
801
834
 
802
835
  function getTestTypeFromFramework (testFramework) {
803
- if (testFramework === 'playwright' || testFramework === 'cypress') {
836
+ if (testFramework === 'playwright' || testFramework === 'cypress' || testFramework === 'webdriverio') {
804
837
  return 'browser'
805
838
  }
806
839
  return 'test'
@@ -860,9 +893,16 @@ function setRumTestCorrelation (context, activeSpan) {
860
893
  return testSpan
861
894
  }
862
895
 
863
- function getTestParentSpan (tracer) {
896
+ /**
897
+ * Returns the synthetic parent that makes a test event the root of its trace.
898
+ *
899
+ * @param {import('../../tracer')} tracer
900
+ * @param {string} [testExecutionId]
901
+ * @returns {import('../../opentracing/span_context')}
902
+ */
903
+ function getTestParentSpan (tracer, testExecutionId) {
864
904
  return tracer.extract('text_map', {
865
- 'x-datadog-trace-id': id().toString(10),
905
+ 'x-datadog-trace-id': testExecutionId || id().toString(10),
866
906
  'x-datadog-parent-id': '0000000000000000',
867
907
  })
868
908
  }
@@ -871,6 +911,7 @@ function getTestCommonTags (name, suite, version, testFramework) {
871
911
  return {
872
912
  [SPAN_TYPE]: 'test',
873
913
  [TEST_TYPE]: getTestTypeFromFramework(testFramework),
914
+ [TEST_FRAMEWORK]: testFramework,
874
915
  [SAMPLING_RULE_DECISION]: 1,
875
916
  [SAMPLING_PRIORITY]: AUTO_KEEP,
876
917
  [TEST_NAME]: name,
@@ -884,6 +925,10 @@ function getTestCommonTags (name, suite, version, testFramework) {
884
925
  /**
885
926
  * We want to make sure that test suites are reported the same way for
886
927
  * every OS, so we replace `path.sep` by `/`
928
+ *
929
+ * @param {string | undefined} testSuiteAbsolutePath
930
+ * @param {string} sourceRoot
931
+ * @returns {string}
887
932
  */
888
933
  function getTestSuitePath (testSuiteAbsolutePath, sourceRoot) {
889
934
  if (!testSuiteAbsolutePath) {
@@ -941,8 +986,7 @@ function getCodeOwnersFileEntries (rootDir) {
941
986
  const lines = codeOwnersContent.split('\n')
942
987
 
943
988
  for (const line of lines) {
944
- const [content] = line.split('#')
945
- const trimmed = content.trim()
989
+ const trimmed = getSegment(line, '#', 0).trim()
946
990
  if (trimmed === '') continue
947
991
  const [pattern, ...owners] = trimmed.split(/\s+/)
948
992
  entries.push(setCodeOwnersPatternRegex({ pattern, owners }))
@@ -988,9 +1032,7 @@ function codeOwnersPatternToRegexSource (pattern) {
988
1032
 
989
1033
  if (character === '\\') {
990
1034
  const escapedCharacter = pattern[i + 1]
991
- source += escapedCharacter === undefined
992
- ? escapeRegexCharacter(character)
993
- : escapeRegexCharacter(escapedCharacter)
1035
+ source += escapeRegexCharacter(escapedCharacter ?? character)
994
1036
  i++
995
1037
  } else if (character === '*') {
996
1038
  if (pattern[i + 1] === '*') {
@@ -1101,6 +1143,7 @@ function getCodeOwnersForFilename (filename, entries) {
1101
1143
 
1102
1144
  function getTestLevelCommonTags (command, testFrameworkVersion, testFramework) {
1103
1145
  return {
1146
+ [TEST_FRAMEWORK]: testFramework,
1104
1147
  [TEST_FRAMEWORK_VERSION]: testFrameworkVersion,
1105
1148
  [LIBRARY_VERSION]: ddTraceVersion,
1106
1149
  [TEST_TYPE]: getTestTypeFromFramework(testFramework),
@@ -1184,7 +1227,7 @@ function getCoveredFilenamesFromCoverage (coverage) {
1184
1227
  }
1185
1228
 
1186
1229
  function getCoverageMap (coverage) {
1187
- if (coverage?.files && coverage?.fileCoverageFor) {
1230
+ if (coverage?.files && coverage.fileCoverageFor) {
1188
1231
  return coverage
1189
1232
  }
1190
1233
  return istanbul.createCoverageMap(coverage)
@@ -1499,46 +1542,6 @@ function parseAnnotations (annotations) {
1499
1542
  }, {})
1500
1543
  }
1501
1544
 
1502
- /**
1503
- * Given a test's first-execution duration (ms) and the slow_test_retries map
1504
- * from the backend, return how many EFD retries to run.
1505
- *
1506
- * Returns 0 when the test is too slow to retry (≥ 5 min).
1507
- *
1508
- * @param {number} durationMs
1509
- * @param {Record<string, number>} slowTestRetries e.g. { '5s': 10, '10s': 5, '30s': 3, '5m': 2 }
1510
- * @returns {number}
1511
- */
1512
- function getEfdRetryCount (durationMs, slowTestRetries) {
1513
- for (const { limitMs, key } of EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS) {
1514
- if (durationMs < limitMs) {
1515
- return slowTestRetries[key] ?? 0
1516
- }
1517
- }
1518
- return 0 // ≥ 5 min — abort
1519
- }
1520
-
1521
- /**
1522
- * Returns the maximum retry count configured by the backend for EFD.
1523
- *
1524
- * @param {Record<string, number> | undefined} slowTestRetries
1525
- * @returns {number | undefined}
1526
- */
1527
- function getMaxEfdRetryCount (slowTestRetries) {
1528
- if (slowTestRetries === undefined) return
1529
-
1530
- const retryCounts = Object.values(slowTestRetries)
1531
- if (retryCounts.length === 0) return
1532
-
1533
- let maxRetries = 0
1534
- for (const retryCount of retryCounts) {
1535
- if (retryCount > maxRetries) {
1536
- maxRetries = retryCount
1537
- }
1538
- }
1539
- return maxRetries
1540
- }
1541
-
1542
1545
  function getIsFaultyEarlyFlakeDetection (projectSuites, testsBySuiteName, faultyThresholdPercentage) {
1543
1546
  let newSuites = 0
1544
1547
  for (const suite of projectSuites) {
@@ -1680,7 +1683,8 @@ function isFailedTestReplaySupported (testFramework, frameworkVersion) {
1680
1683
 
1681
1684
  function getLibraryCapabilitiesTags (testFramework, frameworkVersion, options = {}) {
1682
1685
  return {
1683
- [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework)
1686
+ [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: !options.omitTestImpactAnalysis &&
1687
+ isTiaSupported(testFramework)
1684
1688
  ? '1'
1685
1689
  : undefined,
1686
1690
  [DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: isEarlyFlakeDetectionSupported(testFramework, frameworkVersion)
@@ -1840,6 +1844,17 @@ function getModifiedFilesFromDiff (diff) {
1840
1844
  * @typedef {Map<string, AttemptToFixExecutionResult>} AttemptToFixExecutions
1841
1845
  */
1842
1846
 
1847
+ /**
1848
+ * Returns a collision-free identity for a test's suite and name.
1849
+ *
1850
+ * @param {string | undefined} testSuite
1851
+ * @param {string} testName
1852
+ * @returns {string}
1853
+ */
1854
+ function getTestOptimizationIdentity (testSuite, testName) {
1855
+ return JSON.stringify([testSuite, testName])
1856
+ }
1857
+
1843
1858
  /**
1844
1859
  * Formats a test name for user-facing Test Optimization summaries.
1845
1860
  *
@@ -1851,6 +1866,79 @@ function formatTestOptimizationName (testSuite, testName) {
1851
1866
  return testSuite ? `${testSuite} › ${testName}` : testName
1852
1867
  }
1853
1868
 
1869
+ /**
1870
+ * Replaces characters that could make one test occupy multiple CI log lines.
1871
+ *
1872
+ * @param {string} value
1873
+ * @returns {string}
1874
+ */
1875
+ function sanitizeTestOptimizationName (value) {
1876
+ return stripVTControlCharacters(value)
1877
+ .replaceAll(TEST_OPTIMIZATION_NAME_CONTROL_RE, '')
1878
+ .replaceAll(TEST_OPTIMIZATION_NAME_WHITESPACE_RE, ' ')
1879
+ .trim()
1880
+ }
1881
+
1882
+ /**
1883
+ * Truncates the beginning of a value while preserving its distinguishing suffix.
1884
+ *
1885
+ * @param {string} value
1886
+ * @param {number} maxLength
1887
+ * @returns {string}
1888
+ */
1889
+ function truncateTestOptimizationNameStart (value, maxLength) {
1890
+ if (value.length <= maxLength) return value
1891
+
1892
+ let start = value.length - maxLength + 1
1893
+ const code = value.charCodeAt(start)
1894
+ if (code >= 0xDC_00 && code <= 0xDF_FF) start++
1895
+ return `…${value.slice(start)}`
1896
+ }
1897
+
1898
+ /**
1899
+ * Truncates the middle of a value while preserving both its context and suffix.
1900
+ *
1901
+ * @param {string} value
1902
+ * @param {number} maxLength
1903
+ * @returns {string}
1904
+ */
1905
+ function truncateTestOptimizationNameMiddle (value, maxLength) {
1906
+ if (value.length <= maxLength) return value
1907
+
1908
+ let prefixLength = Math.ceil((maxLength - 1) / 2)
1909
+ let suffixLength = maxLength - prefixLength - 1
1910
+ const prefixCode = value.charCodeAt(prefixLength - 1)
1911
+ if (prefixCode >= 0xD8_00 && prefixCode <= 0xDB_FF) prefixLength--
1912
+ const suffixCode = value.charCodeAt(value.length - suffixLength)
1913
+ if (suffixCode >= 0xDC_00 && suffixCode <= 0xDF_FF) suffixLength--
1914
+ return `${value.slice(0, prefixLength)}…${value.slice(-suffixLength)}`
1915
+ }
1916
+
1917
+ /**
1918
+ * Formats a bounded, single-line test name for CI output.
1919
+ *
1920
+ * @param {string | undefined} testSuite
1921
+ * @param {string} testName
1922
+ * @returns {string}
1923
+ */
1924
+ function formatTestOptimizationDisplayName (testSuite, testName) {
1925
+ const sanitizedSuite = testSuite ? sanitizeTestOptimizationName(testSuite) : ''
1926
+ const sanitizedName = sanitizeTestOptimizationName(testName)
1927
+ if (!sanitizedSuite) {
1928
+ return truncateTestOptimizationNameMiddle(sanitizedName, MAX_TEST_OPTIMIZATION_NAME_LENGTH)
1929
+ }
1930
+
1931
+ const separator = ' › '
1932
+ if (sanitizedSuite.length + separator.length + sanitizedName.length <= MAX_TEST_OPTIMIZATION_NAME_LENGTH) {
1933
+ return `${sanitizedSuite}${separator}${sanitizedName}`
1934
+ }
1935
+
1936
+ const suiteLength = Math.min(60, sanitizedSuite.length)
1937
+ const testNameLength = MAX_TEST_OPTIMIZATION_NAME_LENGTH - separator.length - suiteLength
1938
+ return truncateTestOptimizationNameStart(sanitizedSuite, suiteLength) +
1939
+ `${separator}${truncateTestOptimizationNameMiddle(sanitizedName, testNameLength)}`
1940
+ }
1941
+
1854
1942
  /**
1855
1943
  * Renders a bounded bullet list for Test Optimization summaries.
1856
1944
  *
@@ -1862,7 +1950,12 @@ function formatTestOptimizationList (items) {
1862
1950
  const more = items.length - shown.length
1863
1951
  const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
1864
1952
 
1865
- return shown.map(({ text, suffix }) => ` • ${text}${suffix ? ` (${suffix})` : ''}`).join('\n') + moreSuffix
1953
+ return shown
1954
+ .map(({ text, suffix }) => ` • ${truncateTestOptimizationNameMiddle(
1955
+ sanitizeTestOptimizationName(text),
1956
+ MAX_TEST_OPTIMIZATION_NAME_LENGTH
1957
+ )}${suffix ? ` (${suffix})` : ''}`)
1958
+ .join('\n') + moreSuffix
1866
1959
  }
1867
1960
 
1868
1961
  /**
@@ -1875,15 +1968,67 @@ function formatTestOptimizationList (items) {
1875
1968
  function logAttemptToFixTestExecution (testSuite, testName, loggedAttemptToFixTests) {
1876
1969
  if (!testName) return
1877
1970
 
1878
- const name = formatTestOptimizationName(testSuite, testName)
1879
1971
  if (loggedAttemptToFixTests) {
1880
- if (loggedAttemptToFixTests.has(name)) return
1972
+ const identity = getTestOptimizationIdentity(testSuite, testName)
1973
+ if (loggedAttemptToFixTests.has(identity)) return
1881
1974
 
1882
- loggedAttemptToFixTests.add(name)
1975
+ loggedAttemptToFixTests.add(identity)
1883
1976
  }
1884
1977
 
1885
1978
  // eslint-disable-next-line no-console -- Intentional user-facing attempt-to-fix progress report
1886
- console.warn(`Datadog Test Optimization: attempting to fix ${name}`)
1979
+ console.warn(`Datadog Test Optimization: attempting to fix ${formatTestOptimizationDisplayName(testSuite, testName)}`)
1980
+ }
1981
+
1982
+ /**
1983
+ * @typedef {object} TestManagementExecutionResult
1984
+ * @property {string} name
1985
+ * @property {'disabled'|'quarantined'} action
1986
+ * @property {boolean} failed
1987
+ */
1988
+
1989
+ /**
1990
+ * @typedef {Map<string, TestManagementExecutionResult>} TestManagementExecutions
1991
+ */
1992
+
1993
+ /**
1994
+ * Records the effective non-Attempt-to-Fix Test Management action for one execution.
1995
+ *
1996
+ * @param {{
1997
+ * testSuite?: string,
1998
+ * testName: string,
1999
+ * status?: string,
2000
+ * isAttemptToFix?: boolean,
2001
+ * isDisabled?: boolean,
2002
+ * isQuarantined?: boolean
2003
+ * }} execution
2004
+ * @param {TestManagementExecutions} [executions]
2005
+ */
2006
+ function recordTestManagementExecution (execution, executions = testManagementExecutions) {
2007
+ if (!execution?.testName || execution.isAttemptToFix) return
2008
+
2009
+ const action = execution.isDisabled
2010
+ ? 'disabled'
2011
+ : execution.isQuarantined ? 'quarantined' : undefined
2012
+ if (!action) return
2013
+ if (action === 'quarantined' && execution.status !== 'pass' && execution.status !== 'fail') return
2014
+ if (getValueFromEnvSources('DD_TEST_MANAGEMENT_REPORT_ENABLED') === false) return
2015
+
2016
+ const identity = getTestOptimizationIdentity(execution.testSuite, execution.testName)
2017
+ const name = formatTestOptimizationName(execution.testSuite, execution.testName)
2018
+ let result = executions.get(identity)
2019
+
2020
+ if (!result) {
2021
+ result = {
2022
+ name,
2023
+ action,
2024
+ failed: false,
2025
+ }
2026
+ executions.set(identity, result)
2027
+ } else if (action === 'disabled') {
2028
+ result.action = action
2029
+ }
2030
+
2031
+ result.failed ||= execution.status === 'fail'
1887
2032
  }
1888
2033
 
1889
2034
  /**
@@ -1900,10 +2045,12 @@ function logAttemptToFixTestExecution (testSuite, testName, loggedAttemptToFixTe
1900
2045
  */
1901
2046
  function recordAttemptToFixExecution (attemptToFixExecutions, execution) {
1902
2047
  if (!execution?.testName) return
2048
+ if (getValueFromEnvSources('DD_TEST_MANAGEMENT_REPORT_ENABLED') === false) return
1903
2049
 
1904
2050
  const { testSuite, testName, status, isDisabled, isQuarantined } = execution
2051
+ const identity = getTestOptimizationIdentity(testSuite, testName)
1905
2052
  const name = formatTestOptimizationName(testSuite, testName)
1906
- let result = attemptToFixExecutions.get(name)
2053
+ let result = attemptToFixExecutions.get(identity)
1907
2054
 
1908
2055
  if (!result) {
1909
2056
  result = {
@@ -1913,12 +2060,12 @@ function recordAttemptToFixExecution (attemptToFixExecutions, execution) {
1913
2060
  isDisabled: false,
1914
2061
  isQuarantined: false,
1915
2062
  }
1916
- attemptToFixExecutions.set(name, result)
2063
+ attemptToFixExecutions.set(identity, result)
1917
2064
  }
1918
2065
 
1919
2066
  result.executions++
1920
- result.isDisabled = result.isDisabled || !!isDisabled
1921
- result.isQuarantined = result.isQuarantined || !!isQuarantined
2067
+ result.isDisabled ||= !!isDisabled
2068
+ result.isQuarantined ||= !!isQuarantined
1922
2069
 
1923
2070
  if (status === 'fail') {
1924
2071
  result.failedCount++
@@ -1949,6 +2096,27 @@ function collectAttemptToFixExecutionFromTraceSpan (span, attemptToFixExecutions
1949
2096
  })
1950
2097
  }
1951
2098
 
2099
+ /**
2100
+ * Records a Test Management execution from a serialized worker trace span.
2101
+ *
2102
+ * @param {{ meta?: Record<string, string> }} span
2103
+ * @param {TestManagementExecutions} executions
2104
+ * @returns {void}
2105
+ */
2106
+ function collectTestManagementExecutionFromTraceSpan (span, executions) {
2107
+ const meta = span.meta
2108
+ if (!meta) return
2109
+
2110
+ recordTestManagementExecution({
2111
+ testSuite: meta[TEST_SUITE],
2112
+ testName: meta[TEST_NAME],
2113
+ status: meta[TEST_STATUS],
2114
+ isAttemptToFix: meta[TEST_MANAGEMENT_IS_ATTEMPT_TO_FIX] === 'true',
2115
+ isDisabled: meta[TEST_MANAGEMENT_IS_DISABLED] === 'true',
2116
+ isQuarantined: meta[TEST_MANAGEMENT_IS_QUARANTINED] === 'true',
2117
+ }, executions)
2118
+ }
2119
+
1952
2120
  /**
1953
2121
  * Scans serialized worker trace payloads and populates Test Optimization summary data.
1954
2122
  * Silently ignores parse errors.
@@ -1956,11 +2124,16 @@ function collectAttemptToFixExecutionFromTraceSpan (span, attemptToFixExecutions
1956
2124
  * @param {string} data - JSON-serialized traces from a worker
1957
2125
  * @param {{
1958
2126
  * newTestsWithDynamicNames?: Set<string>,
1959
- * attemptToFixExecutions?: AttemptToFixExecutions
2127
+ * attemptToFixExecutions?: AttemptToFixExecutions,
2128
+ * testManagementExecutions?: TestManagementExecutions
1960
2129
  * }} summaries
1961
2130
  */
1962
2131
  function collectTestOptimizationSummariesFromTraces (data, summaries) {
1963
- const { newTestsWithDynamicNames, attemptToFixExecutions } = summaries
2132
+ const {
2133
+ newTestsWithDynamicNames,
2134
+ attemptToFixExecutions,
2135
+ testManagementExecutions: executions = testManagementExecutions,
2136
+ } = summaries
1964
2137
 
1965
2138
  try {
1966
2139
  const traces = JSON.parse(data)
@@ -1972,6 +2145,7 @@ function collectTestOptimizationSummariesFromTraces (data, summaries) {
1972
2145
  if (attemptToFixExecutions) {
1973
2146
  collectAttemptToFixExecutionFromTraceSpan(span, attemptToFixExecutions)
1974
2147
  }
2148
+ collectTestManagementExecutionFromTraceSpan(span, executions)
1975
2149
  }
1976
2150
  }
1977
2151
  } catch {
@@ -2018,7 +2192,10 @@ function hasAttemptToFixManagementNotes (result) {
2018
2192
  }
2019
2193
 
2020
2194
  function addAttemptToFixResultLine (lines, result) {
2021
- lines.push(` • ${result.name}`)
2195
+ lines.push(` • ${truncateTestOptimizationNameMiddle(
2196
+ sanitizeTestOptimizationName(result.name),
2197
+ MAX_TEST_OPTIMIZATION_NAME_LENGTH
2198
+ )}`)
2022
2199
 
2023
2200
  for (const note of getAttemptToFixManagementNotes(result)) {
2024
2201
  lines.push(` ${note}`)
@@ -2029,9 +2206,10 @@ function addAttemptToFixResultLine (lines, result) {
2029
2206
  * Formats the attempt-to-fix end-of-session summary.
2030
2207
  *
2031
2208
  * @param {AttemptToFixExecutions} attemptToFixExecutions
2209
+ * @param {boolean} [includeTestNames]
2032
2210
  * @returns {string}
2033
2211
  */
2034
- function formatAttemptToFixSummary (attemptToFixExecutions) {
2212
+ function formatAttemptToFixSummary (attemptToFixExecutions, includeTestNames = true) {
2035
2213
  if (attemptToFixExecutions.size === 0) return ''
2036
2214
 
2037
2215
  const results = [...attemptToFixExecutions.values()]
@@ -2043,9 +2221,11 @@ function formatAttemptToFixSummary (attemptToFixExecutions) {
2043
2221
  `Attempt to fix passed: all ${totalExecutions} execution(s) passed for ${results.length} test(s).`,
2044
2222
  ]
2045
2223
 
2046
- for (const result of results) {
2047
- if (hasAttemptToFixManagementNotes(result)) {
2048
- addAttemptToFixResultLine(lines, result)
2224
+ if (includeTestNames) {
2225
+ for (const result of results) {
2226
+ if (hasAttemptToFixManagementNotes(result)) {
2227
+ addAttemptToFixResultLine(lines, result)
2228
+ }
2049
2229
  }
2050
2230
  }
2051
2231
 
@@ -2061,13 +2241,67 @@ function formatAttemptToFixSummary (attemptToFixExecutions) {
2061
2241
  `across ${failedResults.length} of ${results.length} test(s).`,
2062
2242
  ]
2063
2243
 
2064
- for (const result of failedResults) {
2065
- addAttemptToFixResultLine(lines, result)
2066
- }
2067
- for (const result of results) {
2068
- if (result.failedCount === 0 && hasAttemptToFixManagementNotes(result)) {
2244
+ if (includeTestNames) {
2245
+ for (const result of failedResults) {
2069
2246
  addAttemptToFixResultLine(lines, result)
2070
2247
  }
2248
+ for (const result of results) {
2249
+ if (result.failedCount === 0 && hasAttemptToFixManagementNotes(result)) {
2250
+ addAttemptToFixResultLine(lines, result)
2251
+ }
2252
+ }
2253
+ }
2254
+
2255
+ return lines.join('\n')
2256
+ }
2257
+
2258
+ /**
2259
+ * Formats the disabled and quarantined Test Management summary.
2260
+ *
2261
+ * @param {TestManagementExecutions} executions
2262
+ * @param {boolean} [includeTestNames]
2263
+ * @returns {string}
2264
+ */
2265
+ function formatTestManagementSummary (executions, includeTestNames = true) {
2266
+ if (executions.size === 0) return ''
2267
+
2268
+ const results = [...executions.values()]
2269
+ const disabledResults = results.filter(result => result.action === 'disabled')
2270
+ const quarantinedResults = results.filter(result => result.action === 'quarantined')
2271
+ const failedQuarantinedResults = quarantinedResults.filter(result => result.failed)
2272
+ const lines = ['Test Management']
2273
+
2274
+ if (disabledResults.length > 0) {
2275
+ const testLabel = disabledResults.length === 1 ? 'test' : 'tests'
2276
+ lines.push(` Disabled: ${disabledResults.length} ${testLabel} skipped.`)
2277
+ }
2278
+ if (quarantinedResults.length > 0) {
2279
+ const testLabel = quarantinedResults.length === 1 ? 'test' : 'tests'
2280
+ if (failedQuarantinedResults.length > 0) {
2281
+ const failureLabel = failedQuarantinedResults.length === 1 ? 'failure' : 'failures'
2282
+ lines.push(
2283
+ ` Quarantined: ${quarantinedResults.length} ${testLabel} run; ` +
2284
+ `${failedQuarantinedResults.length} ${failureLabel} did not affect the test session.`
2285
+ )
2286
+ } else {
2287
+ lines.push(` Quarantined: ${quarantinedResults.length} ${testLabel} run; all passed.`)
2288
+ }
2289
+ }
2290
+
2291
+ if (includeTestNames) {
2292
+ lines.push('', ' Affected tests:')
2293
+ results.sort((a, b) => a.name.localeCompare(b.name))
2294
+ for (const result of results) {
2295
+ const status = result.action === 'disabled'
2296
+ ? 'disabled'
2297
+ : result.failed ? 'quarantined, failed' : 'quarantined, passed'
2298
+ lines.push(
2299
+ ` • [${status}] ${truncateTestOptimizationNameMiddle(
2300
+ sanitizeTestOptimizationName(result.name),
2301
+ MAX_TEST_OPTIMIZATION_NAME_LENGTH
2302
+ )}`
2303
+ )
2304
+ }
2071
2305
  }
2072
2306
 
2073
2307
  return lines.join('\n')
@@ -2098,25 +2332,57 @@ function formatDynamicNamesSummary (newTestsWithDynamicNames) {
2098
2332
  *
2099
2333
  * @param {{
2100
2334
  * attemptToFixExecutions?: AttemptToFixExecutions,
2335
+ * testManagementExecutions?: TestManagementExecutions,
2101
2336
  * newTestsWithDynamicNames?: Set<string>,
2102
2337
  * extraSections?: string[]
2103
2338
  * }} summary
2104
2339
  */
2105
2340
  function logTestOptimizationSummary (summary) {
2106
- const { attemptToFixExecutions, newTestsWithDynamicNames, extraSections = [] } = summary
2341
+ const {
2342
+ attemptToFixExecutions,
2343
+ testManagementExecutions: executions = testManagementExecutions,
2344
+ newTestsWithDynamicNames,
2345
+ extraSections = [],
2346
+ } = summary
2107
2347
  const sections = []
2108
- const attemptToFixSummary = attemptToFixExecutions
2109
- ? formatAttemptToFixSummary(attemptToFixExecutions)
2348
+ const isTestManagementReportEnabled =
2349
+ getValueFromEnvSources('DD_TEST_MANAGEMENT_REPORT_ENABLED') !== false
2350
+ const testManagementTestCount = (attemptToFixExecutions?.size || 0) + executions.size
2351
+ const includeTestNames = testManagementTestCount <= MAX_TEST_MANAGEMENT_REPORT_ITEMS
2352
+ const attemptToFixSummary = isTestManagementReportEnabled && attemptToFixExecutions
2353
+ ? formatAttemptToFixSummary(attemptToFixExecutions, includeTestNames)
2354
+ : ''
2355
+ const testManagementSummary = isTestManagementReportEnabled
2356
+ ? formatTestManagementSummary(executions, includeTestNames)
2110
2357
  : ''
2111
2358
  const dynamicNamesSummary = newTestsWithDynamicNames
2112
2359
  ? formatDynamicNamesSummary(newTestsWithDynamicNames)
2113
2360
  : ''
2114
2361
 
2115
2362
  if (attemptToFixSummary) sections.push(attemptToFixSummary)
2363
+ if (testManagementSummary) sections.push(testManagementSummary)
2364
+ if (attemptToFixSummary || testManagementSummary) {
2365
+ if (!includeTestNames) {
2366
+ sections.push(`Individual test names omitted because ${testManagementTestCount} tests were affected.`)
2367
+ }
2368
+ sections.push('Hide this report: DD_TEST_MANAGEMENT_REPORT_ENABLED=false')
2369
+ }
2116
2370
  sections.push(...extraSections.filter(Boolean))
2117
2371
  if (dynamicNamesSummary) sections.push(dynamicNamesSummary)
2118
2372
 
2119
- if (sections.length === 0) return
2373
+ if (sections.length === 0) {
2374
+ executions.clear()
2375
+ if (executions !== testManagementExecutions) {
2376
+ testManagementExecutions.clear()
2377
+ }
2378
+ if (attemptToFixExecutions) {
2379
+ attemptToFixExecutions.clear()
2380
+ }
2381
+ if (newTestsWithDynamicNames) {
2382
+ newTestsWithDynamicNames.clear()
2383
+ }
2384
+ return
2385
+ }
2120
2386
 
2121
2387
  const line = '-'.repeat(50)
2122
2388
  // eslint-disable-next-line no-console -- Intentional user-facing session summary
@@ -2125,6 +2391,10 @@ function logTestOptimizationSummary (summary) {
2125
2391
  if (attemptToFixExecutions) {
2126
2392
  attemptToFixExecutions.clear()
2127
2393
  }
2394
+ executions.clear()
2395
+ if (executions !== testManagementExecutions) {
2396
+ testManagementExecutions.clear()
2397
+ }
2128
2398
  if (newTestsWithDynamicNames) {
2129
2399
  newTestsWithDynamicNames.clear()
2130
2400
  }