dd-trace 6.10.0 → 6.12.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 (173) hide show
  1. package/LICENSE-3rdparty.csv +5 -0
  2. package/README.md +0 -17
  3. package/index.d.ts +128 -6
  4. package/initialize.mjs +28 -17
  5. package/openfeature.js +2 -2
  6. package/package.json +27 -23
  7. package/packages/datadog-esbuild/index.js +0 -34
  8. package/packages/datadog-instrumentations/src/anthropic.js +210 -15
  9. package/packages/datadog-instrumentations/src/aws-sdk.js +9 -1
  10. package/packages/datadog-instrumentations/src/browser-bunyan.js +11 -0
  11. package/packages/datadog-instrumentations/src/bunyan.js +3 -16
  12. package/packages/datadog-instrumentations/src/cucumber.js +30 -15
  13. package/packages/datadog-instrumentations/src/cypress-config.js +182 -16
  14. package/packages/datadog-instrumentations/src/cypress-legacy-finalizers.js +30 -0
  15. package/packages/datadog-instrumentations/src/cypress.js +2 -0
  16. package/packages/datadog-instrumentations/src/fastify.js +2 -0
  17. package/packages/datadog-instrumentations/src/helpers/bunyan.js +22 -0
  18. package/packages/datadog-instrumentations/src/helpers/hooks.js +2 -1
  19. package/packages/datadog-instrumentations/src/helpers/pool-acquire.js +9 -4
  20. package/packages/datadog-instrumentations/src/helpers/register.js +10 -8
  21. package/packages/datadog-instrumentations/src/jest.js +25 -17
  22. package/packages/datadog-instrumentations/src/mariadb.js +278 -21
  23. package/packages/datadog-instrumentations/src/mocha/main.js +539 -55
  24. package/packages/datadog-instrumentations/src/mocha/utils.js +135 -19
  25. package/packages/datadog-instrumentations/src/playwright-reporter.js +247 -0
  26. package/packages/datadog-instrumentations/src/playwright.js +182 -35
  27. package/packages/datadog-instrumentations/src/router.js +2 -0
  28. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +6 -8
  29. package/packages/datadog-instrumentations/src/vitest-main.js +11 -3
  30. package/packages/datadog-plugin-aws-sdk/src/base.js +1 -0
  31. package/packages/datadog-plugin-aws-sdk/src/services/eventbridge.js +163 -44
  32. package/packages/datadog-plugin-aws-sdk/src/services/kinesis.js +2 -8
  33. package/packages/datadog-plugin-azure-functions/src/index.js +1 -1
  34. package/packages/datadog-plugin-browser-bunyan/src/index.js +9 -0
  35. package/packages/datadog-plugin-bunyan/src/index.js +5 -8
  36. package/packages/datadog-plugin-cucumber/src/index.js +11 -0
  37. package/packages/datadog-plugin-cypress/src/after-run.js +5 -1
  38. package/packages/datadog-plugin-cypress/src/after-spec.js +5 -1
  39. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +81 -29
  40. package/packages/datadog-plugin-cypress/src/finalization.js +76 -0
  41. package/packages/datadog-plugin-cypress/src/index.js +12 -6
  42. package/packages/datadog-plugin-cypress/src/plugin.js +16 -3
  43. package/packages/datadog-plugin-dd-trace-api/src/index.js +4 -0
  44. package/packages/datadog-plugin-electron/src/index.js +2 -0
  45. package/packages/datadog-plugin-fs/src/index.js +1 -1
  46. package/packages/datadog-plugin-jest/src/index.js +6 -0
  47. package/packages/datadog-plugin-mocha/src/index.js +56 -1
  48. package/packages/datadog-plugin-nats/src/index.js +1 -1
  49. package/packages/datadog-plugin-openai/src/tracing.js +41 -30
  50. package/packages/datadog-plugin-openai-agents/src/integration.js +1 -0
  51. package/packages/datadog-plugin-playwright/src/index.js +46 -13
  52. package/packages/datadog-plugin-vitest/src/index.js +6 -0
  53. package/packages/datadog-webpack/index.js +0 -11
  54. package/packages/dd-trace/index.js +0 -1
  55. package/packages/dd-trace/src/agent/info.js +6 -5
  56. package/packages/dd-trace/src/aiguard/client.js +100 -1
  57. package/packages/dd-trace/src/aiguard/errors.js +41 -0
  58. package/packages/dd-trace/src/aiguard/evaluation.js +357 -0
  59. package/packages/dd-trace/src/aiguard/integrations/anthropic.js +70 -0
  60. package/packages/dd-trace/src/aiguard/integrations/index.js +4 -0
  61. package/packages/dd-trace/src/aiguard/messages/anthropic.js +423 -0
  62. package/packages/dd-trace/src/aiguard/messages/openai.js +1 -24
  63. package/packages/dd-trace/src/aiguard/messages/utils.js +28 -0
  64. package/packages/dd-trace/src/aiguard/messages/vercel-ai.js +1 -11
  65. package/packages/dd-trace/src/aiguard/sdk.js +22 -278
  66. package/packages/dd-trace/src/appsec/iast/taint-tracking/rewriter.js +9 -12
  67. package/packages/dd-trace/src/appsec/reporter.js +2 -0
  68. package/packages/dd-trace/src/appsec/telemetry/index.js +13 -0
  69. package/packages/dd-trace/src/appsec/telemetry/rasp.js +16 -0
  70. package/packages/dd-trace/src/appsec/telemetry/waf.js +24 -0
  71. package/packages/dd-trace/src/carrier.js +107 -48
  72. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +1 -0
  73. package/packages/dd-trace/src/ci-visibility/early-flake-detection/get-known-tests.js +5 -5
  74. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +29 -3
  75. package/packages/dd-trace/src/ci-visibility/exporters/agentless/coverage-writer.js +27 -9
  76. package/packages/dd-trace/src/ci-visibility/exporters/agentless/di-logs-writer.js +21 -5
  77. package/packages/dd-trace/src/ci-visibility/exporters/agentless/request-tracker.js +164 -0
  78. package/packages/dd-trace/src/ci-visibility/exporters/agentless/writer.js +26 -8
  79. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +365 -32
  80. package/packages/dd-trace/src/ci-visibility/exporters/git/git_metadata.js +6 -4
  81. package/packages/dd-trace/src/ci-visibility/exporters/request.js +201 -0
  82. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/index.js +5 -3
  83. package/packages/dd-trace/src/ci-visibility/exporters/test-worker/writer.js +9 -5
  84. package/packages/dd-trace/src/ci-visibility/intelligent-test-runner/get-skippable-suites.js +7 -4
  85. package/packages/dd-trace/src/ci-visibility/requests/get-library-configuration.js +4 -2
  86. package/packages/dd-trace/src/ci-visibility/requests/rate-limit.js +39 -0
  87. package/packages/dd-trace/src/ci-visibility/requests/request.js +3 -6
  88. package/packages/dd-trace/src/ci-visibility/requests/upload-coverage-report.js +4 -2
  89. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +10 -4
  90. package/packages/dd-trace/src/ci-visibility/test-management/get-test-management-tests.js +5 -5
  91. package/packages/dd-trace/src/config/generated-config-types.d.ts +4 -0
  92. package/packages/dd-trace/src/config/index.js +13 -2
  93. package/packages/dd-trace/src/config/major-overrides.js +7 -0
  94. package/packages/dd-trace/src/config/supported-configurations.json +14 -0
  95. package/packages/dd-trace/src/datastreams/index.js +2 -0
  96. package/packages/dd-trace/src/datastreams/manager.js +9 -2
  97. package/packages/dd-trace/src/datastreams/processor.js +15 -13
  98. package/packages/dd-trace/src/datastreams/size.js +5 -0
  99. package/packages/dd-trace/src/debugger/constants.js +1 -0
  100. package/packages/dd-trace/src/debugger/devtools_client/condition.js +1 -1
  101. package/packages/dd-trace/src/debugger/devtools_client/index.js +5 -11
  102. package/packages/dd-trace/src/debugger/index.js +4 -2
  103. package/packages/dd-trace/src/debugger/inspect-segment.js +154 -0
  104. package/packages/dd-trace/src/evp_proxy/constants.js +8 -0
  105. package/packages/dd-trace/src/evp_proxy/direct.js +64 -0
  106. package/packages/dd-trace/src/evp_proxy/discovery.js +133 -0
  107. package/packages/dd-trace/src/evp_proxy/path.js +22 -0
  108. package/packages/dd-trace/src/exporters/agent/writer.js +49 -9
  109. package/packages/dd-trace/src/exporters/common/buffering-exporter.js +6 -4
  110. package/packages/dd-trace/src/exporters/common/request.js +5 -2
  111. package/packages/dd-trace/src/exporters/common/writer.js +11 -6
  112. package/packages/dd-trace/src/llmobs/experiments/client.js +167 -5
  113. package/packages/dd-trace/src/llmobs/experiments/dataset.js +484 -92
  114. package/packages/dd-trace/src/llmobs/experiments/experiment.js +302 -29
  115. package/packages/dd-trace/src/llmobs/experiments/index.js +75 -64
  116. package/packages/dd-trace/src/llmobs/experiments/noop.js +135 -7
  117. package/packages/dd-trace/src/llmobs/experiments/result.js +1 -0
  118. package/packages/dd-trace/src/llmobs/experiments/util.js +101 -9
  119. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +12 -3
  120. package/packages/dd-trace/src/llmobs/plugins/base.js +4 -2
  121. package/packages/dd-trace/src/noop/proxy.js +3 -4
  122. package/packages/dd-trace/src/openfeature/constants/constants.js +0 -18
  123. package/packages/dd-trace/src/openfeature/flagging_provider.js +18 -2
  124. package/packages/dd-trace/src/openfeature/index.js +7 -4
  125. package/packages/dd-trace/src/openfeature/remote_config.js +1 -1
  126. package/packages/dd-trace/src/openfeature/writers/base.js +153 -11
  127. package/packages/dd-trace/src/openfeature/writers/exposures.js +62 -14
  128. package/packages/dd-trace/src/openfeature/writers/util.js +97 -15
  129. package/packages/dd-trace/src/opentelemetry/context_manager.js +7 -7
  130. package/packages/dd-trace/src/opentelemetry/logs/batch_log_processor.js +2 -1
  131. package/packages/dd-trace/src/opentelemetry/logs/logger.js +67 -11
  132. package/packages/dd-trace/src/opentelemetry/logs/otlp_transformer.js +14 -3
  133. package/packages/dd-trace/src/opentelemetry/metrics/index.js +25 -12
  134. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_exporter.js +2 -5
  135. package/packages/dd-trace/src/opentelemetry/metrics/otlp_span_stats_transformer.js +96 -43
  136. package/packages/dd-trace/src/opentracing/propagation/text_map.js +22 -13
  137. package/packages/dd-trace/src/opentracing/tracer.js +5 -2
  138. package/packages/dd-trace/src/otel-thread-ctx.js +387 -0
  139. package/packages/dd-trace/src/plugin_manager.js +3 -3
  140. package/packages/dd-trace/src/plugins/ci_plugin.js +6 -1
  141. package/packages/dd-trace/src/plugins/index.js +1 -0
  142. package/packages/dd-trace/src/plugins/util/jest.js +4 -3
  143. package/packages/dd-trace/src/plugins/util/test.js +9 -9
  144. package/packages/dd-trace/src/plugins/util/web.js +3 -0
  145. package/packages/dd-trace/src/profiling/profiler.js +1 -1
  146. package/packages/dd-trace/src/profiling/profilers/space.js +1 -3
  147. package/packages/dd-trace/src/profiling/profilers/wall.js +37 -90
  148. package/packages/dd-trace/src/profiling/webspan-utils.js +45 -0
  149. package/packages/dd-trace/src/proxy.js +38 -29
  150. package/packages/dd-trace/src/ritm.js +9 -2
  151. package/packages/dd-trace/src/serverless.js +37 -0
  152. package/packages/dd-trace/src/span_format.js +1 -0
  153. package/packages/dd-trace/src/span_stats.js +30 -3
  154. package/packages/dd-trace/src/storage-channels.js +86 -0
  155. package/packages/dd-trace/src/telemetry/metrics.js +62 -8
  156. package/packages/dd-trace/src/telemetry/send-data.js +1 -0
  157. package/packages/dd-trace/src/tracer.js +9 -2
  158. package/packages/dd-trace/src/tracer_metadata.js +14 -1
  159. package/packages/dd-trace/src/web-tags-cache.js +132 -0
  160. package/vendor/dist/@datadog/openfeature-node-server/LICENSE +201 -0
  161. package/vendor/dist/@datadog/openfeature-node-server/index.js +1 -0
  162. package/vendor/dist/@datadog/sketches-js/index.js +1 -1
  163. package/vendor/dist/https-proxy-agent/LICENSE +22 -0
  164. package/vendor/dist/https-proxy-agent/index.js +4 -0
  165. package/vendor/dist/protobufjs/index.js +1 -1
  166. package/vendor/dist/protobufjs/minimal/index.js +1 -1
  167. package/vendor/dist/proxy-from-env/LICENSE +20 -0
  168. package/vendor/dist/proxy-from-env/index.js +1 -0
  169. package/packages/datadog-instrumentations/src/helpers/optional-peer-bundler.js +0 -65
  170. package/packages/datadog-webpack/src/optional-peer-loader.js +0 -17
  171. package/packages/dd-trace/src/feature-registry.js +0 -29
  172. package/packages/dd-trace/src/openfeature/register.js +0 -35
  173. package/packages/dd-trace/src/openfeature/require-provider.js +0 -18
@@ -35,9 +35,13 @@ const testSuiteErrorCh = channel('ci:mocha:test-suite:error')
35
35
  /** @typedef {{ length: number, [index: number]: unknown } & Iterable<unknown>} ArgumentsLike */
36
36
 
37
37
  const testToContext = new WeakMap()
38
+ const originalEfdFns = new WeakMap()
38
39
  const originalFns = new WeakMap()
40
+ const originalPendingByTest = new WeakMap()
41
+ const originalRetriesByTest = new WeakMap()
39
42
  const testToStartLine = new WeakMap()
40
43
  const testFileToSuiteCtx = new Map()
44
+ const datadogRetryOriginals = new WeakMap()
41
45
  const wrappedFunctions = new WeakSet()
42
46
  const newTests = {}
43
47
  const efdTests = {}
@@ -169,6 +173,7 @@ function wrapOriginalEfdTest (test, retryPolicy) {
169
173
  }
170
174
  test._ddEfdDurationWrapped = true
171
175
  const originalFn = test.fn
176
+ originalEfdFns.set(test, originalFn)
172
177
  test.fn = shimmer.wrapFunction(originalFn, originalFn => function () {
173
178
  const start = performance.now()
174
179
  const recordDuration = () => {
@@ -265,6 +270,7 @@ function isEarlyFlakeDetectionTest (test, config) {
265
270
  function retryTest (test, numRetries, tags, retryPolicy) {
266
271
  const suite = test.parent
267
272
  const isEfdRetry = tags.includes('_ddIsEfdRetry')
273
+ if (!originalRetriesByTest.has(test)) originalRetriesByTest.set(test, test.retries())
268
274
  disableMochaRetries(test)
269
275
  if (isEfdRetry) {
270
276
  wrapOriginalEfdTest(test, retryPolicy)
@@ -272,6 +278,7 @@ function retryTest (test, numRetries, tags, retryPolicy) {
272
278
  for (let retryIndex = 0; retryIndex < numRetries; retryIndex++) {
273
279
  const clonedTest = test.clone()
274
280
  disableMochaRetries(clonedTest)
281
+ datadogRetryOriginals.set(clonedTest, test)
275
282
  suite.addTest(clonedTest)
276
283
  if (isEfdRetry) {
277
284
  clonedTest._ddEfdRetryIndex = retryIndex + 1
@@ -292,6 +299,94 @@ function retryTest (test, numRetries, tags, retryPolicy) {
292
299
  clonedTest[tag] = true
293
300
  }
294
301
  }
302
+ if (clonedTest._ddIsQuarantined && !clonedTest._ddIsAttemptToFix) {
303
+ testsQuarantined.add(clonedTest)
304
+ }
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Restores a runnable function wrapped with its Test Optimization context.
310
+ *
311
+ * @param {import('mocha').Runnable} runnable
312
+ * @returns {void}
313
+ */
314
+ function restoreRunnableFunction (runnable) {
315
+ const wrappedFunction = runnable.fn
316
+ if (!wrappedFunctions.has(wrappedFunction)) return
317
+
318
+ runnable.fn = originalFns.get(wrappedFunction)
319
+ }
320
+
321
+ /**
322
+ * Restores a test function wrapped to measure its EFD duration.
323
+ *
324
+ * @param {import('mocha').Test} test
325
+ * @returns {void}
326
+ */
327
+ function restoreEfdTestFunction (test) {
328
+ if (!originalEfdFns.has(test)) return
329
+
330
+ test.fn = originalEfdFns.get(test)
331
+ originalEfdFns.delete(test)
332
+ delete test._ddEfdDurationWrapped
333
+ }
334
+
335
+ /**
336
+ * Clears state that belongs to one Mocha runner execution and removes retry clones from prior executions.
337
+ *
338
+ * @param {import('mocha').Suite} rootSuite
339
+ * @returns {void}
340
+ */
341
+ function resetRunState (rootSuite) {
342
+ for (const key of Object.keys(newTests)) delete newTests[key]
343
+ for (const key of Object.keys(efdTests)) delete efdTests[key]
344
+ newTestsWithDynamicNames.clear()
345
+ testsAttemptToFix.clear()
346
+ testsQuarantined.clear()
347
+ testsStatuses.clear()
348
+ efdRetryCountByTestFullName.clear()
349
+ efdSlowAbortedTests.clear()
350
+ attemptToFixExecutions.clear()
351
+ loggedAttemptToFixTests.clear()
352
+
353
+ const suites = [rootSuite]
354
+ while (suites.length) {
355
+ const suite = suites.pop()
356
+ const originalTests = []
357
+ const retainedTests = new Set()
358
+ for (const runnable of suite.tests) {
359
+ const test = datadogRetryOriginals.get(runnable) || runnable
360
+ if (retainedTests.has(test)) continue
361
+ retainedTests.add(test)
362
+
363
+ restoreRunnableFunction(test)
364
+ restoreEfdTestFunction(test)
365
+ if (originalPendingByTest.has(test)) {
366
+ test.pending = originalPendingByTest.get(test)
367
+ originalPendingByTest.delete(test)
368
+ }
369
+ if (originalRetriesByTest.has(test)) {
370
+ test.retries(originalRetriesByTest.get(test))
371
+ originalRetriesByTest.delete(test)
372
+ }
373
+
374
+ delete test._ddIsAttemptToFix
375
+ delete test._ddIsDisabled
376
+ delete test._ddIsQuarantined
377
+ delete test._ddIsModified
378
+ delete test._ddIsNew
379
+ delete test._ddShouldSkipEfdRetry
380
+ delete test._ddTestFinishStarted
381
+ delete test._ddTestFinishPublished
382
+ delete test._ddIsFinalAttempt
383
+ delete test._ddHookFailed
384
+ delete test._ddReporterStartFailed
385
+ delete test._ddReporterTerminalFailed
386
+ originalTests.push(test)
387
+ }
388
+ suite.tests = originalTests
389
+ suites.push(...suite.suites)
295
390
  }
296
391
  }
297
392
 
@@ -371,6 +466,19 @@ function getTestContext (test) {
371
466
  return testToContext.get(key)
372
467
  }
373
468
 
469
+ /**
470
+ * Claims publication of a test finish event across Mocha's terminal event paths.
471
+ *
472
+ * @param {object} test
473
+ * @returns {boolean}
474
+ */
475
+ function startTestFinish (test) {
476
+ if (test._ddTestFinishStarted || test._ddTestFinishPublished) return false
477
+
478
+ test._ddTestFinishStarted = true
479
+ return true
480
+ }
481
+
374
482
  /**
375
483
  * Copies Test Management metadata from Mocha's original runnable to its native retry clone.
376
484
  * @param {{
@@ -413,11 +521,7 @@ function runnableWrapper (RunnablePackage, libraryConfig) {
413
521
  const test = isTestHook ? this.ctx.currentTest : this
414
522
 
415
523
  // we restore the original user defined function
416
- if (wrappedFunctions.has(this.fn)) {
417
- const originalFn = originalFns.get(this.fn)
418
- this.fn = originalFn
419
- wrappedFunctions.delete(this.fn)
420
- }
524
+ restoreRunnableFunction(this)
421
525
 
422
526
  if (isDatadogManagedRetryTest(test, libraryConfig)) {
423
527
  disableMochaRetries(this)
@@ -460,11 +564,7 @@ function getOnTestHandler (isMain) {
460
564
 
461
565
  // This may be a retry. If this is the case, `test.fn` is already wrapped,
462
566
  // so we need to restore it.
463
- if (wrappedFunctions.has(test.fn)) {
464
- const originalFn = originalFns.get(test.fn)
465
- test.fn = originalFn
466
- wrappedFunctions.delete(test.fn)
467
- }
567
+ restoreRunnableFunction(test)
468
568
 
469
569
  inheritDatadogPropertiesFromRetriedTest(test)
470
570
 
@@ -527,6 +627,9 @@ function getOnTestHandler (isMain) {
527
627
  }
528
628
 
529
629
  if (!isAttemptToFix && isDisabled) {
630
+ if (!originalPendingByTest.has(test)) {
631
+ originalPendingByTest.set(test, test.pending)
632
+ }
530
633
  test.pending = true
531
634
  }
532
635
 
@@ -701,9 +804,16 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
701
804
  if (test._ddShouldSkipEfdRetry) {
702
805
  return
703
806
  }
807
+ const shouldWaitForHitProbe = test._retriedTest?._ddShouldWaitForHitProbe
704
808
  const ctx = getTestContext(test)
705
809
  const status = getTestStatus(test)
706
- const shouldFinishTest = ctx && (!getAfterEachHooks(test).length || (test._ddIsDisabled && !test._ddIsAttemptToFix))
810
+ const shouldFinishTest = ctx && (
811
+ test._ddReporterStartFailed ||
812
+ test._ddReporterTerminalFailed ||
813
+ !getAfterEachHooks(test).length ||
814
+ (test._ddIsDisabled && !test._ddIsAttemptToFix)
815
+ )
816
+ if (shouldFinishTest && !startTestFinish(test)) return
707
817
  let testFinishInfo
708
818
  let isFinalAttempt = false
709
819
 
@@ -729,11 +839,12 @@ function getOnTestEndHandler (config, finalAttemptHandlers) {
729
839
  finalAttemptHandlers?.onStart?.(test)
730
840
  }
731
841
 
732
- if (test._retriedTest?._ddShouldWaitForHitProbe) {
842
+ if (shouldWaitForHitProbe) {
733
843
  await waitForHitProbe()
734
844
  }
735
845
 
736
846
  if (shouldFinishTest) {
847
+ test._ddTestFinishPublished = true
737
848
  testFinishCh.publish({
738
849
  status,
739
850
  hasBeenRetried: isMochaRetry(test),
@@ -760,10 +871,11 @@ function getOnHookEndHandler (config, finalAttemptHandlers) {
760
871
  const ctx = getTestContext(test)
761
872
  // Disabled tests are already finished in getOnTestEndHandler,
762
873
  // skip to avoid double-publishing
763
- if (ctx && (!test._ddIsDisabled || test._ddIsAttemptToFix)) {
874
+ if (ctx && (!test._ddIsDisabled || test._ddIsAttemptToFix) && startTestFinish(test)) {
764
875
  const testFinishInfo = getTestFinishInfo(test, status, config, ctx.err || test.err)
765
876
  const isFinalAttempt = testFinishInfo.finalStatus !== undefined
766
877
  const publishTestFinish = () => {
878
+ test._ddTestFinishPublished = true
767
879
  testFinishCh.publish({
768
880
  status,
769
881
  hasBeenRetried: isMochaRetry(test),
@@ -887,11 +999,13 @@ function getOnFailHandler (isMain, config) {
887
999
  testContext = getTestContext(test)
888
1000
  }
889
1001
  if (testContext) {
890
- if (isHook) {
891
- err.message = `${testOrHook.fullTitle()}: ${err.message}`
892
- testContext.err = err
1002
+ if (isHook && startTestFinish(test)) {
1003
+ const hookError = new Error(`${testOrHook.fullTitle()}: ${err.message}`, { cause: err })
1004
+ hookError.name = err.name
1005
+ hookError.stack = err.stack
1006
+ testContext.err = hookError
893
1007
  errorCh.runStores(testContext, () => {})
894
- const testFinishInfo = getTestFinishInfo(test, 'fail', config, err)
1008
+ const testFinishInfo = getTestFinishInfo(test, 'fail', config, hookError)
895
1009
  // ATR never retries hook failures: this.retries(N) is set in runnableWrapper
896
1010
  // which only runs when the test function executes — hooks bypass that path,
897
1011
  // so _retries stays at -1 and getIsLastRetry returns false, leaving finalStatus
@@ -906,6 +1020,7 @@ function getOnFailHandler (isMain, config) {
906
1020
  // test.state is never set to 'failed' for hook failures (Mocha marks the hook,
907
1021
  // not the test). Flag it so finishRootSuiteForFile can compute the correct status.
908
1022
  test._ddHookFailed = true
1023
+ test._ddTestFinishPublished = true
909
1024
  testFinishCh.publish({
910
1025
  status: 'fail',
911
1026
  hasBeenRetried: isMochaRetry(test),
@@ -1034,7 +1149,7 @@ function getRunTestsWrapper (runTests, config) {
1034
1149
  retryTest(
1035
1150
  test,
1036
1151
  config.earlyFlakeDetectionRetryPolicy.schedulingRetryCount,
1037
- ['_ddIsModified', '_ddIsEfdRetry'],
1152
+ ['_ddIsModified', '_ddIsEfdRetry', test._ddIsQuarantined && '_ddIsQuarantined'],
1038
1153
  config.earlyFlakeDetectionRetryPolicy
1039
1154
  )
1040
1155
  }
@@ -1053,7 +1168,7 @@ function getRunTestsWrapper (runTests, config) {
1053
1168
  retryTest(
1054
1169
  test,
1055
1170
  config.earlyFlakeDetectionRetryPolicy.schedulingRetryCount,
1056
- ['_ddIsNew', '_ddIsEfdRetry'],
1171
+ ['_ddIsNew', '_ddIsEfdRetry', test._ddIsQuarantined && '_ddIsQuarantined'],
1057
1172
  config.earlyFlakeDetectionRetryPolicy
1058
1173
  )
1059
1174
  }
@@ -1088,6 +1203,7 @@ module.exports = {
1088
1203
  getOnPendingHandler,
1089
1204
  testFileToSuiteCtx,
1090
1205
  getRunTestsWrapper,
1206
+ resetRunState,
1091
1207
  newTests,
1092
1208
  efdTests,
1093
1209
  newTestsWithDynamicNames,
@@ -0,0 +1,247 @@
1
+ 'use strict'
2
+
3
+ const { channel } = require('dc-polyfill')
4
+
5
+ const reporterErrorCh = channel('ci:playwright:reporter:error')
6
+ const reporterRunSummaryCh = channel('ci:playwright:reporter:run-summary')
7
+ const reporterSuiteHookErrorCh = channel('ci:playwright:reporter:suite-hook-error')
8
+ const suitesWithHookErrors = new Set()
9
+ const PLAYWRIGHT_REPORTER_ERROR_MESSAGE = 'Error in reporter'
10
+ const PLAYWRIGHT_REPORTER_ERROR_CALLER_RE =
11
+ /^\s*at wrapAsync .*?[\\/]playwright[\\/]lib[\\/]runner[\\/]index\.js:\d+:\d+\)?$/
12
+
13
+ /**
14
+ * Identifies the private Playwright 1.60-1.61 reporter error fallback without
15
+ * interpreting identical user console output as a framework error.
16
+ *
17
+ * @param {unknown} message - First console.error argument
18
+ * @returns {boolean}
19
+ */
20
+ function isPlaywrightReporterError (message) {
21
+ if (message !== PLAYWRIGHT_REPORTER_ERROR_MESSAGE) return false
22
+
23
+ const originalPrepareStackTrace = Error.prepareStackTrace
24
+ try {
25
+ Error.prepareStackTrace = (_, callSites) => callSites
26
+ const stack = new Error('Playwright reporter error provenance').stack
27
+ return PLAYWRIGHT_REPORTER_ERROR_CALLER_RE.test(`at ${stack?.[2]?.toString() || ''}`)
28
+ } finally {
29
+ Error.prepareStackTrace = originalPrepareStackTrace
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Returns whether a finalized Playwright result contains a failed suite hook.
35
+ *
36
+ * @param {Array<object>} steps
37
+ * @returns {boolean}
38
+ */
39
+ function hasFailedSuiteHook (steps) {
40
+ if (!steps) return false
41
+
42
+ for (const step of steps) {
43
+ if (step.error && (step.title === 'beforeAll hook' || step.title === 'afterAll hook')) return true
44
+ if (hasFailedSuiteHook(step.steps)) return true
45
+ }
46
+ return false
47
+ }
48
+
49
+ class DatadogPlaywrightReporter {
50
+ /**
51
+ * Creates a reporter that observes the finalized Playwright result independently of user reporters.
52
+ *
53
+ * @param {object} [options]
54
+ * @param {boolean} [options.captureReporterErrors]
55
+ */
56
+ constructor ({ captureReporterErrors = true } = {}) {
57
+ this.captureReporterErrors = captureReporterErrors
58
+ this.fatalErrorCount = 0
59
+ }
60
+
61
+ /**
62
+ * Identifies this reporter as implementing Playwright's reporter v2 contract.
63
+ *
64
+ * @returns {'v2'}
65
+ */
66
+ version () {
67
+ return 'v2'
68
+ }
69
+
70
+ /**
71
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
72
+ *
73
+ * @param {object} config
74
+ * @returns {void}
75
+ */
76
+ onConfigure (config) {
77
+ this.failOnFlakyTests = config.failOnFlakyTests
78
+ }
79
+
80
+ /**
81
+ * Restores console error after Playwright completes the reporter lifecycle.
82
+ *
83
+ * @returns {void}
84
+ */
85
+ static restoreConsoleError () {
86
+ // eslint-disable-next-line no-console
87
+ if (console.error === DatadogPlaywrightReporter.consoleError) {
88
+ try {
89
+ // eslint-disable-next-line no-console
90
+ console.error = DatadogPlaywrightReporter.originalConsoleError
91
+ } catch {}
92
+ }
93
+ DatadogPlaywrightReporter.consoleError = undefined
94
+ DatadogPlaywrightReporter.originalConsoleError = undefined
95
+ }
96
+
97
+ /**
98
+ * Records the suite so finalized test outcomes can be counted in onEnd.
99
+ *
100
+ * @param {object} configOrSuite
101
+ * @param {object} [suite]
102
+ * @returns {void}
103
+ */
104
+ onBegin (configOrSuite, suite) {
105
+ this.suite = suite || configOrSuite
106
+ if (suite) this.failOnFlakyTests = configOrSuite.failOnFlakyTests
107
+ suitesWithHookErrors.clear()
108
+ }
109
+
110
+ /**
111
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
112
+ *
113
+ * @returns {void}
114
+ */
115
+ onTestBegin () {}
116
+
117
+ /**
118
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
119
+ *
120
+ * @returns {void}
121
+ */
122
+ onStdOut () {}
123
+
124
+ /**
125
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
126
+ *
127
+ * @returns {void}
128
+ */
129
+ onStdErr () {}
130
+
131
+ /**
132
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
133
+ *
134
+ * @returns {void}
135
+ */
136
+ onTestEnd () {}
137
+
138
+ /**
139
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
140
+ *
141
+ * @returns {void}
142
+ */
143
+ onStepBegin () {}
144
+
145
+ /**
146
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
147
+ *
148
+ * @returns {void}
149
+ */
150
+ onStepEnd () {}
151
+
152
+ /**
153
+ * Marks the beginning of reporter finalization so later reporter errors can be identified.
154
+ *
155
+ * @returns {void}
156
+ */
157
+ onEnd () {
158
+ let failureCount = this.fatalErrorCount
159
+ let quarantinedFailureCount = 0
160
+ let hasIncompleteTests = suitesWithHookErrors.size > 0
161
+ const tests = this.suite?.allTests?.()
162
+ if (tests) {
163
+ for (const test of tests) {
164
+ const outcome = test.outcome()
165
+ const finalResult = test.results.at(-1)
166
+ hasIncompleteTests ||= hasFailedSuiteHook(finalResult?.steps)
167
+ if (outcome === 'unexpected' || (outcome === 'flaky' && this.failOnFlakyTests)) {
168
+ failureCount += 1
169
+ const finalResultStatus = finalResult?.status
170
+ const hasFailed = finalResultStatus === 'failed' || finalResultStatus === 'timedOut'
171
+ if (outcome === 'unexpected' && hasFailed && test._ddIsQuarantined && !test._ddIsAttemptToFix) {
172
+ quarantinedFailureCount += 1
173
+ }
174
+ } else if (outcome === 'skipped' && !test._ddShouldSkipEfdRetry &&
175
+ (!test._ddIsDisabled || test._ddIsAttemptToFix)) {
176
+ const { results } = test
177
+ hasIncompleteTests ||= results.some(result => result.status === 'interrupted') ||
178
+ !results.length || test.expectedStatus !== 'skipped'
179
+ }
180
+ }
181
+ }
182
+ reporterRunSummaryCh.publish({ failureCount, quarantinedFailureCount, hasIncompleteTests })
183
+ suitesWithHookErrors.clear()
184
+
185
+ if (!this.captureReporterErrors) return
186
+
187
+ this.isFinalizing = true
188
+ // Playwright 1.60 and 1.61 only expose reporter errors through this exact console call.
189
+ // eslint-disable-next-line no-console
190
+ const originalConsoleError = console.error
191
+ const reporter = this
192
+ const consoleError = function (message, error) {
193
+ if (isPlaywrightReporterError(message)) {
194
+ reporter.onError(error)
195
+ }
196
+ return originalConsoleError.apply(this, arguments)
197
+ }
198
+ try {
199
+ // eslint-disable-next-line no-console
200
+ console.error = consoleError
201
+ } catch {
202
+ return
203
+ }
204
+ // An accessor may ignore the assignment without throwing.
205
+ // eslint-disable-next-line no-console
206
+ if (console.error !== consoleError) return
207
+
208
+ DatadogPlaywrightReporter.originalConsoleError = originalConsoleError
209
+ DatadogPlaywrightReporter.consoleError = consoleError
210
+ }
211
+
212
+ /**
213
+ * Implements the reporter v2 lifecycle hook required by older Playwright versions.
214
+ *
215
+ * @returns {void}
216
+ */
217
+ onExit () {}
218
+
219
+ /**
220
+ * Reports errors emitted by Playwright while later reporters are finalizing.
221
+ *
222
+ * @param {unknown} error
223
+ * @returns {void}
224
+ */
225
+ onError (error) {
226
+ if (this.isFinalizing) {
227
+ reporterErrorCh.publish(error)
228
+ } else {
229
+ this.fatalErrorCount += 1
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Keeps the internal reporter from affecting Playwright's output reporter selection.
235
+ *
236
+ * @returns {boolean}
237
+ */
238
+ printsToStdio () {
239
+ return false
240
+ }
241
+ }
242
+
243
+ reporterSuiteHookErrorCh.subscribe((testSuiteAbsolutePath) => {
244
+ suitesWithHookErrors.add(testSuiteAbsolutePath)
245
+ })
246
+
247
+ module.exports = DatadogPlaywrightReporter