codeceptjs 4.0.0-beta.7.esm-aria → 4.0.0-beta.8.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.
Files changed (68) hide show
  1. package/README.md +46 -3
  2. package/bin/codecept.js +9 -0
  3. package/bin/test-server.js +64 -0
  4. package/docs/webapi/click.mustache +5 -1
  5. package/lib/ai.js +66 -102
  6. package/lib/codecept.js +99 -24
  7. package/lib/command/generate.js +33 -1
  8. package/lib/command/init.js +7 -3
  9. package/lib/command/run-workers.js +31 -2
  10. package/lib/command/run.js +15 -0
  11. package/lib/command/workers/runTests.js +331 -58
  12. package/lib/config.js +16 -5
  13. package/lib/container.js +15 -13
  14. package/lib/effects.js +1 -1
  15. package/lib/element/WebElement.js +327 -0
  16. package/lib/event.js +10 -1
  17. package/lib/helper/AI.js +11 -11
  18. package/lib/helper/ApiDataFactory.js +34 -6
  19. package/lib/helper/Appium.js +156 -42
  20. package/lib/helper/GraphQL.js +3 -3
  21. package/lib/helper/GraphQLDataFactory.js +4 -4
  22. package/lib/helper/JSONResponse.js +48 -40
  23. package/lib/helper/Mochawesome.js +24 -2
  24. package/lib/helper/Playwright.js +841 -153
  25. package/lib/helper/Puppeteer.js +263 -67
  26. package/lib/helper/REST.js +21 -0
  27. package/lib/helper/WebDriver.js +105 -16
  28. package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
  29. package/lib/helper/extras/PlaywrightReactVueLocator.js +52 -0
  30. package/lib/helper/extras/PlaywrightRestartOpts.js +12 -1
  31. package/lib/helper/network/actions.js +8 -6
  32. package/lib/listener/config.js +11 -3
  33. package/lib/listener/enhancedGlobalRetry.js +110 -0
  34. package/lib/listener/globalTimeout.js +19 -4
  35. package/lib/listener/helpers.js +8 -2
  36. package/lib/listener/retryEnhancer.js +85 -0
  37. package/lib/listener/steps.js +12 -0
  38. package/lib/mocha/asyncWrapper.js +13 -3
  39. package/lib/mocha/cli.js +1 -1
  40. package/lib/mocha/factory.js +3 -0
  41. package/lib/mocha/gherkin.js +1 -1
  42. package/lib/mocha/test.js +6 -0
  43. package/lib/mocha/ui.js +13 -0
  44. package/lib/output.js +62 -18
  45. package/lib/plugin/coverage.js +16 -3
  46. package/lib/plugin/enhancedRetryFailedStep.js +99 -0
  47. package/lib/plugin/htmlReporter.js +3648 -0
  48. package/lib/plugin/retryFailedStep.js +1 -0
  49. package/lib/plugin/stepByStepReport.js +1 -1
  50. package/lib/recorder.js +28 -3
  51. package/lib/result.js +100 -23
  52. package/lib/retryCoordinator.js +207 -0
  53. package/lib/step/base.js +1 -1
  54. package/lib/step/comment.js +2 -2
  55. package/lib/step/meta.js +1 -1
  56. package/lib/template/heal.js +1 -1
  57. package/lib/template/prompts/generatePageObject.js +31 -0
  58. package/lib/template/prompts/healStep.js +13 -0
  59. package/lib/template/prompts/writeStep.js +9 -0
  60. package/lib/test-server.js +334 -0
  61. package/lib/utils/mask_data.js +47 -0
  62. package/lib/utils.js +87 -6
  63. package/lib/workerStorage.js +2 -1
  64. package/lib/workers.js +179 -23
  65. package/package.json +58 -47
  66. package/typings/index.d.ts +19 -7
  67. package/typings/promiseBasedTypes.d.ts +5525 -3759
  68. package/typings/types.d.ts +5791 -3781
@@ -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) {
@@ -116,7 +116,7 @@ const gherkinParser = (text, file) => {
116
116
  )
117
117
  continue
118
118
  }
119
- if (child.scenario && (currentLanguage ? currentLanguage.contexts.ScenarioOutline.includes(child.scenario.keyword) : child.scenario.keyword === 'Scenario Outline')) {
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 { maskSensitiveData } from 'invisi-data'
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) outputProcess = String(process).length === 1 ? `[0${process}]` : `[${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 = isMaskedData() ? maskSensitiveData(msg) : 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 = isMaskedData() ? maskSensitiveData(msg) : 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
- print(styles.error(msg))
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
- print(styles.success(msg))
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 = isMaskedData() ? maskSensitiveData(stepLine) : stepLine
128
- print(' '.repeat(output.stepShift), truncate(_stepLine, output.stepShift))
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
- print(` ${colors.magenta.bold(test.title)}`)
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
- print(` ${colors.green.bold(figures.tick)} ${test.title} ${colors.grey(`in ${test.duration}ms`)}`)
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
- print(` ${colors.red.bold(figures.cross)} ${test.title} ${colors.grey(`in ${test.duration}ms`)}`)
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
- print(` ${colors.yellow.bold('S')} ${test.title}`)
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
@@ -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, async () => {
153
- output.print(`writing ${coverageOptions.outputDir}`)
154
- await coverageReport.generate()
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
+ }