codeceptjs 4.0.0-rc.2 → 4.0.0-rc.8
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.
- package/README.md +39 -27
- package/bin/mcp-server.js +610 -0
- package/docs/webapi/appendField.mustache +5 -0
- package/docs/webapi/attachFile.mustache +12 -0
- package/docs/webapi/checkOption.mustache +1 -1
- package/docs/webapi/clearField.mustache +5 -0
- package/docs/webapi/dontSeeElement.mustache +4 -0
- package/docs/webapi/dontSeeInField.mustache +5 -0
- package/docs/webapi/fillField.mustache +5 -0
- package/docs/webapi/moveCursorTo.mustache +5 -1
- package/docs/webapi/seeElement.mustache +4 -0
- package/docs/webapi/seeInField.mustache +5 -0
- package/docs/webapi/selectOption.mustache +5 -0
- package/docs/webapi/uncheckOption.mustache +1 -1
- package/lib/codecept.js +20 -17
- package/lib/command/init.js +0 -3
- package/lib/command/run-workers.js +1 -0
- package/lib/container.js +19 -4
- package/lib/element/WebElement.js +52 -0
- package/lib/helper/Appium.js +8 -8
- package/lib/helper/Playwright.js +169 -87
- package/lib/helper/Puppeteer.js +181 -64
- package/lib/helper/WebDriver.js +141 -53
- package/lib/helper/errors/MultipleElementsFound.js +27 -110
- package/lib/helper/scripts/dropFile.js +11 -0
- package/lib/html.js +14 -1
- package/lib/listener/globalRetry.js +32 -6
- package/lib/mocha/cli.js +10 -0
- package/lib/plugin/aiTrace.js +464 -0
- package/lib/plugin/retryFailedStep.js +28 -19
- package/lib/plugin/stepByStepReport.js +5 -1
- package/lib/utils.js +48 -0
- package/lib/workers.js +49 -7
- package/package.json +5 -3
- package/lib/listener/enhancedGlobalRetry.js +0 -110
- package/lib/plugin/enhancedRetryFailedStep.js +0 -99
- package/lib/plugin/htmlReporter.js +0 -3648
- package/lib/retryCoordinator.js +0 -207
- package/typings/promiseBasedTypes.d.ts +0 -9469
- package/typings/types.d.ts +0 -11402
package/lib/utils.js
CHANGED
|
@@ -150,6 +150,24 @@ export const decodeUrl = function (url) {
|
|
|
150
150
|
return decodeURIComponent(decodeURIComponent(decodeURIComponent(url)))
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
export const normalizePath = function (path) {
|
|
154
|
+
if (path === '' || path === '/') return '/'
|
|
155
|
+
return path
|
|
156
|
+
.replace(/\/+/g, '/')
|
|
157
|
+
.replace(/\/$/, '') || '/'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const resolveUrl = function (url, baseUrl) {
|
|
161
|
+
if (!url) return url
|
|
162
|
+
if (url.indexOf('http') === 0) return url
|
|
163
|
+
if (!baseUrl) return url
|
|
164
|
+
try {
|
|
165
|
+
return new URL(url, baseUrl).href
|
|
166
|
+
} catch (e) {
|
|
167
|
+
return url
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
153
171
|
export const xpathLocator = {
|
|
154
172
|
/**
|
|
155
173
|
* @param {string} string
|
|
@@ -640,6 +658,36 @@ export const base64EncodeFile = function (filePath) {
|
|
|
640
658
|
return Buffer.from(fs.readFileSync(filePath)).toString('base64')
|
|
641
659
|
}
|
|
642
660
|
|
|
661
|
+
export const getMimeType = function (fileName) {
|
|
662
|
+
const ext = path.extname(fileName).toLowerCase()
|
|
663
|
+
const mimeTypes = {
|
|
664
|
+
'.jpg': 'image/jpeg',
|
|
665
|
+
'.jpeg': 'image/jpeg',
|
|
666
|
+
'.png': 'image/png',
|
|
667
|
+
'.gif': 'image/gif',
|
|
668
|
+
'.bmp': 'image/bmp',
|
|
669
|
+
'.svg': 'image/svg+xml',
|
|
670
|
+
'.webp': 'image/webp',
|
|
671
|
+
'.pdf': 'application/pdf',
|
|
672
|
+
'.txt': 'text/plain',
|
|
673
|
+
'.html': 'text/html',
|
|
674
|
+
'.css': 'text/css',
|
|
675
|
+
'.js': 'application/javascript',
|
|
676
|
+
'.json': 'application/json',
|
|
677
|
+
'.xml': 'application/xml',
|
|
678
|
+
'.zip': 'application/zip',
|
|
679
|
+
'.csv': 'text/csv',
|
|
680
|
+
'.doc': 'application/msword',
|
|
681
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
682
|
+
'.xls': 'application/vnd.ms-excel',
|
|
683
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
684
|
+
'.mp3': 'audio/mpeg',
|
|
685
|
+
'.mp4': 'video/mp4',
|
|
686
|
+
'.wav': 'audio/wav',
|
|
687
|
+
}
|
|
688
|
+
return mimeTypes[ext] || 'application/octet-stream'
|
|
689
|
+
}
|
|
690
|
+
|
|
643
691
|
export const markdownToAnsi = function (markdown) {
|
|
644
692
|
return (
|
|
645
693
|
markdown
|
package/lib/workers.js
CHANGED
|
@@ -28,7 +28,7 @@ const pathToWorker = path.join(__dirname, 'command', 'workers', 'runTests.js')
|
|
|
28
28
|
|
|
29
29
|
const initializeCodecept = async (configPath, options = {}) => {
|
|
30
30
|
const config = await mainConfig.load(configPath || '.')
|
|
31
|
-
const codecept = new Codecept(config, options)
|
|
31
|
+
const codecept = new Codecept(config, { ...options, skipDefaultListeners: true })
|
|
32
32
|
await codecept.init(getTestRoot(configPath))
|
|
33
33
|
codecept.loadTests()
|
|
34
34
|
|
|
@@ -625,13 +625,32 @@ class Workers extends EventEmitter {
|
|
|
625
625
|
|
|
626
626
|
break
|
|
627
627
|
case event.suite.before:
|
|
628
|
-
|
|
628
|
+
{
|
|
629
|
+
const suite = deserializeSuite(message.data)
|
|
630
|
+
this.emit(event.suite.before, suite)
|
|
631
|
+
event.dispatcher.emit(event.suite.before, suite)
|
|
632
|
+
}
|
|
633
|
+
break
|
|
634
|
+
case event.suite.after:
|
|
635
|
+
{
|
|
636
|
+
const suite = deserializeSuite(message.data)
|
|
637
|
+
this.emit(event.suite.after, suite)
|
|
638
|
+
event.dispatcher.emit(event.suite.after, suite)
|
|
639
|
+
}
|
|
629
640
|
break
|
|
630
641
|
case event.test.before:
|
|
631
|
-
|
|
642
|
+
{
|
|
643
|
+
const test = deserializeTest(message.data)
|
|
644
|
+
this.emit(event.test.before, test)
|
|
645
|
+
event.dispatcher.emit(event.test.before, test)
|
|
646
|
+
}
|
|
632
647
|
break
|
|
633
648
|
case event.test.started:
|
|
634
|
-
|
|
649
|
+
{
|
|
650
|
+
const test = deserializeTest(message.data)
|
|
651
|
+
this.emit(event.test.started, test)
|
|
652
|
+
event.dispatcher.emit(event.test.started, test)
|
|
653
|
+
}
|
|
635
654
|
break
|
|
636
655
|
case event.test.failed:
|
|
637
656
|
// For hook failures, emit immediately as there won't be a test.finished event
|
|
@@ -645,7 +664,11 @@ class Workers extends EventEmitter {
|
|
|
645
664
|
// Skip individual passed events - we'll emit based on finished state
|
|
646
665
|
break
|
|
647
666
|
case event.test.skipped:
|
|
648
|
-
|
|
667
|
+
{
|
|
668
|
+
const test = deserializeTest(message.data)
|
|
669
|
+
this.emit(event.test.skipped, test)
|
|
670
|
+
event.dispatcher.emit(event.test.skipped, test)
|
|
671
|
+
}
|
|
649
672
|
break
|
|
650
673
|
case event.test.finished:
|
|
651
674
|
// Handle different types of test completion properly
|
|
@@ -674,28 +697,47 @@ class Workers extends EventEmitter {
|
|
|
674
697
|
}
|
|
675
698
|
}
|
|
676
699
|
|
|
677
|
-
|
|
700
|
+
const test = deserializeTest(data)
|
|
701
|
+
this.emit(event.test.finished, test)
|
|
702
|
+
event.dispatcher.emit(event.test.finished, test)
|
|
678
703
|
}
|
|
679
704
|
break
|
|
680
705
|
case event.test.after:
|
|
681
|
-
|
|
706
|
+
{
|
|
707
|
+
const test = deserializeTest(message.data)
|
|
708
|
+
this.emit(event.test.after, test)
|
|
709
|
+
event.dispatcher.emit(event.test.after, test)
|
|
710
|
+
}
|
|
682
711
|
break
|
|
683
712
|
case event.step.finished:
|
|
684
713
|
this.emit(event.step.finished, message.data)
|
|
714
|
+
event.dispatcher.emit(event.step.finished, message.data)
|
|
685
715
|
break
|
|
686
716
|
case event.step.started:
|
|
687
717
|
this.emit(event.step.started, message.data)
|
|
718
|
+
event.dispatcher.emit(event.step.started, message.data)
|
|
688
719
|
break
|
|
689
720
|
case event.step.passed:
|
|
690
721
|
this.emit(event.step.passed, message.data)
|
|
722
|
+
event.dispatcher.emit(event.step.passed, message.data)
|
|
691
723
|
break
|
|
692
724
|
case event.step.failed:
|
|
693
725
|
this.emit(event.step.failed, message.data, message.data.error)
|
|
726
|
+
event.dispatcher.emit(event.step.failed, message.data, message.data.error)
|
|
694
727
|
break
|
|
695
728
|
case event.hook.failed:
|
|
696
729
|
// Hook failures are already reported as test failures by the worker
|
|
697
730
|
// Just emit the hook.failed event for listeners
|
|
698
731
|
this.emit(event.hook.failed, message.data)
|
|
732
|
+
event.dispatcher.emit(event.hook.failed, message.data)
|
|
733
|
+
break
|
|
734
|
+
case event.hook.passed:
|
|
735
|
+
this.emit(event.hook.passed, message.data)
|
|
736
|
+
event.dispatcher.emit(event.hook.passed, message.data)
|
|
737
|
+
break
|
|
738
|
+
case event.hook.finished:
|
|
739
|
+
this.emit(event.hook.finished, message.data)
|
|
740
|
+
event.dispatcher.emit(event.hook.finished, message.data)
|
|
699
741
|
break
|
|
700
742
|
}
|
|
701
743
|
})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeceptjs",
|
|
3
|
-
"version": "4.0.0-rc.
|
|
3
|
+
"version": "4.0.0-rc.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Supercharged End 2 End Testing Framework for NodeJS",
|
|
6
6
|
"keywords": [
|
|
@@ -44,9 +44,10 @@
|
|
|
44
44
|
"./store": "./lib/store.js"
|
|
45
45
|
},
|
|
46
46
|
"bin": {
|
|
47
|
-
"codeceptjs": "./bin/codecept.js"
|
|
47
|
+
"codeceptjs": "./bin/codecept.js",
|
|
48
|
+
"codeceptjs-mcp": "./bin/mcp-server.js"
|
|
48
49
|
},
|
|
49
|
-
"repository": "
|
|
50
|
+
"repository": "codeceptjs/CodeceptJS",
|
|
50
51
|
"scripts": {
|
|
51
52
|
"test-server": "node bin/test-server.js test/data/rest/db.json --host 0.0.0.0 -p 8010 --read-only",
|
|
52
53
|
"test-server:writable": "node bin/test-server.js test/data/rest/db.json --host 0.0.0.0 -p 8010",
|
|
@@ -90,6 +91,7 @@
|
|
|
90
91
|
"@cucumber/cucumber-expressions": "18",
|
|
91
92
|
"@cucumber/gherkin": "38.0.0",
|
|
92
93
|
"@cucumber/messages": "32.0.1",
|
|
94
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
93
95
|
"@xmldom/xmldom": "0.9.8",
|
|
94
96
|
"acorn": "8.15.0",
|
|
95
97
|
"ai": "^6.0.43",
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import event from '../event.js'
|
|
2
|
-
import output from '../output.js'
|
|
3
|
-
import Config from '../config.js'
|
|
4
|
-
import { isNotSet } from '../utils.js'
|
|
5
|
-
|
|
6
|
-
const hooks = ['Before', 'After', 'BeforeSuite', 'AfterSuite']
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Priority levels for retry mechanisms (higher number = higher priority)
|
|
10
|
-
* This ensures consistent behavior when multiple retry mechanisms are active
|
|
11
|
-
*/
|
|
12
|
-
const RETRY_PRIORITIES = {
|
|
13
|
-
MANUAL_STEP: 100, // I.retry() or step.retry() - highest priority
|
|
14
|
-
STEP_PLUGIN: 50, // retryFailedStep plugin
|
|
15
|
-
SCENARIO_CONFIG: 30, // Global scenario retry config
|
|
16
|
-
FEATURE_CONFIG: 20, // Global feature retry config
|
|
17
|
-
HOOK_CONFIG: 10, // Hook retry config - lowest priority
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Enhanced global retry mechanism that coordinates with other retry types
|
|
22
|
-
*/
|
|
23
|
-
export default function () {
|
|
24
|
-
event.dispatcher.on(event.suite.before, suite => {
|
|
25
|
-
let retryConfig = Config.get('retry')
|
|
26
|
-
if (!retryConfig) return
|
|
27
|
-
|
|
28
|
-
if (Number.isInteger(+retryConfig)) {
|
|
29
|
-
// is number - apply as feature-level retry
|
|
30
|
-
const retryNum = +retryConfig
|
|
31
|
-
output.log(`[Global Retry] Feature retries: ${retryNum}`)
|
|
32
|
-
|
|
33
|
-
// Only set if not already set by higher priority mechanism
|
|
34
|
-
if (isNotSet(suite.retries())) {
|
|
35
|
-
suite.retries(retryNum)
|
|
36
|
-
suite.opts.retryPriority = RETRY_PRIORITIES.FEATURE_CONFIG
|
|
37
|
-
}
|
|
38
|
-
return
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (!Array.isArray(retryConfig)) {
|
|
42
|
-
retryConfig = [retryConfig]
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const config of retryConfig) {
|
|
46
|
-
if (config.grep) {
|
|
47
|
-
if (!suite.title.includes(config.grep)) continue
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Handle hook retries with priority awareness
|
|
51
|
-
hooks
|
|
52
|
-
.filter(hook => !!config[hook])
|
|
53
|
-
.forEach(hook => {
|
|
54
|
-
const retryKey = `retry${hook}`
|
|
55
|
-
if (isNotSet(suite.opts[retryKey])) {
|
|
56
|
-
suite.opts[retryKey] = config[hook]
|
|
57
|
-
suite.opts[`${retryKey}Priority`] = RETRY_PRIORITIES.HOOK_CONFIG
|
|
58
|
-
}
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// Handle feature-level retries
|
|
62
|
-
if (config.Feature) {
|
|
63
|
-
if (isNotSet(suite.retries()) || (suite.opts.retryPriority || 0) <= RETRY_PRIORITIES.FEATURE_CONFIG) {
|
|
64
|
-
suite.retries(config.Feature)
|
|
65
|
-
suite.opts.retryPriority = RETRY_PRIORITIES.FEATURE_CONFIG
|
|
66
|
-
output.log(`[Global Retry] Feature retries: ${config.Feature}`)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
event.dispatcher.on(event.test.before, test => {
|
|
73
|
-
let retryConfig = Config.get('retry')
|
|
74
|
-
if (!retryConfig) return
|
|
75
|
-
|
|
76
|
-
if (Number.isInteger(+retryConfig)) {
|
|
77
|
-
// Only set if not already set by higher priority mechanism
|
|
78
|
-
if (test.retries() === -1) {
|
|
79
|
-
test.retries(retryConfig)
|
|
80
|
-
test.opts.retryPriority = RETRY_PRIORITIES.SCENARIO_CONFIG
|
|
81
|
-
output.log(`[Global Retry] Scenario retries: ${retryConfig}`)
|
|
82
|
-
}
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!Array.isArray(retryConfig)) {
|
|
87
|
-
retryConfig = [retryConfig]
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
retryConfig = retryConfig.filter(config => !!config.Scenario)
|
|
91
|
-
|
|
92
|
-
for (const config of retryConfig) {
|
|
93
|
-
if (config.grep) {
|
|
94
|
-
if (!test.fullTitle().includes(config.grep)) continue
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (config.Scenario) {
|
|
98
|
-
// Respect priority system
|
|
99
|
-
if (test.retries() === -1 || (test.opts.retryPriority || 0) <= RETRY_PRIORITIES.SCENARIO_CONFIG) {
|
|
100
|
-
test.retries(config.Scenario)
|
|
101
|
-
test.opts.retryPriority = RETRY_PRIORITIES.SCENARIO_CONFIG
|
|
102
|
-
output.log(`[Global Retry] Scenario retries: ${config.Scenario}`)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
})
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Export priority constants for use by other retry mechanisms
|
|
110
|
-
export { RETRY_PRIORITIES }
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import event from '../event.js'
|
|
2
|
-
import recorder from '../recorder.js'
|
|
3
|
-
import store from '../store.js'
|
|
4
|
-
import output from '../output.js'
|
|
5
|
-
import { RETRY_PRIORITIES } from '../retryCoordinator.js'
|
|
6
|
-
|
|
7
|
-
const defaultConfig = {
|
|
8
|
-
retries: 3,
|
|
9
|
-
defaultIgnoredSteps: ['amOnPage', 'wait*', 'send*', 'execute*', 'run*', 'have*'],
|
|
10
|
-
factor: 1.5,
|
|
11
|
-
ignoredSteps: [],
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Enhanced retryFailedStep plugin that coordinates with other retry mechanisms
|
|
16
|
-
*
|
|
17
|
-
* This plugin provides step-level retries and coordinates with global retry settings
|
|
18
|
-
* to avoid conflicts and provide predictable behavior.
|
|
19
|
-
*/
|
|
20
|
-
export default config => {
|
|
21
|
-
config = Object.assign({}, defaultConfig, config)
|
|
22
|
-
config.ignoredSteps = config.ignoredSteps.concat(config.defaultIgnoredSteps)
|
|
23
|
-
const customWhen = config.when
|
|
24
|
-
|
|
25
|
-
let enableRetry = false
|
|
26
|
-
|
|
27
|
-
const when = err => {
|
|
28
|
-
if (!enableRetry) return false
|
|
29
|
-
if (store.debugMode) return false
|
|
30
|
-
if (!store.autoRetries) return false
|
|
31
|
-
if (customWhen) return customWhen(err)
|
|
32
|
-
return true
|
|
33
|
-
}
|
|
34
|
-
config.when = when
|
|
35
|
-
|
|
36
|
-
event.dispatcher.on(event.step.started, step => {
|
|
37
|
-
// if a step is ignored - return
|
|
38
|
-
for (const ignored of config.ignoredSteps) {
|
|
39
|
-
if (step.name === ignored) return
|
|
40
|
-
if (ignored instanceof RegExp) {
|
|
41
|
-
if (step.name.match(ignored)) return
|
|
42
|
-
} else if (ignored.indexOf('*') && step.name.startsWith(ignored.slice(0, -1))) return
|
|
43
|
-
}
|
|
44
|
-
enableRetry = true // enable retry for a step
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
event.dispatcher.on(event.step.finished, () => {
|
|
48
|
-
enableRetry = false
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
event.dispatcher.on(event.test.before, test => {
|
|
52
|
-
// pass disableRetryFailedStep is a preferred way to disable retries
|
|
53
|
-
// test.disableRetryFailedStep is used for backward compatibility
|
|
54
|
-
if (test.opts.disableRetryFailedStep || test.disableRetryFailedStep) {
|
|
55
|
-
store.autoRetries = false
|
|
56
|
-
output.log(`[Step Retry] Disabled for test: ${test.title}`)
|
|
57
|
-
return // disable retry when a test is not active
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Check if step retries should be disabled due to higher priority scenario retries
|
|
61
|
-
const scenarioRetries = test.retries()
|
|
62
|
-
const stepRetryPriority = RETRY_PRIORITIES.STEP_PLUGIN
|
|
63
|
-
const scenarioPriority = test.opts.retryPriority || 0
|
|
64
|
-
|
|
65
|
-
if (scenarioRetries > 0 && config.deferToScenarioRetries !== false) {
|
|
66
|
-
// Scenario retries are configured with higher or equal priority
|
|
67
|
-
// Option 1: Disable step retries (conservative approach)
|
|
68
|
-
store.autoRetries = false
|
|
69
|
-
output.log(`[Step Retry] Deferred to scenario retries (${scenarioRetries} retries)`)
|
|
70
|
-
return
|
|
71
|
-
|
|
72
|
-
// Option 2: Reduce step retries to avoid excessive total retries
|
|
73
|
-
// const reducedStepRetries = Math.max(1, Math.floor(config.retries / scenarioRetries))
|
|
74
|
-
// config.retries = reducedStepRetries
|
|
75
|
-
// output.log(`[Step Retry] Reduced to ${reducedStepRetries} retries due to scenario retries (${scenarioRetries})`)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// this option is used to set the retries inside _before() block of helpers
|
|
79
|
-
store.autoRetries = true
|
|
80
|
-
test.opts.conditionalRetries = config.retries
|
|
81
|
-
test.opts.stepRetryPriority = stepRetryPriority
|
|
82
|
-
|
|
83
|
-
recorder.retry(config)
|
|
84
|
-
|
|
85
|
-
output.log(`[Step Retry] Enabled with ${config.retries} retries for test: ${test.title}`)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
// Add coordination info for debugging
|
|
89
|
-
event.dispatcher.on(event.test.finished, test => {
|
|
90
|
-
if (test.state === 'passed' && test.opts.conditionalRetries && store.autoRetries) {
|
|
91
|
-
const stepRetries = test.opts.conditionalRetries || 0
|
|
92
|
-
const scenarioRetries = test.retries() || 0
|
|
93
|
-
|
|
94
|
-
if (stepRetries > 0 && scenarioRetries > 0) {
|
|
95
|
-
output.log(`[Retry Coordination] Test used both step retries (${stepRetries}) and scenario retries (${scenarioRetries})`)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
})
|
|
99
|
-
}
|