Skip to content

Commit 0fecba0

Browse files
committed
Normalize retries config for test execution
1 parent fe367d6 commit 0fecba0

File tree

1 file changed

+66
-23
lines changed

1 file changed

+66
-23
lines changed

packages/driver/src/cypress/mocha.ts

+66-23
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,15 @@ interface CypressTest extends Mocha.Test {
4545

4646
type Strategy = 'detect-flake-and-pass-on-threshold' | 'detect-flake-but-always-fail' | undefined
4747

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+
}
5854

5955
// 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) {
6157
// @ts-expect-error
6258
const totalAttemptsAlreadyExecuted = test.currentRetry() + 1
6359
let shouldAttemptsContinue: boolean = true
@@ -82,21 +78,21 @@ export function calculateTestStatus (test: CypressTest, strategy: Strategy, opti
8278
const passingAttempts = passedTests.length
8379

8480
// 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) :
8783
null
8884

89-
const neededPassingAttemptsLeft = strategy !== 'detect-flake-but-always-fail' ?
85+
const neededPassingAttemptsLeft = config.strategy !== 'detect-flake-but-always-fail' ?
9086
(passesRequired as number) - passingAttempts :
9187
null
9288

9389
// 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) :
9692
null
9793

9894
// 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)) {
10096
outerTestStatus = 'passed'
10197
test.final = true
10298
shouldAttemptsContinue = false
@@ -105,13 +101,13 @@ export function calculateTestStatus (test: CypressTest, strategy: Strategy, opti
105101
// For strategy "detect-flake-and-pass-on-threshold" or no strategy (current GA retries):
106102
// If we haven't met our max attempt limit AND we have enough remaining attempts that can satisfy the passing requirement.
107103
// 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)) ||
109105
// For strategy "detect-flake-but-always-fail":
110106
// If we haven't met our max attempt limit AND
111107
// stopIfAnyPassed is false OR
112108
// stopIfAnyPassed is true and no tests have passed yet.
113109
// 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))
115111
)) {
116112
test.final = false
117113
shouldAttemptsContinue = true
@@ -132,7 +128,7 @@ export function calculateTestStatus (test: CypressTest, strategy: Strategy, opti
132128
}
133129

134130
return {
135-
strategy,
131+
strategy: config.strategy,
136132
shouldAttemptsContinue,
137133
attempts: totalAttemptsAlreadyExecuted,
138134
outerStatus: outerTestStatus,
@@ -438,16 +434,63 @@ function patchTestClone () {
438434
}
439435
}
440436

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+
441485
function createCalculateTestStatus (Cypress: Cypress.Cypress) {
442486
// Adds a method to the test object called 'calculateTestStatus'
443487
// which is used inside our mocha patch (./driver/patches/mocha+7.0.1.dev.patch)
444488
// in order to calculate test retries. This prototype functions as a light abstraction around
445489
// 'calculateTestStatus', which makes the function easier to unit-test
446490
Test.prototype.calculateTestStatus = function () {
447-
let retriesConfig = Cypress.config('retries')
491+
const retriesConfig = getNormalizedRetriesConfig(Cypress)
448492

449-
// @ts-expect-error
450-
return calculateTestStatus(this, retriesConfig?.experimentalStrategy, retriesConfig?.experimentalOptions)
493+
return calculateTestStatus(this, retriesConfig)
451494
}
452495
}
453496

0 commit comments

Comments
 (0)