dd-trace 6.8.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 (179) 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 +9 -3
  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/vitest.js +517 -0
  20. package/ci/test-optimization-validation/generated-files.js +38 -28
  21. package/ci/test-optimization-validation/generated-test-contract.js +8 -4
  22. package/ci/test-optimization-validation/generated-verifier.js +24 -17
  23. package/ci/test-optimization-validation/literal-glob.js +52 -0
  24. package/ci/test-optimization-validation/manifest-loader.js +3 -0
  25. package/ci/test-optimization-validation/manifest-scaffold.js +300 -50
  26. package/ci/test-optimization-validation/manifest-schema.js +80 -17
  27. package/ci/test-optimization-validation/offline-fixtures.js +13 -4
  28. package/ci/test-optimization-validation/package-check.js +94 -0
  29. package/ci/test-optimization-validation/plan-writer.js +183 -10
  30. package/ci/test-optimization-validation/preflight-runner.js +110 -31
  31. package/ci/test-optimization-validation/project-build-artifact.js +31 -0
  32. package/ci/test-optimization-validation/redaction.js +18 -27
  33. package/ci/test-optimization-validation/report-writer.js +182 -48
  34. package/ci/test-optimization-validation/result-semantics.js +20 -3
  35. package/ci/test-optimization-validation/runner-command.js +40 -20
  36. package/ci/test-optimization-validation/runner-contract.js +79 -43
  37. package/ci/test-optimization-validation/scenarios/basic-reporting.js +59 -5
  38. package/ci/test-optimization-validation/scenarios/ci-wiring.js +141 -35
  39. package/ci/test-optimization-validation/scenarios/helpers.js +30 -6
  40. package/ci/test-optimization-validation/static-diagnosis.js +10 -3
  41. package/ci/test-optimization-validation/test-output.js +2 -1
  42. package/ci/test-optimization-validation/validation-blocker.js +21 -0
  43. package/ci/vitest-no-worker-init-setup.mjs +30 -34
  44. package/index.d.ts +98 -1
  45. package/init.js +1 -17
  46. package/initialize.mjs +11 -0
  47. package/loader-hook.mjs +6 -4
  48. package/package.json +5 -3
  49. package/packages/datadog-core/src/utils/src/parse-tags.js +0 -1
  50. package/packages/datadog-esbuild/src/utils.js +16 -1
  51. package/packages/datadog-instrumentations/src/ai.js +61 -34
  52. package/packages/datadog-instrumentations/src/bluebird.js +6 -6
  53. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +81 -5
  54. package/packages/datadog-instrumentations/src/cucumber.js +124 -47
  55. package/packages/datadog-instrumentations/src/dns.js +21 -2
  56. package/packages/datadog-instrumentations/src/electron.js +1 -0
  57. package/packages/datadog-instrumentations/src/fastify.js +3 -1
  58. package/packages/datadog-instrumentations/src/grpc/client.js +4 -2
  59. package/packages/datadog-instrumentations/src/helpers/callback-instrumentor.js +3 -1
  60. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -0
  61. package/packages/datadog-instrumentations/src/helpers/rewriter/index.js +33 -9
  62. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/webdriverio.js +118 -0
  63. package/packages/datadog-instrumentations/src/helpers/rewriter/loader.mjs +20 -2
  64. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.js +32 -0
  65. package/packages/datadog-instrumentations/src/helpers/rewriter/targets.json +61 -0
  66. package/packages/datadog-instrumentations/src/helpers/router-helper.js +37 -6
  67. package/packages/datadog-instrumentations/src/jest/coverage-backfill.js +1 -1
  68. package/packages/datadog-instrumentations/src/jest.js +979 -233
  69. package/packages/datadog-instrumentations/src/mocha/main.js +107 -9
  70. package/packages/datadog-instrumentations/src/mocha/utils.js +64 -42
  71. package/packages/datadog-instrumentations/src/mocha/webdriverio-protocol.js +25 -0
  72. package/packages/datadog-instrumentations/src/mocha/worker.js +12 -18
  73. package/packages/datadog-instrumentations/src/mysql.js +1 -1
  74. package/packages/datadog-instrumentations/src/next.js +155 -23
  75. package/packages/datadog-instrumentations/src/openai.js +14 -0
  76. package/packages/datadog-instrumentations/src/pg.js +180 -6
  77. package/packages/datadog-instrumentations/src/playwright.js +173 -79
  78. package/packages/datadog-instrumentations/src/promise-js.js +4 -4
  79. package/packages/datadog-instrumentations/src/promise.js +3 -3
  80. package/packages/datadog-instrumentations/src/router.js +195 -19
  81. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +43 -10
  82. package/packages/datadog-instrumentations/src/vitest-main.js +360 -56
  83. package/packages/datadog-instrumentations/src/vitest-util.js +42 -7
  84. package/packages/datadog-instrumentations/src/vitest-worker.js +234 -17
  85. package/packages/datadog-instrumentations/src/webdriverio.js +194 -18
  86. package/packages/datadog-instrumentations/src/when.js +3 -3
  87. package/packages/datadog-instrumentations/src/ws.js +5 -1
  88. package/packages/datadog-plugin-aws-sdk/src/services/bedrockruntime/utils.js +59 -48
  89. package/packages/datadog-plugin-child_process/src/scrub-cmd-params.js +1 -3
  90. package/packages/datadog-plugin-cucumber/src/index.js +5 -1
  91. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +50 -46
  92. package/packages/datadog-plugin-cypress/src/support.js +7 -4
  93. package/packages/datadog-plugin-electron/src/net.js +10 -4
  94. package/packages/datadog-plugin-http/src/client.js +1 -1
  95. package/packages/datadog-plugin-http2/src/client.js +1 -1
  96. package/packages/datadog-plugin-jest/src/index.js +1 -18
  97. package/packages/datadog-plugin-jest/src/util.js +2 -0
  98. package/packages/datadog-plugin-kafkajs/src/producer.js +2 -1
  99. package/packages/datadog-plugin-mocha/src/index.js +318 -1
  100. package/packages/datadog-plugin-next/src/index.js +57 -9
  101. package/packages/datadog-plugin-openai/src/tracing.js +4 -0
  102. package/packages/datadog-plugin-pg/src/index.js +65 -1
  103. package/packages/datadog-plugin-playwright/src/index.js +1 -1
  104. package/packages/datadog-plugin-vitest/src/index.js +95 -7
  105. package/packages/datadog-shimmer/src/shimmer.js +6 -2
  106. package/packages/dd-trace/src/aiguard/sdk.js +3 -0
  107. package/packages/dd-trace/src/appsec/{blocking.js → blocking/index.js} +26 -6
  108. package/packages/dd-trace/src/appsec/handlers/auth.js +62 -0
  109. package/packages/dd-trace/src/appsec/handlers/http-request.js +215 -0
  110. package/packages/dd-trace/src/appsec/handlers/http-response.js +78 -0
  111. package/packages/dd-trace/src/appsec/handlers/http-shared.js +24 -0
  112. package/packages/dd-trace/src/appsec/handlers/payments.js +104 -0
  113. package/packages/dd-trace/src/appsec/iast/analyzers/hsts-header-missing-analyzer.js +1 -1
  114. package/packages/dd-trace/src/appsec/iast/vulnerabilities-formatter/utils.js +1 -1
  115. package/packages/dd-trace/src/appsec/index.js +48 -476
  116. package/packages/dd-trace/src/appsec/sdk/track_event.js +9 -0
  117. package/packages/dd-trace/src/ci-visibility/coverage-report-discovery.js +46 -14
  118. package/packages/dd-trace/src/ci-visibility/efd-retry-policy.js +89 -0
  119. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +27 -13
  120. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +17 -0
  121. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +21 -1
  122. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +22 -7
  123. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +9 -14
  124. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +21 -5
  125. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache-schema.js +1 -1
  126. package/packages/dd-trace/src/ci-visibility/test-optimization-http-cache.js +5 -1
  127. package/packages/dd-trace/src/config/generated-config-types.d.ts +2 -0
  128. package/packages/dd-trace/src/config/helper.js +2 -0
  129. package/packages/dd-trace/src/config/index.js +16 -5
  130. package/packages/dd-trace/src/config/remote_config.js +1 -1
  131. package/packages/dd-trace/src/config/supported-configurations.json +8 -0
  132. package/packages/dd-trace/src/debugger/devtools_client/status.js +1 -1
  133. package/packages/dd-trace/src/encode/coverage-ci-visibility.js +0 -1
  134. package/packages/dd-trace/src/guardrails/apply-pm2-env.js +16 -0
  135. package/packages/dd-trace/src/guardrails/log.js +1 -10
  136. package/packages/dd-trace/src/llmobs/eval-metric.js +249 -0
  137. package/packages/dd-trace/src/llmobs/experiments/index.js +33 -25
  138. package/packages/dd-trace/src/llmobs/experiments/util.js +4 -2
  139. package/packages/dd-trace/src/llmobs/noop.js +2 -0
  140. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +1 -1
  141. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +1 -1
  142. package/packages/dd-trace/src/llmobs/plugins/bedrockruntime.js +5 -5
  143. package/packages/dd-trace/src/llmobs/plugins/langchain/index.js +1 -1
  144. package/packages/dd-trace/src/llmobs/sdk.js +149 -68
  145. package/packages/dd-trace/src/llmobs/telemetry.js +18 -0
  146. package/packages/dd-trace/src/llmobs/writers/base.js +3 -0
  147. package/packages/dd-trace/src/llmobs/writers/util.js +2 -0
  148. package/packages/dd-trace/src/log/channels.js +1 -9
  149. package/packages/dd-trace/src/log/levels.js +12 -0
  150. package/packages/dd-trace/src/openfeature/writers/exposures.js +1 -1
  151. package/packages/dd-trace/src/openfeature/writers/util.js +1 -1
  152. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +1 -1
  153. package/packages/dd-trace/src/opentelemetry/metrics/meter.js +8 -4
  154. package/packages/dd-trace/src/opentelemetry/metrics/periodic_metric_reader.js +1 -1
  155. package/packages/dd-trace/src/opentelemetry/span.js +4 -0
  156. package/packages/dd-trace/src/plugins/ci_plugin.js +21 -5
  157. package/packages/dd-trace/src/plugins/index.js +1 -0
  158. package/packages/dd-trace/src/plugins/util/ci.js +6 -0
  159. package/packages/dd-trace/src/plugins/util/git.js +6 -4
  160. package/packages/dd-trace/src/plugins/util/http-otel-semantics.js +2 -2
  161. package/packages/dd-trace/src/plugins/util/ip_extractor.js +1 -1
  162. package/packages/dd-trace/src/plugins/util/stacktrace.js +3 -0
  163. package/packages/dd-trace/src/plugins/util/test.js +319 -75
  164. package/packages/dd-trace/src/plugins/util/url.js +1 -1
  165. package/packages/dd-trace/src/plugins/util/web.js +23 -1
  166. package/packages/dd-trace/src/profiler.js +1 -1
  167. package/packages/dd-trace/src/profiling/exporters/event_serializer.js +1 -1
  168. package/packages/dd-trace/src/profiling/profilers/events.js +2 -2
  169. package/packages/dd-trace/src/proxy.js +1 -0
  170. package/packages/dd-trace/src/ritm.js +5 -0
  171. package/packages/dd-trace/src/runtime_metrics/runtime_metrics.js +4 -2
  172. package/packages/dd-trace/src/sampling_rule.js +4 -2
  173. package/packages/dd-trace/src/service-naming/schemas/v0/storage.js +1 -1
  174. package/packages/dd-trace/src/service-naming/schemas/v1/storage.js +1 -1
  175. package/packages/dd-trace/src/telemetry/logs/index.js +3 -0
  176. package/packages/dd-trace/src/telemetry/telemetry.js +1 -1
  177. package/vendor/dist/pprof-format/index.js +1 -1
  178. package/version.js +10 -8
  179. /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
 
@@ -123,13 +124,10 @@ const TEST_RETRY_REASON = 'test.retry_reason'
123
124
  const TEST_HAS_FAILED_ALL_RETRIES = 'test.has_failed_all_retries'
124
125
  const TEST_IS_MODIFIED = 'test.is_modified'
125
126
  const TEST_HAS_DYNAMIC_NAME = '_dd.has_dynamic_name'
126
- const EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS = [
127
- { limitMs: 5 * 1000, key: '5s' },
128
- { limitMs: 10 * 1000, key: '10s' },
129
- { limitMs: 30 * 1000, key: '30s' },
130
- { limitMs: 5 * 60 * 1000, key: '5m' },
131
- ]
132
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
133
131
 
134
132
  // Matches patterns that are almost certainly runtime-generated values in test names:
135
133
  // - Unix timestamps in ms (13 digits, years ~2020-2090) or s (10 digits)
@@ -162,6 +160,7 @@ const TEST_CODE_COVERAGE_ENABLED = 'test.code_coverage.enabled'
162
160
  const TEST_ITR_UNSKIPPABLE = 'test.itr.unskippable'
163
161
  const TEST_ITR_FORCED_RUN = 'test.itr.forced_run'
164
162
  const ITR_CORRELATION_ID = 'itr_correlation_id'
163
+ const TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE = 'Test Impact Analysis skipped all tests.'
165
164
 
166
165
  const TEST_CODE_COVERAGE_LINES_PCT = 'test.code_coverage.lines_pct'
167
166
 
@@ -180,17 +179,22 @@ const JEST_WORKER_QUARANTINE_PAYLOAD_CODE = 64
180
179
 
181
180
  // cucumber worker variables
182
181
  const CUCUMBER_WORKER_TRACE_PAYLOAD_CODE = 70
182
+ const CUCUMBER_WORKER_TELEMETRY_PAYLOAD_CODE = 71
183
183
 
184
184
  // mocha worker variables
185
185
  const MOCHA_WORKER_TRACE_PAYLOAD_CODE = 80
186
186
  const MOCHA_WORKER_LOGS_PAYLOAD_CODE = 81
187
+ const MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE = 82
187
188
 
188
189
  // playwright worker variables
189
190
  const PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE = 90
191
+ const PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE = 91
190
192
 
191
193
  // vitest worker variables
192
194
  const VITEST_WORKER_TRACE_PAYLOAD_CODE = 100
195
+ const VITEST_WORKER_COVERAGE_PAYLOAD_CODE = 101
193
196
  const VITEST_WORKER_LOGS_PAYLOAD_CODE = 102
197
+ const VITEST_WORKER_TELEMETRY_PAYLOAD_CODE = 103
194
198
 
195
199
  const TEST_IS_TEST_FRAMEWORK_WORKER = 'test.is_test_framework_worker'
196
200
 
@@ -211,7 +215,7 @@ const DD_CI_LIBRARY_CONFIGURATION_ERROR_KNOWN_TESTS = '_dd.ci.library_configurat
211
215
  const DD_CI_LIBRARY_CONFIGURATION_ERROR_TEST_MANAGEMENT_TESTS =
212
216
  '_dd.ci.library_configuration_error.test_management_tests'
213
217
 
214
- const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'vitest', 'webdriverio'])
218
+ const UNSUPPORTED_TIA_FRAMEWORKS = new Set(['playwright', 'webdriverio'])
215
219
  const MINIMUM_FRAMEWORK_VERSION_FOR_EFD = {
216
220
  playwright: '>=1.38.0',
217
221
  }
@@ -300,7 +304,10 @@ const TEST_MANAGEMENT_IS_QUARANTINED = 'test.test_management.is_quarantined'
300
304
  const TEST_MANAGEMENT_ENABLED = 'test.test_management.enabled'
301
305
  const TEST_MANAGEMENT_ATTEMPT_TO_FIX_PASSED = 'test.test_management.attempt_to_fix_passed'
302
306
 
307
+ const MAX_TEST_MANAGEMENT_REPORT_ITEMS = 5
308
+ const MAX_TEST_OPTIMIZATION_NAME_LENGTH = 160
303
309
  const MAX_TEST_OPTIMIZATION_SUMMARY_ITEMS = 10
310
+ const testManagementExecutions = new Map()
304
311
 
305
312
  // Impacted tests
306
313
  const POSSIBLE_BASE_BRANCHES = ['main', 'master', 'preprod', 'prod', 'dev', 'development', 'trunk']
@@ -472,11 +479,16 @@ module.exports = {
472
479
  JEST_WORKER_TELEMETRY_PAYLOAD_CODE,
473
480
  JEST_WORKER_QUARANTINE_PAYLOAD_CODE,
474
481
  CUCUMBER_WORKER_TRACE_PAYLOAD_CODE,
482
+ CUCUMBER_WORKER_TELEMETRY_PAYLOAD_CODE,
475
483
  MOCHA_WORKER_TRACE_PAYLOAD_CODE,
476
484
  MOCHA_WORKER_LOGS_PAYLOAD_CODE,
485
+ MOCHA_WORKER_TELEMETRY_PAYLOAD_CODE,
477
486
  PLAYWRIGHT_WORKER_TRACE_PAYLOAD_CODE,
487
+ PLAYWRIGHT_WORKER_TELEMETRY_PAYLOAD_CODE,
478
488
  VITEST_WORKER_TRACE_PAYLOAD_CODE,
489
+ VITEST_WORKER_COVERAGE_PAYLOAD_CODE,
479
490
  VITEST_WORKER_LOGS_PAYLOAD_CODE,
491
+ VITEST_WORKER_TELEMETRY_PAYLOAD_CODE,
480
492
  TEST_IS_TEST_FRAMEWORK_WORKER,
481
493
  TEST_SOURCE_START,
482
494
  TEST_SKIPPED_BY_ITR,
@@ -515,6 +527,7 @@ module.exports = {
515
527
  TEST_ITR_UNSKIPPABLE,
516
528
  TEST_ITR_FORCED_RUN,
517
529
  ITR_CORRELATION_ID,
530
+ TEST_IMPACT_ANALYSIS_ALL_TESTS_SKIPPED_MESSAGE,
518
531
  addIntelligentTestRunnerSpanTags,
519
532
  getCoveredFilenamesFromCoverage,
520
533
  getCoveredFilesFromCoverage,
@@ -531,9 +544,6 @@ module.exports = {
531
544
  removeInvalidMetadata,
532
545
  parseAnnotations,
533
546
  getIsFaultyEarlyFlakeDetection,
534
- EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS,
535
- getEfdRetryCount,
536
- getMaxEfdRetryCount,
537
547
  TEST_BROWSER_DRIVER,
538
548
  TEST_BROWSER_DRIVER_VERSION,
539
549
  TEST_BROWSER_NAME,
@@ -584,8 +594,10 @@ module.exports = {
584
594
  collectDynamicNamesFromTraces,
585
595
  collectTestOptimizationSummariesFromTraces,
586
596
  logDynamicNamesWarning,
597
+ recordTestManagementExecution,
587
598
  recordAttemptToFixExecution,
588
599
  collectAttemptToFixExecutionsFromTraces,
600
+ formatTestManagementSummary,
589
601
  formatAttemptToFixSummary,
590
602
  logAttemptToFixTestExecution,
591
603
  formatDynamicNamesSummary,
@@ -732,6 +744,7 @@ function checkShaDiscrepancies (ciMetadata, userProvidedGitMetadata) {
732
744
  *
733
745
  * @param {string=} testFramework
734
746
  * @param {TestEnvironmentConfig=} config
747
+ * @param {boolean=} shouldSkipGitMetadataExtraction
735
748
  * @returns {TestEnvironmentMetadata}
736
749
  */
737
750
  function getTestEnvironmentMetadata (testFramework, config, shouldSkipGitMetadataExtraction = false) {
@@ -912,6 +925,10 @@ function getTestCommonTags (name, suite, version, testFramework) {
912
925
  /**
913
926
  * We want to make sure that test suites are reported the same way for
914
927
  * every OS, so we replace `path.sep` by `/`
928
+ *
929
+ * @param {string | undefined} testSuiteAbsolutePath
930
+ * @param {string} sourceRoot
931
+ * @returns {string}
915
932
  */
916
933
  function getTestSuitePath (testSuiteAbsolutePath, sourceRoot) {
917
934
  if (!testSuiteAbsolutePath) {
@@ -1525,46 +1542,6 @@ function parseAnnotations (annotations) {
1525
1542
  }, {})
1526
1543
  }
1527
1544
 
1528
- /**
1529
- * Given a test's first-execution duration (ms) and the slow_test_retries map
1530
- * from the backend, return how many EFD retries to run.
1531
- *
1532
- * Returns 0 when the test is too slow to retry (≥ 5 min).
1533
- *
1534
- * @param {number} durationMs
1535
- * @param {Record<string, number>} slowTestRetries e.g. { '5s': 10, '10s': 5, '30s': 3, '5m': 2 }
1536
- * @returns {number}
1537
- */
1538
- function getEfdRetryCount (durationMs, slowTestRetries) {
1539
- for (const { limitMs, key } of EARLY_FLAKE_DETECTION_RETRY_THRESHOLDS) {
1540
- if (durationMs < limitMs) {
1541
- return slowTestRetries[key] ?? 0
1542
- }
1543
- }
1544
- return 0 // ≥ 5 min — abort
1545
- }
1546
-
1547
- /**
1548
- * Returns the maximum retry count configured by the backend for EFD.
1549
- *
1550
- * @param {Record<string, number> | undefined} slowTestRetries
1551
- * @returns {number | undefined}
1552
- */
1553
- function getMaxEfdRetryCount (slowTestRetries) {
1554
- if (slowTestRetries === undefined) return
1555
-
1556
- const retryCounts = Object.values(slowTestRetries)
1557
- if (retryCounts.length === 0) return
1558
-
1559
- let maxRetries = 0
1560
- for (const retryCount of retryCounts) {
1561
- if (retryCount > maxRetries) {
1562
- maxRetries = retryCount
1563
- }
1564
- }
1565
- return maxRetries
1566
- }
1567
-
1568
1545
  function getIsFaultyEarlyFlakeDetection (projectSuites, testsBySuiteName, faultyThresholdPercentage) {
1569
1546
  let newSuites = 0
1570
1547
  for (const suite of projectSuites) {
@@ -1706,7 +1683,8 @@ function isFailedTestReplaySupported (testFramework, frameworkVersion) {
1706
1683
 
1707
1684
  function getLibraryCapabilitiesTags (testFramework, frameworkVersion, options = {}) {
1708
1685
  return {
1709
- [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: isTiaSupported(testFramework)
1686
+ [DD_CAPABILITIES_TEST_IMPACT_ANALYSIS]: !options.omitTestImpactAnalysis &&
1687
+ isTiaSupported(testFramework)
1710
1688
  ? '1'
1711
1689
  : undefined,
1712
1690
  [DD_CAPABILITIES_EARLY_FLAKE_DETECTION]: isEarlyFlakeDetectionSupported(testFramework, frameworkVersion)
@@ -1866,6 +1844,17 @@ function getModifiedFilesFromDiff (diff) {
1866
1844
  * @typedef {Map<string, AttemptToFixExecutionResult>} AttemptToFixExecutions
1867
1845
  */
1868
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
+
1869
1858
  /**
1870
1859
  * Formats a test name for user-facing Test Optimization summaries.
1871
1860
  *
@@ -1877,6 +1866,79 @@ function formatTestOptimizationName (testSuite, testName) {
1877
1866
  return testSuite ? `${testSuite} › ${testName}` : testName
1878
1867
  }
1879
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
+
1880
1942
  /**
1881
1943
  * Renders a bounded bullet list for Test Optimization summaries.
1882
1944
  *
@@ -1888,7 +1950,12 @@ function formatTestOptimizationList (items) {
1888
1950
  const more = items.length - shown.length
1889
1951
  const moreSuffix = more > 0 ? `\n ... and ${more} more` : ''
1890
1952
 
1891
- 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
1892
1959
  }
1893
1960
 
1894
1961
  /**
@@ -1901,15 +1968,67 @@ function formatTestOptimizationList (items) {
1901
1968
  function logAttemptToFixTestExecution (testSuite, testName, loggedAttemptToFixTests) {
1902
1969
  if (!testName) return
1903
1970
 
1904
- const name = formatTestOptimizationName(testSuite, testName)
1905
1971
  if (loggedAttemptToFixTests) {
1906
- if (loggedAttemptToFixTests.has(name)) return
1972
+ const identity = getTestOptimizationIdentity(testSuite, testName)
1973
+ if (loggedAttemptToFixTests.has(identity)) return
1907
1974
 
1908
- loggedAttemptToFixTests.add(name)
1975
+ loggedAttemptToFixTests.add(identity)
1909
1976
  }
1910
1977
 
1911
1978
  // eslint-disable-next-line no-console -- Intentional user-facing attempt-to-fix progress report
1912
- 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'
1913
2032
  }
1914
2033
 
1915
2034
  /**
@@ -1926,10 +2045,12 @@ function logAttemptToFixTestExecution (testSuite, testName, loggedAttemptToFixTe
1926
2045
  */
1927
2046
  function recordAttemptToFixExecution (attemptToFixExecutions, execution) {
1928
2047
  if (!execution?.testName) return
2048
+ if (getValueFromEnvSources('DD_TEST_MANAGEMENT_REPORT_ENABLED') === false) return
1929
2049
 
1930
2050
  const { testSuite, testName, status, isDisabled, isQuarantined } = execution
2051
+ const identity = getTestOptimizationIdentity(testSuite, testName)
1931
2052
  const name = formatTestOptimizationName(testSuite, testName)
1932
- let result = attemptToFixExecutions.get(name)
2053
+ let result = attemptToFixExecutions.get(identity)
1933
2054
 
1934
2055
  if (!result) {
1935
2056
  result = {
@@ -1939,7 +2060,7 @@ function recordAttemptToFixExecution (attemptToFixExecutions, execution) {
1939
2060
  isDisabled: false,
1940
2061
  isQuarantined: false,
1941
2062
  }
1942
- attemptToFixExecutions.set(name, result)
2063
+ attemptToFixExecutions.set(identity, result)
1943
2064
  }
1944
2065
 
1945
2066
  result.executions++
@@ -1975,6 +2096,27 @@ function collectAttemptToFixExecutionFromTraceSpan (span, attemptToFixExecutions
1975
2096
  })
1976
2097
  }
1977
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
+
1978
2120
  /**
1979
2121
  * Scans serialized worker trace payloads and populates Test Optimization summary data.
1980
2122
  * Silently ignores parse errors.
@@ -1982,11 +2124,16 @@ function collectAttemptToFixExecutionFromTraceSpan (span, attemptToFixExecutions
1982
2124
  * @param {string} data - JSON-serialized traces from a worker
1983
2125
  * @param {{
1984
2126
  * newTestsWithDynamicNames?: Set<string>,
1985
- * attemptToFixExecutions?: AttemptToFixExecutions
2127
+ * attemptToFixExecutions?: AttemptToFixExecutions,
2128
+ * testManagementExecutions?: TestManagementExecutions
1986
2129
  * }} summaries
1987
2130
  */
1988
2131
  function collectTestOptimizationSummariesFromTraces (data, summaries) {
1989
- const { newTestsWithDynamicNames, attemptToFixExecutions } = summaries
2132
+ const {
2133
+ newTestsWithDynamicNames,
2134
+ attemptToFixExecutions,
2135
+ testManagementExecutions: executions = testManagementExecutions,
2136
+ } = summaries
1990
2137
 
1991
2138
  try {
1992
2139
  const traces = JSON.parse(data)
@@ -1998,6 +2145,7 @@ function collectTestOptimizationSummariesFromTraces (data, summaries) {
1998
2145
  if (attemptToFixExecutions) {
1999
2146
  collectAttemptToFixExecutionFromTraceSpan(span, attemptToFixExecutions)
2000
2147
  }
2148
+ collectTestManagementExecutionFromTraceSpan(span, executions)
2001
2149
  }
2002
2150
  }
2003
2151
  } catch {
@@ -2044,7 +2192,10 @@ function hasAttemptToFixManagementNotes (result) {
2044
2192
  }
2045
2193
 
2046
2194
  function addAttemptToFixResultLine (lines, result) {
2047
- lines.push(` • ${result.name}`)
2195
+ lines.push(` • ${truncateTestOptimizationNameMiddle(
2196
+ sanitizeTestOptimizationName(result.name),
2197
+ MAX_TEST_OPTIMIZATION_NAME_LENGTH
2198
+ )}`)
2048
2199
 
2049
2200
  for (const note of getAttemptToFixManagementNotes(result)) {
2050
2201
  lines.push(` ${note}`)
@@ -2055,9 +2206,10 @@ function addAttemptToFixResultLine (lines, result) {
2055
2206
  * Formats the attempt-to-fix end-of-session summary.
2056
2207
  *
2057
2208
  * @param {AttemptToFixExecutions} attemptToFixExecutions
2209
+ * @param {boolean} [includeTestNames]
2058
2210
  * @returns {string}
2059
2211
  */
2060
- function formatAttemptToFixSummary (attemptToFixExecutions) {
2212
+ function formatAttemptToFixSummary (attemptToFixExecutions, includeTestNames = true) {
2061
2213
  if (attemptToFixExecutions.size === 0) return ''
2062
2214
 
2063
2215
  const results = [...attemptToFixExecutions.values()]
@@ -2069,9 +2221,11 @@ function formatAttemptToFixSummary (attemptToFixExecutions) {
2069
2221
  `Attempt to fix passed: all ${totalExecutions} execution(s) passed for ${results.length} test(s).`,
2070
2222
  ]
2071
2223
 
2072
- for (const result of results) {
2073
- if (hasAttemptToFixManagementNotes(result)) {
2074
- addAttemptToFixResultLine(lines, result)
2224
+ if (includeTestNames) {
2225
+ for (const result of results) {
2226
+ if (hasAttemptToFixManagementNotes(result)) {
2227
+ addAttemptToFixResultLine(lines, result)
2228
+ }
2075
2229
  }
2076
2230
  }
2077
2231
 
@@ -2087,13 +2241,67 @@ function formatAttemptToFixSummary (attemptToFixExecutions) {
2087
2241
  `across ${failedResults.length} of ${results.length} test(s).`,
2088
2242
  ]
2089
2243
 
2090
- for (const result of failedResults) {
2091
- addAttemptToFixResultLine(lines, result)
2092
- }
2093
- for (const result of results) {
2094
- if (result.failedCount === 0 && hasAttemptToFixManagementNotes(result)) {
2244
+ if (includeTestNames) {
2245
+ for (const result of failedResults) {
2095
2246
  addAttemptToFixResultLine(lines, result)
2096
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
+ }
2097
2305
  }
2098
2306
 
2099
2307
  return lines.join('\n')
@@ -2124,25 +2332,57 @@ function formatDynamicNamesSummary (newTestsWithDynamicNames) {
2124
2332
  *
2125
2333
  * @param {{
2126
2334
  * attemptToFixExecutions?: AttemptToFixExecutions,
2335
+ * testManagementExecutions?: TestManagementExecutions,
2127
2336
  * newTestsWithDynamicNames?: Set<string>,
2128
2337
  * extraSections?: string[]
2129
2338
  * }} summary
2130
2339
  */
2131
2340
  function logTestOptimizationSummary (summary) {
2132
- const { attemptToFixExecutions, newTestsWithDynamicNames, extraSections = [] } = summary
2341
+ const {
2342
+ attemptToFixExecutions,
2343
+ testManagementExecutions: executions = testManagementExecutions,
2344
+ newTestsWithDynamicNames,
2345
+ extraSections = [],
2346
+ } = summary
2133
2347
  const sections = []
2134
- const attemptToFixSummary = attemptToFixExecutions
2135
- ? 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)
2136
2357
  : ''
2137
2358
  const dynamicNamesSummary = newTestsWithDynamicNames
2138
2359
  ? formatDynamicNamesSummary(newTestsWithDynamicNames)
2139
2360
  : ''
2140
2361
 
2141
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
+ }
2142
2370
  sections.push(...extraSections.filter(Boolean))
2143
2371
  if (dynamicNamesSummary) sections.push(dynamicNamesSummary)
2144
2372
 
2145
- 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
+ }
2146
2386
 
2147
2387
  const line = '-'.repeat(50)
2148
2388
  // eslint-disable-next-line no-console -- Intentional user-facing session summary
@@ -2151,6 +2391,10 @@ function logTestOptimizationSummary (summary) {
2151
2391
  if (attemptToFixExecutions) {
2152
2392
  attemptToFixExecutions.clear()
2153
2393
  }
2394
+ executions.clear()
2395
+ if (executions !== testManagementExecutions) {
2396
+ testManagementExecutions.clear()
2397
+ }
2154
2398
  if (newTestsWithDynamicNames) {
2155
2399
  newTestsWithDynamicNames.clear()
2156
2400
  }
@@ -14,7 +14,7 @@ const INT_SEGMENT = /^[1-9][0-9]+$/ // Integer of size at least 2 (>=10)
14
14
  const INT_ID_SEGMENT = /^(?=.*[0-9])[0-9._-]{3,}$/ // Mixed string with digits and delimiters
15
15
  const HEX_SEGMENT = /^(?=.*[0-9])[A-Fa-f0-9]{6,}$/ // Hexadecimal digits of size at least 6 with at least one decimal digit
16
16
  const HEX_ID_SEGMENT = /^(?=.*[0-9])[A-Fa-f0-9._-]{6,}$/ // Mixed string with hex digits and delimiters
17
- const STRING_SEGMENT = /^.{20,}|[%&'()*+,:=@]/ // Long string or a string containing special characters
17
+ const STRING_SEGMENT = /(?:^.{20,}|[%&'()*+,:=@])/ // Long string or a string containing special characters
18
18
 
19
19
  /**
20
20
  * Extract full URL from HTTP request