codeceptjs 4.0.0-beta.5 → 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 (179) hide show
  1. package/README.md +0 -45
  2. package/bin/codecept.js +46 -57
  3. package/lib/actor.js +15 -11
  4. package/lib/ai.js +6 -5
  5. package/lib/assert/empty.js +9 -8
  6. package/lib/assert/equal.js +15 -17
  7. package/lib/assert/error.js +2 -2
  8. package/lib/assert/include.js +9 -11
  9. package/lib/assert/throws.js +1 -1
  10. package/lib/assert/truth.js +8 -5
  11. package/lib/assert.js +18 -18
  12. package/lib/codecept.js +66 -107
  13. package/lib/colorUtils.js +48 -50
  14. package/lib/command/check.js +32 -27
  15. package/lib/command/configMigrate.js +11 -10
  16. package/lib/command/definitions.js +16 -10
  17. package/lib/command/dryRun.js +16 -16
  18. package/lib/command/generate.js +29 -26
  19. package/lib/command/gherkin/init.js +36 -38
  20. package/lib/command/gherkin/snippets.js +14 -14
  21. package/lib/command/gherkin/steps.js +21 -18
  22. package/lib/command/info.js +8 -8
  23. package/lib/command/init.js +34 -31
  24. package/lib/command/interactive.js +11 -10
  25. package/lib/command/list.js +10 -9
  26. package/lib/command/run-multiple/chunk.js +5 -5
  27. package/lib/command/run-multiple/collection.js +5 -5
  28. package/lib/command/run-multiple/run.js +3 -3
  29. package/lib/command/run-multiple.js +16 -13
  30. package/lib/command/run-rerun.js +6 -7
  31. package/lib/command/run-workers.js +10 -24
  32. package/lib/command/run.js +8 -8
  33. package/lib/command/utils.js +20 -18
  34. package/lib/command/workers/runTests.js +117 -269
  35. package/lib/config.js +111 -49
  36. package/lib/container.js +299 -102
  37. package/lib/data/context.js +6 -5
  38. package/lib/data/dataScenarioConfig.js +1 -1
  39. package/lib/data/dataTableArgument.js +1 -1
  40. package/lib/data/table.js +1 -1
  41. package/lib/effects.js +94 -10
  42. package/lib/els.js +11 -9
  43. package/lib/event.js +11 -10
  44. package/lib/globals.js +141 -0
  45. package/lib/heal.js +12 -12
  46. package/lib/helper/AI.js +1 -1
  47. package/lib/helper/ApiDataFactory.js +16 -13
  48. package/lib/helper/FileSystem.js +32 -12
  49. package/lib/helper/GraphQL.js +1 -1
  50. package/lib/helper/GraphQLDataFactory.js +1 -1
  51. package/lib/helper/JSONResponse.js +19 -30
  52. package/lib/helper/Mochawesome.js +9 -28
  53. package/lib/helper/Playwright.js +668 -265
  54. package/lib/helper/Puppeteer.js +284 -169
  55. package/lib/helper/REST.js +29 -12
  56. package/lib/helper/WebDriver.js +191 -71
  57. package/lib/helper/errors/ConnectionRefused.js +6 -6
  58. package/lib/helper/errors/ElementAssertion.js +11 -16
  59. package/lib/helper/errors/ElementNotFound.js +5 -9
  60. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  61. package/lib/helper/extras/Console.js +11 -11
  62. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  63. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  64. package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
  65. package/lib/helper/extras/Popup.js +1 -1
  66. package/lib/helper/extras/React.js +29 -30
  67. package/lib/helper/network/actions.js +33 -48
  68. package/lib/helper/network/utils.js +76 -83
  69. package/lib/helper/scripts/blurElement.js +6 -6
  70. package/lib/helper/scripts/focusElement.js +6 -6
  71. package/lib/helper/scripts/highlightElement.js +9 -9
  72. package/lib/helper/scripts/isElementClickable.js +34 -34
  73. package/lib/helper.js +2 -1
  74. package/lib/history.js +23 -20
  75. package/lib/hooks.js +10 -10
  76. package/lib/html.js +90 -100
  77. package/lib/index.js +48 -21
  78. package/lib/listener/config.js +8 -9
  79. package/lib/listener/emptyRun.js +6 -7
  80. package/lib/listener/exit.js +4 -3
  81. package/lib/listener/globalRetry.js +5 -5
  82. package/lib/listener/globalTimeout.js +11 -10
  83. package/lib/listener/helpers.js +33 -14
  84. package/lib/listener/mocha.js +3 -4
  85. package/lib/listener/result.js +4 -5
  86. package/lib/listener/steps.js +7 -18
  87. package/lib/listener/store.js +3 -3
  88. package/lib/locator.js +213 -192
  89. package/lib/mocha/asyncWrapper.js +108 -75
  90. package/lib/mocha/bdd.js +99 -13
  91. package/lib/mocha/cli.js +60 -27
  92. package/lib/mocha/factory.js +75 -19
  93. package/lib/mocha/featureConfig.js +1 -1
  94. package/lib/mocha/gherkin.js +57 -25
  95. package/lib/mocha/hooks.js +12 -3
  96. package/lib/mocha/index.js +13 -4
  97. package/lib/mocha/inject.js +22 -5
  98. package/lib/mocha/scenarioConfig.js +2 -2
  99. package/lib/mocha/suite.js +9 -2
  100. package/lib/mocha/test.js +10 -13
  101. package/lib/mocha/ui.js +28 -31
  102. package/lib/output.js +11 -9
  103. package/lib/parser.js +44 -44
  104. package/lib/pause.js +15 -16
  105. package/lib/plugin/analyze.js +19 -12
  106. package/lib/plugin/auth.js +20 -21
  107. package/lib/plugin/autoDelay.js +12 -8
  108. package/lib/plugin/coverage.js +12 -8
  109. package/lib/plugin/customLocator.js +3 -3
  110. package/lib/plugin/customReporter.js +3 -2
  111. package/lib/plugin/heal.js +14 -9
  112. package/lib/plugin/pageInfo.js +10 -10
  113. package/lib/plugin/pauseOnFail.js +4 -3
  114. package/lib/plugin/retryFailedStep.js +47 -5
  115. package/lib/plugin/screenshotOnFail.js +75 -37
  116. package/lib/plugin/stepByStepReport.js +14 -14
  117. package/lib/plugin/stepTimeout.js +4 -3
  118. package/lib/plugin/subtitles.js +6 -5
  119. package/lib/recorder.js +33 -23
  120. package/lib/rerun.js +69 -26
  121. package/lib/result.js +4 -4
  122. package/lib/secret.js +18 -17
  123. package/lib/session.js +95 -89
  124. package/lib/step/base.js +6 -6
  125. package/lib/step/config.js +1 -1
  126. package/lib/step/func.js +3 -3
  127. package/lib/step/helper.js +3 -3
  128. package/lib/step/meta.js +4 -4
  129. package/lib/step/record.js +11 -11
  130. package/lib/step/retry.js +3 -3
  131. package/lib/step/section.js +3 -3
  132. package/lib/step.js +7 -10
  133. package/lib/steps.js +9 -5
  134. package/lib/store.js +1 -1
  135. package/lib/timeout.js +1 -7
  136. package/lib/transform.js +8 -8
  137. package/lib/translation.js +32 -18
  138. package/lib/utils.js +68 -97
  139. package/lib/workerStorage.js +16 -17
  140. package/lib/workers.js +145 -171
  141. package/package.json +63 -57
  142. package/translations/de-DE.js +2 -2
  143. package/translations/fr-FR.js +2 -2
  144. package/translations/index.js +23 -10
  145. package/translations/it-IT.js +2 -2
  146. package/translations/ja-JP.js +2 -2
  147. package/translations/nl-NL.js +2 -2
  148. package/translations/pl-PL.js +2 -2
  149. package/translations/pt-BR.js +2 -2
  150. package/translations/ru-RU.js +2 -2
  151. package/translations/utils.js +11 -2
  152. package/translations/zh-CN.js +2 -2
  153. package/translations/zh-TW.js +2 -2
  154. package/typings/index.d.ts +7 -18
  155. package/typings/promiseBasedTypes.d.ts +3769 -5450
  156. package/typings/types.d.ts +3953 -5778
  157. package/bin/test-server.js +0 -53
  158. package/lib/element/WebElement.js +0 -327
  159. package/lib/helper/Nightmare.js +0 -1486
  160. package/lib/helper/Protractor.js +0 -1840
  161. package/lib/helper/TestCafe.js +0 -1391
  162. package/lib/helper/clientscripts/nightmare.js +0 -213
  163. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
  164. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  165. package/lib/helper/testcafe/testcafe-utils.js +0 -61
  166. package/lib/listener/retryEnhancer.js +0 -85
  167. package/lib/plugin/allure.js +0 -15
  168. package/lib/plugin/autoLogin.js +0 -5
  169. package/lib/plugin/commentStep.js +0 -141
  170. package/lib/plugin/eachElement.js +0 -127
  171. package/lib/plugin/fakerTransform.js +0 -49
  172. package/lib/plugin/htmlReporter.js +0 -1947
  173. package/lib/plugin/retryTo.js +0 -16
  174. package/lib/plugin/selenoid.js +0 -364
  175. package/lib/plugin/standardActingHelpers.js +0 -6
  176. package/lib/plugin/tryTo.js +0 -16
  177. package/lib/plugin/wdio.js +0 -247
  178. package/lib/test-server.js +0 -323
  179. package/lib/within.js +0 -90
@@ -1,14 +1,21 @@
1
- const debug = require('debug')('codeceptjs:analyze')
2
- const { isMainThread } = require('node:worker_threads')
3
- const { arrowRight } = require('figures')
4
- const container = require('../container')
5
- const store = require('../store')
6
- const ai = require('../ai')
7
- const colors = require('chalk')
8
- const ora = require('ora-classic')
9
- const event = require('../event')
10
- const output = require('../output')
11
- const { ansiRegExp, base64EncodeFile, markdownToAnsi } = require('../utils')
1
+ import debugFactory from 'debug'
2
+ const debug = debugFactory('codeceptjs:analyze')
3
+ import { isMainThread } from 'node:worker_threads'
4
+ import figures from 'figures'
5
+ const { arrowRight } = figures
6
+ import Container from '../container.js'
7
+ // Container already imported correctly above
8
+ import store from '../store.js'
9
+
10
+ import aiModule from '../ai.js'
11
+ const ai = aiModule.default || aiModule
12
+ import colors from 'chalk'
13
+ import ora from 'ora'
14
+ import event from '../event.js'
15
+
16
+ import output from '../output.js'
17
+
18
+ import { ansiRegExp, base64EncodeFile, markdownToAnsi } from '../utils.js'
12
19
 
13
20
  const MAX_DATA_LENGTH = 5000
14
21
 
@@ -212,7 +219,7 @@ const defaultConfig = {
212
219
  * @param {Object} config - Plugin configuration
213
220
  * @returns {void}
214
221
  */
215
- module.exports = function (config = {}) {
222
+ export default function (config = {}) {
216
223
  config = Object.assign(defaultConfig, config)
217
224
 
218
225
  event.dispatcher.on(event.workers.before, () => {
@@ -1,14 +1,13 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const { fileExists } = require('../utils')
4
- const CommentStep = require('../step/comment')
5
- const Section = require('../step/section')
6
- const container = require('../container')
7
- const store = require('../store')
8
- const event = require('../event')
9
- const recorder = require('../recorder')
10
- const { debug } = require('../output')
11
- const { isAsyncFunction } = require('../utils')
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { fileExists, isAsyncFunction } from '../utils.js'
4
+ import CommentStep from '../step/comment.js'
5
+ import Section from '../step/section.js'
6
+ import container from '../container.js'
7
+ import store from '../store.js'
8
+ import event from '../event.js'
9
+ import recorder from '../recorder.js'
10
+ import output from '../output.js'
12
11
 
13
12
  const defaultUser = {
14
13
  fetch: I => I.grabCookie(),
@@ -253,7 +252,7 @@ const defaultConfig = {
253
252
  *
254
253
  *
255
254
  */
256
- module.exports = function (config) {
255
+ export default function (config) {
257
256
  config = Object.assign(defaultConfig, config)
258
257
  Object.keys(config.users).map(
259
258
  u =>
@@ -292,12 +291,12 @@ module.exports = function (config) {
292
291
 
293
292
  if (isPlaywrightSession() && test?.opts?.cookies) {
294
293
  if (test.opts.user == name) {
295
- debug(`Cookies already loaded for ${name}`)
294
+ output.debug(`Cookies already loaded for ${name}`)
296
295
 
297
296
  alreadyLoggedIn(name)
298
297
  return
299
298
  } else {
300
- debug(`Cookies already loaded for ${test.opts.user}, but not for ${name}`)
299
+ output.debug(`Cookies already loaded for ${test.opts.user}, but not for ${name}`)
301
300
  await I.deleteCookie()
302
301
  }
303
302
  }
@@ -317,11 +316,11 @@ module.exports = function (config) {
317
316
  section.end()
318
317
  const cookies = await userSession.fetch(I)
319
318
  if (!cookies) {
320
- debug("Cannot save user session with empty cookies from auto login's fetch method")
319
+ output.debug("Cannot save user session with empty cookies from auto login's fetch method")
321
320
  return
322
321
  }
323
322
  if (config.saveToFile) {
324
- debug(`Saved user session into file for ${name}`)
323
+ output.debug(`Saved user session into file for ${name}`)
325
324
  fs.writeFileSync(path.join(global.output_dir, `${name}_session.json`), JSON.stringify(cookies))
326
325
  }
327
326
  store[`${name}_session`] = cookies
@@ -339,13 +338,13 @@ module.exports = function (config) {
339
338
  }
340
339
  section.end()
341
340
  recorder.session.catch(err => {
342
- debug(`Failed auto login for ${name} due to ${err}`)
343
- debug('Logging in again')
341
+ output.debug(`Failed auto login for ${name} due to ${err}`)
342
+ output.debug('Logging in again')
344
343
  recorder.session.start('auto login')
345
344
  return loginAndSave()
346
345
  .then(() => {
347
346
  recorder.add(() => recorder.session.restore('auto login'))
348
- recorder.catch(() => debug('continue'))
347
+ recorder.catch(() => output.debug('continue'))
349
348
  })
350
349
  .catch(err => {
351
350
  recorder.session.restore('auto login')
@@ -365,7 +364,7 @@ module.exports = function (config) {
365
364
  const suite = store.currentSuite
366
365
  if (!suite) return
367
366
 
368
- debug(`enabling auth as ${name} for each test of suite ${suite.title}`)
367
+ output.debug(`enabling auth as ${name} for each test of suite ${suite.title}`)
369
368
 
370
369
  // we are setting test opts so they can be picked up by Playwright if it starts browser for this test
371
370
  suite.eachTest(test => {
@@ -420,7 +419,7 @@ function loadCookiesFromFile(config) {
420
419
  } catch (err) {
421
420
  throw new Error(`Could not load session from ${fileName}\n${err}`)
422
421
  }
423
- debug(`Loaded user session for ${name}`)
422
+ output.debug(`Loaded user session for ${name}`)
424
423
  }
425
424
  }
426
425
 
@@ -1,8 +1,12 @@
1
- const Container = require('../container')
2
- const store = require('../store')
3
- const recorder = require('../recorder')
4
- const event = require('../event')
5
- const { log } = require('../output')
1
+ import Container from '../container.js'
2
+
3
+ import store from '../store.js'
4
+
5
+ import recorder from '../recorder.js'
6
+
7
+ import event from '../event.js'
8
+
9
+ import output from '../output.js'
6
10
  const standardActingHelpers = Container.STANDARD_ACTING_HELPERS
7
11
 
8
12
  const methodsToDelay = ['click', 'fillField', 'checkOption', 'pressKey', 'doubleClick', 'rightClick']
@@ -51,7 +55,7 @@ const defaultConfig = {
51
55
  * * `delayAfter`: put a delay after a command. 200ms by default
52
56
  *
53
57
  */
54
- module.exports = function (config) {
58
+ export default function (config) {
55
59
  const affectedHelpers = [...standardActingHelpers, 'REST']
56
60
  const helpers = Container.helpers()
57
61
  let helper
@@ -71,7 +75,7 @@ module.exports = function (config) {
71
75
 
72
76
  recorder.add('auto-delay', async () => {
73
77
  if (store.debugMode) return // no need to delay in debug
74
- log(`Delaying for ${config.delayBefore}ms`)
78
+ output.log(`Delaying for ${config.delayBefore}ms`)
75
79
  return new Promise(resolve => {
76
80
  setTimeout(resolve, config.delayBefore)
77
81
  })
@@ -83,7 +87,7 @@ module.exports = function (config) {
83
87
 
84
88
  recorder.add('auto-delay', async () => {
85
89
  if (store.debugMode) return // no need to delay in debug
86
- log(`Delaying for ${config.delayAfter}ms`)
90
+ output.log(`Delaying for ${config.delayAfter}ms`)
87
91
  return new Promise(resolve => {
88
92
  setTimeout(resolve, config.delayAfter)
89
93
  })
@@ -1,10 +1,14 @@
1
- const debugModule = require('debug')
2
- const { CoverageReport } = require('monocart-coverage-reports')
3
- const Container = require('../container')
4
- const recorder = require('../recorder')
5
- const event = require('../event')
6
- const output = require('../output')
7
- const { deepMerge } = require('../utils')
1
+ import debugModule from 'debug'
2
+ import { CoverageReport } from 'monocart-coverage-reports'
3
+ import Container from '../container.js'
4
+
5
+ import recorder from '../recorder.js'
6
+
7
+ import event from '../event.js'
8
+
9
+ import output from '../output.js'
10
+
11
+ import { deepMerge } from '../utils.js'
8
12
 
9
13
  const defaultConfig = {
10
14
  name: 'CodeceptJS Coverage Report',
@@ -113,7 +117,7 @@ const v8CoverageHelpers = {
113
117
  * * `sourcePath`: option to resolve a custom path.
114
118
  *
115
119
  */
116
- module.exports = function (config) {
120
+ export default function (config) {
117
121
  config = deepMerge(defaultConfig, config)
118
122
 
119
123
  if (config.debug) config.logging = 'debug'
@@ -1,5 +1,5 @@
1
- const Locator = require('../locator')
2
- const { xpathLocator } = require('../utils')
1
+ import Locator from '../locator.js'
2
+ import { xpathLocator } from '../utils.js'
3
3
 
4
4
  const defaultConfig = {
5
5
  prefix: '$',
@@ -111,7 +111,7 @@ const defaultConfig = {
111
111
  * I.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]
112
112
  * ```
113
113
  */
114
- module.exports = config => {
114
+ export default function (config) {
115
115
  config = { ...defaultConfig, ...config }
116
116
 
117
117
  Locator.addFilter((value, locatorObj) => {
@@ -1,9 +1,10 @@
1
- const event = require('../event')
1
+ import event from '../event.js'
2
+
2
3
 
3
4
  /**
4
5
  * Sample custom reporter for CodeceptJS.
5
6
  */
6
- module.exports = function (config) {
7
+ export default function (config) {
7
8
  event.dispatcher.on(event.hook.finished, hook => {
8
9
  if (config.onHookFinished) {
9
10
  config.onHookFinished(hook)
@@ -1,11 +1,16 @@
1
- const debug = require('debug')('codeceptjs:heal')
2
- const colors = require('chalk')
3
- const recorder = require('../recorder')
4
- const event = require('../event')
5
- const output = require('../output')
6
- const heal = require('../heal')
7
- const store = require('../store')
8
- const container = require('../container')
1
+ import debugFactory from 'debug'
2
+ const debug = debugFactory('codeceptjs:heal')
3
+ import colors from 'chalk'
4
+ import recorder from '../recorder.js'
5
+
6
+ import event from '../event.js'
7
+
8
+ import output from '../output.js'
9
+
10
+ import healModule from '../heal.js'
11
+ const heal = healModule.default || healModule
12
+ import store from '../store.js'
13
+
9
14
 
10
15
  const defaultConfig = {
11
16
  healLimit: 2,
@@ -29,7 +34,7 @@ const defaultConfig = {
29
34
  * * `healLimit` - how many steps can be healed in a single test (default: 2)
30
35
  *
31
36
  */
32
- module.exports = function (config = {}) {
37
+ export default function (config = {}) {
33
38
  if (store.debugMode && !process.env.DEBUG) {
34
39
  event.dispatcher.on(event.test.failed, () => {
35
40
  output.plugin('heal', 'Healing is disabled in --debug mode, use DEBUG="codeceptjs:heal" to enable it in debug mode')
@@ -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,7 +113,8 @@ 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
 
@@ -112,9 +125,38 @@ module.exports = config => {
112
125
  store.autoRetries = false
113
126
  return // disable retry when a test is not active
114
127
  }
128
+
129
+ // Don't apply plugin retry logic if there are already manual retries configured
130
+ // Check if any retry configs exist that aren't from this plugin
131
+ const hasManualRetries = recorder.retries.some(retry => retry !== config)
132
+ if (hasManualRetries) {
133
+ store.autoRetries = false
134
+ return
135
+ }
136
+
115
137
  // this option is used to set the retries inside _before() block of helpers
116
138
  store.autoRetries = true
117
139
  test.opts.conditionalRetries = config.retries
140
+ // debug: record applied retries value for tests
141
+ if (process.env.DEBUG_RETRY_PLUGIN) {
142
+ // eslint-disable-next-line no-console
143
+ console.log('[retryFailedStep] applying retries =', config.retries, 'for test', test.title)
144
+ }
118
145
  recorder.retry(config)
119
146
  })
147
+
148
+ // Fallback for environments where event.test.before wasn't emitted (runner scenarios)
149
+ event.dispatcher.on(event.test.started, test => {
150
+ if (test.opts?.disableRetryFailedStep || test.disableRetryFailedStep) return
151
+
152
+ // Don't apply plugin retry logic if there are already manual retries configured
153
+ // Check if any retry configs exist that aren't from this plugin
154
+ const hasManualRetries = recorder.retries.some(retry => retry !== config)
155
+ if (hasManualRetries) return
156
+
157
+ if (!store.autoRetries) {
158
+ store.autoRetries = true
159
+ test.opts.conditionalRetries = test.opts.conditionalRetries || config.retries
160
+ }
161
+ })
120
162
  }
@@ -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 = {}