codeceptjs 4.0.0-beta.4 → 4.0.0-beta.6.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 (188) hide show
  1. package/README.md +89 -119
  2. package/bin/codecept.js +53 -54
  3. package/docs/webapi/clearCookie.mustache +1 -1
  4. package/lib/actor.js +70 -102
  5. package/lib/ai.js +131 -121
  6. package/lib/assert/empty.js +11 -12
  7. package/lib/assert/equal.js +16 -21
  8. package/lib/assert/error.js +2 -2
  9. package/lib/assert/include.js +11 -15
  10. package/lib/assert/throws.js +3 -5
  11. package/lib/assert/truth.js +10 -7
  12. package/lib/assert.js +18 -18
  13. package/lib/codecept.js +112 -101
  14. package/lib/colorUtils.js +48 -50
  15. package/lib/command/check.js +206 -0
  16. package/lib/command/configMigrate.js +13 -14
  17. package/lib/command/definitions.js +24 -36
  18. package/lib/command/dryRun.js +16 -16
  19. package/lib/command/generate.js +38 -39
  20. package/lib/command/gherkin/init.js +36 -38
  21. package/lib/command/gherkin/snippets.js +76 -74
  22. package/lib/command/gherkin/steps.js +21 -18
  23. package/lib/command/info.js +49 -15
  24. package/lib/command/init.js +41 -37
  25. package/lib/command/interactive.js +22 -13
  26. package/lib/command/list.js +11 -10
  27. package/lib/command/run-multiple/chunk.js +50 -47
  28. package/lib/command/run-multiple/collection.js +5 -5
  29. package/lib/command/run-multiple/run.js +3 -3
  30. package/lib/command/run-multiple.js +27 -47
  31. package/lib/command/run-rerun.js +6 -7
  32. package/lib/command/run-workers.js +15 -66
  33. package/lib/command/run.js +8 -8
  34. package/lib/command/utils.js +22 -21
  35. package/lib/command/workers/runTests.js +131 -241
  36. package/lib/config.js +111 -49
  37. package/lib/container.js +589 -244
  38. package/lib/data/context.js +16 -18
  39. package/lib/data/dataScenarioConfig.js +9 -9
  40. package/lib/data/dataTableArgument.js +7 -7
  41. package/lib/data/table.js +6 -12
  42. package/lib/effects.js +307 -0
  43. package/lib/els.js +160 -0
  44. package/lib/event.js +24 -19
  45. package/lib/globals.js +141 -0
  46. package/lib/heal.js +89 -81
  47. package/lib/helper/AI.js +3 -2
  48. package/lib/helper/ApiDataFactory.js +19 -19
  49. package/lib/helper/Appium.js +47 -51
  50. package/lib/helper/FileSystem.js +35 -15
  51. package/lib/helper/GraphQL.js +1 -1
  52. package/lib/helper/GraphQLDataFactory.js +4 -4
  53. package/lib/helper/JSONResponse.js +72 -45
  54. package/lib/helper/Mochawesome.js +14 -11
  55. package/lib/helper/Playwright.js +832 -434
  56. package/lib/helper/Puppeteer.js +393 -292
  57. package/lib/helper/REST.js +32 -27
  58. package/lib/helper/WebDriver.js +320 -219
  59. package/lib/helper/errors/ConnectionRefused.js +6 -6
  60. package/lib/helper/errors/ElementAssertion.js +11 -16
  61. package/lib/helper/errors/ElementNotFound.js +5 -9
  62. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  63. package/lib/helper/extras/Console.js +11 -11
  64. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  65. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  66. package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
  67. package/lib/helper/extras/Popup.js +22 -22
  68. package/lib/helper/extras/React.js +29 -30
  69. package/lib/helper/network/actions.js +33 -48
  70. package/lib/helper/network/utils.js +76 -83
  71. package/lib/helper/scripts/blurElement.js +6 -6
  72. package/lib/helper/scripts/focusElement.js +6 -6
  73. package/lib/helper/scripts/highlightElement.js +9 -9
  74. package/lib/helper/scripts/isElementClickable.js +34 -34
  75. package/lib/helper.js +2 -1
  76. package/lib/history.js +23 -20
  77. package/lib/hooks.js +10 -10
  78. package/lib/html.js +90 -100
  79. package/lib/index.js +48 -21
  80. package/lib/listener/config.js +8 -9
  81. package/lib/listener/emptyRun.js +54 -0
  82. package/lib/listener/exit.js +10 -12
  83. package/lib/listener/{retry.js → globalRetry.js} +10 -10
  84. package/lib/listener/globalTimeout.js +166 -0
  85. package/lib/listener/helpers.js +43 -24
  86. package/lib/listener/mocha.js +4 -5
  87. package/lib/listener/result.js +11 -0
  88. package/lib/listener/steps.js +26 -23
  89. package/lib/listener/store.js +20 -0
  90. package/lib/locator.js +213 -192
  91. package/lib/mocha/asyncWrapper.js +264 -0
  92. package/lib/mocha/bdd.js +167 -0
  93. package/lib/mocha/cli.js +341 -0
  94. package/lib/mocha/factory.js +160 -0
  95. package/lib/{interfaces → mocha}/featureConfig.js +33 -13
  96. package/lib/{interfaces → mocha}/gherkin.js +75 -45
  97. package/lib/mocha/hooks.js +121 -0
  98. package/lib/mocha/index.js +21 -0
  99. package/lib/mocha/inject.js +46 -0
  100. package/lib/{interfaces → mocha}/scenarioConfig.js +32 -8
  101. package/lib/mocha/suite.js +89 -0
  102. package/lib/mocha/test.js +178 -0
  103. package/lib/mocha/types.d.ts +42 -0
  104. package/lib/mocha/ui.js +229 -0
  105. package/lib/output.js +86 -64
  106. package/lib/parser.js +44 -44
  107. package/lib/pause.js +160 -139
  108. package/lib/plugin/analyze.js +403 -0
  109. package/lib/plugin/{autoLogin.js → auth.js} +137 -43
  110. package/lib/plugin/autoDelay.js +19 -15
  111. package/lib/plugin/coverage.js +22 -27
  112. package/lib/plugin/customLocator.js +5 -5
  113. package/lib/plugin/customReporter.js +53 -0
  114. package/lib/plugin/heal.js +49 -17
  115. package/lib/plugin/pageInfo.js +140 -0
  116. package/lib/plugin/pauseOnFail.js +4 -3
  117. package/lib/plugin/retryFailedStep.js +60 -19
  118. package/lib/plugin/screenshotOnFail.js +80 -83
  119. package/lib/plugin/stepByStepReport.js +70 -31
  120. package/lib/plugin/stepTimeout.js +7 -13
  121. package/lib/plugin/subtitles.js +10 -9
  122. package/lib/recorder.js +167 -126
  123. package/lib/rerun.js +94 -50
  124. package/lib/result.js +161 -0
  125. package/lib/secret.js +18 -17
  126. package/lib/session.js +95 -89
  127. package/lib/step/base.js +239 -0
  128. package/lib/step/comment.js +10 -0
  129. package/lib/step/config.js +50 -0
  130. package/lib/step/func.js +46 -0
  131. package/lib/step/helper.js +50 -0
  132. package/lib/step/meta.js +99 -0
  133. package/lib/step/record.js +74 -0
  134. package/lib/step/retry.js +11 -0
  135. package/lib/step/section.js +55 -0
  136. package/lib/step.js +18 -332
  137. package/lib/steps.js +54 -0
  138. package/lib/store.js +37 -5
  139. package/lib/template/heal.js +2 -11
  140. package/lib/timeout.js +60 -0
  141. package/lib/transform.js +8 -8
  142. package/lib/translation.js +32 -18
  143. package/lib/utils.js +354 -250
  144. package/lib/workerStorage.js +16 -16
  145. package/lib/workers.js +366 -282
  146. package/package.json +107 -95
  147. package/translations/de-DE.js +5 -4
  148. package/translations/fr-FR.js +5 -4
  149. package/translations/index.js +23 -9
  150. package/translations/it-IT.js +5 -4
  151. package/translations/ja-JP.js +5 -4
  152. package/translations/nl-NL.js +76 -0
  153. package/translations/pl-PL.js +5 -4
  154. package/translations/pt-BR.js +5 -4
  155. package/translations/ru-RU.js +5 -4
  156. package/translations/utils.js +18 -0
  157. package/translations/zh-CN.js +5 -4
  158. package/translations/zh-TW.js +5 -4
  159. package/typings/index.d.ts +177 -186
  160. package/typings/promiseBasedTypes.d.ts +3573 -5941
  161. package/typings/types.d.ts +4042 -6370
  162. package/lib/cli.js +0 -256
  163. package/lib/helper/ExpectHelper.js +0 -391
  164. package/lib/helper/Nightmare.js +0 -1504
  165. package/lib/helper/Protractor.js +0 -1863
  166. package/lib/helper/SoftExpectHelper.js +0 -381
  167. package/lib/helper/TestCafe.js +0 -1414
  168. package/lib/helper/clientscripts/nightmare.js +0 -213
  169. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
  170. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  171. package/lib/helper/testcafe/testcafe-utils.js +0 -62
  172. package/lib/interfaces/bdd.js +0 -81
  173. package/lib/listener/artifacts.js +0 -19
  174. package/lib/listener/timeout.js +0 -109
  175. package/lib/mochaFactory.js +0 -113
  176. package/lib/plugin/allure.js +0 -15
  177. package/lib/plugin/commentStep.js +0 -136
  178. package/lib/plugin/debugErrors.js +0 -67
  179. package/lib/plugin/eachElement.js +0 -127
  180. package/lib/plugin/fakerTransform.js +0 -49
  181. package/lib/plugin/retryTo.js +0 -127
  182. package/lib/plugin/selenoid.js +0 -384
  183. package/lib/plugin/standardActingHelpers.js +0 -3
  184. package/lib/plugin/tryTo.js +0 -115
  185. package/lib/plugin/wdio.js +0 -249
  186. package/lib/scenario.js +0 -224
  187. package/lib/ui.js +0 -236
  188. package/lib/within.js +0 -70
package/lib/recorder.js CHANGED
@@ -1,35 +1,35 @@
1
- const debug = require('debug')('codeceptjs:recorder');
2
- const promiseRetry = require('promise-retry');
3
- const chalk = require('chalk');
4
- const { printObjectProperties } = require('./utils');
5
- const { log } = require('./output');
6
-
7
- const MAX_TASKS = 100;
8
-
9
- let promise;
10
- let running = false;
11
- let errFn;
12
- let queueId = 0;
13
- let sessionId = null;
14
- let asyncErr = null;
15
- let ignoredErrs = [];
16
-
17
- let tasks = [];
18
- let oldPromises = [];
1
+ import debugModule from 'debug'
2
+ const debug = debugModule('codeceptjs:recorder')
3
+ import promiseRetry from 'promise-retry'
4
+ import chalk from 'chalk'
5
+ import { printObjectProperties } from './utils.js'
6
+ import output from './output.js'
7
+ import { TimeoutError } from './timeout.js'
8
+ const MAX_TASKS = 100
9
+
10
+ let promise
11
+ let running = false
12
+ let errFn
13
+ let queueId = 0
14
+ let sessionId = null
15
+ let asyncErr = null
16
+ let ignoredErrs = []
17
+
18
+ let tasks = []
19
+ let oldPromises = []
19
20
 
20
21
  const defaultRetryOptions = {
21
22
  retries: 0,
22
23
  minTimeout: 150,
23
24
  maxTimeout: 10000,
24
- };
25
+ }
25
26
 
26
27
  /**
27
28
  * Singleton object to record all test steps as promises and run them in chain.
28
29
  * @alias recorder
29
30
  * @interface
30
31
  */
31
- module.exports = {
32
-
32
+ export default {
33
33
  /**
34
34
  * @type {Array<Object<string, *>>}
35
35
  * @inner
@@ -43,11 +43,11 @@ module.exports = {
43
43
  * @inner
44
44
  */
45
45
  start() {
46
- debug('Starting recording promises');
47
- running = true;
48
- asyncErr = null;
49
- errFn = null;
50
- this.reset();
46
+ debug('Starting recording promises')
47
+ running = true
48
+ asyncErr = null
49
+ errFn = null
50
+ this.reset()
51
51
  },
52
52
 
53
53
  /**
@@ -55,7 +55,7 @@ module.exports = {
55
55
  * @inner
56
56
  */
57
57
  isRunning() {
58
- return running;
58
+ return running
59
59
  },
60
60
 
61
61
  /**
@@ -64,7 +64,7 @@ module.exports = {
64
64
  */
65
65
  startUnlessRunning() {
66
66
  if (!this.isRunning()) {
67
- this.start();
67
+ this.start()
68
68
  }
69
69
  },
70
70
 
@@ -76,7 +76,7 @@ module.exports = {
76
76
  * @inner
77
77
  */
78
78
  errHandler(fn) {
79
- errFn = fn;
79
+ errFn = fn
80
80
  },
81
81
 
82
82
  /**
@@ -87,16 +87,16 @@ module.exports = {
87
87
  * @inner
88
88
  */
89
89
  reset() {
90
- if (promise && running) this.catch();
91
- queueId++;
92
- sessionId = null;
93
- asyncErr = null;
94
- log(`${currentQueue()} Starting recording promises`);
95
- promise = Promise.resolve();
96
- oldPromises = [];
97
- tasks = [];
98
- ignoredErrs = [];
99
- this.session.running = false;
90
+ if (promise && running) this.catch()
91
+ queueId++
92
+ sessionId = null
93
+ asyncErr = null
94
+ output.log(`${currentQueue()} Starting recording promises`)
95
+ promise = Promise.resolve()
96
+ oldPromises = []
97
+ tasks = []
98
+ ignoredErrs = []
99
+ this.session.running = false
100
100
  // reset this retries makes the retryFailedStep plugin won't work if there is Before/BeforeSuit block due to retries is undefined on Scenario
101
101
  // this.retries = [];
102
102
  },
@@ -123,12 +123,16 @@ module.exports = {
123
123
  * @inner
124
124
  */
125
125
  start(name) {
126
- debug(`${currentQueue()}Starting <${name}> session`);
127
- tasks.push('--->');
128
- oldPromises.push(promise);
129
- this.running = true;
130
- sessionId = name;
131
- promise = Promise.resolve();
126
+ if (sessionId) {
127
+ debug(`${currentQueue()}Session already started as ${sessionId}`)
128
+ this.restore(sessionId)
129
+ }
130
+ debug(`${currentQueue()}Starting <${name}> session`)
131
+ tasks.push('--->')
132
+ oldPromises.push(promise)
133
+ this.running = true
134
+ sessionId = name
135
+ promise = Promise.resolve()
132
136
  },
133
137
 
134
138
  /**
@@ -136,12 +140,12 @@ module.exports = {
136
140
  * @inner
137
141
  */
138
142
  restore(name) {
139
- tasks.push('<---');
140
- debug(`${currentQueue()}Finalize <${name}> session`);
141
- this.running = false;
142
- sessionId = null;
143
- this.catch(errFn);
144
- promise = promise.then(() => oldPromises.pop());
143
+ tasks.push('<---')
144
+ debug(`${currentQueue()}Finalize <${name}> session`)
145
+ this.running = false
146
+ sessionId = null
147
+ this.catch(errFn)
148
+ promise = promise.then(() => oldPromises.pop())
145
149
  },
146
150
 
147
151
  /**
@@ -149,9 +153,8 @@ module.exports = {
149
153
  * @inner
150
154
  */
151
155
  catch(fn) {
152
- promise = promise.catch(fn);
156
+ promise = promise.catch(fn)
153
157
  },
154
-
155
158
  },
156
159
 
157
160
  /**
@@ -171,42 +174,48 @@ module.exports = {
171
174
  */
172
175
  add(taskName, fn = undefined, force = false, retry = undefined, timeout = undefined) {
173
176
  if (typeof taskName === 'function') {
174
- fn = taskName;
175
- taskName = fn.toString();
176
- if (retry === undefined) retry = false;
177
+ fn = taskName
178
+ taskName = fn.toString()
179
+ if (retry === undefined) retry = false
177
180
  }
178
- if (retry === undefined) retry = true;
181
+ if (retry === undefined) retry = true
179
182
  if (!running && !force) {
180
- return;
183
+ return Promise.resolve()
181
184
  }
182
- tasks.push(taskName);
183
- debug(chalk.gray(`${currentQueue()} Queued | ${taskName}`));
185
+ tasks.push(taskName)
186
+ debug(chalk.gray(`${currentQueue()} Queued | ${taskName}`))
184
187
 
185
- return promise = Promise.resolve(promise).then((res) => {
188
+ return (promise = Promise.resolve(promise).then(res => {
186
189
  // prefer options for non-conditional retries
187
- const retryOpts = this.retries.sort((r1, r2) => r1.when && !r2.when).slice(-1).pop();
190
+ const retryOpts = this.retries
191
+ .sort((r1, r2) => r1.when && !r2.when)
192
+ .slice(-1)
193
+ .pop()
188
194
  // no retries or unnamed tasks
195
+ debug(`${currentQueue()} Running | ${taskName} | Timeout: ${timeout || 'None'}`)
196
+ if (retryOpts) debug(`${currentQueue()} Retry opts`, JSON.stringify(retryOpts))
197
+
189
198
  if (!retryOpts || !taskName || !retry) {
190
- const [promise, timer] = getTimeoutPromise(timeout, taskName);
191
- return Promise.race([promise, Promise.resolve(res).then(fn)]).finally(() => clearTimeout(timer));
199
+ const [promise, timer] = getTimeoutPromise(timeout, taskName)
200
+ return Promise.race([promise, Promise.resolve(res).then(fn)]).finally(() => clearTimeout(timer))
192
201
  }
193
202
 
194
- debug(`${currentQueue()} Running | ${taskName}`);
195
-
196
- const retryRules = this.retries.slice().reverse();
203
+ const retryRules = this.retries.slice().reverse()
197
204
  return promiseRetry(Object.assign(defaultRetryOptions, retryOpts), (retry, number) => {
198
- if (number > 1) log(`${currentQueue()}Retrying... Attempt #${number}`);
199
- const [promise, timer] = getTimeoutPromise(timeout, taskName);
200
- return Promise.race([promise, Promise.resolve(res).then(fn)]).finally(() => clearTimeout(timer)).catch((err) => {
201
- if (ignoredErrs.includes(err)) return;
202
- for (const retryObj of retryRules) {
203
- if (!retryObj.when) return retry(err);
204
- if (retryObj.when && retryObj.when(err)) return retry(err);
205
- }
206
- throw err;
207
- });
208
- });
209
- });
205
+ if (number > 1) output.log(`${currentQueue()}Retrying... Attempt #${number}`)
206
+ const [promise, timer] = getTimeoutPromise(timeout, taskName)
207
+ return Promise.race([promise, Promise.resolve(res).then(fn)])
208
+ .finally(() => clearTimeout(timer))
209
+ .catch(err => {
210
+ if (ignoredErrs.includes(err)) return
211
+ for (const retryObj of retryRules) {
212
+ if (!retryObj.when) return retry(err)
213
+ if (retryObj.when && retryObj.when(err)) return retry(err)
214
+ }
215
+ throw err
216
+ })
217
+ })
218
+ }))
210
219
  },
211
220
 
212
221
  /**
@@ -215,15 +224,17 @@ module.exports = {
215
224
  * @inner
216
225
  */
217
226
  retry(opts) {
218
- if (!promise) return;
227
+ if (!promise) return
219
228
 
220
229
  if (opts === null) {
221
- opts = {};
230
+ opts = {}
222
231
  }
223
232
  if (Number.isInteger(opts)) {
224
- opts = { retries: opts };
233
+ opts = { retries: opts }
225
234
  }
226
- return this.add(() => this.retries.push(opts));
235
+ // Push retry options immediately so first step can see them
236
+ this.retries.push(opts)
237
+ return Promise.resolve()
227
238
  },
228
239
 
229
240
  /**
@@ -232,20 +243,26 @@ module.exports = {
232
243
  * @inner
233
244
  */
234
245
  catch(customErrFn) {
235
- const fnDescription = customErrFn?.toString()?.replace(/\s{2,}/g, ' ').replace(/\n/g, ' ')?.slice(0, 50);
236
- debug(chalk.gray(`${currentQueue()} Queued | catch with error handler ${fnDescription || ''}`));
237
- return promise = promise.catch((err) => {
238
- log(`${currentQueue()}Error | ${err} ${fnDescription}...`);
239
- if (!(err instanceof Error)) { // strange things may happen
240
- err = new Error(`[Wrapped Error] ${printObjectProperties(err)}`); // we should be prepared for them
246
+ const fnDescription = customErrFn
247
+ ?.toString()
248
+ ?.replace(/\s{2,}/g, ' ')
249
+ .replace(/\n/g, ' ')
250
+ ?.slice(0, 50)
251
+ debug(chalk.gray(`${currentQueue()} Queued | catch with error handler ${fnDescription || ''}`))
252
+ if (!promise) promise = Promise.resolve()
253
+ return (promise = promise.catch(err => {
254
+ output.log(`${currentQueue()}Error | ${err} ${fnDescription}...`)
255
+ if (!(err instanceof Error)) {
256
+ // strange things may happen
257
+ err = new Error(`[Wrapped Error] ${printObjectProperties(err)}`) // we should be prepared for them
241
258
  }
242
259
  if (customErrFn) {
243
- customErrFn(err);
260
+ customErrFn(err)
244
261
  } else if (errFn) {
245
- errFn(err);
262
+ errFn(err)
246
263
  }
247
- this.stop();
248
- });
264
+ this.stop()
265
+ }))
249
266
  },
250
267
 
251
268
  /**
@@ -254,17 +271,37 @@ module.exports = {
254
271
  * @inner
255
272
  */
256
273
  catchWithoutStop(customErrFn) {
257
- const fnDescription = customErrFn?.toString()?.replace(/\s{2,}/g, ' ').replace(/\n/g, ' ')?.slice(0, 50);
258
- return promise = promise.catch((err) => {
259
- if (ignoredErrs.includes(err)) return; // already caught
260
- log(`${currentQueue()} Error (Non-Terminated) | ${err} | ${fnDescription || ''}...`);
261
- if (!(err instanceof Error)) { // strange things may happen
262
- err = new Error(`[Wrapped Error] ${JSON.stringify(err)}`); // we should be prepared for them
274
+ const fnDescription = customErrFn
275
+ ?.toString()
276
+ ?.replace(/\s{2,}/g, ' ')
277
+ .replace(/\n/g, ' ')
278
+ ?.slice(0, 50)
279
+ if (!promise) promise = Promise.resolve()
280
+ return (promise = promise.catch(err => {
281
+ if (ignoredErrs.includes(err)) return // already caught
282
+
283
+ // Handle terminal errors - don't continue, re-throw immediately
284
+ if (err && (err.isTerminal || (err.message && (err.message.includes('ERR_ABORTED') || err.message.includes('frame was detached') || err.message.includes('Target page, context or browser has been closed'))))) {
285
+ output.log(`${currentQueue()} Terminal Error | ${err} | ${fnDescription || ''}...`)
286
+ throw err // Re-throw terminal errors immediately
287
+ }
288
+
289
+ output.log(`${currentQueue()} Error (Non-Terminated) | ${err} | ${fnDescription || ''}...`)
290
+ if (!(err instanceof Error)) {
291
+ // strange things may happen
292
+ err = new Error(`[Wrapped Error] ${JSON.stringify(err)}`) // we should be prepared for them
263
293
  }
264
294
  if (customErrFn) {
265
- return customErrFn(err);
295
+ try {
296
+ const result = customErrFn(err)
297
+ // If customErrFn returns a value (not throwing), treat it as handled
298
+ return result
299
+ } catch (thrownErr) {
300
+ // If customErrFn throws an error, propagate it up
301
+ throw thrownErr
302
+ }
266
303
  }
267
- });
304
+ }))
268
305
  },
269
306
 
270
307
  /**
@@ -276,15 +313,15 @@ module.exports = {
276
313
  */
277
314
 
278
315
  throw(err) {
279
- if (ignoredErrs.includes(err)) return promise; // already caught
316
+ if (ignoredErrs.includes(err)) return promise // already caught
280
317
  return this.add(`throw error: ${err.message}`, () => {
281
- if (ignoredErrs.includes(err)) return; // already caught
282
- throw err;
283
- });
318
+ if (ignoredErrs.includes(err)) return // already caught
319
+ throw err
320
+ })
284
321
  },
285
322
 
286
323
  ignoreErr(err) {
287
- ignoredErrs.push(err);
324
+ ignoredErrs.push(err)
288
325
  },
289
326
 
290
327
  /**
@@ -293,7 +330,7 @@ module.exports = {
293
330
  */
294
331
  saveFirstAsyncError(err) {
295
332
  if (asyncErr === null) {
296
- asyncErr = err;
333
+ asyncErr = err
297
334
  }
298
335
  },
299
336
 
@@ -302,7 +339,7 @@ module.exports = {
302
339
  * @inner
303
340
  */
304
341
  getAsyncErr() {
305
- return asyncErr;
342
+ return asyncErr
306
343
  },
307
344
 
308
345
  /**
@@ -310,7 +347,7 @@ module.exports = {
310
347
  * @inner
311
348
  */
312
349
  cleanAsyncErr() {
313
- asyncErr = null;
350
+ asyncErr = null
314
351
  },
315
352
 
316
353
  /**
@@ -319,9 +356,9 @@ module.exports = {
319
356
  * @inner
320
357
  */
321
358
  stop() {
322
- debug(this.toString());
323
- log(`${currentQueue()} Stopping recording promises`);
324
- running = false;
359
+ debug(this.toString())
360
+ output.log(`${currentQueue()} Stopping recording promises`)
361
+ running = false
325
362
  },
326
363
 
327
364
  /**
@@ -332,7 +369,7 @@ module.exports = {
332
369
  * @inner
333
370
  */
334
371
  promise() {
335
- return promise;
372
+ return promise
336
373
  },
337
374
 
338
375
  /**
@@ -341,7 +378,7 @@ module.exports = {
341
378
  * @inner
342
379
  */
343
380
  scheduled() {
344
- return tasks.slice(-MAX_TASKS).join('\n');
381
+ return tasks.slice(-MAX_TASKS).join('\n')
345
382
  },
346
383
 
347
384
  /**
@@ -350,7 +387,7 @@ module.exports = {
350
387
  * @inner
351
388
  */
352
389
  getQueueId() {
353
- return queueId;
390
+ return queueId
354
391
  },
355
392
 
356
393
  /**
@@ -359,21 +396,25 @@ module.exports = {
359
396
  * @inner
360
397
  */
361
398
  toString() {
362
- return `Queue: ${currentQueue()}\n\nTasks: ${this.scheduled()}`;
399
+ return `Queue: ${currentQueue()}\n\nTasks: ${this.scheduled()}`
363
400
  },
364
-
365
- };
401
+ }
366
402
 
367
403
  function getTimeoutPromise(timeoutMs, taskName) {
368
- let timer;
369
- if (timeoutMs) debug(`Timing out in ${timeoutMs}ms`);
370
- return [new Promise((done, reject) => {
371
- timer = setTimeout(() => { reject(new Error(`Action ${taskName} was interrupted on step timeout ${timeoutMs}ms`)); }, timeoutMs || 2e9);
372
- }), timer];
404
+ let timer
405
+ if (timeoutMs) debug(`Timing out in ${timeoutMs}ms`)
406
+ return [
407
+ new Promise((done, reject) => {
408
+ timer = setTimeout(() => {
409
+ reject(new TimeoutError(`Action ${taskName} was interrupted on timeout ${timeoutMs}ms`))
410
+ }, timeoutMs || 2e9)
411
+ }),
412
+ timer,
413
+ ]
373
414
  }
374
415
 
375
416
  function currentQueue() {
376
- let session = '';
377
- if (sessionId) session = `<${sessionId}> `;
378
- return `[${queueId}] ${session}`;
417
+ let session = ''
418
+ if (sessionId) session = `<${sessionId}> `
419
+ return `[${queueId}] ${session}`
379
420
  }
package/lib/rerun.js CHANGED
@@ -1,81 +1,125 @@
1
- const fsPath = require('path');
2
- const container = require('./container');
3
- const event = require('./event');
4
- const BaseCodecept = require('./codecept');
5
- const output = require('./output');
1
+ import fsPath from 'path'
2
+ import container from './container.js'
3
+ import event from './event.js'
4
+ import BaseCodecept from './codecept.js'
5
+ import output from './output.js'
6
+ import { createRequire } from 'module'
7
+
8
+ const require = createRequire(import.meta.url)
6
9
 
7
10
  class CodeceptRerunner extends BaseCodecept {
8
- runOnce(test) {
9
- return new Promise((resolve, reject) => {
10
- // @ts-ignore
11
- container.createMocha();
12
- const mocha = container.mocha();
13
- this.testFiles.forEach((file) => {
14
- delete require.cache[file];
15
- });
16
- mocha.files = this.testFiles;
17
- if (test) {
18
- if (!fsPath.isAbsolute(test)) {
19
- test = fsPath.join(global.codecept_dir, test);
20
- }
21
- mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test);
22
- }
11
+ async runOnce(test) {
12
+ await container.started()
13
+
14
+ // Ensure translations are loaded for Gherkin features
15
+ try {
16
+ const { loadTranslations } = await import('./mocha/gherkin.js')
17
+ await loadTranslations()
18
+ } catch (e) {
19
+ // Ignore if gherkin module not available
20
+ }
21
+
22
+ return new Promise(async (resolve, reject) => {
23
23
  try {
24
- mocha.run((failures) => {
25
- if (failures === 0) {
26
- resolve();
24
+ // Create a fresh Mocha instance for each run
25
+ // @ts-ignore
26
+ container.createMocha()
27
+ const mocha = container.mocha()
28
+
29
+ let filesToRun = this.testFiles
30
+ if (test) {
31
+ if (!fsPath.isAbsolute(test)) {
32
+ test = fsPath.join(global.codecept_dir, test)
33
+ }
34
+ filesToRun = this.testFiles.filter(t => fsPath.basename(t, '.js') === test || t === test)
35
+ }
36
+
37
+ // Clear any existing tests/suites
38
+ mocha.suite.suites = []
39
+ mocha.suite.tests = []
40
+
41
+ // Manually load each test file by importing it
42
+ for (const file of filesToRun) {
43
+ try {
44
+ // Clear CommonJS cache if available (for mixed environments)
45
+ try {
46
+ delete require.cache[file]
47
+ } catch (e) {
48
+ // ESM modules don't have require.cache, ignore
49
+ }
50
+
51
+ // Force reload the module by using a cache-busting query parameter
52
+ const fileUrl = `${fsPath.resolve(file)}?t=${Date.now()}`
53
+ await import(fileUrl)
54
+ } catch (e) {
55
+ console.error(`Error loading test file ${file}:`, e)
56
+ }
57
+ }
58
+
59
+ const done = () => {
60
+ event.emit(event.all.result, container.result())
61
+ event.emit(event.all.after, this)
62
+
63
+ // Check if there were any failures
64
+ if (container.result().hasFailed) {
65
+ reject(new Error('Test run failed'))
27
66
  } else {
28
- reject(new Error(`${failures} tests fail`));
67
+ resolve(undefined)
29
68
  }
30
- });
69
+ }
70
+
71
+ event.emit(event.all.before, this)
72
+ mocha.run(() => done())
31
73
  } catch (e) {
32
- reject(e);
74
+ output.error(e.stack)
75
+ reject(e)
33
76
  }
34
- });
77
+ })
35
78
  }
36
79
 
37
80
  async runTests(test) {
38
- const configRerun = this.config.rerun || {};
39
- const minSuccess = configRerun.minSuccess || 1;
40
- const maxReruns = configRerun.maxReruns || 1;
81
+ const configRerun = this.config.rerun || {}
82
+ const minSuccess = configRerun.minSuccess || 1
83
+ const maxReruns = configRerun.maxReruns || 1
41
84
  if (minSuccess > maxReruns) {
42
- process.exitCode = 1;
43
- throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`);
85
+ process.exitCode = 1
86
+ throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`)
44
87
  }
45
88
  if (maxReruns === 1) {
46
- await this.runOnce(test);
47
- return;
89
+ await this.runOnce(test)
90
+ return
48
91
  }
49
- let successCounter = 0;
50
- let rerunsCounter = 0;
92
+ let successCounter = 0
93
+ let rerunsCounter = 0
51
94
  while (rerunsCounter < maxReruns && successCounter < minSuccess) {
52
- rerunsCounter++;
95
+ container.result().reset() // reset result
96
+ rerunsCounter++
53
97
  try {
54
- await this.runOnce(test);
55
- successCounter++;
56
- output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`);
98
+ await this.runOnce(test)
99
+ successCounter++
100
+ output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`)
57
101
  } catch (e) {
58
- output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`);
59
- console.error(e);
102
+ output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`)
103
+ console.error(e)
60
104
  }
61
105
  }
62
106
  if (successCounter < minSuccess) {
63
- throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`);
107
+ throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`)
64
108
  }
65
109
  }
66
110
 
67
111
  async run(test) {
68
- event.emit(event.all.before, this);
112
+ event.emit(event.all.before, this)
69
113
  try {
70
- await this.runTests(test);
114
+ await this.runTests(test)
71
115
  } catch (e) {
72
- output.error(e.stack);
73
- throw e;
116
+ output.error(e.stack)
117
+ throw e
74
118
  } finally {
75
- event.emit(event.all.result, this);
76
- event.emit(event.all.after, this);
119
+ event.emit(event.all.result, this)
120
+ event.emit(event.all.after, this)
77
121
  }
78
122
  }
79
123
  }
80
124
 
81
- module.exports = CodeceptRerunner;
125
+ export default CodeceptRerunner