dd-trace 6.0.0 → 6.1.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 (77) hide show
  1. package/LICENSE-3rdparty.csv +1 -2
  2. package/index.d.ts +21 -1
  3. package/package.json +32 -29
  4. package/packages/datadog-instrumentations/src/ai.js +45 -0
  5. package/packages/datadog-instrumentations/src/apollo-server.js +5 -13
  6. package/packages/datadog-instrumentations/src/child_process.js +3 -3
  7. package/packages/datadog-instrumentations/src/claude-agent-sdk.js +587 -0
  8. package/packages/datadog-instrumentations/src/cucumber.js +102 -43
  9. package/packages/datadog-instrumentations/src/cypress-config.js +11 -3
  10. package/packages/datadog-instrumentations/src/fastify.js +27 -8
  11. package/packages/datadog-instrumentations/src/helpers/hooks.js +1 -0
  12. package/packages/datadog-instrumentations/src/helpers/instrument.js +7 -0
  13. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/ai.js +6 -6
  14. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/claude-agent-sdk.js +17 -0
  15. package/packages/datadog-instrumentations/src/helpers/rewriter/instrumentations/index.js +1 -0
  16. package/packages/datadog-instrumentations/src/jest.js +713 -63
  17. package/packages/datadog-instrumentations/src/mocha/main.js +24 -3
  18. package/packages/datadog-instrumentations/src/mocha/utils.js +128 -22
  19. package/packages/datadog-instrumentations/src/mocha/worker.js +13 -0
  20. package/packages/datadog-instrumentations/src/mongodb.js +74 -0
  21. package/packages/datadog-instrumentations/src/mongoose.js +4 -5
  22. package/packages/datadog-instrumentations/src/playwright.js +13 -9
  23. package/packages/datadog-instrumentations/src/vitest-main-no-worker-init.js +4 -0
  24. package/packages/datadog-instrumentations/src/vitest-main.js +28 -1
  25. package/packages/datadog-instrumentations/src/vitest-util.js +5 -0
  26. package/packages/datadog-instrumentations/src/vitest-worker.js +104 -34
  27. package/packages/datadog-plugin-ai/src/index.js +1 -1
  28. package/packages/datadog-plugin-ai/src/tracing.js +66 -3
  29. package/packages/datadog-plugin-ai/src/utils.js +17 -4
  30. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/context.js +1 -0
  31. package/packages/datadog-plugin-aws-durable-execution-sdk-js/src/handler.js +1 -0
  32. package/packages/datadog-plugin-child_process/src/index.js +13 -2
  33. package/packages/datadog-plugin-claude-agent-sdk/src/index.js +31 -0
  34. package/packages/datadog-plugin-claude-agent-sdk/src/tracing.js +107 -0
  35. package/packages/datadog-plugin-claude-agent-sdk/src/util.js +15 -0
  36. package/packages/datadog-plugin-cucumber/src/index.js +14 -23
  37. package/packages/datadog-plugin-cypress/src/cypress-plugin.js +351 -11
  38. package/packages/datadog-plugin-cypress/src/index.js +47 -2
  39. package/packages/datadog-plugin-cypress/src/plugin.js +1 -0
  40. package/packages/datadog-plugin-graphql/src/execute.js +21 -10
  41. package/packages/datadog-plugin-graphql/src/index.js +17 -1
  42. package/packages/datadog-plugin-graphql/src/utils.js +9 -2
  43. package/packages/datadog-plugin-graphql/src/validate.js +1 -1
  44. package/packages/datadog-plugin-jest/src/index.js +30 -14
  45. package/packages/datadog-plugin-mocha/src/index.js +39 -14
  46. package/packages/datadog-plugin-mongodb-core/src/bulk-write.js +43 -0
  47. package/packages/datadog-plugin-mongodb-core/src/index.js +8 -443
  48. package/packages/datadog-plugin-mongodb-core/src/query.js +452 -0
  49. package/packages/datadog-plugin-playwright/src/index.js +3 -2
  50. package/packages/datadog-plugin-vitest/src/index.js +51 -9
  51. package/packages/dd-trace/src/aiguard/index.js +9 -14
  52. package/packages/dd-trace/src/aiguard/integrations/index.js +34 -0
  53. package/packages/dd-trace/src/aiguard/integrations/openai.js +47 -33
  54. package/packages/dd-trace/src/aiguard/integrations/vercel-ai.js +42 -28
  55. package/packages/dd-trace/src/appsec/graphql.js +9 -6
  56. package/packages/dd-trace/src/appsec/iast/analyzers/nosql-injection-mongodb-analyzer.js +3 -10
  57. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/index.js +26 -1
  58. package/packages/dd-trace/src/ci-visibility/dynamic-instrumentation/worker/index.js +115 -24
  59. package/packages/dd-trace/src/ci-visibility/exporters/agent-proxy/index.js +3 -0
  60. package/packages/dd-trace/src/ci-visibility/exporters/agentless/index.js +3 -0
  61. package/packages/dd-trace/src/ci-visibility/exporters/ci-visibility-exporter.js +75 -7
  62. package/packages/dd-trace/src/ci-visibility/requests/upload-test-screenshot.js +156 -0
  63. package/packages/dd-trace/src/config/generated-config-types.d.ts +10 -0
  64. package/packages/dd-trace/src/config/supported-configurations.json +37 -0
  65. package/packages/dd-trace/src/crashtracking/crashtracker.js +2 -2
  66. package/packages/dd-trace/src/llmobs/plugins/ai/ddTelemetry.js +403 -0
  67. package/packages/dd-trace/src/llmobs/plugins/ai/index.js +8 -395
  68. package/packages/dd-trace/src/llmobs/plugins/ai/util.js +27 -0
  69. package/packages/dd-trace/src/llmobs/plugins/ai/vercelTelemetry.js +363 -0
  70. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/index.js +270 -0
  71. package/packages/dd-trace/src/llmobs/plugins/claude-agent-sdk/utils.js +10 -0
  72. package/packages/dd-trace/src/opentelemetry/span.js +5 -0
  73. package/packages/dd-trace/src/plugin_manager.js +16 -0
  74. package/packages/dd-trace/src/plugins/ci_plugin.js +147 -7
  75. package/packages/dd-trace/src/plugins/index.js +2 -0
  76. package/packages/dd-trace/src/plugins/util/test.js +4 -0
  77. package/packages/dd-trace/src/profiling/exporters/agent.js +28 -3
@@ -88,6 +88,9 @@ const CHILD_MESSAGE_CALL = 1
88
88
  const FLUSH_TIMEOUT = 10_000
89
89
  const JEST_SESSION_STATE = Symbol.for('dd-trace:jest:session')
90
90
  const JEST_BAIL_REPORTER_PATH = require.resolve('./jest/bail-reporter')
91
+ const DD_JEST_HANDLE_TEST_EVENT_WRAPPED = Symbol('dd-trace:jest:handle-test-event-wrapped')
92
+ const DD_JEST_HANDLE_TEST_EVENT_DATADOG = Symbol('dd-trace:jest:handle-test-event-datadog')
93
+ const DD_JEST_CONCURRENT_TEST_ORIGINAL = Symbol('dd-trace:jest:concurrent-test-original')
91
94
  const isJestWorker = !!getEnvironmentVariable('JEST_WORKER_ID')
92
95
  const jestSessionState = globalThis[JEST_SESSION_STATE] || (globalThis[JEST_SESSION_STATE] = {})
93
96
 
@@ -132,6 +135,8 @@ let isConsoleErrorWrapped = false
132
135
  const testContexts = new WeakMap()
133
136
  const originalTestFns = new WeakMap()
134
137
  const originalHookFns = new WeakMap()
138
+ const concurrentHookContextQueues = new WeakMap()
139
+ const concurrentHookFns = new WeakMap()
135
140
  const retriedTestsToNumAttempts = new Map()
136
141
  const newTestsTestStatuses = new Map()
137
142
  const attemptToFixRetriedTestsStatuses = new Map()
@@ -151,14 +156,28 @@ const efdNewTestCandidates = new Set()
151
156
  // Tests that are genuinely new (not in known tests list).
152
157
  const newTests = new Set()
153
158
  const testSuiteJestObjects = new Map()
159
+ const testSuiteDatadogEnvironments = new Map()
154
160
  const wrappedJestGlobals = new WeakSet()
155
161
  const wrappedJestObjects = new WeakSet()
156
162
  const wrappedWorkerInitializers = new WeakSet()
157
163
  const publishedRuntimeReferenceErrors = new WeakMap()
158
164
  const wrappedCoverageReporters = new WeakSet()
159
165
  const coverageReporterRequires = new WeakMap()
166
+ const handledJestEvents = new WeakSet()
167
+
168
+ /**
169
+ * @typedef {object} ConcurrentTestOptions
170
+ * @property {(...args: unknown[]) => unknown} [concurrentTest]
171
+ * @property {unknown} [concurrentTestThisArg]
172
+ * @property {boolean} [isAttemptToFixRetry]
173
+ * @property {boolean} [isEfdRetry]
174
+ * @property {boolean} [isModified]
175
+ * @property {(...args: unknown[]) => unknown} [serialTest]
176
+ * @property {unknown} [serialTestThisArg]
177
+ * @property {(...args: unknown[]) => unknown} [sourceTestFn]
178
+ * @property {string} [testParameters]
179
+ */
160
180
 
161
- const BREAKPOINT_HIT_GRACE_PERIOD_MS = 200
162
181
  const ATR_RETRY_SUPPRESSION_FLAG = '_ddDisableAtrRetry'
163
182
  const MINIMUM_JEST_VERSION = DD_MAJOR >= 6 ? '>=28.0.0' : '>=24.8.0'
164
183
  const MINIMUM_JEST_VERSION_BEFORE_30 = DD_MAJOR >= 6 ? '>=28.0.0 <30.0.0' : '>=24.8.0 <30.0.0'
@@ -170,6 +189,7 @@ const atrSuppressedErrors = new Map()
170
189
  let hasWarnedDeprecatedJestVersion = false
171
190
  let isJestCoverageBackfillSupported = false
172
191
  let hasFinishedTestSession = false
192
+ let jestEachBind
173
193
 
174
194
  // Track quarantined tests whose errors were suppressed, keyed by "suite › testName"
175
195
  const quarantinedFailingTests = new Set()
@@ -364,6 +384,83 @@ function wrapConsoleErrorForJestReferenceErrors () {
364
384
  }
365
385
  }
366
386
 
387
+ function isDatadogJestEventHandled (event) {
388
+ return event && typeof event === 'object' && handledJestEvents.has(event)
389
+ }
390
+
391
+ function markDatadogJestEventHandled (event) {
392
+ if (event && typeof event === 'object') {
393
+ handledJestEvents.add(event)
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Gets the original Jest concurrent registration function behind a Datadog wrapper.
399
+ *
400
+ * @param {(...args: unknown[]) => unknown} concurrentTest
401
+ * @returns {(...args: unknown[]) => unknown}
402
+ */
403
+ function getOriginalConcurrentTest (concurrentTest) {
404
+ return concurrentTest[DD_JEST_CONCURRENT_TEST_ORIGINAL] || concurrentTest
405
+ }
406
+
407
+ /**
408
+ * Marks a Datadog concurrent registration wrapper with its original Jest function.
409
+ *
410
+ * @param {(...args: unknown[]) => unknown} wrappedConcurrentTest
411
+ * @param {(...args: unknown[]) => unknown} originalConcurrentTest
412
+ * @returns {void}
413
+ */
414
+ function setOriginalConcurrentTest (wrappedConcurrentTest, originalConcurrentTest) {
415
+ Object.defineProperty(wrappedConcurrentTest, DD_JEST_CONCURRENT_TEST_ORIGINAL, {
416
+ configurable: true,
417
+ value: originalConcurrentTest,
418
+ })
419
+ }
420
+
421
+ /**
422
+ * Wraps a custom Jest environment handler so Datadog still observes events even
423
+ * when the custom environment does not call `super.handleTestEvent`.
424
+ *
425
+ * @param {(event: object, state: object) => Promise<void>|void} handleTestEvent
426
+ * @param {(event: object, state: object) => Promise<void>|void} datadogHandleTestEvent
427
+ * @returns {(event: object, state: object) => Promise<void>|void}
428
+ */
429
+ function getWrappedCustomHandleTestEvent (handleTestEvent, datadogHandleTestEvent) {
430
+ if (
431
+ !handleTestEvent ||
432
+ handleTestEvent === datadogHandleTestEvent ||
433
+ handleTestEvent[DD_JEST_HANDLE_TEST_EVENT_WRAPPED]
434
+ ) {
435
+ return handleTestEvent || datadogHandleTestEvent
436
+ }
437
+
438
+ const wrappedHandleTestEvent = function (event, state) {
439
+ const result = handleTestEvent.call(this, event, state)
440
+ const runDatadogHandler = value => {
441
+ if (isDatadogJestEventHandled(event)) return value
442
+
443
+ const datadogResult = datadogHandleTestEvent.call(this, event, state)
444
+ if (typeof datadogResult?.then === 'function') {
445
+ return datadogResult.then(() => value)
446
+ }
447
+ return value
448
+ }
449
+
450
+ if (event.name === 'add_test') {
451
+ runDatadogHandler(result)
452
+ return result
453
+ }
454
+
455
+ if (typeof result?.then === 'function') {
456
+ return result.then(runDatadogHandler)
457
+ }
458
+ return runDatadogHandler(result)
459
+ }
460
+ wrappedHandleTestEvent[DD_JEST_HANDLE_TEST_EVENT_WRAPPED] = true
461
+ return wrappedHandleTestEvent
462
+ }
463
+
367
464
  function getWrappedEnvironment (BaseEnvironment, jestVersion) {
368
465
  return class DatadogEnvironment extends BaseEnvironment {
369
466
  constructor (config, context) {
@@ -375,6 +472,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
375
472
  this.hasSnapshotTests = undefined
376
473
  this.testSuiteAbsolutePath = context.testPath
377
474
  activeTestSuiteAbsolutePath = this.testSuiteAbsolutePath
475
+ testSuiteDatadogEnvironments.set(this.testSuiteAbsolutePath, this)
378
476
  wrapConsoleErrorForJestReferenceErrors()
379
477
  this.globalConfig = config.globalConfig
380
478
 
@@ -398,6 +496,12 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
398
496
  this.isKnownTestsEnabled = this.testEnvironmentOptions._ddIsKnownTestsEnabled
399
497
  this.isTestManagementTestsEnabled = this.testEnvironmentOptions._ddIsTestManagementTestsEnabled
400
498
  this.isImpactedTestsEnabled = this.testEnvironmentOptions._ddIsImpactedTestsEnabled
499
+ this.hasConcurrentTests = false
500
+ this.concurrentTestContexts = new Map()
501
+ this.concurrentTestStates = new WeakMap()
502
+ this.concurrentTestSourceFns = new WeakMap()
503
+ this.wrappedConcurrentTestFunctions = new WeakSet()
504
+ this.jestEachBind = undefined
401
505
 
402
506
  if (this.isKnownTestsEnabled) {
403
507
  earlyFlakeDetectionSlowTestRetries = this.testEnvironmentOptions._ddEarlyFlakeDetectionSlowTestRetries ?? {}
@@ -445,6 +549,390 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
445
549
  this.isImpactedTestsEnabled = false
446
550
  }
447
551
  }
552
+
553
+ this[DD_JEST_HANDLE_TEST_EVENT_DATADOG] = DatadogEnvironment.prototype.handleTestEvent
554
+ this.wrapCustomHandleTestEvent(DatadogEnvironment.prototype.handleTestEvent)
555
+ }
556
+
557
+ /**
558
+ * Rechecks custom `handleTestEvent` implementations after subclass instance fields
559
+ * and constructors have run.
560
+ *
561
+ * @returns {Promise<void>|void}
562
+ */
563
+ setup () {
564
+ this.wrapCustomHandleTestEvent(DatadogEnvironment.prototype.handleTestEvent)
565
+
566
+ if (super.setup) {
567
+ const result = super.setup()
568
+ if (typeof result?.then === 'function') {
569
+ return result.then(() => {
570
+ this.wrapCustomHandleTestEvent(DatadogEnvironment.prototype.handleTestEvent)
571
+ })
572
+ }
573
+ this.wrapCustomHandleTestEvent(DatadogEnvironment.prototype.handleTestEvent)
574
+ return result
575
+ }
576
+ }
577
+
578
+ /**
579
+ * Wraps Jest's concurrent test registration methods so eager concurrent bodies
580
+ * in older Jest versions execute inside their test span context.
581
+ *
582
+ * @param {object} state
583
+ * @returns {void}
584
+ */
585
+ wrapConcurrentTest (state) {
586
+ this.concurrentTestState = state
587
+ this.wrapConcurrentTestGlobals(this.global.test, state)
588
+ }
589
+
590
+ /**
591
+ * Wraps one Jest `test` function object's concurrent registration methods.
592
+ *
593
+ * @param {Function|undefined} test
594
+ * @param {object} state
595
+ * @returns {void}
596
+ */
597
+ wrapConcurrentTestGlobals (test, state) {
598
+ if (!state) return
599
+
600
+ const concurrentTest = test?.concurrent
601
+ if (typeof concurrentTest !== 'function') return
602
+
603
+ this.wrapConcurrentTestFunction(test, 'concurrent', state, test, test)
604
+ this.wrapConcurrentTestFunction(test.concurrent, 'only', state, test.only, test)
605
+ this.wrapConcurrentTestFunction(test.concurrent, 'failing', state, test.failing, test)
606
+ this.wrapConcurrentTestFunction(test.concurrent.only, 'failing', state, test.only?.failing, test.only)
607
+ }
608
+
609
+ /**
610
+ * Wraps one concurrent test function variant.
611
+ *
612
+ * @param {object} target
613
+ * @param {string} methodName
614
+ * @param {object} state
615
+ * @param {Function|undefined} serialTest
616
+ * @param {unknown} serialTestThisArg
617
+ * @returns {void}
618
+ */
619
+ wrapConcurrentTestFunction (target, methodName, state, serialTest, serialTestThisArg) {
620
+ let concurrentTest = target?.[methodName]
621
+ if (typeof concurrentTest !== 'function') return
622
+ if (this.wrappedConcurrentTestFunctions.has(concurrentTest)) return
623
+
624
+ const originalConcurrentTest = getOriginalConcurrentTest(concurrentTest)
625
+ if (originalConcurrentTest !== concurrentTest) {
626
+ target[methodName] = originalConcurrentTest
627
+ concurrentTest = originalConcurrentTest
628
+ }
629
+ if (typeof concurrentTest !== 'function') return
630
+
631
+ const environment = this
632
+ shimmer.wrap(target, methodName, concurrentTest => {
633
+ return function wrappedConcurrentTest (testName, testFn, ...args) {
634
+ if (typeof testFn !== 'function') {
635
+ return concurrentTest.apply(this, arguments)
636
+ }
637
+
638
+ environment.hasConcurrentTests = true
639
+ const asyncError = environment.isImpactedTestsEnabled
640
+ ? new Error('Datadog concurrent test registration')
641
+ : undefined
642
+ const wrappedTestFn = environment.createConcurrentTestFn(testName, testFn, asyncError, state, {
643
+ concurrentTest,
644
+ concurrentTestThisArg: this,
645
+ serialTest,
646
+ serialTestThisArg,
647
+ sourceTestFn: environment.concurrentTestSourceFns.get(testFn),
648
+ })
649
+ return concurrentTest.call(this, testName, wrappedTestFn, ...args)
650
+ }
651
+ })
652
+
653
+ const wrappedConcurrentTest = target[methodName]
654
+ setOriginalConcurrentTest(wrappedConcurrentTest, concurrentTest)
655
+ this.wrappedConcurrentTestFunctions.add(wrappedConcurrentTest)
656
+ this.bindConcurrentEach(wrappedConcurrentTest, methodName === 'failing')
657
+ }
658
+
659
+ /**
660
+ * Rebuilds a concurrent `.each` helper so each generated row calls the wrapped test function.
661
+ *
662
+ * @param {Function} concurrentTest
663
+ * @param {boolean} needsEachError
664
+ * @returns {void}
665
+ */
666
+ bindConcurrentEach (concurrentTest, needsEachError) {
667
+ if (typeof concurrentTest?.each !== 'function') return
668
+
669
+ const bind = this.getJestEachBind()
670
+ if (typeof bind !== 'function') return
671
+
672
+ const environment = this
673
+ concurrentTest.each = function wrappedConcurrentEach (...eachArgs) {
674
+ const testParameters = getFormattedJestTestParameters(eachArgs)
675
+ return function (...testArgs) {
676
+ let parameterIndex = 0
677
+ const sourceTestFn = testArgs[1]
678
+ const concurrentEachTest = function (testName, testFn, ...callArgs) {
679
+ const parameters = testParameters?.[parameterIndex++]
680
+ if (parameters !== undefined) {
681
+ environment.appendNameToParams(testName, parameters)
682
+ }
683
+ if (typeof testFn === 'function' && typeof sourceTestFn === 'function') {
684
+ environment.concurrentTestSourceFns.set(testFn, sourceTestFn)
685
+ }
686
+ return concurrentTest.call(this, testName, testFn, ...callArgs)
687
+ }
688
+ const eachBind = bind(concurrentEachTest, false, needsEachError).apply(this, eachArgs)
689
+ return eachBind.apply(this, testArgs)
690
+ }
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Creates a Jest test function wrapper and stores its concurrent execution state.
696
+ *
697
+ * @param {string} testName
698
+ * @param {(...args: unknown[]) => unknown} testFn
699
+ * @param {Error|undefined} asyncError
700
+ * @param {object} state
701
+ * @param {ConcurrentTestOptions|undefined} options
702
+ * @returns {(...args: unknown[]) => unknown}
703
+ */
704
+ createConcurrentTestFn (testName, testFn, asyncError, state, options) {
705
+ const concurrentTestState = {
706
+ concurrentTest: options?.concurrentTest,
707
+ concurrentTestThisArg: options?.concurrentTestThisArg,
708
+ ctx: this.createConcurrentTestContext(testName, testFn, asyncError, state, options),
709
+ numExecutions: 0,
710
+ serialTest: options?.serialTest,
711
+ serialTestThisArg: options?.serialTestThisArg,
712
+ state,
713
+ testFn,
714
+ }
715
+ concurrentTestState.ctx.concurrentTestState = concurrentTestState
716
+ const environment = this
717
+ const wrappedTestFn = shimmer.wrapFunction(testFn, testFn => function (...args) {
718
+ return environment.runConcurrentTestFn(concurrentTestState, testFn, this, args)
719
+ })
720
+ this.concurrentTestStates.set(wrappedTestFn, concurrentTestState)
721
+ return wrappedTestFn
722
+ }
723
+
724
+ /**
725
+ * Gets Jest's table-driven test binder from the user's Jest installation.
726
+ *
727
+ * @returns {Function|undefined}
728
+ */
729
+ getJestEachBind () {
730
+ if (this.jestEachBind !== undefined) return this.jestEachBind
731
+
732
+ if (typeof jestEachBind === 'function') {
733
+ this.jestEachBind = jestEachBind
734
+ return this.jestEachBind
735
+ }
736
+
737
+ let jestEach
738
+ try {
739
+ jestEach = createRequire(path.join(this.rootDir, 'package.json'))('jest-each')
740
+ } catch {
741
+ try {
742
+ jestEach = createRequire(path.join(process.cwd(), 'package.json'))('jest-each')
743
+ } catch {
744
+ jestEach = undefined
745
+ }
746
+ }
747
+ this.jestEachBind = jestEach?.bind
748
+ return this.jestEachBind
749
+ }
750
+
751
+ /**
752
+ * Creates the context used to start and finish a concurrent test span.
753
+ *
754
+ * @param {string} testName
755
+ * @param {(...args: unknown[]) => unknown} testFn
756
+ * @param {Error|undefined} asyncError
757
+ * @param {object} state
758
+ * @param {ConcurrentTestOptions|undefined} options
759
+ * @returns {object}
760
+ */
761
+ createConcurrentTestContext (testName, testFn, asyncError, state, options) {
762
+ const testFullName = this.getTestNameFromAddTestEvent({ testName }, state)
763
+ const isNewTest = this.isKnownTestsEnabled && !this.knownTestsForThisSuite?.includes(testFullName)
764
+ const isAttemptToFix = this.isTestManagementTestsEnabled &&
765
+ this.testManagementTestsForThisSuite?.attemptToFix?.includes(testFullName)
766
+ const sourceTestFn = options?.sourceTestFn || testFn
767
+ const ctx = {
768
+ name: testFullName,
769
+ suite: this.testSuite,
770
+ testSourceFile: this.testSourceFile,
771
+ displayName: this.displayName,
772
+ testParameters: options?.testParameters || getTestParametersString(this.nameToParams, testName),
773
+ frameworkVersion: jestVersion,
774
+ isNew: isNewTest,
775
+ isEfdRetry: options?.isEfdRetry === true,
776
+ isAttemptToFix,
777
+ isAttemptToFixRetry: options?.isAttemptToFixRetry === true,
778
+ isJestRetry: false,
779
+ isDisabled: this.testManagementTestsForThisSuite?.disabled?.includes(testFullName),
780
+ isQuarantined: this.testManagementTestsForThisSuite?.quarantined?.includes(testFullName),
781
+ isModified: options?.isModified ?? this.isTestModified(asyncError, sourceTestFn),
782
+ hasDynamicName: isNewTest && DYNAMIC_NAME_RE.test(testFullName),
783
+ testSuiteAbsolutePath: this.testSuiteAbsolutePath,
784
+ }
785
+ let contexts = this.concurrentTestContexts.get(testFullName)
786
+ if (contexts) {
787
+ contexts.push(ctx)
788
+ } else {
789
+ contexts = [ctx]
790
+ this.concurrentTestContexts.set(testFullName, contexts)
791
+ }
792
+ return ctx
793
+ }
794
+
795
+ /**
796
+ * Checks if a test overlaps modified lines for impacted-test detection.
797
+ *
798
+ * @param {Error|undefined} asyncError
799
+ * @param {Function|undefined} testFn
800
+ * @returns {boolean}
801
+ */
802
+ isTestModified (asyncError, testFn) {
803
+ if (!this.isImpactedTestsEnabled || !asyncError || typeof testFn !== 'function') return false
804
+
805
+ const testStartLine = getTestLineStart(asyncError, this.testSuite)
806
+ const testEndLine = getTestEndLine(testFn, testStartLine)
807
+ return isModifiedTest(
808
+ this.testSourceFile,
809
+ testStartLine,
810
+ testEndLine,
811
+ this.modifiedFiles,
812
+ 'jest'
813
+ )
814
+ }
815
+
816
+ /**
817
+ * Gets the next registered concurrent test context for a full Jest test name.
818
+ *
819
+ * @param {string} testName
820
+ * @returns {object|undefined}
821
+ */
822
+ getNextConcurrentTestContext (testName) {
823
+ const contexts = this.concurrentTestContexts.get(testName)
824
+ if (!contexts?.length) return
825
+
826
+ const ctx = contexts.shift()
827
+ if (contexts.length === 0) {
828
+ this.concurrentTestContexts.delete(testName)
829
+ }
830
+ return ctx
831
+ }
832
+
833
+ /**
834
+ * Removes one registered concurrent test context from the name fallback queue.
835
+ *
836
+ * @param {string} testName
837
+ * @param {object} ctx
838
+ * @returns {void}
839
+ */
840
+ removeConcurrentTestContext (testName, ctx) {
841
+ const contexts = this.concurrentTestContexts.get(testName)
842
+ if (!contexts?.length) return
843
+
844
+ const contextIndex = contexts.indexOf(ctx)
845
+ if (contextIndex === -1) return
846
+
847
+ contexts.splice(contextIndex, 1)
848
+ if (contexts.length === 0) {
849
+ this.concurrentTestContexts.delete(testName)
850
+ }
851
+ }
852
+
853
+ /**
854
+ * Records formatted parameters for one table-driven Jest test declaration.
855
+ *
856
+ * @param {string} name
857
+ * @param {unknown[]} params
858
+ * @returns {void}
859
+ */
860
+ setNameToParams (name, params) {
861
+ this.nameToParams[name] = [...params]
862
+ }
863
+
864
+ /**
865
+ * Appends formatted parameters for one table-driven Jest test row.
866
+ *
867
+ * @param {string} name
868
+ * @param {unknown[]|object} params
869
+ * @returns {void}
870
+ */
871
+ appendNameToParams (name, params) {
872
+ if (this.nameToParams[name]) {
873
+ this.nameToParams[name].push(params)
874
+ } else {
875
+ this.nameToParams[name] = [params]
876
+ }
877
+ }
878
+
879
+ /**
880
+ * Runs a concurrent test function inside its test span context.
881
+ *
882
+ * @param {{ ctx: object, numExecutions: number }} concurrentTestState
883
+ * @param {(...args: unknown[]) => unknown} testFn
884
+ * @param {object} thisArg
885
+ * @param {unknown[]} args
886
+ * @returns {unknown|void}
887
+ */
888
+ runConcurrentTestFn (concurrentTestState, testFn, thisArg, args) {
889
+ const ctx = concurrentTestState.ctx
890
+ if (ctx.isDisabled && !ctx.isAttemptToFix) {
891
+ const done = args[0]
892
+ if (typeof done === 'function') {
893
+ done()
894
+ }
895
+ return
896
+ }
897
+
898
+ const runTest = () => testFn.apply(thisArg, args)
899
+ if (!ctx.isAttemptToFixRetry && !ctx.isEfdRetry) {
900
+ ctx.isJestRetry = concurrentTestState.numExecutions > 0
901
+ }
902
+ concurrentTestState.numExecutions++
903
+
904
+ if (ctx.currentStore) {
905
+ return testFnCh.runStores(ctx, runTest)
906
+ }
907
+
908
+ let result
909
+ testStartCh.runStores(ctx, () => {
910
+ result = testFnCh.runStores(ctx, runTest)
911
+ })
912
+ return result
913
+ }
914
+
915
+ /**
916
+ * Ensures Datadog handles Jest circus events even when a custom environment overrides
917
+ * `handleTestEvent` without calling `super.handleTestEvent`.
918
+ *
919
+ * @param {(event: object, state: object) => Promise<void>|void} datadogHandleTestEvent
920
+ * @returns {void}
921
+ */
922
+ wrapCustomHandleTestEvent (datadogHandleTestEvent) {
923
+ const descriptor = Object.getOwnPropertyDescriptor(this, 'handleTestEvent')
924
+ let handleTestEvent = getWrappedCustomHandleTestEvent(this.handleTestEvent, datadogHandleTestEvent)
925
+
926
+ Object.defineProperty(this, 'handleTestEvent', {
927
+ configurable: true,
928
+ enumerable: descriptor?.enumerable ?? false,
929
+ get () {
930
+ return handleTestEvent
931
+ },
932
+ set (value) {
933
+ handleTestEvent = getWrappedCustomHandleTestEvent(value, datadogHandleTestEvent)
934
+ },
935
+ })
448
936
  }
449
937
 
450
938
  /**
@@ -558,12 +1046,47 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
558
1046
  // Generic function to handle test retries
559
1047
  retryTest ({
560
1048
  jestEvent,
1049
+ forceSerial,
561
1050
  retryCount,
562
1051
  retryType,
563
1052
  }) {
564
1053
  const { testName, fn, timeout } = jestEvent
1054
+ const isAttemptToFixRetry = retryType === 'Test Management (Attempt to Fix)'
1055
+ const isEfdRetry = retryType === 'Impacted tests' || retryType === 'Early flake detection'
565
1056
  for (let retryIndex = 0; retryIndex < retryCount; retryIndex++) {
566
- if (this.global.test) {
1057
+ const concurrentTestState = this.concurrentTestStates.get(fn)
1058
+ if (concurrentTestState) {
1059
+ const test = forceSerial
1060
+ ? concurrentTestState.serialTest
1061
+ : (
1062
+ concurrentTestState.concurrentTest ||
1063
+ getOriginalConcurrentTest(this.global.test?.concurrent)
1064
+ )
1065
+ if (typeof test !== 'function') {
1066
+ log.error('%s could not retry concurrent test because test is undefined', retryType)
1067
+ continue
1068
+ }
1069
+ const retryFn = this.createConcurrentTestFn(
1070
+ testName,
1071
+ concurrentTestState.testFn,
1072
+ undefined,
1073
+ concurrentTestState.state,
1074
+ {
1075
+ concurrentTest: concurrentTestState.concurrentTest,
1076
+ concurrentTestThisArg: concurrentTestState.concurrentTestThisArg,
1077
+ isAttemptToFixRetry,
1078
+ isEfdRetry,
1079
+ isModified: concurrentTestState.ctx.isModified,
1080
+ serialTest: concurrentTestState.serialTest,
1081
+ serialTestThisArg: concurrentTestState.serialTestThisArg,
1082
+ testParameters: concurrentTestState.ctx.testParameters,
1083
+ }
1084
+ )
1085
+ const thisArg = forceSerial
1086
+ ? concurrentTestState.serialTestThisArg
1087
+ : concurrentTestState.concurrentTestThisArg
1088
+ test.call(thisArg, testName, retryFn, timeout)
1089
+ } else if (this.global.test) {
567
1090
  this.global.test(testName, fn, timeout)
568
1091
  } else {
569
1092
  log.error('%s could not retry test because global.test is undefined', retryType)
@@ -579,25 +1102,37 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
579
1102
  }
580
1103
 
581
1104
  async handleTestEvent (event, state) {
1105
+ if (isDatadogJestEventHandled(event)) return
1106
+ markDatadogJestEventHandled(event)
1107
+
582
1108
  if (super.handleTestEvent) {
583
1109
  await super.handleTestEvent(event, state)
584
1110
  }
585
1111
 
586
- const setNameToParams = (name, params) => { this.nameToParams[name] = [...params] }
587
-
1112
+ if (event.name === 'setup') {
1113
+ this.wrapConcurrentTest(state)
1114
+ }
588
1115
  if (event.name === 'setup' && this.global.test) {
1116
+ const environment = this
589
1117
  shimmer.wrap(this.global.test, 'each', each => function (...args) {
590
1118
  const testParameters = getFormattedJestTestParameters(args)
591
1119
  const eachBind = each.apply(this, args)
592
1120
  return function (...args) {
593
1121
  const [testName] = args
594
- setNameToParams(testName, testParameters)
1122
+ environment.setNameToParams(testName, testParameters)
595
1123
  return eachBind.apply(this, args)
596
1124
  }
597
1125
  })
598
1126
  }
599
1127
  if (event.name === 'test_start') {
600
1128
  const testName = getJestTestName(event.test)
1129
+ const concurrentTestState = this.concurrentTestStates.get(event.test.fn)
1130
+ let concurrentCtx = concurrentTestState?.ctx
1131
+ if (concurrentCtx) {
1132
+ this.removeConcurrentTestContext(testName, concurrentCtx)
1133
+ } else {
1134
+ concurrentCtx = this.getNextConcurrentTestContext(testName)
1135
+ }
601
1136
  if (testsToBeRetried.has(testName)) {
602
1137
  // This is needed because we're retrying tests with the same name
603
1138
  this.resetSnapshotState()
@@ -607,7 +1142,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
607
1142
  let isNewTest = false
608
1143
  let numEfdRetry = null
609
1144
  let numOfAttemptsToFixRetries = null
610
- const testParameters = getTestParametersString(this.nameToParams, event.test.name)
1145
+ const testParameters = concurrentCtx?.testParameters ||
1146
+ concurrentTestState?.ctx?.testParameters ||
1147
+ getTestParametersString(this.nameToParams, event.test.name)
611
1148
 
612
1149
  let isAttemptToFix = false
613
1150
  let isDisabled = false
@@ -624,17 +1161,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
624
1161
  }
625
1162
  }
626
1163
 
627
- let isModified = false
628
- if (this.isImpactedTestsEnabled) {
629
- const testStartLine = getTestLineStart(event.test.asyncError, this.testSuite)
630
- const testEndLine = getTestEndLine(event.test.fn, testStartLine)
631
- isModified = isModifiedTest(
632
- this.testSourceFile,
633
- testStartLine,
634
- testEndLine,
635
- this.modifiedFiles,
636
- 'jest'
637
- )
1164
+ let isModified = this.isTestModified(event.test.asyncError, event.test.fn)
1165
+ if (concurrentCtx?.isModified || concurrentTestState?.ctx?.isModified) {
1166
+ isModified = true
638
1167
  }
639
1168
 
640
1169
  if (this.isKnownTestsEnabled) {
@@ -651,23 +1180,26 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
651
1180
 
652
1181
  const isJestRetry = event.test?.invocations > 1
653
1182
  const hasDynamicName = isNewTest && DYNAMIC_NAME_RE.test(testName)
654
- const ctx = {
655
- name: testName,
656
- suite: this.testSuite,
657
- testSourceFile: this.testSourceFile,
658
- displayName: this.displayName,
659
- testParameters,
660
- frameworkVersion: jestVersion,
661
- isNew: isNewTest,
662
- isEfdRetry: numEfdRetry > 0,
663
- isAttemptToFix,
664
- isAttemptToFixRetry: numOfAttemptsToFixRetries > 0,
665
- isJestRetry,
666
- isDisabled,
667
- isQuarantined,
668
- isModified,
669
- hasDynamicName,
670
- testSuiteAbsolutePath: this.testSuiteAbsolutePath,
1183
+ const ctx = concurrentCtx || concurrentTestState?.ctx || {}
1184
+ ctx.name = testName
1185
+ ctx.suite = this.testSuite
1186
+ ctx.testSourceFile = this.testSourceFile
1187
+ ctx.displayName = this.displayName
1188
+ ctx.testParameters = testParameters
1189
+ ctx.frameworkVersion = jestVersion
1190
+ ctx.isNew = isNewTest
1191
+ ctx.isEfdRetry = ctx.isEfdRetry || numEfdRetry > 0
1192
+ ctx.isAttemptToFix = isAttemptToFix
1193
+ ctx.isAttemptToFixRetry = ctx.isAttemptToFixRetry || numOfAttemptsToFixRetries > 0
1194
+ ctx.isJestRetry = isJestRetry
1195
+ ctx.isDisabled = isDisabled
1196
+ ctx.isQuarantined = isQuarantined
1197
+ ctx.isModified = isModified
1198
+ ctx.hasDynamicName = hasDynamicName
1199
+ ctx.testSuiteAbsolutePath = this.testSuiteAbsolutePath
1200
+ if (concurrentTestState) {
1201
+ ctx.concurrentTestState = concurrentTestState
1202
+ concurrentTestState.ctx = ctx
671
1203
  }
672
1204
  testContexts.set(event.test, ctx)
673
1205
 
@@ -675,7 +1207,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
675
1207
  logAttemptToFixTestExecution(this.testSuite, testName, loggedAttemptToFixTests)
676
1208
  }
677
1209
 
678
- testStartCh.runStores(ctx, () => {
1210
+ const wrapTestAndHooks = () => {
679
1211
  let p = event.test.parent
680
1212
  const hooks = []
681
1213
  while (p != null) {
@@ -694,6 +1226,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
694
1226
  })
695
1227
  hook.fn = newHookFn
696
1228
  }
1229
+
697
1230
  const originalFn = event.test.fn
698
1231
  originalTestFns.set(event.test, originalFn)
699
1232
 
@@ -702,7 +1235,53 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
702
1235
  })
703
1236
 
704
1237
  event.test.fn = newFn
705
- })
1238
+ }
1239
+
1240
+ const isConcurrentTest = event.test.concurrent || ctx.concurrentTestState
1241
+ if (isConcurrentTest) {
1242
+ if (!ctx.currentStore && event.test.errors?.length) {
1243
+ testStartCh.runStores(ctx, () => {})
1244
+ }
1245
+ } else if (ctx.currentStore) {
1246
+ wrapTestAndHooks()
1247
+ } else {
1248
+ testStartCh.runStores(ctx, wrapTestAndHooks)
1249
+ }
1250
+ }
1251
+
1252
+ if (event.name === 'hook_start' && (event.hook.type === 'beforeEach' || event.hook.type === 'afterEach')) {
1253
+ const ctx = testContexts.get(event.test) || this.concurrentTestStates.get(event.test?.fn)?.ctx
1254
+ if (ctx?.concurrentTestState && (!ctx.isDisabled || ctx.isAttemptToFix)) {
1255
+ let hookFn = event.hook.fn
1256
+ if (originalHookFns.has(event.hook)) {
1257
+ hookFn = originalHookFns.get(event.hook)
1258
+ } else {
1259
+ originalHookFns.set(event.hook, hookFn)
1260
+ }
1261
+ let hookContexts = concurrentHookContextQueues.get(event.hook)
1262
+ if (!hookContexts) {
1263
+ hookContexts = []
1264
+ concurrentHookContextQueues.set(event.hook, hookContexts)
1265
+ }
1266
+ hookContexts.push(ctx)
1267
+ if (!ctx.currentStore) {
1268
+ testStartCh.runStores(ctx, () => {})
1269
+ }
1270
+ let concurrentHookFn = concurrentHookFns.get(event.hook)
1271
+ if (!concurrentHookFn) {
1272
+ concurrentHookFn = shimmer.wrapFunction(hookFn, hookFn => function (...args) {
1273
+ const hookContexts = concurrentHookContextQueues.get(event.hook)
1274
+ const [hookCtx] = hookContexts || []
1275
+ if (hookCtx) {
1276
+ hookContexts.shift()
1277
+ return testFnCh.runStores(hookCtx, () => hookFn.apply(this, args))
1278
+ }
1279
+ return hookFn.apply(this, args)
1280
+ })
1281
+ concurrentHookFns.set(event.hook, concurrentHookFn)
1282
+ }
1283
+ event.hook.fn = concurrentHookFn
1284
+ }
706
1285
  }
707
1286
 
708
1287
  if (event.name === 'hook_start' && (event.hook.type === 'beforeAll' || event.hook.type === 'afterAll')) {
@@ -719,6 +1298,10 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
719
1298
  }
720
1299
 
721
1300
  if (event.name === 'add_test') {
1301
+ if (event.concurrent) {
1302
+ this.hasConcurrentTests = true
1303
+ }
1304
+
722
1305
  if (event.failing) {
723
1306
  return
724
1307
  }
@@ -741,15 +1324,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
741
1324
  })
742
1325
  }
743
1326
  if (!isAttemptToFix && this.isImpactedTestsEnabled) {
744
- const testStartLine = getTestLineStart(event.asyncError, this.testSuite)
745
- const testEndLine = getTestEndLine(event.fn, testStartLine)
746
- const isModified = isModifiedTest(
747
- this.testSourceFile,
748
- testStartLine,
749
- testEndLine,
750
- this.modifiedFiles,
751
- 'jest'
752
- )
1327
+ const concurrentTestState = this.concurrentTestStates.get(event.fn)
1328
+ const isModified = concurrentTestState?.ctx?.isModified ||
1329
+ this.isTestModified(event.asyncError, event.fn)
753
1330
  if (isModified && !retriedTestsToNumAttempts.has(testFullName) && this.isEarlyFlakeDetectionEnabled) {
754
1331
  retriedTestsToNumAttempts.set(testFullName, 0)
755
1332
  testsToBeRetried.add(testFullName)
@@ -788,7 +1365,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
788
1365
  status = 'fail'
789
1366
  }
790
1367
  // restore in case it is retried
791
- event.test.fn = originalTestFns.get(event.test)
1368
+ if (originalTestFns.has(event.test)) {
1369
+ event.test.fn = originalTestFns.get(event.test)
1370
+ }
792
1371
  // If ATR retry is being suppressed for this test (due to EFD or Attempt to Fix taking precedence)
793
1372
  // and the test has errors for this attempt, store the errors temporarily and clear them
794
1373
  // so Jest won't treat this attempt as failed (the real status will be reported after retries).
@@ -857,6 +1436,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
857
1436
  state.currentDescribeBlock = event.test.parent ?? originalDescribeBlock
858
1437
  state.hasStarted = false
859
1438
  this.retryTest({
1439
+ forceSerial: true,
860
1440
  jestEvent: {
861
1441
  testName: event.test.name,
862
1442
  fn: event.test.fn,
@@ -905,14 +1485,16 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
905
1485
  const promises = {}
906
1486
  const numRetries = this.global[RETRY_TIMES]
907
1487
  const numTestExecutions = event.test?.invocations
908
- const willBeRetriedByFailedTestReplay = numRetries > 0 && numTestExecutions - 1 < numRetries
909
- const mightHitBreakpoint = this.isDiEnabled && numTestExecutions >= 2
1488
+ const willBeRetriedByAutoTestRetry = numRetries > 0 && numTestExecutions - 1 < numRetries
1489
+ const isFailedTestReplayAllowed = !this.hasConcurrentTests
1490
+ const willBeRetriedByFailedTestReplay = isFailedTestReplayAllowed && willBeRetriedByAutoTestRetry
1491
+ const mightHitBreakpoint = this.isDiEnabled && isFailedTestReplayAllowed && numTestExecutions >= 2
910
1492
 
911
1493
  // For quarantined tests, track failures so the session can be marked as passing later,
912
1494
  // and suppress errors so Jest does not mark the test suite as failing.
913
1495
  // The actual status ('fail') is already captured above for dd-trace reporting.
914
1496
  // Only suppress on the final execution — not when ATR/EFD/ATF will retry the test.
915
- if (!event.test?.[ATR_RETRY_SUPPRESSION_FLAG] && !willBeRetriedByFailedTestReplay) {
1497
+ if (!event.test?.[ATR_RETRY_SUPPRESSION_FLAG] && !willBeRetriedByAutoTestRetry) {
916
1498
  const quarantineCtx = testContexts.get(event.test)
917
1499
  if (quarantineCtx?.isQuarantined && !quarantineCtx.isAttemptToFix && event.test.errors?.length) {
918
1500
  quarantinedFailingTests.add(`${quarantineCtx.suite} › ${quarantineCtx.name}`)
@@ -925,6 +1507,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
925
1507
  log.warn('"ci:jest:test_done": no context found for test "%s"', testName)
926
1508
  return
927
1509
  }
1510
+ if (ctx.concurrentTestState && !ctx.currentStore && !ctx.isDisabled) {
1511
+ testStartCh.runStores(ctx, () => {})
1512
+ }
928
1513
 
929
1514
  const finalStatus = this.getFinalStatus(testName,
930
1515
  status,
@@ -940,18 +1525,13 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
940
1525
  ...ctx.currentStore,
941
1526
  error: formatJestError(originalError),
942
1527
  shouldSetProbe,
1528
+ shouldWaitForHitProbe: mightHitBreakpoint,
943
1529
  promises,
944
1530
  })
945
1531
  }
946
1532
 
947
- // After finishing it might take a bit for the snapshot to be handled.
948
- // This means that tests retried with DI are BREAKPOINT_HIT_GRACE_PERIOD_MS slower at least.
949
- if (status === 'fail' && mightHitBreakpoint) {
950
- await new Promise(resolve => {
951
- realSetTimeout(() => {
952
- resolve()
953
- }, BREAKPOINT_HIT_GRACE_PERIOD_MS)
954
- })
1533
+ if (promises.hitBreakpointPromise) {
1534
+ await promises.hitBreakpointPromise
955
1535
  }
956
1536
 
957
1537
  let isAtrRetry = false
@@ -969,11 +1549,18 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
969
1549
  isAtrRetry,
970
1550
  finalStatus,
971
1551
  earlyFlakeAbortReason: efdSlowAbortedTests.has(testName) ? 'slow' : undefined,
1552
+ promises,
972
1553
  })
973
1554
 
1555
+ if (promises.hitBreakpointPromise) {
1556
+ await promises.hitBreakpointPromise
1557
+ }
974
1558
  if (promises.isProbeReady) {
975
1559
  await promises.isProbeReady
976
1560
  }
1561
+ if (ctx.concurrentTestState) {
1562
+ ctx.currentStore = undefined
1563
+ }
977
1564
  }
978
1565
  if (event.name === 'run_finish') {
979
1566
  for (const [test, errors] of atrSuppressedErrors) {
@@ -1003,6 +1590,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
1003
1590
  retriedTestsToNumAttempts.clear()
1004
1591
  attemptToFixRetriedTestsStatuses.clear()
1005
1592
  testsToBeRetried.clear()
1593
+ testSuiteDatadogEnvironments.delete(this.testSuiteAbsolutePath)
1006
1594
  }
1007
1595
  if (event.name === 'test_skip' || event.name === 'test_todo') {
1008
1596
  const testName = getJestTestName(event.test)
@@ -1506,11 +2094,23 @@ addHook({
1506
2094
  versions: [MINIMUM_JEST_VERSION],
1507
2095
  }, getTestEnvironment)
1508
2096
 
2097
+ addHook({
2098
+ name: 'jest-environment-node',
2099
+ file: 'build/index.js',
2100
+ versions: [MINIMUM_JEST_VERSION],
2101
+ }, getTestEnvironment)
2102
+
1509
2103
  addHook({
1510
2104
  name: 'jest-environment-jsdom',
1511
2105
  versions: [MINIMUM_JEST_VERSION],
1512
2106
  }, getTestEnvironment)
1513
2107
 
2108
+ addHook({
2109
+ name: 'jest-environment-jsdom',
2110
+ file: 'build/index.js',
2111
+ versions: [MINIMUM_JEST_VERSION],
2112
+ }, getTestEnvironment)
2113
+
1514
2114
  addHook({
1515
2115
  name: '@happy-dom/jest-environment',
1516
2116
  versions: ['>=10.0.0'],
@@ -1927,6 +2527,19 @@ function cleanupTestSuiteState (testSuiteAbsolutePath) {
1927
2527
  testSuiteJestObjects.delete(testSuiteAbsolutePath)
1928
2528
  }
1929
2529
 
2530
+ /**
2531
+ * Rechecks custom handlers after Jest has finished constructing the environment.
2532
+ *
2533
+ * @param {object} environment
2534
+ * @returns {void}
2535
+ */
2536
+ function wrapEnvironmentCustomHandleTestEvent (environment) {
2537
+ const datadogHandleTestEvent = environment[DD_JEST_HANDLE_TEST_EVENT_DATADOG]
2538
+ if (typeof environment.wrapCustomHandleTestEvent === 'function' && datadogHandleTestEvent) {
2539
+ environment.wrapCustomHandleTestEvent(datadogHandleTestEvent)
2540
+ }
2541
+ }
2542
+
1930
2543
  addHook({
1931
2544
  name: '@jest/core',
1932
2545
  file: 'build/TestScheduler.js',
@@ -1960,10 +2573,7 @@ if (DD_MAJOR < 6) {
1960
2573
  })
1961
2574
  }
1962
2575
 
1963
- addHook({
1964
- name: '@jest/test-sequencer',
1965
- versions: ['>=28'],
1966
- }, (sequencerPackage, frameworkVersion) => {
2576
+ function testSequencerWrapper (sequencerPackage, frameworkVersion) {
1967
2577
  shimmer.wrap(sequencerPackage.default.prototype, 'shard', shard => function (...args) {
1968
2578
  const shardedTests = shard.apply(this, args)
1969
2579
 
@@ -1976,7 +2586,18 @@ addHook({
1976
2586
  return applySuiteSkipping(shardedTests, rootDir, frameworkVersion)
1977
2587
  })
1978
2588
  return sequencerPackage
1979
- })
2589
+ }
2590
+
2591
+ addHook({
2592
+ name: '@jest/test-sequencer',
2593
+ versions: ['>=28'],
2594
+ }, testSequencerWrapper)
2595
+
2596
+ addHook({
2597
+ name: '@jest/test-sequencer',
2598
+ file: 'build/index.js',
2599
+ versions: ['>=28'],
2600
+ }, testSequencerWrapper)
1980
2601
 
1981
2602
  addHook({
1982
2603
  name: '@jest/core',
@@ -2021,6 +2642,9 @@ function jestAdapterWrapper (jestAdapter, jestVersion) {
2021
2642
  if (!environment || !environment.testEnvironmentOptions) {
2022
2643
  return adapter.apply(this, args)
2023
2644
  }
2645
+
2646
+ wrapEnvironmentCustomHandleTestEvent(environment)
2647
+
2024
2648
  testSuiteStartCh.publish({
2025
2649
  testSuite: environment.testSuite,
2026
2650
  testEnvironmentOptions: environment.testEnvironmentOptions,
@@ -2300,6 +2924,14 @@ addHook({
2300
2924
  file: 'build/SearchSource.js',
2301
2925
  }, searchSourceWrapper)
2302
2926
 
2927
+ addHook({
2928
+ name: 'jest-each',
2929
+ versions: [MINIMUM_JEST_VERSION],
2930
+ }, jestEach => {
2931
+ cacheJestEachBind(jestEach)
2932
+ return jestEach
2933
+ })
2934
+
2303
2935
  // from 25.1.0 on, readConfigs becomes async
2304
2936
  addHook({
2305
2937
  name: 'jest-config',
@@ -2394,6 +3026,17 @@ function wrapJestGlobalsForRuntime (runtime) {
2394
3026
  })
2395
3027
  }
2396
3028
 
3029
+ function wrapConcurrentJestGlobalsForRuntime (runtime, jestGlobals) {
3030
+ const environment = testSuiteDatadogEnvironments.get(runtime?._testPath)
3031
+ environment?.wrapConcurrentTestGlobals(jestGlobals?.test, environment.concurrentTestState)
3032
+ }
3033
+
3034
+ function cacheJestEachBind (jestEach) {
3035
+ if (typeof jestEach?.bind === 'function') {
3036
+ jestEachBind = jestEach.bind
3037
+ }
3038
+ }
3039
+
2397
3040
  function getLastLoggedReferenceError (runtime) {
2398
3041
  const loggedReferenceErrors = runtime?.loggedReferenceErrors
2399
3042
  if (!loggedReferenceErrors?.size) return
@@ -2472,7 +3115,11 @@ addHook({
2472
3115
  shimmer.wrap(Runtime.prototype, 'requireModule', requireModule => function (from, moduleName) {
2473
3116
  wrapJestGlobalsForRuntime(this)
2474
3117
  try {
2475
- return requireModule.apply(this, arguments)
3118
+ const returnedValue = requireModule.apply(this, arguments)
3119
+ if (moduleName === '@jest/globals') {
3120
+ wrapConcurrentJestGlobalsForRuntime(this, returnedValue)
3121
+ }
3122
+ return returnedValue
2476
3123
  } catch (error) {
2477
3124
  if (isBetweenTestsReferenceError(error)) {
2478
3125
  reportBetweenTestsReferenceError(this, moduleName, error.message)
@@ -2505,6 +3152,9 @@ addHook({
2505
3152
  let returnedValue
2506
3153
  try {
2507
3154
  returnedValue = requireModuleOrMock.apply(this, arguments)
3155
+ if (moduleName === '@jest/globals') {
3156
+ wrapConcurrentJestGlobalsForRuntime(this, returnedValue)
3157
+ }
2508
3158
  } catch (error) {
2509
3159
  if (isBetweenTestsReferenceError(error)) {
2510
3160
  reportBetweenTestsReferenceError(this, moduleName, error.message)