@@ -45,19 +45,15 @@ interface CypressTest extends Mocha.Test {
45
45
46
46
type Strategy = 'detect-flake-and-pass-on-threshold' | 'detect-flake-but-always-fail' | undefined
47
47
48
- type Options < T > = T extends 'detect-flake-and-pass-on-threshold' ?
49
- {
50
- maxRetries : number
51
- passesRequired : number
52
- } :
53
- T extends 'detect-flake-but-always-fail' ? {
54
- maxRetries : number
55
- stopIfAnyPassed : boolean
56
- } :
57
- undefined
48
+ type NormalizedRetriesConfig = {
49
+ strategy ?: Strategy
50
+ maxRetries ?: number
51
+ passesRequired ?: number
52
+ stopIfAnyPassed ?: boolean
53
+ }
58
54
59
55
// NOTE: 'calculateTestStatus' is marked as an individual function to make functionality easier to test.
60
- export function calculateTestStatus ( test : CypressTest , strategy : Strategy , options : Options < Strategy > ) {
56
+ export function calculateTestStatus ( test : CypressTest , config : NormalizedRetriesConfig ) {
61
57
// @ts -expect-error
62
58
const totalAttemptsAlreadyExecuted = test . currentRetry ( ) + 1
63
59
let shouldAttemptsContinue : boolean = true
@@ -82,21 +78,21 @@ export function calculateTestStatus (test: CypressTest, strategy: Strategy, opti
82
78
const passingAttempts = passedTests . length
83
79
84
80
// Below variables are used for when strategy is "detect-flake-and-pass-on-threshold" or no strategy is defined
85
- let passesRequired = strategy !== 'detect-flake-but-always-fail' ?
86
- ( ( options as Options < 'detect-flake-and-pass-on-threshold' > | undefined ) ? .passesRequired || 1 ) :
81
+ let passesRequired = config . strategy !== 'detect-flake-but-always-fail' ?
82
+ ( config . passesRequired || 1 ) :
87
83
null
88
84
89
- const neededPassingAttemptsLeft = strategy !== 'detect-flake-but-always-fail' ?
85
+ const neededPassingAttemptsLeft = config . strategy !== 'detect-flake-but-always-fail' ?
90
86
( passesRequired as number ) - passingAttempts :
91
87
null
92
88
93
89
// Below variables are used for when strategy is only "detect-flake-but-always-fail"
94
- let stopIfAnyPassed = strategy === 'detect-flake-but-always-fail' ?
95
- ( ( options as Options < 'detect-flake-but-always-fail' > ) . stopIfAnyPassed || false ) :
90
+ let stopIfAnyPassed = config . strategy === 'detect-flake-but-always-fail' ?
91
+ ( config . stopIfAnyPassed || false ) :
96
92
null
97
93
98
94
// Do we have the required amount of passes? If yes, we no longer need to keep running the test.
99
- if ( strategy !== 'detect-flake-but-always-fail' && passingAttempts >= ( passesRequired as number ) ) {
95
+ if ( config . strategy !== 'detect-flake-but-always-fail' && passingAttempts >= ( passesRequired as number ) ) {
100
96
outerTestStatus = 'passed'
101
97
test . final = true
102
98
shouldAttemptsContinue = false
@@ -105,13 +101,13 @@ export function calculateTestStatus (test: CypressTest, strategy: Strategy, opti
105
101
// For strategy "detect-flake-and-pass-on-threshold" or no strategy (current GA retries):
106
102
// If we haven't met our max attempt limit AND we have enough remaining attempts that can satisfy the passing requirement.
107
103
// retry the test.
108
- ( strategy !== 'detect-flake-but-always-fail' && remainingAttempts >= ( neededPassingAttemptsLeft as number ) ) ||
104
+ ( config . strategy !== 'detect-flake-but-always-fail' && remainingAttempts >= ( neededPassingAttemptsLeft as number ) ) ||
109
105
// For strategy "detect-flake-but-always-fail":
110
106
// If we haven't met our max attempt limit AND
111
107
// stopIfAnyPassed is false OR
112
108
// stopIfAnyPassed is true and no tests have passed yet.
113
109
// retry the test.
114
- ( strategy === 'detect-flake-but-always-fail' && ( ! stopIfAnyPassed || stopIfAnyPassed && passingAttempts === 0 ) )
110
+ ( config . strategy === 'detect-flake-but-always-fail' && ( ! stopIfAnyPassed || stopIfAnyPassed && passingAttempts === 0 ) )
115
111
) ) {
116
112
test . final = false
117
113
shouldAttemptsContinue = true
@@ -132,7 +128,7 @@ export function calculateTestStatus (test: CypressTest, strategy: Strategy, opti
132
128
}
133
129
134
130
return {
135
- strategy,
131
+ strategy : config . strategy ,
136
132
shouldAttemptsContinue,
137
133
attempts : totalAttemptsAlreadyExecuted ,
138
134
outerStatus : outerTestStatus ,
@@ -438,16 +434,63 @@ function patchTestClone () {
438
434
}
439
435
}
440
436
437
+ function getNormalizedRetriesConfig ( Cypress : Cypress . Cypress ) : NormalizedRetriesConfig {
438
+ const retriesConfig = Cypress . config ( 'retries' )
439
+ const isInOpenMode = Cypress . config ( 'isInteractive' )
440
+
441
+ if ( retriesConfig == null ) {
442
+ return { }
443
+ }
444
+
445
+ if ( typeof retriesConfig === 'number' ) {
446
+ return {
447
+ strategy : 'detect-flake-and-pass-on-threshold' ,
448
+ maxRetries : retriesConfig ,
449
+ passesRequired : 1 ,
450
+ }
451
+ }
452
+
453
+ const enablementKey : 'openMode' | 'runMode' = isInOpenMode ? 'openMode' : 'runMode'
454
+ const enablementValue = retriesConfig [ enablementKey ]
455
+
456
+ // if retries are explicitly disabled, return an empty object
457
+ if ( enablementValue === false ) {
458
+ return { }
459
+ }
460
+
461
+ // by default, retries are disabled in open mode
462
+ if ( ! enablementValue && isInOpenMode ) {
463
+ return { }
464
+ }
465
+
466
+ if ( typeof enablementValue === 'number' ) {
467
+ return {
468
+ strategy : 'detect-flake-and-pass-on-threshold' ,
469
+ maxRetries : enablementValue ,
470
+ passesRequired : 1 ,
471
+ }
472
+ }
473
+
474
+ const config = retriesConfig as Cypress . RetryStrategy
475
+
476
+ // TODO: For GA, rename experimentalStrategy to strategy, experimentalOptions to options
477
+ return {
478
+ strategy : config . experimentalStrategy ,
479
+ maxRetries : config . experimentalOptions ?. maxRetries ,
480
+ passesRequired : config . experimentalOptions ?. [ 'passesRequired' ] ,
481
+ stopIfAnyPassed : config . experimentalOptions ?. [ 'stopIfAnyPassed' ] ,
482
+ }
483
+ }
484
+
441
485
function createCalculateTestStatus ( Cypress : Cypress . Cypress ) {
442
486
// Adds a method to the test object called 'calculateTestStatus'
443
487
// which is used inside our mocha patch (./driver/patches/mocha+7.0.1.dev.patch)
444
488
// in order to calculate test retries. This prototype functions as a light abstraction around
445
489
// 'calculateTestStatus', which makes the function easier to unit-test
446
490
Test . prototype . calculateTestStatus = function ( ) {
447
- let retriesConfig = Cypress . config ( 'retries' )
491
+ const retriesConfig = getNormalizedRetriesConfig ( Cypress )
448
492
449
- // @ts -expect-error
450
- return calculateTestStatus ( this , retriesConfig ?. experimentalStrategy , retriesConfig ?. experimentalOptions )
493
+ return calculateTestStatus ( this , retriesConfig )
451
494
}
452
495
}
453
496
0 commit comments