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
@@ -1,9 +1,10 @@
1
- const { isGenerator } = require('../utils')
2
- const DataTable = require('./table')
3
- const DataScenarioConfig = require('./dataScenarioConfig')
4
- const Secret = require('../secret')
1
+ import { isGenerator } from '../utils.js'
2
+ import DataTable from './table.js'
3
+ import DataScenarioConfig from './dataScenarioConfig.js'
4
+ import secretModule from '../secret.js'
5
+ const Secret = secretModule.default || secretModule
5
6
 
6
- module.exports = function (context) {
7
+ export default function (context) {
7
8
  context.Data = function (dataTable) {
8
9
  const data = detectDataType(dataTable)
9
10
  return {
@@ -13,8 +14,8 @@ module.exports = function (context) {
13
14
  fn = opts
14
15
  opts = {}
15
16
  }
16
- opts.data = data.map((dataRow) => dataRow.data)
17
- data.forEach((dataRow) => {
17
+ opts.data = data.map(dataRow => dataRow.data)
18
+ data.forEach(dataRow => {
18
19
  const dataTitle = replaceTitle(title, dataRow)
19
20
  if (dataRow.skip) {
20
21
  context.xScenario(dataTitle)
@@ -32,8 +33,8 @@ module.exports = function (context) {
32
33
  fn = opts
33
34
  opts = {}
34
35
  }
35
- opts.data = data.map((dataRow) => dataRow.data)
36
- data.forEach((dataRow) => {
36
+ opts.data = data.map(dataRow => dataRow.data)
37
+ data.forEach(dataRow => {
37
38
  const dataTitle = replaceTitle(title, dataRow)
38
39
  if (dataRow.skip) {
39
40
  context.xScenario(dataTitle)
@@ -51,8 +52,8 @@ module.exports = function (context) {
51
52
  context.xData = function (dataTable) {
52
53
  const data = detectDataType(dataTable)
53
54
  return {
54
- Scenario: (title) => {
55
- data.forEach((dataRow) => {
55
+ Scenario: title => {
56
+ data.forEach(dataRow => {
56
57
  const dataTitle = replaceTitle(title, dataRow)
57
58
  context.xScenario(dataTitle)
58
59
  })
@@ -69,10 +70,7 @@ function replaceTitle(title, dataRow) {
69
70
 
70
71
  // if `dataRow` is object and has own `toString()` method,
71
72
  // it should be printed
72
- if (
73
- Object.prototype.toString.call(dataRow.data) === Object().toString() &&
74
- dataRow.data.toString() !== Object().toString()
75
- ) {
73
+ if (Object.prototype.toString.call(dataRow.data) === Object().toString() && dataRow.data.toString() !== Object().toString()) {
76
74
  return `${title} | ${dataRow.data}`
77
75
  }
78
76
 
@@ -102,7 +100,7 @@ function detectDataType(dataTable) {
102
100
  return dataTable()
103
101
  }
104
102
  if (Array.isArray(dataTable)) {
105
- return dataTable.map((item) => {
103
+ return dataTable.map(item => {
106
104
  if (isTableDataRow(item)) {
107
105
  return item
108
106
  }
@@ -117,10 +115,10 @@ function detectDataType(dataTable) {
117
115
  }
118
116
 
119
117
  function maskSecretInTitle(scenarios) {
120
- scenarios.forEach((scenario) => {
118
+ scenarios.forEach(scenario => {
121
119
  const res = []
122
120
 
123
- scenario.test.title.split(',').forEach((item) => {
121
+ scenario.test.title.split(',').forEach(item => {
124
122
  res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"'))
125
123
  })
126
124
 
@@ -10,7 +10,7 @@ class DataScenarioConfig {
10
10
  * @param {*} err
11
11
  */
12
12
  throws(err) {
13
- this.scenarios.forEach((scenario) => scenario.throws(err))
13
+ this.scenarios.forEach(scenario => scenario.throws(err))
14
14
  return this
15
15
  }
16
16
 
@@ -21,7 +21,7 @@ class DataScenarioConfig {
21
21
  *
22
22
  */
23
23
  fails() {
24
- this.scenarios.forEach((scenario) => scenario.fails())
24
+ this.scenarios.forEach(scenario => scenario.fails())
25
25
  return this
26
26
  }
27
27
 
@@ -31,7 +31,7 @@ class DataScenarioConfig {
31
31
  * @param {*} retries
32
32
  */
33
33
  retry(retries) {
34
- this.scenarios.forEach((scenario) => scenario.retry(retries))
34
+ this.scenarios.forEach(scenario => scenario.retry(retries))
35
35
  return this
36
36
  }
37
37
 
@@ -40,7 +40,7 @@ class DataScenarioConfig {
40
40
  * @param {*} timeout
41
41
  */
42
42
  timeout(timeout) {
43
- this.scenarios.forEach((scenario) => scenario.timeout(timeout))
43
+ this.scenarios.forEach(scenario => scenario.timeout(timeout))
44
44
  return this
45
45
  }
46
46
 
@@ -49,7 +49,7 @@ class DataScenarioConfig {
49
49
  * Helper name can be omitted and values will be applied to first helper.
50
50
  */
51
51
  config(helper, obj) {
52
- this.scenarios.forEach((scenario) => scenario.config(helper, obj))
52
+ this.scenarios.forEach(scenario => scenario.config(helper, obj))
53
53
  return this
54
54
  }
55
55
 
@@ -58,7 +58,7 @@ class DataScenarioConfig {
58
58
  * @param {*} tagName
59
59
  */
60
60
  tag(tagName) {
61
- this.scenarios.forEach((scenario) => scenario.tag(tagName))
61
+ this.scenarios.forEach(scenario => scenario.tag(tagName))
62
62
  return this
63
63
  }
64
64
 
@@ -67,7 +67,7 @@ class DataScenarioConfig {
67
67
  * @param {*} obj
68
68
  */
69
69
  inject(obj) {
70
- this.scenarios.forEach((scenario) => scenario.inject(obj))
70
+ this.scenarios.forEach(scenario => scenario.inject(obj))
71
71
  return this
72
72
  }
73
73
 
@@ -76,9 +76,9 @@ class DataScenarioConfig {
76
76
  * @param {*} dependencies
77
77
  */
78
78
  injectDependencies(dependencies) {
79
- this.scenarios.forEach((scenario) => scenario.injectDependencies(dependencies))
79
+ this.scenarios.forEach(scenario => scenario.injectDependencies(dependencies))
80
80
  return this
81
81
  }
82
82
  }
83
83
 
84
- module.exports = DataScenarioConfig
84
+ export default DataScenarioConfig
@@ -5,8 +5,8 @@
5
5
  class DataTableArgument {
6
6
  /** @param {*} gherkinDataTable */
7
7
  constructor(gherkinDataTable) {
8
- this.rawData = gherkinDataTable.rows.map((row) => {
9
- return row.cells.map((cell) => {
8
+ this.rawData = gherkinDataTable.rows.map(row => {
9
+ return row.cells.map(cell => {
10
10
  return cell.value
11
11
  })
12
12
  })
@@ -34,7 +34,7 @@ class DataTableArgument {
34
34
  hashes() {
35
35
  const copy = this.raw()
36
36
  const header = copy.shift()
37
- return copy.map((row) => {
37
+ return copy.map(row => {
38
38
  const r = {}
39
39
  row.forEach((cell, index) => (r[header[index]] = cell))
40
40
  return r
@@ -47,20 +47,20 @@ class DataTableArgument {
47
47
  */
48
48
  rowsHash() {
49
49
  const rows = this.raw()
50
- const everyRowHasTwoColumns = rows.every((row) => row.length === 2)
50
+ const everyRowHasTwoColumns = rows.every(row => row.length === 2)
51
51
  if (!everyRowHasTwoColumns) {
52
52
  throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns')
53
53
  }
54
54
  /** @type {Record<string, string>} */
55
55
  const result = {}
56
- rows.forEach((x) => (result[x[0]] = x[1]))
56
+ rows.forEach(x => (result[x[0]] = x[1]))
57
57
  return result
58
58
  }
59
59
 
60
60
  /** Transposed the data */
61
61
  transpose() {
62
- this.rawData = this.rawData[0].map((x, i) => this.rawData.map((y) => y[i]))
62
+ this.rawData = this.rawData[0].map((x, i) => this.rawData.map(y => y[i]))
63
63
  }
64
64
  }
65
65
 
66
- module.exports = DataTableArgument
66
+ export default DataTableArgument
package/lib/data/table.js CHANGED
@@ -10,13 +10,10 @@ class DataTable {
10
10
 
11
11
  /** @param {Array<*>} array */
12
12
  add(array) {
13
- if (array.length !== this.array.length)
14
- throw new Error(
15
- `There is too many elements in given data array. Please provide data in this format: ${this.array}`,
16
- )
13
+ if (array.length !== this.array.length) throw new Error(`There is too many elements in given data array. Please provide data in this format: ${this.array}`)
17
14
  const tempObj = {}
18
15
  let arrayCounter = 0
19
- this.array.forEach((elem) => {
16
+ this.array.forEach(elem => {
20
17
  tempObj[elem] = array[arrayCounter]
21
18
  tempObj.toString = () => JSON.stringify(tempObj)
22
19
  arrayCounter++
@@ -26,13 +23,10 @@ class DataTable {
26
23
 
27
24
  /** @param {Array<*>} array */
28
25
  xadd(array) {
29
- if (array.length !== this.array.length)
30
- throw new Error(
31
- `There is too many elements in given data array. Please provide data in this format: ${this.array}`,
32
- )
26
+ if (array.length !== this.array.length) throw new Error(`There is too many elements in given data array. Please provide data in this format: ${this.array}`)
33
27
  const tempObj = {}
34
28
  let arrayCounter = 0
35
- this.array.forEach((elem) => {
29
+ this.array.forEach(elem => {
36
30
  tempObj[elem] = array[arrayCounter]
37
31
  tempObj.toString = () => JSON.stringify(tempObj)
38
32
  arrayCounter++
@@ -42,8 +36,8 @@ class DataTable {
42
36
 
43
37
  /** @param {Function} func */
44
38
  filter(func) {
45
- return this.rows.filter((row) => func(row.data))
39
+ return this.rows.filter(row => func(row.data))
46
40
  }
47
41
  }
48
42
 
49
- module.exports = DataTable
43
+ export default DataTable
package/lib/effects.js ADDED
@@ -0,0 +1,307 @@
1
+ import recorder from './recorder.js'
2
+ import output from './output.js'
3
+ import store from './store.js'
4
+ import event from './event.js'
5
+ import container from './container.js'
6
+ import MetaStep from './step/meta.js'
7
+ import { isAsyncFunction } from './utils.js'
8
+
9
+ /**
10
+ * @param {CodeceptJS.LocatorOrString} context
11
+ * @param {Function} fn
12
+ * @return {Promise<*> | undefined}
13
+ */
14
+ function within(context, fn) {
15
+ const helpers = store.dryRun ? {} : container.helpers()
16
+ const locator = typeof context === 'object' ? JSON.stringify(context) : context
17
+
18
+ return recorder.add(
19
+ 'register within wrapper',
20
+ () => {
21
+ const metaStep = new WithinStep(locator, fn)
22
+ const defineMetaStep = step => (step.metaStep = metaStep)
23
+ recorder.session.start('within')
24
+
25
+ event.dispatcher.prependListener(event.step.before, defineMetaStep)
26
+
27
+ Object.keys(helpers).forEach(helper => {
28
+ if (helpers[helper]._withinBegin) recorder.add(`[${helper}] start within`, () => helpers[helper]._withinBegin(context))
29
+ })
30
+
31
+ const finalize = () => {
32
+ event.dispatcher.removeListener(event.step.before, defineMetaStep)
33
+ recorder.add('Finalize session within session', () => {
34
+ output.stepShift = 1
35
+ recorder.session.restore('within')
36
+ })
37
+ }
38
+ const finishHelpers = () => {
39
+ Object.keys(helpers).forEach(helper => {
40
+ if (helpers[helper]._withinEnd) recorder.add(`[${helper}] finish within`, () => helpers[helper]._withinEnd())
41
+ })
42
+ }
43
+
44
+ if (isAsyncFunction(fn)) {
45
+ return fn()
46
+ .then(res => {
47
+ finishHelpers()
48
+ finalize()
49
+ return recorder.promise().then(() => res)
50
+ })
51
+ .catch(e => {
52
+ finalize()
53
+ recorder.throw(e)
54
+ })
55
+ }
56
+
57
+ let res
58
+ try {
59
+ res = fn()
60
+ } catch (err) {
61
+ recorder.throw(err)
62
+ } finally {
63
+ finishHelpers()
64
+ recorder.catch(err => {
65
+ output.stepShift = 1
66
+ throw err
67
+ })
68
+ }
69
+ finalize()
70
+ return recorder.promise().then(() => res)
71
+ },
72
+ false,
73
+ false,
74
+ )
75
+ }
76
+
77
+ class WithinStep extends MetaStep {
78
+ constructor(locator, fn) {
79
+ super('Within')
80
+ this.args = [locator]
81
+ }
82
+
83
+ toString() {
84
+ return `${this.prefix}Within ${this.humanizeArgs()}${this.suffix}`
85
+ }
86
+ }
87
+
88
+ /**
89
+ * A utility function for CodeceptJS tests that acts as a soft assertion.
90
+ * Executes a callback within a recorded session, ensuring errors are handled gracefully without failing the test immediately.
91
+ *
92
+ * @async
93
+ * @function hopeThat
94
+ * @param {Function} callback - The callback function containing the logic to validate.
95
+ * This function should perform the desired assertion or condition check.
96
+ * @returns {Promise<boolean|any>} A promise resolving to `true` if the assertion or condition was successful,
97
+ * or `false` if an error occurred.
98
+ *
99
+ * @description
100
+ * - Designed for use in CodeceptJS tests as a "soft assertion."
101
+ * Unlike standard assertions, it does not stop the test execution on failure.
102
+ * - Starts a new recorder session named 'hopeThat' and manages state restoration.
103
+ * - Logs errors and attaches them as notes to the test, enabling post-test reporting of soft assertion failures.
104
+ * - Resets the `store.hopeThat` flag after the execution, ensuring clean state for subsequent operations.
105
+ *
106
+ * @example
107
+ * const { hopeThat } = require('codeceptjs/effects')
108
+ * await hopeThat(() => {
109
+ * I.see('Welcome'); // Perform a soft assertion
110
+ * });
111
+ *
112
+ * @throws Will handle errors that occur during the callback execution. Errors are logged and attached as notes to the test.
113
+ */
114
+ async function hopeThat(callback) {
115
+ if (store.dryRun) return
116
+ const sessionName = 'hopeThat'
117
+
118
+ let result = false
119
+ return recorder.add(
120
+ 'hopeThat',
121
+ () => {
122
+ recorder.session.start(sessionName)
123
+ store.hopeThat = true
124
+ callback()
125
+ recorder.add(() => {
126
+ result = true
127
+ recorder.session.restore(sessionName)
128
+ return result
129
+ })
130
+ recorder.session.catch(err => {
131
+ result = false
132
+ const msg = err.inspect ? err.inspect() : err.toString()
133
+ output.debug(`Unsuccessful assertion > ${msg}`)
134
+ event.dispatcher.once(event.test.finished, test => {
135
+ if (!test.notes) test.notes = []
136
+ test.notes.push({ type: 'conditionalError', text: msg })
137
+ })
138
+ recorder.session.restore(sessionName)
139
+ return result
140
+ })
141
+ return recorder.add(
142
+ 'result',
143
+ () => {
144
+ store.hopeThat = undefined
145
+ return result
146
+ },
147
+ true,
148
+ false,
149
+ )
150
+ },
151
+ false,
152
+ false,
153
+ )
154
+ }
155
+
156
+ /**
157
+ * A CodeceptJS utility function to retry a step or callback multiple times with a specified polling interval.
158
+ *
159
+ * @async
160
+ * @function retryTo
161
+ * @param {Function} callback - The function to execute, which will be retried upon failure.
162
+ * Receives the current retry count as an argument.
163
+ * @param {number} maxTries - The maximum number of attempts to retry the callback.
164
+ * @param {number} [pollInterval=200] - The delay (in milliseconds) between retry attempts.
165
+ * @returns {Promise<void|any>} A promise that resolves when the callback executes successfully, or rejects after reaching the maximum retries.
166
+ *
167
+ * @description
168
+ * - This function is designed for use in CodeceptJS tests to handle intermittent or flaky test steps.
169
+ * - Starts a new recorder session for each retry attempt, ensuring proper state management and error handling.
170
+ * - Logs errors and retries the callback until it either succeeds or the maximum number of attempts is reached.
171
+ * - Restores the session state after each attempt, whether successful or not.
172
+ *
173
+ * @example
174
+ * const { hopeThat } = require('codeceptjs/effects')
175
+ * await retryTo((tries) => {
176
+ * if (tries < 3) {
177
+ * I.see('Non-existent element'); // Simulates a failure
178
+ * } else {
179
+ * I.see('Welcome'); // Succeeds on the 3rd attempt
180
+ * }
181
+ * }, 5, 300); // Retry up to 5 times, with a 300ms interval
182
+ *
183
+ * @throws Will reject with the last error encountered if the maximum retries are exceeded.
184
+ */
185
+ async function retryTo(callback, maxTries, pollInterval = 200) {
186
+ const sessionName = 'retryTo'
187
+
188
+ return new Promise((done, reject) => {
189
+ let tries = 1
190
+
191
+ function handleRetryException(err) {
192
+ recorder.throw(err)
193
+ reject(err)
194
+ }
195
+
196
+ const tryBlock = async () => {
197
+ tries++
198
+ recorder.session.start(`${sessionName} ${tries}`)
199
+ try {
200
+ await callback(tries)
201
+ } catch (err) {
202
+ handleRetryException(err)
203
+ }
204
+
205
+ // Call done if no errors
206
+ recorder.add(() => {
207
+ recorder.session.restore(`${sessionName} ${tries}`)
208
+ done(null)
209
+ })
210
+
211
+ // Catch errors and retry
212
+ recorder.session.catch(err => {
213
+ recorder.session.restore(`${sessionName} ${tries}`)
214
+ if (tries <= maxTries) {
215
+ output.debug(`Error ${err}... Retrying`)
216
+ recorder.add(`${sessionName} ${tries}`, () => setTimeout(tryBlock, pollInterval))
217
+ } else {
218
+ // if maxTries reached
219
+ handleRetryException(err)
220
+ }
221
+ })
222
+ }
223
+
224
+ recorder.add(sessionName, tryBlock).catch(err => {
225
+ console.error('An error occurred:', err)
226
+ done(null)
227
+ })
228
+ })
229
+ }
230
+
231
+ /**
232
+ * A CodeceptJS utility function to attempt a step or callback without failing the test.
233
+ * If the step fails, the test continues execution without interruption, and the result is logged.
234
+ *
235
+ * @async
236
+ * @function tryTo
237
+ * @param {Function} callback - The function to execute, which may succeed or fail.
238
+ * This function contains the logic to be attempted.
239
+ * @returns {Promise<boolean|any>} A promise resolving to `true` if the step succeeds, or `false` if it fails.
240
+ *
241
+ * @description
242
+ * - Useful for scenarios where certain steps are optional or their failure should not interrupt the test flow.
243
+ * - Starts a new recorder session named 'tryTo' for isolation and error handling.
244
+ * - Captures errors during execution and logs them for debugging purposes.
245
+ * - Ensures the `store.tryTo` flag is reset after execution to maintain a clean state.
246
+ *
247
+ * @example
248
+ * const { tryTo } = require('codeceptjs/effects')
249
+ * const wasSuccessful = await tryTo(() => {
250
+ * I.see('Welcome'); // Attempt to find an element on the page
251
+ * });
252
+ *
253
+ * if (!wasSuccessful) {
254
+ * I.say('Optional step failed, but test continues.');
255
+ * }
256
+ *
257
+ * @throws Will handle errors internally, logging them and returning `false` as the result.
258
+ */
259
+ async function tryTo(callback) {
260
+ if (store.dryRun) return
261
+ const sessionName = 'tryTo'
262
+
263
+ let result = false
264
+ let isAutoRetriesEnabled = store.autoRetries
265
+ return recorder.add(
266
+ sessionName,
267
+ () => {
268
+ recorder.session.start(sessionName)
269
+ isAutoRetriesEnabled = store.autoRetries
270
+ if (isAutoRetriesEnabled) output.debug('Auto retries disabled inside tryTo effect')
271
+ store.autoRetries = false
272
+ callback()
273
+ recorder.add(() => {
274
+ result = true
275
+ recorder.session.restore(sessionName)
276
+ return result
277
+ })
278
+ recorder.session.catch(err => {
279
+ result = false
280
+ const msg = err.inspect ? err.inspect() : err.toString()
281
+ output.debug(`Unsuccessful try > ${msg}`)
282
+ recorder.session.restore(sessionName)
283
+ return result
284
+ })
285
+ return recorder.add(
286
+ 'result',
287
+ () => {
288
+ store.autoRetries = isAutoRetriesEnabled
289
+ return result
290
+ },
291
+ true,
292
+ false,
293
+ )
294
+ },
295
+ false,
296
+ false,
297
+ )
298
+ }
299
+
300
+ export { hopeThat, retryTo, tryTo, within }
301
+
302
+ export default {
303
+ hopeThat,
304
+ retryTo,
305
+ tryTo,
306
+ within,
307
+ }