@@ -21,6 +21,7 @@ const {
2121 getFormattedJestTestParameters,
2222 getJestTestName,
2323 getJestSuitesToRun,
24+ getEfdRetryCount,
2425} = require ( '../../datadog-plugin-jest/src/util' )
2526const { addHook, channel } = require ( './helpers/instrument' )
2627
@@ -76,6 +77,7 @@ let hasUnskippableSuites = false
7677let hasForcedToRunSuites = false
7778let isEarlyFlakeDetectionEnabled = false
7879let earlyFlakeDetectionNumRetries = 0
80+ let earlyFlakeDetectionSlowTestRetries = { }
7981let earlyFlakeDetectionFaultyThreshold = 30
8082let isEarlyFlakeDetectionFaulty = false
8183let hasFilteredSkippableSuites = false
@@ -95,6 +97,12 @@ const attemptToFixRetriedTestsStatuses = new Map()
9597const wrappedWorkers = new WeakSet ( )
9698const testSuiteMockedFiles = new Map ( )
9799const testsToBeRetried = new Set ( )
100+ // Per-test: how many EFD retries were determined after the first execution.
101+ const efdDeterminedRetries = new Map ( )
102+ // Tests whose first run exceeded the 5-min threshold — tagged "slow".
103+ const efdSlowAbortedTests = new Set ( )
104+ // Tests added as EFD new-test candidates (not ATF, not impacted).
105+ const efdNewTestCandidates = new Set ( )
98106const testSuiteAbsolutePathsWithFastCheck = new Set ( )
99107const testSuiteJestObjects = new Map ( )
100108
@@ -197,7 +205,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
197205 this . isImpactedTestsEnabled = this . testEnvironmentOptions . _ddIsImpactedTestsEnabled
198206
199207 if ( this . isKnownTestsEnabled ) {
200- earlyFlakeDetectionNumRetries = this . testEnvironmentOptions . _ddEarlyFlakeDetectionNumRetries
208+ earlyFlakeDetectionSlowTestRetries = this . testEnvironmentOptions . _ddEarlyFlakeDetectionSlowTestRetries ?? { }
201209 try {
202210 this . knownTestsForThisSuite = this . getKnownTestsForSuite ( this . testEnvironmentOptions . _ddKnownTests )
203211
@@ -543,11 +551,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
543551 retriedTestsToNumAttempts . set ( testFullName , 0 )
544552 if ( this . isEarlyFlakeDetectionEnabled ) {
545553 testsToBeRetried . add ( testFullName )
546- this . retryTest ( {
547- jestEvent : event ,
548- retryCount : earlyFlakeDetectionNumRetries ,
549- retryType : 'Early flake detection' ,
550- } )
554+ efdNewTestCandidates . add ( testFullName )
555+ // Cloning is deferred to test_done after the first execution,
556+ // when we know the duration and can choose the right retry count.
551557 }
552558 }
553559 }
@@ -572,8 +578,8 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
572578 let attemptToFixFailed = false
573579 let failedAllTests = false
574580 let isAttemptToFix = false
581+ const testName = getJestTestName ( event . test , this . getShouldStripSeedFromTestName ( ) )
575582 if ( this . isTestManagementTestsEnabled ) {
576- const testName = getJestTestName ( event . test , this . getShouldStripSeedFromTestName ( ) )
577583 isAttemptToFix = this . testManagementTestsForThisSuite ?. attemptToFix ?. includes ( testName )
578584 if ( isAttemptToFix ) {
579585 if ( attemptToFixRetriedTestsStatuses . has ( testName ) ) {
@@ -598,9 +604,53 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
598604 }
599605 }
600606
607+ // EFD dynamic cloning: on first execution of a new EFD candidate,
608+ // determine the retry count from the test's duration.
609+ if (
610+ this . isEarlyFlakeDetectionEnabled &&
611+ this . isKnownTestsEnabled &&
612+ efdNewTestCandidates . has ( testName ) &&
613+ event . test . invocations === 1 &&
614+ ! efdDeterminedRetries . has ( testName )
615+ ) {
616+ const durationMs = event . test . duration ?? 0
617+ const retryCount = getEfdRetryCount ( durationMs , earlyFlakeDetectionSlowTestRetries )
618+ efdDeterminedRetries . set ( testName , retryCount )
619+ if ( retryCount > 0 ) {
620+ // Temporarily adjust jest-circus state so that retry tests are registered
621+ // into the correct describe block and bypass the "tests have started" guard.
622+ //
623+ // Problem 1 (jest-circus ≤24): currentDescribeBlock points to ROOT during
624+ // execution, and ROOT's tests loop already finished before children ran.
625+ //
626+ // Problem 2 (jest-circus ≥27): `hasStarted = true` causes `test()` to throw
627+ // "Cannot add a test after tests have started running".
628+ //
629+ // Fix: temporarily point currentDescribeBlock to the test's parent (so retries
630+ // land in the still-iterating children array) and set hasStarted = false (so the
631+ // guard is bypassed). Both are restored immediately after scheduling the retries.
632+ const originalDescribeBlock = state . currentDescribeBlock
633+ const originalHasStarted = state . hasStarted
634+ state . currentDescribeBlock = event . test . parent ?? originalDescribeBlock
635+ state . hasStarted = false
636+ this . retryTest ( {
637+ jestEvent : {
638+ testName : event . test . name ,
639+ fn : event . test . fn ,
640+ timeout : event . test . timeout ,
641+ } ,
642+ retryCount,
643+ retryType : 'Early flake detection' ,
644+ } )
645+ state . currentDescribeBlock = originalDescribeBlock
646+ state . hasStarted = originalHasStarted
647+ } else {
648+ efdSlowAbortedTests . add ( testName )
649+ }
650+ }
651+
601652 let isEfdRetry = false
602653 // We'll store the test statuses of the retries
603- const testName = getJestTestName ( event . test , this . getShouldStripSeedFromTestName ( ) )
604654 if ( this . isKnownTestsEnabled ) {
605655 const isNewTest = retriedTestsToNumAttempts . has ( testName )
606656 if ( isNewTest ) {
@@ -613,7 +663,8 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
613663 const testStatuses = newTestsTestStatuses . get ( testName )
614664 // Check if this is the last EFD retry.
615665 // If it is, we'll set the failedAllTests flag to true if all the tests failed
616- if ( testStatuses . length === earlyFlakeDetectionNumRetries + 1 &&
666+ const efdRetryCount = efdDeterminedRetries . get ( testName ) ?? 0
667+ if ( efdRetryCount > 0 && testStatuses . length === efdRetryCount + 1 &&
617668 testStatuses . every ( status => status === 'fail' ) ) {
618669 failedAllTests = true
619670 }
@@ -671,6 +722,7 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
671722 attemptToFixFailed,
672723 isAtrRetry,
673724 finalStatus,
725+ earlyFlakeAbortReason : efdSlowAbortedTests . has ( testName ) ? 'slow' : undefined ,
674726 } )
675727
676728 if ( promises . isProbeReady ) {
@@ -682,6 +734,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
682734 test . errors = errors
683735 }
684736 atrSuppressedErrors . clear ( )
737+ efdDeterminedRetries . clear ( )
738+ efdSlowAbortedTests . clear ( )
739+ efdNewTestCandidates . clear ( )
685740 }
686741 if ( event . name === 'test_skip' || event . name === 'test_todo' ) {
687742 const testName = getJestTestName ( event . test , this . getShouldStripSeedFromTestName ( ) )
@@ -702,7 +757,9 @@ function getWrappedEnvironment (BaseEnvironment, jestVersion) {
702757 getEfdResult ( { testName, isNewTest, isModifiedTest, isEfdRetry, numberOfExecutedRetries } ) {
703758 const isEfdEnabled = this . isEarlyFlakeDetectionEnabled
704759 const isEfdActive = isEfdEnabled && ( isNewTest || isModifiedTest )
705- const isLastEfdRetry = isEfdRetry && numberOfExecutedRetries >= ( earlyFlakeDetectionNumRetries + 1 )
760+ const retryCount = efdDeterminedRetries . get ( testName ) ?? 0
761+ const isSlowAbort = efdSlowAbortedTests . has ( testName )
762+ const isLastEfdRetry = ( isEfdRetry && numberOfExecutedRetries >= ( retryCount + 1 ) ) || isSlowAbort
706763 const isFinalEfdTestExecution = isEfdActive && isLastEfdRetry
707764
708765 let finalStatus
@@ -939,6 +996,7 @@ function getCliWrapper (isNewJestVersion) {
939996 isSuitesSkippingEnabled = libraryConfig . isSuitesSkippingEnabled
940997 isEarlyFlakeDetectionEnabled = libraryConfig . isEarlyFlakeDetectionEnabled
941998 earlyFlakeDetectionNumRetries = libraryConfig . earlyFlakeDetectionNumRetries
999+ earlyFlakeDetectionSlowTestRetries = libraryConfig . earlyFlakeDetectionSlowTestRetries ?? { }
9421000 earlyFlakeDetectionFaultyThreshold = libraryConfig . earlyFlakeDetectionFaultyThreshold
9431001 isKnownTestsEnabled = libraryConfig . isKnownTestsEnabled
9441002 isTestManagementTestsEnabled = libraryConfig . isTestManagementEnabled
@@ -1514,7 +1572,7 @@ addHook({
15141572 _ddItrCorrelationId,
15151573 _ddKnownTests,
15161574 _ddIsEarlyFlakeDetectionEnabled,
1517- _ddEarlyFlakeDetectionNumRetries ,
1575+ _ddEarlyFlakeDetectionSlowTestRetries ,
15181576 _ddRepositoryRoot,
15191577 _ddIsFlakyTestRetriesEnabled,
15201578 _ddFlakyTestRetriesCount,
0 commit comments