codeceptjs 3.7.0-beta.1 → 3.7.0-beta.11

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 (73) hide show
  1. package/README.md +9 -10
  2. package/bin/codecept.js +7 -0
  3. package/lib/actor.js +46 -92
  4. package/lib/ai.js +130 -121
  5. package/lib/codecept.js +2 -2
  6. package/lib/command/check.js +186 -0
  7. package/lib/command/definitions.js +3 -1
  8. package/lib/command/interactive.js +1 -1
  9. package/lib/command/run-workers.js +2 -54
  10. package/lib/command/workers/runTests.js +64 -225
  11. package/lib/container.js +32 -0
  12. package/lib/effects.js +218 -0
  13. package/lib/els.js +87 -106
  14. package/lib/event.js +18 -17
  15. package/lib/heal.js +10 -0
  16. package/lib/helper/AI.js +2 -1
  17. package/lib/helper/Appium.js +31 -22
  18. package/lib/helper/Playwright.js +22 -1
  19. package/lib/helper/Puppeteer.js +5 -0
  20. package/lib/helper/WebDriver.js +29 -8
  21. package/lib/listener/emptyRun.js +2 -5
  22. package/lib/listener/exit.js +5 -8
  23. package/lib/listener/globalTimeout.js +66 -10
  24. package/lib/listener/result.js +12 -0
  25. package/lib/listener/steps.js +3 -6
  26. package/lib/listener/store.js +9 -1
  27. package/lib/mocha/asyncWrapper.js +15 -3
  28. package/lib/mocha/cli.js +79 -28
  29. package/lib/mocha/featureConfig.js +13 -0
  30. package/lib/mocha/hooks.js +32 -3
  31. package/lib/mocha/inject.js +5 -0
  32. package/lib/mocha/scenarioConfig.js +11 -0
  33. package/lib/mocha/suite.js +27 -1
  34. package/lib/mocha/test.js +102 -3
  35. package/lib/mocha/types.d.ts +11 -0
  36. package/lib/output.js +75 -73
  37. package/lib/pause.js +3 -10
  38. package/lib/plugin/analyze.js +349 -0
  39. package/lib/plugin/autoDelay.js +2 -2
  40. package/lib/plugin/commentStep.js +5 -0
  41. package/lib/plugin/customReporter.js +52 -0
  42. package/lib/plugin/heal.js +30 -0
  43. package/lib/plugin/pageInfo.js +140 -0
  44. package/lib/plugin/retryTo.js +18 -118
  45. package/lib/plugin/screenshotOnFail.js +12 -17
  46. package/lib/plugin/standardActingHelpers.js +4 -1
  47. package/lib/plugin/stepByStepReport.js +6 -5
  48. package/lib/plugin/stepTimeout.js +1 -1
  49. package/lib/plugin/tryTo.js +17 -107
  50. package/lib/recorder.js +5 -5
  51. package/lib/rerun.js +43 -42
  52. package/lib/result.js +161 -0
  53. package/lib/step/base.js +228 -0
  54. package/lib/step/config.js +50 -0
  55. package/lib/step/func.js +46 -0
  56. package/lib/step/helper.js +50 -0
  57. package/lib/step/meta.js +99 -0
  58. package/lib/step/record.js +74 -0
  59. package/lib/step/retry.js +11 -0
  60. package/lib/step/section.js +55 -0
  61. package/lib/step.js +20 -347
  62. package/lib/steps.js +50 -0
  63. package/lib/store.js +4 -0
  64. package/lib/timeout.js +66 -0
  65. package/lib/utils.js +93 -0
  66. package/lib/within.js +2 -2
  67. package/lib/workers.js +29 -49
  68. package/package.json +23 -20
  69. package/typings/index.d.ts +5 -4
  70. package/typings/promiseBasedTypes.d.ts +507 -49
  71. package/typings/types.d.ts +623 -73
  72. package/lib/listener/artifacts.js +0 -19
  73. package/lib/plugin/debugErrors.js +0 -67
package/lib/container.js CHANGED
@@ -1,5 +1,6 @@
1
1
  const glob = require('glob')
2
2
  const path = require('path')
3
+ const debug = require('debug')('codeceptjs:container')
3
4
  const { MetaStep } = require('./step')
4
5
  const { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally } = require('./utils')
5
6
  const Translation = require('./translation')
@@ -8,6 +9,7 @@ const recorder = require('./recorder')
8
9
  const event = require('./event')
9
10
  const WorkerStorage = require('./workerStorage')
10
11
  const store = require('./store')
12
+ const Result = require('./result')
11
13
  const ai = require('./ai')
12
14
 
13
15
  let asyncHelperPromise
@@ -24,12 +26,21 @@ let container = {
24
26
  */
25
27
  mocha: {},
26
28
  translation: {},
29
+ /** @type {Result | null} */
30
+ result: null,
27
31
  }
28
32
 
29
33
  /**
30
34
  * Dependency Injection Container
31
35
  */
32
36
  class Container {
37
+ /**
38
+ * Get the standard acting helpers of CodeceptJS Container
39
+ *
40
+ */
41
+ static get STANDARD_ACTING_HELPERS() {
42
+ return ['Playwright', 'WebDriver', 'Puppeteer', 'Appium', 'TestCafe']
43
+ }
33
44
  /**
34
45
  * Create container with all required helpers and support objects
35
46
  *
@@ -38,6 +49,7 @@ class Container {
38
49
  * @param {*} opts
39
50
  */
40
51
  static create(config, opts) {
52
+ debug('creating container')
41
53
  asyncHelperPromise = Promise.resolve()
42
54
 
43
55
  // dynamically create mocha instance
@@ -52,6 +64,7 @@ class Container {
52
64
  container.translation = loadTranslation(config.translation || null, config.vocabularies || [])
53
65
  container.proxySupport = createSupportObjects(config.include || {})
54
66
  container.plugins = createPlugins(config.plugins || {}, opts)
67
+ container.result = new Result()
55
68
 
56
69
  createActor(config.include?.I)
57
70
 
@@ -125,6 +138,18 @@ class Container {
125
138
  return container.mocha
126
139
  }
127
140
 
141
+ /**
142
+ * Get result
143
+ *
144
+ * @returns {Result}
145
+ */
146
+ static result() {
147
+ if (!container.result) {
148
+ container.result = new Result()
149
+ }
150
+ return container.result
151
+ }
152
+
128
153
  /**
129
154
  * Append new services to container
130
155
  *
@@ -134,6 +159,7 @@ class Container {
134
159
  static append(newContainer) {
135
160
  const deepMerge = require('./utils').deepMerge
136
161
  container = deepMerge(container, newContainer)
162
+ debug('appended', JSON.stringify(newContainer).slice(0, 300))
137
163
  }
138
164
 
139
165
  /**
@@ -150,6 +176,7 @@ class Container {
150
176
  container.plugins = newPlugins || {}
151
177
  asyncHelperPromise = Promise.resolve()
152
178
  store.actor = null
179
+ debug('container cleared')
153
180
  }
154
181
 
155
182
  /**
@@ -216,6 +243,8 @@ function createHelpers(config) {
216
243
  throw new Error(`Helper class from module '${helperName}' is not a class. Use CJS async module syntax.`)
217
244
  }
218
245
 
246
+ debug(`helper ${helperName} async initialized`)
247
+
219
248
  helpers[helperName] = new ResolvedHelperClass(config[helperName])
220
249
  })
221
250
 
@@ -225,6 +254,7 @@ function createHelpers(config) {
225
254
  checkHelperRequirements(HelperClass)
226
255
 
227
256
  helpers[helperName] = new HelperClass(config[helperName])
257
+ debug(`helper ${helperName} initialized`)
228
258
  } catch (err) {
229
259
  throw new Error(`Could not load helper ${helperName} (${err.message})`)
230
260
  }
@@ -322,6 +352,7 @@ function createSupportObjects(config) {
322
352
  if (container.support[name]._init) {
323
353
  container.support[name]._init()
324
354
  }
355
+ debug(`support object ${name} initialized`)
325
356
  } catch (err) {
326
357
  throw new Error(`Initialization failed for ${name}: ${container.support[name]}\n${err.message}\n${err.stack}`)
327
358
  }
@@ -334,6 +365,7 @@ function createSupportObjects(config) {
334
365
  const ms = new MetaStep(name, prop)
335
366
  ms.setContext(currentObject)
336
367
  if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue)
368
+ debug(`metastep is created for ${name}.${prop.toString()}()`)
337
369
  return ms.run.bind(ms, currentValue)
338
370
  }
339
371
 
package/lib/effects.js ADDED
@@ -0,0 +1,218 @@
1
+ const recorder = require('./recorder')
2
+ const { debug } = require('./output')
3
+ const store = require('./store')
4
+ const event = require('./event')
5
+
6
+ /**
7
+ * A utility function for CodeceptJS tests that acts as a soft assertion.
8
+ * Executes a callback within a recorded session, ensuring errors are handled gracefully without failing the test immediately.
9
+ *
10
+ * @async
11
+ * @function hopeThat
12
+ * @param {Function} callback - The callback function containing the logic to validate.
13
+ * This function should perform the desired assertion or condition check.
14
+ * @returns {Promise<boolean|any>} A promise resolving to `true` if the assertion or condition was successful,
15
+ * or `false` if an error occurred.
16
+ *
17
+ * @description
18
+ * - Designed for use in CodeceptJS tests as a "soft assertion."
19
+ * Unlike standard assertions, it does not stop the test execution on failure.
20
+ * - Starts a new recorder session named 'hopeThat' and manages state restoration.
21
+ * - Logs errors and attaches them as notes to the test, enabling post-test reporting of soft assertion failures.
22
+ * - Resets the `store.hopeThat` flag after the execution, ensuring clean state for subsequent operations.
23
+ *
24
+ * @example
25
+ * const { hopeThat } = require('codeceptjs/effects')
26
+ * await hopeThat(() => {
27
+ * I.see('Welcome'); // Perform a soft assertion
28
+ * });
29
+ *
30
+ * @throws Will handle errors that occur during the callback execution. Errors are logged and attached as notes to the test.
31
+ */
32
+ async function hopeThat(callback) {
33
+ if (store.dryRun) return
34
+ const sessionName = 'hopeThat'
35
+
36
+ let result = false
37
+ return recorder.add(
38
+ 'hopeThat',
39
+ () => {
40
+ recorder.session.start(sessionName)
41
+ store.hopeThat = true
42
+ callback()
43
+ recorder.add(() => {
44
+ result = true
45
+ recorder.session.restore(sessionName)
46
+ return result
47
+ })
48
+ recorder.session.catch(err => {
49
+ result = false
50
+ const msg = err.inspect ? err.inspect() : err.toString()
51
+ debug(`Unsuccessful assertion > ${msg}`)
52
+ event.dispatcher.once(event.test.finished, test => {
53
+ test.notes.push({ type: 'conditionalError', text: msg })
54
+ })
55
+ recorder.session.restore(sessionName)
56
+ return result
57
+ })
58
+ return recorder.add(
59
+ 'result',
60
+ () => {
61
+ store.hopeThat = undefined
62
+ return result
63
+ },
64
+ true,
65
+ false,
66
+ )
67
+ },
68
+ false,
69
+ false,
70
+ )
71
+ }
72
+
73
+ /**
74
+ * A CodeceptJS utility function to retry a step or callback multiple times with a specified polling interval.
75
+ *
76
+ * @async
77
+ * @function retryTo
78
+ * @param {Function} callback - The function to execute, which will be retried upon failure.
79
+ * Receives the current retry count as an argument.
80
+ * @param {number} maxTries - The maximum number of attempts to retry the callback.
81
+ * @param {number} [pollInterval=200] - The delay (in milliseconds) between retry attempts.
82
+ * @returns {Promise<void|any>} A promise that resolves when the callback executes successfully, or rejects after reaching the maximum retries.
83
+ *
84
+ * @description
85
+ * - This function is designed for use in CodeceptJS tests to handle intermittent or flaky test steps.
86
+ * - Starts a new recorder session for each retry attempt, ensuring proper state management and error handling.
87
+ * - Logs errors and retries the callback until it either succeeds or the maximum number of attempts is reached.
88
+ * - Restores the session state after each attempt, whether successful or not.
89
+ *
90
+ * @example
91
+ * const { hopeThat } = require('codeceptjs/effects')
92
+ * await retryTo((tries) => {
93
+ * if (tries < 3) {
94
+ * I.see('Non-existent element'); // Simulates a failure
95
+ * } else {
96
+ * I.see('Welcome'); // Succeeds on the 3rd attempt
97
+ * }
98
+ * }, 5, 300); // Retry up to 5 times, with a 300ms interval
99
+ *
100
+ * @throws Will reject with the last error encountered if the maximum retries are exceeded.
101
+ */
102
+ async function retryTo(callback, maxTries, pollInterval = 200) {
103
+ const sessionName = 'retryTo'
104
+
105
+ return new Promise((done, reject) => {
106
+ let tries = 1
107
+
108
+ function handleRetryException(err) {
109
+ recorder.throw(err)
110
+ reject(err)
111
+ }
112
+
113
+ const tryBlock = async () => {
114
+ tries++
115
+ recorder.session.start(`${sessionName} ${tries}`)
116
+ try {
117
+ await callback(tries)
118
+ } catch (err) {
119
+ handleRetryException(err)
120
+ }
121
+
122
+ // Call done if no errors
123
+ recorder.add(() => {
124
+ recorder.session.restore(`${sessionName} ${tries}`)
125
+ done(null)
126
+ })
127
+
128
+ // Catch errors and retry
129
+ recorder.session.catch(err => {
130
+ recorder.session.restore(`${sessionName} ${tries}`)
131
+ if (tries <= maxTries) {
132
+ debug(`Error ${err}... Retrying`)
133
+ recorder.add(`${sessionName} ${tries}`, () => setTimeout(tryBlock, pollInterval))
134
+ } else {
135
+ // if maxTries reached
136
+ handleRetryException(err)
137
+ }
138
+ })
139
+ }
140
+
141
+ recorder.add(sessionName, tryBlock).catch(err => {
142
+ console.error('An error occurred:', err)
143
+ done(null)
144
+ })
145
+ })
146
+ }
147
+
148
+ /**
149
+ * A CodeceptJS utility function to attempt a step or callback without failing the test.
150
+ * If the step fails, the test continues execution without interruption, and the result is logged.
151
+ *
152
+ * @async
153
+ * @function tryTo
154
+ * @param {Function} callback - The function to execute, which may succeed or fail.
155
+ * This function contains the logic to be attempted.
156
+ * @returns {Promise<boolean|any>} A promise resolving to `true` if the step succeeds, or `false` if it fails.
157
+ *
158
+ * @description
159
+ * - Useful for scenarios where certain steps are optional or their failure should not interrupt the test flow.
160
+ * - Starts a new recorder session named 'tryTo' for isolation and error handling.
161
+ * - Captures errors during execution and logs them for debugging purposes.
162
+ * - Ensures the `store.tryTo` flag is reset after execution to maintain a clean state.
163
+ *
164
+ * @example
165
+ * const { tryTo } = require('codeceptjs/effects')
166
+ * const wasSuccessful = await tryTo(() => {
167
+ * I.see('Welcome'); // Attempt to find an element on the page
168
+ * });
169
+ *
170
+ * if (!wasSuccessful) {
171
+ * I.say('Optional step failed, but test continues.');
172
+ * }
173
+ *
174
+ * @throws Will handle errors internally, logging them and returning `false` as the result.
175
+ */
176
+ async function tryTo(callback) {
177
+ if (store.dryRun) return
178
+ const sessionName = 'tryTo'
179
+
180
+ let result = false
181
+ return recorder.add(
182
+ sessionName,
183
+ () => {
184
+ recorder.session.start(sessionName)
185
+ store.tryTo = true
186
+ callback()
187
+ recorder.add(() => {
188
+ result = true
189
+ recorder.session.restore(sessionName)
190
+ return result
191
+ })
192
+ recorder.session.catch(err => {
193
+ result = false
194
+ const msg = err.inspect ? err.inspect() : err.toString()
195
+ debug(`Unsuccessful try > ${msg}`)
196
+ recorder.session.restore(sessionName)
197
+ return result
198
+ })
199
+ return recorder.add(
200
+ 'result',
201
+ () => {
202
+ store.tryTo = undefined
203
+ return result
204
+ },
205
+ true,
206
+ false,
207
+ )
208
+ },
209
+ false,
210
+ false,
211
+ )
212
+ }
213
+
214
+ module.exports = {
215
+ hopeThat,
216
+ retryTo,
217
+ tryTo,
218
+ }
package/lib/els.js CHANGED
@@ -1,115 +1,124 @@
1
- const output = require('./output');
2
- const store = require('./store');
3
- const recorder = require('./recorder');
4
- const container = require('./container');
5
- const event = require('./event');
6
- const Step = require('./step');
7
- const { truth } = require('./assert/truth');
8
- const { isAsyncFunction, humanizeFunction } = require('./utils');
1
+ const output = require('./output')
2
+ const store = require('./store')
3
+ const container = require('./container')
4
+ const StepConfig = require('./step/config')
5
+ const recordStep = require('./step/record')
6
+ const FuncStep = require('./step/func')
7
+ const { truth } = require('./assert/truth')
8
+ const { isAsyncFunction, humanizeFunction } = require('./utils')
9
9
 
10
10
  function element(purpose, locator, fn) {
11
- if (!fn) {
12
- fn = locator;
13
- locator = purpose;
14
- purpose = 'first element';
11
+ let stepConfig
12
+ if (arguments[arguments.length - 1] instanceof StepConfig) {
13
+ stepConfig = arguments[arguments.length - 1]
15
14
  }
16
15
 
17
- const step = prepareStep(purpose, locator, fn);
18
- if (!step) return;
16
+ if (!fn || fn === stepConfig) {
17
+ fn = locator
18
+ locator = purpose
19
+ purpose = 'first element'
20
+ }
19
21
 
20
- return executeStep(step, async () => {
21
- const els = await step.helper._locate(locator);
22
- output.debug(`Found ${els.length} elements, using first element`);
22
+ const step = prepareStep(purpose, locator, fn)
23
+ if (!step) return
23
24
 
24
- return fn(els[0]);
25
- });
25
+ return executeStep(
26
+ step,
27
+ async () => {
28
+ const els = await step.helper._locate(locator)
29
+ output.debug(`Found ${els.length} elements, using first element`)
30
+
31
+ return fn(els[0])
32
+ },
33
+ stepConfig,
34
+ )
26
35
  }
27
36
 
28
37
  function eachElement(purpose, locator, fn) {
29
38
  if (!fn) {
30
- fn = locator;
31
- locator = purpose;
32
- purpose = 'for each element';
39
+ fn = locator
40
+ locator = purpose
41
+ purpose = 'for each element'
33
42
  }
34
43
 
35
- const step = prepareStep(purpose, locator, fn);
36
- if (!step) return;
44
+ const step = prepareStep(purpose, locator, fn)
45
+ if (!step) return
37
46
 
38
47
  return executeStep(step, async () => {
39
- const els = await step.helper._locate(locator);
40
- output.debug(`Found ${els.length} elements for each elements to iterate`);
48
+ const els = await step.helper._locate(locator)
49
+ output.debug(`Found ${els.length} elements for each elements to iterate`)
41
50
 
42
- const errs = [];
43
- let i = 0;
51
+ const errs = []
52
+ let i = 0
44
53
  for (const el of els) {
45
54
  try {
46
- await fn(el, i);
55
+ await fn(el, i)
47
56
  } catch (err) {
48
- output.error(`eachElement: failed operation on element #${i} ${el}`);
49
- errs.push(err);
57
+ output.error(`eachElement: failed operation on element #${i} ${el}`)
58
+ errs.push(err)
50
59
  }
51
- i++;
60
+ i++
52
61
  }
53
62
 
54
63
  if (errs.length) {
55
- throw errs[0];
64
+ throw errs[0]
56
65
  }
57
- });
66
+ })
58
67
  }
59
68
 
60
69
  function expectElement(locator, fn) {
61
- const step = prepareStep('expect element to be', locator, fn);
62
- if (!step) return;
70
+ const step = prepareStep('expect element to be', locator, fn)
71
+ if (!step) return
63
72
 
64
73
  return executeStep(step, async () => {
65
- const els = await step.helper._locate(locator);
66
- output.debug(`Found ${els.length} elements, first will be used for assertion`);
74
+ const els = await step.helper._locate(locator)
75
+ output.debug(`Found ${els.length} elements, first will be used for assertion`)
67
76
 
68
- const result = await fn(els[0]);
69
- const assertion = truth(`element (${locator})`, fn.toString());
70
- assertion.assert(result);
71
- });
77
+ const result = await fn(els[0])
78
+ const assertion = truth(`element (${locator})`, fn.toString())
79
+ assertion.assert(result)
80
+ })
72
81
  }
73
82
 
74
83
  function expectAnyElement(locator, fn) {
75
- const step = prepareStep('expect any element to be', locator, fn);
76
- if (!step) return;
84
+ const step = prepareStep('expect any element to be', locator, fn)
85
+ if (!step) return
77
86
 
78
87
  return executeStep(step, async () => {
79
- const els = await step.helper._locate(locator);
80
- output.debug(`Found ${els.length} elements, at least one should pass the assertion`);
88
+ const els = await step.helper._locate(locator)
89
+ output.debug(`Found ${els.length} elements, at least one should pass the assertion`)
81
90
 
82
- const assertion = truth(`any element of (${locator})`, fn.toString());
91
+ const assertion = truth(`any element of (${locator})`, fn.toString())
83
92
 
84
- let found = false;
93
+ let found = false
85
94
  for (const el of els) {
86
- const result = await fn(el);
95
+ const result = await fn(el)
87
96
  if (result) {
88
- found = true;
89
- break;
97
+ found = true
98
+ break
90
99
  }
91
100
  }
92
- if (!found) throw assertion.getException();
93
- });
101
+ if (!found) throw assertion.getException()
102
+ })
94
103
  }
95
104
 
96
105
  function expectAllElements(locator, fn) {
97
- const step = prepareStep('expect all elements', locator, fn);
98
- if (!step) return;
106
+ const step = prepareStep('expect all elements', locator, fn)
107
+ if (!step) return
99
108
 
100
109
  return executeStep(step, async () => {
101
- const els = await step.helper._locate(locator);
102
- output.debug(`Found ${els.length} elements, all should pass the assertion`);
110
+ const els = await step.helper._locate(locator)
111
+ output.debug(`Found ${els.length} elements, all should pass the assertion`)
103
112
 
104
- let i = 1;
113
+ let i = 1
105
114
  for (const el of els) {
106
- output.debug(`checking element #${i}: ${el}`);
107
- const result = await fn(el);
108
- const assertion = truth(`element #${i} of (${locator})`, humanizeFunction(fn));
109
- assertion.assert(result);
110
- i++;
115
+ output.debug(`checking element #${i}: ${el}`)
116
+ const result = await fn(el)
117
+ const assertion = truth(`element #${i} of (${locator})`, humanizeFunction(fn))
118
+ assertion.assert(result)
119
+ i++
111
120
  }
112
- });
121
+ })
113
122
  }
114
123
 
115
124
  module.exports = {
@@ -118,60 +127,32 @@ module.exports = {
118
127
  expectElement,
119
128
  expectAnyElement,
120
129
  expectAllElements,
121
- };
130
+ }
122
131
 
123
132
  function prepareStep(purpose, locator, fn) {
124
- if (store.dryRun) return;
125
- const helpers = Object.values(container.helpers());
133
+ if (store.dryRun) return
134
+ const helpers = Object.values(container.helpers())
126
135
 
127
- const helper = helpers.filter(h => !!h._locate)[0];
136
+ const helper = helpers.filter(h => !!h._locate)[0]
128
137
 
129
138
  if (!helper) {
130
- throw new Error('No helper enabled with _locate method with returns a list of elements.');
139
+ throw new Error('No helper enabled with _locate method with returns a list of elements.')
131
140
  }
132
141
 
133
142
  if (!isAsyncFunction(fn)) {
134
- throw new Error('Async function should be passed into each element');
143
+ throw new Error('Async function should be passed into each element')
135
144
  }
136
145
 
137
- const isAssertion = purpose.startsWith('expect');
146
+ const isAssertion = purpose.startsWith('expect')
138
147
 
139
- const step = new Step(helper, `${purpose} within "${locator}" ${isAssertion ? 'to be' : 'to'}`);
140
- step.setActor('EL');
141
- step.setArguments([humanizeFunction(fn)]);
142
- step.helperMethod = '_locate';
148
+ const step = new FuncStep(`${purpose} within "${locator}" ${isAssertion ? 'to be' : 'to'}`)
149
+ step.setHelper(helper)
150
+ step.setArguments([humanizeFunction(fn)]) // user defined function is a passed argument
143
151
 
144
- return step;
152
+ return step
145
153
  }
146
154
 
147
- async function executeStep(step, action) {
148
- let error;
149
- const promise = recorder.add('register element wrapper', async () => {
150
- event.emit(event.step.started, step);
151
-
152
- try {
153
- await action();
154
- } catch (err) {
155
- recorder.throw(err);
156
- event.emit(event.step.failed, step, err);
157
- event.emit(event.step.finished, step);
158
- // event.emit(event.step.after, step)
159
- error = err;
160
- // await recorder.promise();
161
- return;
162
- }
163
-
164
- event.emit(event.step.after, step);
165
- event.emit(event.step.passed, step);
166
- event.emit(event.step.finished, step);
167
- });
168
-
169
- // await recorder.promise();
170
-
171
- // if (error) {
172
- // console.log('error', error.inspect())
173
- // return recorder.throw(error);
174
- // }
175
-
176
- return promise;
155
+ async function executeStep(step, action, stepConfig = {}) {
156
+ step.setCallable(action)
157
+ return recordStep(step, [stepConfig])
177
158
  }
package/lib/event.js CHANGED
@@ -1,10 +1,10 @@
1
- const debug = require('debug')('codeceptjs:event');
2
- const events = require('events');
3
- const { error } = require('./output');
1
+ const debug = require('debug')('codeceptjs:event')
2
+ const events = require('events')
3
+ const { error } = require('./output')
4
4
 
5
- const dispatcher = new events.EventEmitter();
5
+ const dispatcher = new events.EventEmitter()
6
6
 
7
- dispatcher.setMaxListeners(50);
7
+ dispatcher.setMaxListeners(50)
8
8
  /**
9
9
  * @namespace
10
10
  * @alias event
@@ -59,6 +59,7 @@ module.exports = {
59
59
  started: 'hook.start',
60
60
  passed: 'hook.passed',
61
61
  failed: 'hook.failed',
62
+ finished: 'hook.finished',
62
63
  },
63
64
 
64
65
  /**
@@ -141,33 +142,33 @@ module.exports = {
141
142
  * @param {*} [param]
142
143
  */
143
144
  emit(event, param) {
144
- let msg = `Emitted | ${event}`;
145
+ let msg = `Emitted | ${event}`
145
146
  if (param && param.toString()) {
146
- msg += ` (${param.toString()})`;
147
+ msg += ` (${param.toString()})`
147
148
  }
148
- debug(msg);
149
+ debug(msg)
149
150
  try {
150
- this.dispatcher.emit.apply(this.dispatcher, arguments);
151
+ this.dispatcher.emit.apply(this.dispatcher, arguments)
151
152
  } catch (err) {
152
- error(`Error processing ${event} event:`);
153
- error(err.stack);
153
+ error(`Error processing ${event} event:`)
154
+ error(err.stack)
154
155
  }
155
156
  },
156
157
 
157
158
  /** for testing only! */
158
159
  cleanDispatcher: () => {
159
- let event;
160
+ let event
160
161
  for (event in this.test) {
161
- this.dispatcher.removeAllListeners(this.test[event]);
162
+ this.dispatcher.removeAllListeners(this.test[event])
162
163
  }
163
164
  for (event in this.suite) {
164
- this.dispatcher.removeAllListeners(this.test[event]);
165
+ this.dispatcher.removeAllListeners(this.test[event])
165
166
  }
166
167
  for (event in this.step) {
167
- this.dispatcher.removeAllListeners(this.test[event]);
168
+ this.dispatcher.removeAllListeners(this.test[event])
168
169
  }
169
170
  for (event in this.all) {
170
- this.dispatcher.removeAllListeners(this.test[event]);
171
+ this.dispatcher.removeAllListeners(this.test[event])
171
172
  }
172
173
  },
173
- };
174
+ }
package/lib/heal.js CHANGED
@@ -129,6 +129,16 @@ class Heal {
129
129
  snippet: codeSnippet,
130
130
  })
131
131
 
132
+ if (failureContext?.test) {
133
+ const test = failureContext.test
134
+ let note = `This test was healed by '${suggestion.name}'`
135
+ note += `\n\nReplace the failed code:\n\n`
136
+ note += colors.red(`- ${failedStep.toCode()}\n`)
137
+ note += colors.green(`+ ${codeSnippet}\n`)
138
+ test.addNote('heal', note)
139
+ test.meta.healed = true
140
+ }
141
+
132
142
  recorder.add('healed', () => output.print(colors.bold.green(` Code healed successfully by ${suggestion.name}`), colors.gray('(no errors thrown)')))
133
143
  this.numHealed++
134
144
  // recorder.session.restore();