codeceptjs 4.0.0-beta.7.esm-aria → 4.0.0-beta.9.esm-aria
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 +46 -3
- package/bin/codecept.js +9 -0
- package/bin/test-server.js +64 -0
- package/docs/webapi/click.mustache +5 -1
- package/lib/ai.js +66 -102
- package/lib/codecept.js +99 -24
- package/lib/command/generate.js +33 -1
- package/lib/command/init.js +7 -3
- package/lib/command/run-workers.js +31 -2
- package/lib/command/run.js +15 -0
- package/lib/command/workers/runTests.js +331 -58
- package/lib/config.js +16 -5
- package/lib/container.js +15 -13
- package/lib/effects.js +1 -1
- package/lib/element/WebElement.js +327 -0
- package/lib/event.js +10 -1
- package/lib/helper/AI.js +11 -11
- package/lib/helper/ApiDataFactory.js +34 -6
- package/lib/helper/Appium.js +156 -42
- package/lib/helper/GraphQL.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +48 -40
- package/lib/helper/Mochawesome.js +24 -2
- package/lib/helper/Playwright.js +841 -153
- package/lib/helper/Puppeteer.js +263 -67
- package/lib/helper/REST.js +21 -0
- package/lib/helper/WebDriver.js +105 -16
- package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
- package/lib/helper/extras/PlaywrightReactVueLocator.js +52 -0
- package/lib/helper/extras/PlaywrightRestartOpts.js +12 -1
- package/lib/helper/network/actions.js +8 -6
- package/lib/listener/config.js +11 -3
- package/lib/listener/enhancedGlobalRetry.js +110 -0
- package/lib/listener/globalTimeout.js +19 -4
- package/lib/listener/helpers.js +8 -2
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +12 -0
- package/lib/mocha/asyncWrapper.js +13 -3
- package/lib/mocha/cli.js +1 -1
- package/lib/mocha/factory.js +3 -0
- package/lib/mocha/gherkin.js +1 -1
- package/lib/mocha/test.js +6 -0
- package/lib/mocha/ui.js +13 -0
- package/lib/output.js +62 -18
- package/lib/plugin/coverage.js +16 -3
- package/lib/plugin/enhancedRetryFailedStep.js +99 -0
- package/lib/plugin/htmlReporter.js +3648 -0
- package/lib/plugin/retryFailedStep.js +1 -0
- package/lib/plugin/stepByStepReport.js +1 -1
- package/lib/recorder.js +28 -3
- package/lib/result.js +100 -23
- package/lib/retryCoordinator.js +207 -0
- package/lib/step/base.js +1 -1
- package/lib/step/comment.js +2 -2
- package/lib/step/meta.js +1 -1
- package/lib/template/heal.js +1 -1
- package/lib/template/prompts/generatePageObject.js +31 -0
- package/lib/template/prompts/healStep.js +13 -0
- package/lib/template/prompts/writeStep.js +9 -0
- package/lib/test-server.js +334 -0
- package/lib/utils/mask_data.js +47 -0
- package/lib/utils.js +87 -6
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +179 -23
- package/package.json +59 -47
- package/typings/index.d.ts +19 -7
- package/typings/promiseBasedTypes.d.ts +5534 -3764
- package/typings/types.d.ts +5789 -3775
package/lib/mocha/factory.js
CHANGED
|
@@ -6,6 +6,7 @@ import reporter from './cli.js'
|
|
|
6
6
|
import gherkinParser, { loadTranslations } from './gherkin.js'
|
|
7
7
|
import output from '../output.js'
|
|
8
8
|
import scenarioUiFunction from './ui.js'
|
|
9
|
+
import { initMochaGlobals } from '../globals.js'
|
|
9
10
|
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url)
|
|
11
12
|
const __dirname = fsPath.dirname(__filename)
|
|
@@ -23,6 +24,8 @@ class MochaFactory {
|
|
|
23
24
|
if (mocha.suite && mocha.suite.emit) {
|
|
24
25
|
const context = {}
|
|
25
26
|
mocha.suite.emit('pre-require', context, '', mocha)
|
|
27
|
+
// Also set globals immediately so they're available when ESM modules load
|
|
28
|
+
initMochaGlobals(context)
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
Mocha.Runner.prototype.uncaught = function (err) {
|
package/lib/mocha/gherkin.js
CHANGED
|
@@ -116,7 +116,7 @@ const gherkinParser = (text, file) => {
|
|
|
116
116
|
)
|
|
117
117
|
continue
|
|
118
118
|
}
|
|
119
|
-
if (child.scenario && (currentLanguage ? currentLanguage.contexts.ScenarioOutline
|
|
119
|
+
if (child.scenario && (currentLanguage ? currentLanguage.contexts.ScenarioOutline === child.scenario.keyword : child.scenario.keyword === 'Scenario Outline')) {
|
|
120
120
|
for (const examples of child.scenario.examples) {
|
|
121
121
|
const fields = examples.tableHeader.cells.map(c => c.value)
|
|
122
122
|
for (const example of examples.tableBody) {
|
package/lib/mocha/test.js
CHANGED
|
@@ -78,6 +78,12 @@ function deserializeTest(test) {
|
|
|
78
78
|
test.parent = Object.assign(new Suite(test.parent?.title || 'Suite'), test.parent)
|
|
79
79
|
enhanceMochaSuite(test.parent)
|
|
80
80
|
if (test.steps) test.steps = test.steps.map(step => Object.assign(new Step(step.title), step))
|
|
81
|
+
|
|
82
|
+
// Restore the custom fullTitle function to maintain consistency with original test
|
|
83
|
+
if (test.parent) {
|
|
84
|
+
test.fullTitle = () => `${test.parent.title}: ${test.title}`
|
|
85
|
+
}
|
|
86
|
+
|
|
81
87
|
return test
|
|
82
88
|
}
|
|
83
89
|
|
package/lib/mocha/ui.js
CHANGED
|
@@ -110,6 +110,19 @@ export default function (suite) {
|
|
|
110
110
|
return new FeatureConfig(suite)
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* Exclusive test suite - runs only this feature.
|
|
115
|
+
* @global
|
|
116
|
+
* @kind constant
|
|
117
|
+
* @type {CodeceptJS.IFeature}
|
|
118
|
+
*/
|
|
119
|
+
context.Feature.only = function (title, opts) {
|
|
120
|
+
const reString = `^${escapeRe(`${title}:`)}`
|
|
121
|
+
mocha.grep(new RegExp(reString))
|
|
122
|
+
process.env.FEATURE_ONLY = true
|
|
123
|
+
return context.Feature(title, opts)
|
|
124
|
+
}
|
|
125
|
+
|
|
113
126
|
/**
|
|
114
127
|
* Pending test suite.
|
|
115
128
|
* @global
|
package/lib/output.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import colors from 'chalk'
|
|
2
2
|
import figures from 'figures'
|
|
3
|
-
import {
|
|
3
|
+
import { maskData, shouldMaskData, getMaskConfig } from './utils/mask_data.js'
|
|
4
4
|
|
|
5
5
|
const styles = {
|
|
6
6
|
error: colors.bgRed.white.bold,
|
|
@@ -50,7 +50,40 @@ const output = {
|
|
|
50
50
|
*/
|
|
51
51
|
process(process) {
|
|
52
52
|
if (process === null) return (outputProcess = '')
|
|
53
|
-
if (process)
|
|
53
|
+
if (process) {
|
|
54
|
+
// Handle objects by converting to empty string or extracting properties
|
|
55
|
+
let processValue = process
|
|
56
|
+
if (typeof process === 'object') {
|
|
57
|
+
// If it's an object, try to extract a numeric value or use empty string
|
|
58
|
+
processValue = process.id || process.index || process.worker || ''
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if this is a run-multiple process (contains : or .)
|
|
62
|
+
// Format: "1.runName:browserName" from run-multiple
|
|
63
|
+
if (String(processValue).includes(':') || (String(processValue).includes('.') && String(processValue).split('.').length > 1)) {
|
|
64
|
+
// Keep original format for run-multiple
|
|
65
|
+
outputProcess = colors.cyan.bold(`[${processValue}]`)
|
|
66
|
+
} else {
|
|
67
|
+
// Standard worker format for run-workers
|
|
68
|
+
const processNum = parseInt(processValue, 10)
|
|
69
|
+
const processStr = !isNaN(processNum) ? String(processNum).padStart(2, '0') : String(processValue).padStart(2, '0')
|
|
70
|
+
|
|
71
|
+
// Assign different colors to different workers for better identification
|
|
72
|
+
const workerColors = [
|
|
73
|
+
colors.cyan, // Worker 01 - Cyan
|
|
74
|
+
colors.magenta, // Worker 02 - Magenta
|
|
75
|
+
colors.green, // Worker 03 - Green
|
|
76
|
+
colors.yellow, // Worker 04 - Yellow
|
|
77
|
+
colors.blue, // Worker 05 - Blue
|
|
78
|
+
colors.red, // Worker 06 - Red
|
|
79
|
+
colors.white, // Worker 07 - White
|
|
80
|
+
colors.gray, // Worker 08 - Gray
|
|
81
|
+
]
|
|
82
|
+
const workerIndex = !isNaN(processNum) ? processNum - 1 : -1
|
|
83
|
+
const colorFn = workerIndex >= 0 && workerColors[workerIndex % workerColors.length] ? workerColors[workerIndex % workerColors.length] : colors.cyan
|
|
84
|
+
outputProcess = colorFn.bold(`[Worker ${processStr}]`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
54
87
|
return outputProcess
|
|
55
88
|
},
|
|
56
89
|
|
|
@@ -59,7 +92,7 @@ const output = {
|
|
|
59
92
|
* @param {string} msg
|
|
60
93
|
*/
|
|
61
94
|
debug(msg) {
|
|
62
|
-
const _msg =
|
|
95
|
+
const _msg = shouldMaskData() ? maskData(msg, getMaskConfig()) : msg
|
|
63
96
|
if (outputLevel >= 2) {
|
|
64
97
|
print(' '.repeat(output.stepShift), styles.debug(`${figures.pointerSmall} ${_msg}`))
|
|
65
98
|
}
|
|
@@ -70,7 +103,7 @@ const output = {
|
|
|
70
103
|
* @param {string} msg
|
|
71
104
|
*/
|
|
72
105
|
log(msg) {
|
|
73
|
-
const _msg =
|
|
106
|
+
const _msg = shouldMaskData() ? maskData(msg, getMaskConfig()) : msg
|
|
74
107
|
if (outputLevel >= 3) {
|
|
75
108
|
print(' '.repeat(output.stepShift), styles.log(truncate(` ${_msg}`, output.stepShift)))
|
|
76
109
|
}
|
|
@@ -81,7 +114,8 @@ const output = {
|
|
|
81
114
|
* @param {string} msg
|
|
82
115
|
*/
|
|
83
116
|
error(msg) {
|
|
84
|
-
|
|
117
|
+
const _msg = shouldMaskData() ? maskData(msg, getMaskConfig()) : msg
|
|
118
|
+
print(styles.error(_msg))
|
|
85
119
|
},
|
|
86
120
|
|
|
87
121
|
/**
|
|
@@ -89,7 +123,8 @@ const output = {
|
|
|
89
123
|
* @param {string} msg
|
|
90
124
|
*/
|
|
91
125
|
success(msg) {
|
|
92
|
-
|
|
126
|
+
const _msg = shouldMaskData() ? maskData(msg, getMaskConfig()) : msg
|
|
127
|
+
print(styles.success(_msg))
|
|
93
128
|
},
|
|
94
129
|
|
|
95
130
|
/**
|
|
@@ -124,8 +159,8 @@ const output = {
|
|
|
124
159
|
stepLine += colors.grey(step.comment.split('\n').join('\n' + ' '.repeat(4)))
|
|
125
160
|
}
|
|
126
161
|
|
|
127
|
-
const _stepLine =
|
|
128
|
-
print(' '.repeat(
|
|
162
|
+
const _stepLine = shouldMaskData() ? maskData(stepLine, getMaskConfig()) : stepLine
|
|
163
|
+
print(' '.repeat(this.stepShift), truncate(_stepLine, this.spaceShift))
|
|
129
164
|
},
|
|
130
165
|
|
|
131
166
|
/** @namespace */
|
|
@@ -147,25 +182,38 @@ const output = {
|
|
|
147
182
|
* @param {Mocha.Test} test
|
|
148
183
|
*/
|
|
149
184
|
started(test) {
|
|
150
|
-
|
|
185
|
+
// Only show feature name in workers mode (when outputProcess is set)
|
|
186
|
+
const featureName = outputProcess && test.parent?.title ? `${colors.cyan.bold(test.parent.title)} › ` : ''
|
|
187
|
+
print(` ${featureName}${colors.magenta.bold(test.title)}`)
|
|
151
188
|
},
|
|
152
189
|
/**
|
|
153
190
|
* @param {Mocha.Test} test
|
|
154
191
|
*/
|
|
155
192
|
passed(test) {
|
|
156
|
-
|
|
193
|
+
// Only show feature name in workers mode (when outputProcess is set)
|
|
194
|
+
const featureName = outputProcess && test.parent?.title ? `${colors.cyan(test.parent.title)} › ` : ''
|
|
195
|
+
const scenarioName = colors.bold(test.title)
|
|
196
|
+
const executionTime = colors.cyan(`in ${test.duration}ms`)
|
|
197
|
+
print(` ${colors.green.bold(figures.tick)} ${featureName}${scenarioName} ${executionTime}`)
|
|
157
198
|
},
|
|
158
199
|
/**
|
|
159
200
|
* @param {Mocha.Test} test
|
|
160
201
|
*/
|
|
161
202
|
failed(test) {
|
|
162
|
-
|
|
203
|
+
// Only show feature name in workers mode (when outputProcess is set)
|
|
204
|
+
const featureName = outputProcess && test.parent?.title ? `${colors.yellow(test.parent.title)} › ` : ''
|
|
205
|
+
const scenarioName = colors.bold(test.title)
|
|
206
|
+
const executionTime = colors.yellow(`in ${test.duration}ms`)
|
|
207
|
+
print(` ${colors.red.bold(figures.cross)} ${featureName}${scenarioName} ${executionTime}`)
|
|
163
208
|
},
|
|
164
209
|
/**
|
|
165
210
|
* @param {Mocha.Test} test
|
|
166
211
|
*/
|
|
167
212
|
skipped(test) {
|
|
168
|
-
|
|
213
|
+
// Only show feature name in workers mode (when outputProcess is set)
|
|
214
|
+
const featureName = outputProcess && test.parent?.title ? `${colors.gray(test.parent.title)} › ` : ''
|
|
215
|
+
const scenarioName = colors.bold(test.title)
|
|
216
|
+
print(` ${colors.yellow.bold('S')} ${featureName}${scenarioName}`)
|
|
169
217
|
},
|
|
170
218
|
},
|
|
171
219
|
|
|
@@ -256,6 +304,8 @@ const output = {
|
|
|
256
304
|
},
|
|
257
305
|
}
|
|
258
306
|
|
|
307
|
+
export default output
|
|
308
|
+
|
|
259
309
|
function print(...msg) {
|
|
260
310
|
if (outputProcess) {
|
|
261
311
|
msg.unshift(outputProcess)
|
|
@@ -278,9 +328,3 @@ function truncate(msg, gap = 0) {
|
|
|
278
328
|
}
|
|
279
329
|
return msg
|
|
280
330
|
}
|
|
281
|
-
|
|
282
|
-
function isMaskedData() {
|
|
283
|
-
return global.maskSensitiveData === true || false
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export default output
|
package/lib/plugin/coverage.js
CHANGED
|
@@ -149,9 +149,22 @@ export default function (config) {
|
|
|
149
149
|
const coverageReport = new CoverageReport(coverageOptions)
|
|
150
150
|
coverageReport.cleanCache()
|
|
151
151
|
|
|
152
|
-
event.dispatcher.on(event.all.after,
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
event.dispatcher.on(event.all.after, () => {
|
|
153
|
+
// Add coverage generation to recorder to ensure it completes before process exit
|
|
154
|
+
recorder.add(
|
|
155
|
+
'generate coverage report',
|
|
156
|
+
async () => {
|
|
157
|
+
try {
|
|
158
|
+
output.print(`writing ${coverageOptions.outputDir}`)
|
|
159
|
+
await coverageReport.generate()
|
|
160
|
+
} catch (error) {
|
|
161
|
+
output.print(`Failed to generate coverage report: ${error.message}`)
|
|
162
|
+
// Don't throw - coverage failure shouldn't fail tests
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
true,
|
|
166
|
+
false,
|
|
167
|
+
)
|
|
155
168
|
})
|
|
156
169
|
|
|
157
170
|
// we're going to try to "start" coverage before each step because this is
|
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
}
|