codeceptjs 3.7.6-beta.4 → 4.0.0-beta.10.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 (191) hide show
  1. package/README.md +1 -3
  2. package/bin/codecept.js +51 -53
  3. package/bin/test-server.js +14 -3
  4. package/docs/webapi/click.mustache +5 -1
  5. package/lib/actor.js +15 -11
  6. package/lib/ai.js +72 -107
  7. package/lib/assert/empty.js +9 -8
  8. package/lib/assert/equal.js +15 -17
  9. package/lib/assert/error.js +2 -2
  10. package/lib/assert/include.js +9 -11
  11. package/lib/assert/throws.js +1 -1
  12. package/lib/assert/truth.js +8 -5
  13. package/lib/assert.js +18 -18
  14. package/lib/codecept.js +102 -75
  15. package/lib/colorUtils.js +48 -50
  16. package/lib/command/check.js +32 -27
  17. package/lib/command/configMigrate.js +11 -10
  18. package/lib/command/definitions.js +16 -10
  19. package/lib/command/dryRun.js +16 -16
  20. package/lib/command/generate.js +62 -27
  21. package/lib/command/gherkin/init.js +36 -38
  22. package/lib/command/gherkin/snippets.js +14 -14
  23. package/lib/command/gherkin/steps.js +21 -18
  24. package/lib/command/info.js +8 -8
  25. package/lib/command/init.js +36 -29
  26. package/lib/command/interactive.js +11 -10
  27. package/lib/command/list.js +10 -9
  28. package/lib/command/run-multiple/chunk.js +5 -5
  29. package/lib/command/run-multiple/collection.js +5 -5
  30. package/lib/command/run-multiple/run.js +3 -3
  31. package/lib/command/run-multiple.js +16 -13
  32. package/lib/command/run-rerun.js +6 -7
  33. package/lib/command/run-workers.js +24 -9
  34. package/lib/command/run.js +23 -8
  35. package/lib/command/utils.js +20 -18
  36. package/lib/command/workers/runTests.js +197 -114
  37. package/lib/config.js +124 -51
  38. package/lib/container.js +438 -87
  39. package/lib/data/context.js +6 -5
  40. package/lib/data/dataScenarioConfig.js +1 -1
  41. package/lib/data/dataTableArgument.js +1 -1
  42. package/lib/data/table.js +1 -1
  43. package/lib/effects.js +94 -10
  44. package/lib/element/WebElement.js +2 -2
  45. package/lib/els.js +11 -9
  46. package/lib/event.js +11 -10
  47. package/lib/globals.js +141 -0
  48. package/lib/heal.js +12 -12
  49. package/lib/helper/AI.js +11 -11
  50. package/lib/helper/ApiDataFactory.js +50 -19
  51. package/lib/helper/Appium.js +19 -27
  52. package/lib/helper/FileSystem.js +32 -12
  53. package/lib/helper/GraphQL.js +3 -3
  54. package/lib/helper/GraphQLDataFactory.js +4 -4
  55. package/lib/helper/JSONResponse.js +25 -29
  56. package/lib/helper/Mochawesome.js +7 -4
  57. package/lib/helper/Playwright.js +902 -164
  58. package/lib/helper/Puppeteer.js +383 -76
  59. package/lib/helper/REST.js +29 -12
  60. package/lib/helper/WebDriver.js +268 -61
  61. package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
  62. package/lib/helper/errors/ConnectionRefused.js +6 -6
  63. package/lib/helper/errors/ElementAssertion.js +11 -16
  64. package/lib/helper/errors/ElementNotFound.js +5 -9
  65. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  66. package/lib/helper/extras/Console.js +11 -11
  67. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  68. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  69. package/lib/helper/extras/PlaywrightReactVueLocator.js +18 -9
  70. package/lib/helper/extras/PlaywrightRestartOpts.js +34 -23
  71. package/lib/helper/extras/Popup.js +1 -1
  72. package/lib/helper/extras/React.js +29 -30
  73. package/lib/helper/network/actions.js +29 -44
  74. package/lib/helper/network/utils.js +76 -83
  75. package/lib/helper/scripts/blurElement.js +6 -6
  76. package/lib/helper/scripts/focusElement.js +6 -6
  77. package/lib/helper/scripts/highlightElement.js +9 -9
  78. package/lib/helper/scripts/isElementClickable.js +34 -34
  79. package/lib/helper.js +2 -1
  80. package/lib/history.js +23 -20
  81. package/lib/hooks.js +10 -10
  82. package/lib/html.js +90 -100
  83. package/lib/index.js +48 -21
  84. package/lib/listener/config.js +19 -12
  85. package/lib/listener/emptyRun.js +6 -7
  86. package/lib/listener/enhancedGlobalRetry.js +6 -6
  87. package/lib/listener/exit.js +4 -3
  88. package/lib/listener/globalRetry.js +5 -5
  89. package/lib/listener/globalTimeout.js +30 -14
  90. package/lib/listener/helpers.js +39 -14
  91. package/lib/listener/mocha.js +3 -4
  92. package/lib/listener/result.js +4 -5
  93. package/lib/listener/retryEnhancer.js +3 -3
  94. package/lib/listener/steps.js +8 -7
  95. package/lib/listener/store.js +3 -3
  96. package/lib/locator.js +213 -192
  97. package/lib/mocha/asyncWrapper.js +105 -62
  98. package/lib/mocha/bdd.js +99 -13
  99. package/lib/mocha/cli.js +59 -26
  100. package/lib/mocha/factory.js +78 -19
  101. package/lib/mocha/featureConfig.js +1 -1
  102. package/lib/mocha/gherkin.js +56 -24
  103. package/lib/mocha/hooks.js +12 -3
  104. package/lib/mocha/index.js +13 -4
  105. package/lib/mocha/inject.js +22 -5
  106. package/lib/mocha/scenarioConfig.js +2 -2
  107. package/lib/mocha/suite.js +9 -2
  108. package/lib/mocha/test.js +10 -7
  109. package/lib/mocha/ui.js +28 -18
  110. package/lib/output.js +10 -8
  111. package/lib/parser.js +44 -44
  112. package/lib/pause.js +15 -16
  113. package/lib/plugin/analyze.js +19 -12
  114. package/lib/plugin/auth.js +20 -21
  115. package/lib/plugin/autoDelay.js +12 -8
  116. package/lib/plugin/coverage.js +28 -11
  117. package/lib/plugin/customLocator.js +3 -3
  118. package/lib/plugin/customReporter.js +3 -2
  119. package/lib/plugin/enhancedRetryFailedStep.js +6 -6
  120. package/lib/plugin/heal.js +14 -9
  121. package/lib/plugin/htmlReporter.js +724 -99
  122. package/lib/plugin/pageInfo.js +10 -10
  123. package/lib/plugin/pauseOnFail.js +4 -3
  124. package/lib/plugin/retryFailedStep.js +48 -5
  125. package/lib/plugin/screenshotOnFail.js +75 -37
  126. package/lib/plugin/stepByStepReport.js +14 -14
  127. package/lib/plugin/stepTimeout.js +4 -3
  128. package/lib/plugin/subtitles.js +6 -5
  129. package/lib/recorder.js +33 -14
  130. package/lib/rerun.js +69 -26
  131. package/lib/result.js +4 -4
  132. package/lib/retryCoordinator.js +2 -2
  133. package/lib/secret.js +18 -17
  134. package/lib/session.js +95 -89
  135. package/lib/step/base.js +7 -7
  136. package/lib/step/comment.js +2 -2
  137. package/lib/step/config.js +1 -1
  138. package/lib/step/func.js +3 -3
  139. package/lib/step/helper.js +3 -3
  140. package/lib/step/meta.js +5 -5
  141. package/lib/step/record.js +11 -11
  142. package/lib/step/retry.js +3 -3
  143. package/lib/step/section.js +3 -3
  144. package/lib/step.js +7 -10
  145. package/lib/steps.js +9 -5
  146. package/lib/store.js +1 -1
  147. package/lib/template/heal.js +1 -1
  148. package/lib/template/prompts/generatePageObject.js +31 -0
  149. package/lib/template/prompts/healStep.js +13 -0
  150. package/lib/template/prompts/writeStep.js +9 -0
  151. package/lib/test-server.js +17 -6
  152. package/lib/timeout.js +1 -7
  153. package/lib/transform.js +8 -8
  154. package/lib/translation.js +32 -18
  155. package/lib/utils/mask_data.js +4 -10
  156. package/lib/utils.js +66 -64
  157. package/lib/workerStorage.js +17 -17
  158. package/lib/workers.js +214 -84
  159. package/package.json +41 -37
  160. package/translations/de-DE.js +2 -2
  161. package/translations/fr-FR.js +2 -2
  162. package/translations/index.js +23 -10
  163. package/translations/it-IT.js +2 -2
  164. package/translations/ja-JP.js +2 -2
  165. package/translations/nl-NL.js +2 -2
  166. package/translations/pl-PL.js +2 -2
  167. package/translations/pt-BR.js +2 -2
  168. package/translations/ru-RU.js +2 -2
  169. package/translations/utils.js +4 -3
  170. package/translations/zh-CN.js +2 -2
  171. package/translations/zh-TW.js +2 -2
  172. package/typings/index.d.ts +5 -3
  173. package/typings/promiseBasedTypes.d.ts +4 -0
  174. package/typings/types.d.ts +4 -0
  175. package/lib/helper/Nightmare.js +0 -1486
  176. package/lib/helper/Protractor.js +0 -1840
  177. package/lib/helper/TestCafe.js +0 -1391
  178. package/lib/helper/clientscripts/nightmare.js +0 -213
  179. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  180. package/lib/helper/testcafe/testcafe-utils.js +0 -61
  181. package/lib/plugin/allure.js +0 -15
  182. package/lib/plugin/autoLogin.js +0 -5
  183. package/lib/plugin/commentStep.js +0 -141
  184. package/lib/plugin/eachElement.js +0 -127
  185. package/lib/plugin/fakerTransform.js +0 -49
  186. package/lib/plugin/retryTo.js +0 -16
  187. package/lib/plugin/selenoid.js +0 -364
  188. package/lib/plugin/standardActingHelpers.js +0 -6
  189. package/lib/plugin/tryTo.js +0 -16
  190. package/lib/plugin/wdio.js +0 -247
  191. package/lib/within.js +0 -90
@@ -1,13 +1,13 @@
1
- const path = require('path')
2
- const fs = require('fs')
3
- const Container = require('../container')
4
- const recorder = require('../recorder')
5
- const event = require('../event')
1
+ import path from 'path'
2
+ import fs from 'fs'
3
+ import Container from '../container.js'
6
4
  const supportedHelpers = Container.STANDARD_ACTING_HELPERS
7
- const { scanForErrorMessages } = require('../html')
8
- const { output } = require('..')
9
- const { humanizeString, ucfirst } = require('../utils')
10
- const { testToFileName } = require('../mocha/test')
5
+ import recorder from '../recorder.js'
6
+ import event from '../event.js'
7
+ import { scanForErrorMessages } from '../html.js'
8
+ import { output } from '../index.js'
9
+ import { humanizeString, ucfirst } from '../utils.js'
10
+ import { testToFileName } from '../mocha/test.js'
11
11
  const defaultConfig = {
12
12
  errorClasses: ['error', 'warning', 'alert', 'danger'],
13
13
  browserLogs: ['error'],
@@ -35,7 +35,7 @@ const defaultConfig = {
35
35
  * * `browserLogs` - list of types of errors to search for in browser logs (default: `['error']`)
36
36
  *
37
37
  */
38
- module.exports = function (config = {}) {
38
+ export default function (config = {}) {
39
39
  const helpers = Container.helpers()
40
40
  let helper
41
41
 
@@ -1,5 +1,6 @@
1
- const event = require('../event')
2
- const pause = require('../pause')
1
+ import event from '../event.js'
2
+
3
+ import pause from '../pause.js'
3
4
 
4
5
  /**
5
6
  * Automatically launches [interactive pause](/basics/#pause) when a test fails.
@@ -21,7 +22,7 @@ const pause = require('../pause')
21
22
  * ```
22
23
  *
23
24
  */
24
- module.exports = () => {
25
+ export default function() {
25
26
  let failed = false
26
27
 
27
28
  event.dispatcher.on(event.test.started, () => {
@@ -1,6 +1,9 @@
1
- const event = require('../event')
2
- const recorder = require('../recorder')
3
- const store = require('../store')
1
+ import event from '../event.js'
2
+
3
+ import recorder from '../recorder.js'
4
+
5
+ import store from '../store.js'
6
+
4
7
  const defaultConfig = {
5
8
  retries: 3,
6
9
  defaultIgnoredSteps: ['amOnPage', 'wait*', 'send*', 'execute*', 'run*', 'have*'],
@@ -74,7 +77,7 @@ const defaultConfig = {
74
77
  * ```
75
78
  *
76
79
  */
77
- module.exports = config => {
80
+ export default function (config) {
78
81
  config = Object.assign(defaultConfig, config)
79
82
  config.ignoredSteps = config.ignoredSteps.concat(config.defaultIgnoredSteps)
80
83
  const customWhen = config.when
@@ -85,11 +88,20 @@ module.exports = config => {
85
88
  if (!enableRetry) return
86
89
  if (store.debugMode) return false
87
90
  if (!store.autoRetries) return false
91
+ // Don't retry terminal errors (e.g., frame detachment errors)
92
+ if (err && err.isTerminal) return false
93
+ // Don't retry navigation errors that are known to be terminal
94
+ if (err && err.message && (err.message.includes('ERR_ABORTED') || err.message.includes('frame was detached') || err.message.includes('Target page, context or browser has been closed'))) return false
88
95
  if (customWhen) return customWhen(err)
89
96
  return true
90
97
  }
91
98
  config.when = when
92
99
 
100
+ // Ensure retry options are available before any steps run
101
+ if (!recorder.retries.find(r => r === config)) {
102
+ recorder.retries.push(config)
103
+ }
104
+
93
105
  event.dispatcher.on(event.step.started, step => {
94
106
  // if a step is ignored - return
95
107
  for (const ignored of config.ignoredSteps) {
@@ -101,20 +113,51 @@ module.exports = config => {
101
113
  enableRetry = true // enable retry for a step
102
114
  })
103
115
 
104
- event.dispatcher.on(event.step.finished, () => {
116
+ // Disable retry only after a successful step; keep it enabled for failure so retry logic can act
117
+ event.dispatcher.on(event.step.passed, () => {
105
118
  enableRetry = false
106
119
  })
107
120
 
108
121
  event.dispatcher.on(event.test.before, test => {
109
122
  // pass disableRetryFailedStep is a preferred way to disable retries
110
123
  // test.disableRetryFailedStep is used for backward compatibility
124
+ if (!test.opts) test.opts = {}
111
125
  if (test.opts.disableRetryFailedStep || test.disableRetryFailedStep) {
112
126
  store.autoRetries = false
113
127
  return // disable retry when a test is not active
114
128
  }
129
+
130
+ // Don't apply plugin retry logic if there are already manual retries configured
131
+ // Check if any retry configs exist that aren't from this plugin
132
+ const hasManualRetries = recorder.retries.some(retry => retry !== config)
133
+ if (hasManualRetries) {
134
+ store.autoRetries = false
135
+ return
136
+ }
137
+
115
138
  // this option is used to set the retries inside _before() block of helpers
116
139
  store.autoRetries = true
117
140
  test.opts.conditionalRetries = config.retries
141
+ // debug: record applied retries value for tests
142
+ if (process.env.DEBUG_RETRY_PLUGIN) {
143
+ // eslint-disable-next-line no-console
144
+ console.log('[retryFailedStep] applying retries =', config.retries, 'for test', test.title)
145
+ }
118
146
  recorder.retry(config)
119
147
  })
148
+
149
+ // Fallback for environments where event.test.before wasn't emitted (runner scenarios)
150
+ event.dispatcher.on(event.test.started, test => {
151
+ if (test.opts?.disableRetryFailedStep || test.disableRetryFailedStep) return
152
+
153
+ // Don't apply plugin retry logic if there are already manual retries configured
154
+ // Check if any retry configs exist that aren't from this plugin
155
+ const hasManualRetries = recorder.retries.some(retry => retry !== config)
156
+ if (hasManualRetries) return
157
+
158
+ if (!store.autoRetries) {
159
+ store.autoRetries = true
160
+ test.opts.conditionalRetries = test.opts.conditionalRetries || config.retries
161
+ }
162
+ })
120
163
  }
@@ -1,13 +1,17 @@
1
- const fs = require('fs')
2
- const path = require('path')
1
+ import fs from 'fs'
2
+ import path from 'path'
3
3
 
4
- const Container = require('../container')
5
- const recorder = require('../recorder')
6
- const event = require('../event')
7
- const output = require('../output')
8
- const { fileExists } = require('../utils')
9
- const Codeceptjs = require('../index')
10
- const { testToFileName } = require('../mocha/test')
4
+ import Container from '../container.js'
5
+
6
+ import recorder from '../recorder.js'
7
+
8
+ import event from '../event.js'
9
+
10
+ import output from '../output.js'
11
+
12
+ import { fileExists } from '../utils.js'
13
+ import Codeceptjs from '../index.js'
14
+ import { testToFileName } from '../mocha/test.js'
11
15
 
12
16
  const defaultConfig = {
13
17
  uniqueScreenshotNames: false,
@@ -43,7 +47,7 @@ const supportedHelpers = Container.STANDARD_ACTING_HELPERS
43
47
  *
44
48
  *
45
49
  */
46
- module.exports = function (config) {
50
+ export default function (config) {
47
51
  const helpers = Container.helpers()
48
52
  let helper
49
53
 
@@ -86,11 +90,28 @@ module.exports = function (config) {
86
90
  let fileName
87
91
 
88
92
  if (options.uniqueScreenshotNames && test) {
89
- fileName = `${testToFileName(test, { unique: true })}.failed.png`
93
+ fileName = `${testToFileName(test, { suffix: '', unique: true })}.failed.png`
90
94
  } else {
91
- fileName = `${testToFileName(test)}.failed.png`
95
+ fileName = `${testToFileName(test, { suffix: '', unique: false })}.failed.png`
96
+ }
97
+ const quietMode = !('output_dir' in global) || !global.output_dir
98
+ if (!quietMode) {
99
+ output.plugin('screenshotOnFail', 'Test failed, try to save a screenshot')
100
+ }
101
+
102
+ // Re-check helpers at runtime in case they weren't ready during plugin init
103
+ const runtimeHelpers = Container.helpers()
104
+ let runtimeHelper = null
105
+ for (const helperName of supportedHelpers) {
106
+ if (Object.keys(runtimeHelpers).indexOf(helperName) > -1) {
107
+ runtimeHelper = runtimeHelpers[helperName]
108
+ break
109
+ }
110
+ }
111
+
112
+ if (runtimeHelper && typeof runtimeHelper.saveScreenshot === 'function') {
113
+ helper = runtimeHelper
92
114
  }
93
- output.plugin('screenshotOnFail', 'Test failed, try to save a screenshot')
94
115
 
95
116
  try {
96
117
  if (options.reportDir) {
@@ -100,36 +121,53 @@ module.exports = function (config) {
100
121
  fs.mkdirSync(mochaReportDir)
101
122
  }
102
123
  }
103
- await helper.saveScreenshot(fileName, options.fullPageScreenshots)
104
124
 
105
- if (!test.artifacts) test.artifacts = {}
106
- test.artifacts.screenshot = path.join(global.output_dir, fileName)
107
- if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments) {
108
- test.attachments = [path.join(global.output_dir, fileName)]
125
+ // Check if browser/page is still available before attempting screenshot
126
+ if (helper.page && helper.page.isClosed && helper.page.isClosed()) {
127
+ throw new Error('Browser page has been closed')
109
128
  }
110
-
111
- const allureReporter = Container.plugins('allure')
112
- if (allureReporter) {
113
- allureReporter.addAttachment('Main session - Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)), dataType)
114
-
115
- if (helper.activeSessionName) {
116
- const sessions = helper.sessionPages || helper.sessionWindows
117
- for (const sessionName in sessions) {
118
- const screenshotFileName = `${sessionName}_${fileName}`
119
- test.artifacts[`${sessionName.replace(/ /g, '_')}_screenshot`] = path.join(global.output_dir, screenshotFileName)
120
- allureReporter.addAttachment(`${sessionName} - Last Seen Screenshot`, fs.readFileSync(path.join(global.output_dir, screenshotFileName)), dataType)
121
- }
122
- }
129
+ if (helper.browser && helper.browser.isConnected && !helper.browser.isConnected()) {
130
+ throw new Error('Browser has been disconnected')
123
131
  }
124
132
 
125
- const cucumberReporter = Container.plugins('cucumberJsonReporter')
126
- if (cucumberReporter) {
127
- cucumberReporter.addScreenshot(test.artifacts.screenshot)
133
+ // Add timeout wrapper to prevent hanging with shorter timeout for ESM
134
+ const screenshotPromise = helper.saveScreenshot(fileName, options.fullPageScreenshots)
135
+ const timeoutPromise = new Promise((_, reject) => {
136
+ setTimeout(() => reject(new Error('Screenshot timeout after 5 seconds')), 5000)
137
+ })
138
+
139
+ await Promise.race([screenshotPromise, timeoutPromise])
140
+
141
+ if (!test.artifacts) test.artifacts = {}
142
+ // Some unit tests may not define global.output_dir; avoid throwing when it is undefined
143
+ // Detect output directory safely (may not be initialized in narrow unit tests)
144
+ const baseOutputDir = 'output_dir' in global && typeof global.output_dir === 'string' && global.output_dir ? global.output_dir : null
145
+ if (baseOutputDir) {
146
+ test.artifacts.screenshot = path.join(baseOutputDir, fileName)
147
+ if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments) {
148
+ test.attachments = [path.join(baseOutputDir, fileName)]
149
+ }
150
+ } else {
151
+ // Fallback: just store the file name to keep tests stable without triggering path errors
152
+ test.artifacts.screenshot = fileName
128
153
  }
129
154
  } catch (err) {
130
- output.plugin(err)
131
- if (err && err.type && err.type === 'RuntimeError' && err.message && (err.message.indexOf('was terminated due to') > -1 || err.message.indexOf('no such window: target window already closed') > -1)) {
132
- output.log(`Can't make screenshot, ${err}`)
155
+ if (!quietMode) {
156
+ output.plugin('screenshotOnFail', `Failed to save screenshot: ${err.message}`)
157
+ }
158
+ // Enhanced error handling for browser closed scenarios
159
+ if (
160
+ err &&
161
+ ((err.message &&
162
+ (err.message.includes('Target page, context or browser has been closed') ||
163
+ err.message.includes('Browser page has been closed') ||
164
+ err.message.includes('Browser has been disconnected') ||
165
+ err.message.includes('was terminated due to') ||
166
+ err.message.includes('no such window: target window already closed') ||
167
+ err.message.includes('Screenshot timeout after'))) ||
168
+ (err.type && err.type === 'RuntimeError'))
169
+ ) {
170
+ output.log(`Can't make screenshot, ${err.message}`)
133
171
  helper.isRunning = false
134
172
  }
135
173
  }
@@ -1,16 +1,16 @@
1
- const colors = require('chalk')
2
- const crypto = require('crypto')
3
- const figures = require('figures')
4
- const fs = require('fs')
5
- const mkdirp = require('mkdirp')
6
- const path = require('path')
7
- const cheerio = require('cheerio')
8
-
9
- const Container = require('../container')
10
- const recorder = require('../recorder')
11
- const event = require('../event')
12
- const output = require('../output')
13
- const { template, deleteDir } = require('../utils')
1
+ import colors from 'chalk'
2
+ import crypto from 'crypto'
3
+ import figures from 'figures'
4
+ import fs from 'fs'
5
+ import { mkdirp } from 'mkdirp'
6
+ import path from 'path'
7
+ import cheerio from 'cheerio'
8
+
9
+ import Container from '../container.js'
10
+ import recorder from '../recorder.js'
11
+ import event from '../event.js'
12
+ import output from '../output.js'
13
+ import { template, deleteDir } from '../utils.js'
14
14
 
15
15
  const supportedHelpers = Container.STANDARD_ACTING_HELPERS
16
16
 
@@ -63,7 +63,7 @@ const templates = {}
63
63
  * @param {*} config
64
64
  */
65
65
 
66
- module.exports = function (config) {
66
+ export default function (config) {
67
67
  const helpers = Container.helpers()
68
68
  let helper
69
69
 
@@ -1,5 +1,6 @@
1
- const event = require('../event')
2
- const { TIMEOUT_ORDER } = require('../timeout')
1
+ import event from '../event.js'
2
+
3
+ import { TIMEOUT_ORDER } from '../timeout.js'
3
4
 
4
5
  const defaultConfig = {
5
6
  timeout: 150,
@@ -61,7 +62,7 @@ const defaultConfig = {
61
62
  * ```
62
63
  *
63
64
  */
64
- module.exports = config => {
65
+ export default function(config) {
65
66
  config = Object.assign(defaultConfig, config)
66
67
  // below override rule makes sure customTimeoutSteps go first but then they override noTimeoutSteps in case of exact pattern match
67
68
  config.customTimeoutSteps = config.customTimeoutSteps.concat(config.noTimeoutSteps).concat(config.customTimeoutSteps)
@@ -1,7 +1,8 @@
1
- const { v4: uuidv4 } = require('uuid')
2
- const fsPromise = require('fs').promises
3
- const path = require('path')
4
- const event = require('../event')
1
+ import { v4 as uuidv4 } from 'uuid'
2
+ import fs from 'fs'
3
+ const fsPromise = fs.promises
4
+ import path from 'path'
5
+ import event from '../event.js'
5
6
 
6
7
  // This will convert a given timestamp in milliseconds to
7
8
  // an SRT recognized timestamp, ie HH:mm:ss,SSS
@@ -28,7 +29,7 @@ let testStartedAt
28
29
  * }
29
30
  * ```
30
31
  */
31
- module.exports = function () {
32
+ export default function () {
32
33
  event.dispatcher.on(event.test.before, _ => {
33
34
  testStartedAt = Date.now()
34
35
  steps = {}
package/lib/recorder.js CHANGED
@@ -1,9 +1,10 @@
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
- const { TimeoutError } = require('./timeout')
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'
7
8
  const MAX_TASKS = 100
8
9
 
9
10
  let promise
@@ -29,7 +30,7 @@ const defaultRetryOptions = {
29
30
  * @alias recorder
30
31
  * @interface
31
32
  */
32
- module.exports = {
33
+ export default {
33
34
  /**
34
35
  * @type {Array<Object<string, *>>}
35
36
  * @inner
@@ -92,7 +93,7 @@ module.exports = {
92
93
  sessionId = null
93
94
  sessionStack = [] // Clear the session stack
94
95
  asyncErr = null
95
- log(`${currentQueue()} Starting recording promises`)
96
+ output.log(`${currentQueue()} Starting recording promises`)
96
97
  promise = Promise.resolve()
97
98
  oldPromises = []
98
99
  tasks = []
@@ -217,7 +218,7 @@ module.exports = {
217
218
 
218
219
  const retryRules = this.retries.slice().reverse()
219
220
  return promiseRetry(Object.assign(defaultRetryOptions, retryOpts), (retry, number) => {
220
- if (number > 1) log(`${currentQueue()}Retrying... Attempt #${number}`)
221
+ if (number > 1) output.log(`${currentQueue()}Retrying... Attempt #${number}`)
221
222
  const [promise, timer] = getTimeoutPromise(timeout, taskName)
222
223
  return Promise.race([promise, Promise.resolve(res).then(fn)])
223
224
  .finally(() => clearTimeout(timer))
@@ -247,7 +248,9 @@ module.exports = {
247
248
  if (Number.isInteger(opts)) {
248
249
  opts = { retries: opts }
249
250
  }
250
- return this.add(() => this.retries.push(opts))
251
+ // Push retry options immediately so first step can see them
252
+ this.retries.push(opts)
253
+ return Promise.resolve()
251
254
  },
252
255
 
253
256
  /**
@@ -262,8 +265,9 @@ module.exports = {
262
265
  .replace(/\n/g, ' ')
263
266
  ?.slice(0, 50)
264
267
  debug(chalk.gray(`${currentQueue()} Queued | catch with error handler ${fnDescription || ''}`))
268
+ if (!promise) promise = Promise.resolve()
265
269
  return (promise = promise.catch(err => {
266
- log(`${currentQueue()}Error | ${err} ${fnDescription}...`)
270
+ output.log(`${currentQueue()}Error | ${err} ${fnDescription}...`)
267
271
  if (!(err instanceof Error)) {
268
272
  // strange things may happen
269
273
  err = new Error(`[Wrapped Error] ${printObjectProperties(err)}`) // we should be prepared for them
@@ -288,15 +292,30 @@ module.exports = {
288
292
  ?.replace(/\s{2,}/g, ' ')
289
293
  .replace(/\n/g, ' ')
290
294
  ?.slice(0, 50)
295
+ if (!promise) promise = Promise.resolve()
291
296
  return (promise = promise.catch(err => {
292
297
  if (ignoredErrs.includes(err)) return // already caught
293
- log(`${currentQueue()} Error (Non-Terminated) | ${err} | ${fnDescription || ''}...`)
298
+
299
+ // Handle terminal errors - don't continue, re-throw immediately
300
+ 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'))))) {
301
+ output.log(`${currentQueue()} Terminal Error | ${err} | ${fnDescription || ''}...`)
302
+ throw err // Re-throw terminal errors immediately
303
+ }
304
+
305
+ output.log(`${currentQueue()} Error (Non-Terminated) | ${err} | ${fnDescription || ''}...`)
294
306
  if (!(err instanceof Error)) {
295
307
  // strange things may happen
296
308
  err = new Error(`[Wrapped Error] ${JSON.stringify(err)}`) // we should be prepared for them
297
309
  }
298
310
  if (customErrFn) {
299
- return customErrFn(err)
311
+ try {
312
+ const result = customErrFn(err)
313
+ // If customErrFn returns a value (not throwing), treat it as handled
314
+ return result
315
+ } catch (thrownErr) {
316
+ // If customErrFn throws an error, propagate it up
317
+ throw thrownErr
318
+ }
300
319
  }
301
320
  }))
302
321
  },
@@ -354,7 +373,7 @@ module.exports = {
354
373
  */
355
374
  stop() {
356
375
  debug(this.toString())
357
- log(`${currentQueue()} Stopping recording promises`)
376
+ output.log(`${currentQueue()} Stopping recording promises`)
358
377
  running = false
359
378
  },
360
379
 
package/lib/rerun.js CHANGED
@@ -1,34 +1,77 @@
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) {
74
+ output.error(e.stack)
32
75
  reject(e)
33
76
  }
34
77
  })
@@ -79,4 +122,4 @@ class CodeceptRerunner extends BaseCodecept {
79
122
  }
80
123
  }
81
124
 
82
- module.exports = CodeceptRerunner
125
+ export default CodeceptRerunner
package/lib/result.js CHANGED
@@ -1,6 +1,6 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const { serializeTest } = require('./mocha/test')
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { serializeTest } from './mocha/test.js'
4
4
 
5
5
  /**
6
6
  * @typedef {Object} Stats Statistics for a test result.
@@ -235,4 +235,4 @@ class Result {
235
235
  }
236
236
  }
237
237
 
238
- module.exports = Result
238
+ export default Result
@@ -1,4 +1,4 @@
1
- const output = require('./output')
1
+ import output from './output.js'
2
2
 
3
3
  /**
4
4
  * Retry Coordinator - Central coordination for all retry mechanisms
@@ -196,7 +196,7 @@ function validateConfig(config) {
196
196
  return warnings
197
197
  }
198
198
 
199
- module.exports = {
199
+ export {
200
200
  RETRY_PRIORITIES,
201
201
  RETRY_TYPES,
202
202
  registerRetry,