codeceptjs 3.7.0-beta.3 → 3.7.0-beta.5

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 (45) hide show
  1. package/README.md +9 -10
  2. package/bin/codecept.js +7 -0
  3. package/lib/actor.js +47 -92
  4. package/lib/command/check.js +173 -0
  5. package/lib/command/definitions.js +2 -0
  6. package/lib/command/run-workers.js +1 -1
  7. package/lib/command/workers/runTests.js +112 -109
  8. package/lib/container.js +9 -0
  9. package/lib/effects.js +123 -0
  10. package/lib/heal.js +10 -0
  11. package/lib/helper/Appium.js +27 -16
  12. package/lib/helper/Playwright.js +15 -0
  13. package/lib/helper/Puppeteer.js +5 -0
  14. package/lib/helper/WebDriver.js +9 -1
  15. package/lib/listener/emptyRun.js +2 -5
  16. package/lib/listener/globalTimeout.js +41 -11
  17. package/lib/listener/steps.js +3 -0
  18. package/lib/mocha/cli.js +22 -5
  19. package/lib/mocha/featureConfig.js +13 -0
  20. package/lib/mocha/scenarioConfig.js +11 -0
  21. package/lib/mocha/test.js +15 -0
  22. package/lib/mocha/types.d.ts +6 -0
  23. package/lib/output.js +74 -73
  24. package/lib/pause.js +3 -7
  25. package/lib/plugin/heal.js +30 -0
  26. package/lib/plugin/stepTimeout.js +1 -1
  27. package/lib/recorder.js +1 -1
  28. package/lib/step/base.js +180 -0
  29. package/lib/step/config.js +50 -0
  30. package/lib/step/helper.js +47 -0
  31. package/lib/step/meta.js +91 -0
  32. package/lib/step/record.js +74 -0
  33. package/lib/step/retry.js +11 -0
  34. package/lib/step/section.js +25 -0
  35. package/lib/step/timeout.js +42 -0
  36. package/lib/step.js +15 -348
  37. package/lib/steps.js +23 -0
  38. package/lib/store.js +2 -0
  39. package/lib/utils.js +58 -0
  40. package/lib/within.js +2 -2
  41. package/lib/workers.js +2 -12
  42. package/package.json +7 -5
  43. package/typings/index.d.ts +5 -4
  44. package/typings/promiseBasedTypes.d.ts +1 -37
  45. package/typings/types.d.ts +39 -64
@@ -1,83 +1,83 @@
1
- const tty = require('tty');
1
+ const tty = require('tty')
2
2
 
3
3
  if (!tty.getWindowSize) {
4
4
  // this is really old method, long removed from Node, but Mocha
5
5
  // reporters fall back on it if they cannot use `process.stdout.getWindowSize`
6
6
  // we need to polyfill it.
7
- tty.getWindowSize = () => [40, 80];
7
+ tty.getWindowSize = () => [40, 80]
8
8
  }
9
9
 
10
- const { parentPort, workerData } = require('worker_threads');
11
- const event = require('../../event');
12
- const container = require('../../container');
13
- const { getConfig } = require('../utils');
14
- const { tryOrDefault, deepMerge } = require('../../utils');
10
+ const { parentPort, workerData } = require('worker_threads')
11
+ const event = require('../../event')
12
+ const container = require('../../container')
13
+ const { getConfig } = require('../utils')
14
+ const { tryOrDefault, deepMerge } = require('../../utils')
15
15
 
16
- let stdout = '';
16
+ let stdout = ''
17
17
 
18
- const stderr = '';
18
+ const stderr = ''
19
19
 
20
20
  // Requiring of Codecept need to be after tty.getWindowSize is available.
21
- const Codecept = require(process.env.CODECEPT_CLASS_PATH || '../../codecept');
21
+ const Codecept = require(process.env.CODECEPT_CLASS_PATH || '../../codecept')
22
22
 
23
- const { options, tests, testRoot, workerIndex } = workerData;
23
+ const { options, tests, testRoot, workerIndex } = workerData
24
24
 
25
25
  // hide worker output
26
26
  if (!options.debug && !options.verbose)
27
27
  process.stdout.write = string => {
28
- stdout += string;
29
- return true;
30
- };
28
+ stdout += string
29
+ return true
30
+ }
31
31
 
32
- const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {});
32
+ const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {})
33
33
 
34
34
  // important deep merge so dynamic things e.g. functions on config are not overridden
35
- const config = deepMerge(getConfig(options.config || testRoot), overrideConfigs);
35
+ const config = deepMerge(getConfig(options.config || testRoot), overrideConfigs)
36
36
 
37
37
  // Load test and run
38
- const codecept = new Codecept(config, options);
39
- codecept.init(testRoot);
40
- codecept.loadTests();
41
- const mocha = container.mocha();
42
- filterTests();
38
+ const codecept = new Codecept(config, options)
39
+ codecept.init(testRoot)
40
+ codecept.loadTests()
41
+ const mocha = container.mocha()
42
+ filterTests()
43
43
 
44
44
  // run tests
45
- (async function () {
45
+ ;(async function () {
46
46
  if (mocha.suite.total()) {
47
- await runTests();
47
+ await runTests()
48
48
  }
49
- })();
49
+ })()
50
50
 
51
51
  async function runTests() {
52
52
  try {
53
- await codecept.bootstrap();
53
+ await codecept.bootstrap()
54
54
  } catch (err) {
55
- throw new Error(`Error while running bootstrap file :${err}`);
55
+ throw new Error(`Error while running bootstrap file :${err}`)
56
56
  }
57
- listenToParentThread();
58
- initializeListeners();
59
- disablePause();
57
+ listenToParentThread()
58
+ initializeListeners()
59
+ disablePause()
60
60
  try {
61
- await codecept.run();
61
+ await codecept.run()
62
62
  } finally {
63
- await codecept.teardown();
63
+ await codecept.teardown()
64
64
  }
65
65
  }
66
66
 
67
67
  function filterTests() {
68
- const files = codecept.testFiles;
69
- mocha.files = files;
70
- mocha.loadFiles();
68
+ const files = codecept.testFiles
69
+ mocha.files = files
70
+ mocha.loadFiles()
71
71
 
72
72
  for (const suite of mocha.suite.suites) {
73
- suite.tests = suite.tests.filter(test => tests.indexOf(test.uid) >= 0);
73
+ suite.tests = suite.tests.filter(test => tests.indexOf(test.uid) >= 0)
74
74
  }
75
75
  }
76
76
 
77
77
  function initializeListeners() {
78
78
  function simplifyError(error) {
79
79
  if (error) {
80
- const { stack, uncaught, message, actual, expected } = error;
80
+ const { stack, uncaught, message, actual, expected } = error
81
81
 
82
82
  return {
83
83
  stack,
@@ -85,36 +85,36 @@ function initializeListeners() {
85
85
  message,
86
86
  actual,
87
87
  expected,
88
- };
88
+ }
89
89
  }
90
90
 
91
- return null;
91
+ return null
92
92
  }
93
93
  function simplifyTest(test, err = null) {
94
- test = { ...test };
94
+ test = { ...test }
95
95
 
96
96
  if (test.start && !test.duration) {
97
- const end = new Date();
98
- test.duration = end - test.start;
97
+ const end = new Date()
98
+ test.duration = end - test.start
99
99
  }
100
100
 
101
101
  if (test.err) {
102
- err = simplifyError(test.err);
103
- test.status = 'failed';
102
+ err = simplifyError(test.err)
103
+ test.status = 'failed'
104
104
  } else if (err) {
105
- err = simplifyError(err);
106
- test.status = 'failed';
105
+ err = simplifyError(err)
106
+ test.status = 'failed'
107
107
  }
108
- const parent = {};
108
+ const parent = {}
109
109
  if (test.parent) {
110
- parent.title = test.parent.title;
110
+ parent.title = test.parent.title
111
111
  }
112
112
 
113
113
  if (test.opts) {
114
114
  Object.keys(test.opts).forEach(k => {
115
- if (typeof test.opts[k] === 'object') delete test.opts[k];
116
- if (typeof test.opts[k] === 'function') delete test.opts[k];
117
- });
115
+ if (typeof test.opts[k] === 'object') delete test.opts[k]
116
+ if (typeof test.opts[k] === 'function') delete test.opts[k]
117
+ })
118
118
  }
119
119
 
120
120
  return {
@@ -125,30 +125,33 @@ function initializeListeners() {
125
125
  retries: test._retries,
126
126
  title: test.title,
127
127
  status: test.status,
128
+ notes: test.notes || [],
129
+ meta: test.meta || {},
130
+ artifacts: test.artifacts || [],
128
131
  duration: test.duration || 0,
129
132
  err,
130
133
  parent,
131
134
  steps: test.steps && test.steps.length > 0 ? simplifyStepsInTestObject(test.steps, err) : [],
132
- };
135
+ }
133
136
  }
134
137
 
135
138
  function simplifyStepsInTestObject(steps, err) {
136
- steps = [...steps];
137
- const _steps = [];
139
+ steps = [...steps]
140
+ const _steps = []
138
141
 
139
142
  for (step of steps) {
140
- const _args = [];
143
+ const _args = []
141
144
 
142
145
  if (step.args) {
143
146
  for (const arg of step.args) {
144
147
  // check if arg is a JOI object
145
148
  if (arg && arg.$_root) {
146
- _args.push(JSON.stringify(arg).slice(0, 300));
149
+ _args.push(JSON.stringify(arg).slice(0, 300))
147
150
  // check if arg is a function
148
151
  } else if (arg && typeof arg === 'function') {
149
- _args.push(arg.name);
152
+ _args.push(arg.name)
150
153
  } else {
151
- _args.push(arg);
154
+ _args.push(arg)
152
155
  }
153
156
  }
154
157
  }
@@ -164,38 +167,38 @@ function initializeListeners() {
164
167
  finishedAt: step.finishedAt,
165
168
  duration: step.duration,
166
169
  err,
167
- });
170
+ })
168
171
  }
169
172
 
170
- return _steps;
173
+ return _steps
171
174
  }
172
175
 
173
176
  function simplifyStep(step, err = null) {
174
- step = { ...step };
177
+ step = { ...step }
175
178
 
176
179
  if (step.startTime && !step.duration) {
177
- const end = new Date();
178
- step.duration = end - step.startTime;
180
+ const end = new Date()
181
+ step.duration = end - step.startTime
179
182
  }
180
183
 
181
184
  if (step.err) {
182
- err = simplifyError(step.err);
183
- step.status = 'failed';
185
+ err = simplifyError(step.err)
186
+ step.status = 'failed'
184
187
  } else if (err) {
185
- err = simplifyError(err);
186
- step.status = 'failed';
188
+ err = simplifyError(err)
189
+ step.status = 'failed'
187
190
  }
188
191
 
189
- const parent = {};
192
+ const parent = {}
190
193
  if (step.metaStep) {
191
- parent.title = step.metaStep.actor;
194
+ parent.title = step.metaStep.actor
192
195
  }
193
196
 
194
197
  if (step.opts) {
195
198
  Object.keys(step.opts).forEach(k => {
196
- if (typeof step.opts[k] === 'object') delete step.opts[k];
197
- if (typeof step.opts[k] === 'function') delete step.opts[k];
198
- });
199
+ if (typeof step.opts[k] === 'object') delete step.opts[k]
200
+ if (typeof step.opts[k] === 'function') delete step.opts[k]
201
+ })
199
202
  }
200
203
 
201
204
  return {
@@ -207,43 +210,43 @@ function initializeListeners() {
207
210
  err,
208
211
  parent,
209
212
  test: simplifyTest(step.test),
210
- };
213
+ }
211
214
  }
212
215
 
213
- collectStats();
216
+ collectStats()
214
217
  // suite
215
- event.dispatcher.on(event.suite.before, suite => sendToParentThread({ event: event.suite.before, workerIndex, data: simplifyTest(suite) }));
216
- event.dispatcher.on(event.suite.after, suite => sendToParentThread({ event: event.suite.after, workerIndex, data: simplifyTest(suite) }));
218
+ event.dispatcher.on(event.suite.before, suite => sendToParentThread({ event: event.suite.before, workerIndex, data: simplifyTest(suite) }))
219
+ event.dispatcher.on(event.suite.after, suite => sendToParentThread({ event: event.suite.after, workerIndex, data: simplifyTest(suite) }))
217
220
 
218
221
  // calculate duration
219
- event.dispatcher.on(event.test.started, test => (test.start = new Date()));
222
+ event.dispatcher.on(event.test.started, test => (test.start = new Date()))
220
223
 
221
224
  // tests
222
- event.dispatcher.on(event.test.before, test => sendToParentThread({ event: event.test.before, workerIndex, data: simplifyTest(test) }));
223
- event.dispatcher.on(event.test.after, test => sendToParentThread({ event: event.test.after, workerIndex, data: simplifyTest(test) }));
225
+ event.dispatcher.on(event.test.before, test => sendToParentThread({ event: event.test.before, workerIndex, data: simplifyTest(test) }))
226
+ event.dispatcher.on(event.test.after, test => sendToParentThread({ event: event.test.after, workerIndex, data: simplifyTest(test) }))
224
227
  // we should force-send correct errors to prevent race condition
225
- event.dispatcher.on(event.test.finished, (test, err) => sendToParentThread({ event: event.test.finished, workerIndex, data: simplifyTest(test, err) }));
226
- event.dispatcher.on(event.test.failed, (test, err) => sendToParentThread({ event: event.test.failed, workerIndex, data: simplifyTest(test, err) }));
227
- event.dispatcher.on(event.test.passed, (test, err) => sendToParentThread({ event: event.test.passed, workerIndex, data: simplifyTest(test, err) }));
228
- event.dispatcher.on(event.test.started, test => sendToParentThread({ event: event.test.started, workerIndex, data: simplifyTest(test) }));
229
- event.dispatcher.on(event.test.skipped, test => sendToParentThread({ event: event.test.skipped, workerIndex, data: simplifyTest(test) }));
228
+ event.dispatcher.on(event.test.finished, (test, err) => sendToParentThread({ event: event.test.finished, workerIndex, data: simplifyTest(test, err) }))
229
+ event.dispatcher.on(event.test.failed, (test, err) => sendToParentThread({ event: event.test.failed, workerIndex, data: simplifyTest(test, err) }))
230
+ event.dispatcher.on(event.test.passed, (test, err) => sendToParentThread({ event: event.test.passed, workerIndex, data: simplifyTest(test, err) }))
231
+ event.dispatcher.on(event.test.started, test => sendToParentThread({ event: event.test.started, workerIndex, data: simplifyTest(test) }))
232
+ event.dispatcher.on(event.test.skipped, test => sendToParentThread({ event: event.test.skipped, workerIndex, data: simplifyTest(test) }))
230
233
 
231
234
  // steps
232
- event.dispatcher.on(event.step.finished, step => sendToParentThread({ event: event.step.finished, workerIndex, data: simplifyStep(step) }));
233
- event.dispatcher.on(event.step.started, step => sendToParentThread({ event: event.step.started, workerIndex, data: simplifyStep(step) }));
234
- event.dispatcher.on(event.step.passed, step => sendToParentThread({ event: event.step.passed, workerIndex, data: simplifyStep(step) }));
235
- event.dispatcher.on(event.step.failed, step => sendToParentThread({ event: event.step.failed, workerIndex, data: simplifyStep(step) }));
235
+ event.dispatcher.on(event.step.finished, step => sendToParentThread({ event: event.step.finished, workerIndex, data: simplifyStep(step) }))
236
+ event.dispatcher.on(event.step.started, step => sendToParentThread({ event: event.step.started, workerIndex, data: simplifyStep(step) }))
237
+ event.dispatcher.on(event.step.passed, step => sendToParentThread({ event: event.step.passed, workerIndex, data: simplifyStep(step) }))
238
+ event.dispatcher.on(event.step.failed, step => sendToParentThread({ event: event.step.failed, workerIndex, data: simplifyStep(step) }))
236
239
 
237
- event.dispatcher.on(event.hook.failed, (test, err) => sendToParentThread({ event: event.hook.failed, workerIndex, data: simplifyTest(test, err) }));
238
- event.dispatcher.on(event.hook.passed, (test, err) => sendToParentThread({ event: event.hook.passed, workerIndex, data: simplifyTest(test, err) }));
239
- event.dispatcher.on(event.all.failures, data => sendToParentThread({ event: event.all.failures, workerIndex, data }));
240
+ event.dispatcher.on(event.hook.failed, (test, err) => sendToParentThread({ event: event.hook.failed, workerIndex, data: simplifyTest(test, err) }))
241
+ event.dispatcher.on(event.hook.passed, (test, err) => sendToParentThread({ event: event.hook.passed, workerIndex, data: simplifyTest(test, err) }))
242
+ event.dispatcher.on(event.all.failures, data => sendToParentThread({ event: event.all.failures, workerIndex, data }))
240
243
 
241
244
  // all
242
- event.dispatcher.once(event.all.result, () => parentPort.close());
245
+ event.dispatcher.once(event.all.result, () => parentPort.close())
243
246
  }
244
247
 
245
248
  function disablePause() {
246
- global.pause = () => {};
249
+ global.pause = () => {}
247
250
  }
248
251
 
249
252
  function collectStats() {
@@ -253,36 +256,36 @@ function collectStats() {
253
256
  skipped: 0,
254
257
  tests: 0,
255
258
  pending: 0,
256
- };
259
+ }
257
260
  event.dispatcher.on(event.test.skipped, () => {
258
- stats.skipped++;
259
- });
261
+ stats.skipped++
262
+ })
260
263
  event.dispatcher.on(event.test.passed, () => {
261
- stats.passes++;
262
- });
264
+ stats.passes++
265
+ })
263
266
  event.dispatcher.on(event.test.failed, test => {
264
267
  if (test.ctx._runnable.title.includes('hook: AfterSuite')) {
265
- stats.failedHooks += 1;
268
+ stats.failedHooks += 1
266
269
  }
267
- stats.failures++;
268
- });
270
+ stats.failures++
271
+ })
269
272
  event.dispatcher.on(event.test.skipped, () => {
270
- stats.pending++;
271
- });
273
+ stats.pending++
274
+ })
272
275
  event.dispatcher.on(event.test.finished, () => {
273
- stats.tests++;
274
- });
276
+ stats.tests++
277
+ })
275
278
  event.dispatcher.once(event.all.after, () => {
276
- sendToParentThread({ event: event.all.after, data: stats });
277
- });
279
+ sendToParentThread({ event: event.all.after, data: stats })
280
+ })
278
281
  }
279
282
 
280
283
  function sendToParentThread(data) {
281
- parentPort.postMessage(data);
284
+ parentPort.postMessage(data)
282
285
  }
283
286
 
284
287
  function listenToParentThread() {
285
288
  parentPort.on('message', eventData => {
286
- container.append({ support: eventData.data });
287
- });
289
+ container.append({ support: eventData.data })
290
+ })
288
291
  }
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')
@@ -38,6 +39,7 @@ class Container {
38
39
  * @param {*} opts
39
40
  */
40
41
  static create(config, opts) {
42
+ debug('creating container')
41
43
  asyncHelperPromise = Promise.resolve()
42
44
 
43
45
  // dynamically create mocha instance
@@ -134,6 +136,7 @@ class Container {
134
136
  static append(newContainer) {
135
137
  const deepMerge = require('./utils').deepMerge
136
138
  container = deepMerge(container, newContainer)
139
+ debug('appended', JSON.stringify(newContainer).slice(0, 300))
137
140
  }
138
141
 
139
142
  /**
@@ -150,6 +153,7 @@ class Container {
150
153
  container.plugins = newPlugins || {}
151
154
  asyncHelperPromise = Promise.resolve()
152
155
  store.actor = null
156
+ debug('container cleared')
153
157
  }
154
158
 
155
159
  /**
@@ -216,6 +220,8 @@ function createHelpers(config) {
216
220
  throw new Error(`Helper class from module '${helperName}' is not a class. Use CJS async module syntax.`)
217
221
  }
218
222
 
223
+ debug(`helper ${helperName} async initialized`)
224
+
219
225
  helpers[helperName] = new ResolvedHelperClass(config[helperName])
220
226
  })
221
227
 
@@ -225,6 +231,7 @@ function createHelpers(config) {
225
231
  checkHelperRequirements(HelperClass)
226
232
 
227
233
  helpers[helperName] = new HelperClass(config[helperName])
234
+ debug(`helper ${helperName} initialized`)
228
235
  } catch (err) {
229
236
  throw new Error(`Could not load helper ${helperName} (${err.message})`)
230
237
  }
@@ -322,6 +329,7 @@ function createSupportObjects(config) {
322
329
  if (container.support[name]._init) {
323
330
  container.support[name]._init()
324
331
  }
332
+ debug(`support object ${name} initialized`)
325
333
  } catch (err) {
326
334
  throw new Error(`Initialization failed for ${name}: ${container.support[name]}\n${err.message}\n${err.stack}`)
327
335
  }
@@ -334,6 +342,7 @@ function createSupportObjects(config) {
334
342
  const ms = new MetaStep(name, prop)
335
343
  ms.setContext(currentObject)
336
344
  if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue)
345
+ debug(`metastep is created for ${name}.${prop.toString()}()`)
337
346
  return ms.run.bind(ms, currentValue)
338
347
  }
339
348
 
package/lib/effects.js ADDED
@@ -0,0 +1,123 @@
1
+ const recorder = require('./recorder')
2
+ const { debug } = require('./output')
3
+ const store = require('./store')
4
+
5
+ /**
6
+ * @module hopeThat
7
+ *
8
+ * `hopeThat` is a utility function for CodeceptJS tests that allows for soft assertions.
9
+ * It enables conditional assertions without terminating the test upon failure.
10
+ * This is particularly useful in scenarios like A/B testing, handling unexpected elements,
11
+ * or performing multiple assertions where you want to collect all results before deciding
12
+ * on the test outcome.
13
+ *
14
+ * ## Use Cases
15
+ *
16
+ * - **Multiple Conditional Assertions**: Perform several assertions and evaluate all their outcomes together.
17
+ * - **A/B Testing**: Handle different variants in A/B tests without failing the entire test upon one variant's failure.
18
+ * - **Unexpected Elements**: Manage elements that may or may not appear, such as "Accept Cookie" banners.
19
+ *
20
+ * ## Examples
21
+ *
22
+ * ### Multiple Conditional Assertions
23
+ *
24
+ * Add the assertion library:
25
+ * ```js
26
+ * const assert = require('assert');
27
+ * const { hopeThat } = require('codeceptjs/effects');
28
+ * ```
29
+ *
30
+ * Use `hopeThat` with assertions:
31
+ * ```js
32
+ * const result1 = await hopeThat(() => I.see('Hello, user'));
33
+ * const result2 = await hopeThat(() => I.seeElement('.welcome'));
34
+ * assert.ok(result1 && result2, 'Assertions were not successful');
35
+ * ```
36
+ *
37
+ * ### Optional Click
38
+ *
39
+ * ```js
40
+ * const { hopeThat } = require('codeceptjs/effects');
41
+ *
42
+ * I.amOnPage('/');
43
+ * await hopeThat(() => I.click('Agree', '.cookies'));
44
+ * ```
45
+ *
46
+ * Performs a soft assertion within CodeceptJS tests.
47
+ *
48
+ * This function records the execution of a callback containing assertion logic.
49
+ * If the assertion fails, it logs the failure without stopping the test execution.
50
+ * It is useful for scenarios where multiple assertions are performed, and you want
51
+ * to evaluate all outcomes before deciding on the test result.
52
+ *
53
+ * ## Usage
54
+ *
55
+ * ```js
56
+ * const result = await hopeThat(() => I.see('Welcome'));
57
+ *
58
+ * // If the text "Welcome" is on the page, result => true
59
+ * // If the text "Welcome" is not on the page, result => false
60
+ * ```
61
+ *
62
+ * @async
63
+ * @function hopeThat
64
+ * @param {Function} callback - The callback function containing the soft assertion logic.
65
+ * @returns {Promise<boolean | any>} - Resolves to `true` if the assertion is successful, or `false` if it fails.
66
+ *
67
+ * @example
68
+ * // Multiple Conditional Assertions
69
+ * const assert = require('assert');
70
+ * const { hopeThat } = require('codeceptjs/effects');
71
+ *
72
+ * const result1 = await hopeThat(() => I.see('Hello, user'));
73
+ * const result2 = await hopeThat(() => I.seeElement('.welcome'));
74
+ * assert.ok(result1 && result2, 'Assertions were not successful');
75
+ *
76
+ * @example
77
+ * // Optional Click
78
+ * const { hopeThat } = require('codeceptjs/effects');
79
+ *
80
+ * I.amOnPage('/');
81
+ * await hopeThat(() => I.click('Agree', '.cookies'));
82
+ */
83
+ async function hopeThat(callback) {
84
+ if (store.dryRun) return
85
+ const sessionName = 'hopeThat'
86
+
87
+ let result = false
88
+ return recorder.add(
89
+ 'hopeThat',
90
+ () => {
91
+ recorder.session.start(sessionName)
92
+ store.hopeThat = true
93
+ callback()
94
+ recorder.add(() => {
95
+ result = true
96
+ recorder.session.restore(sessionName)
97
+ return result
98
+ })
99
+ recorder.session.catch(err => {
100
+ result = false
101
+ const msg = err.inspect ? err.inspect() : err.toString()
102
+ debug(`Unsuccessful assertion > ${msg}`)
103
+ recorder.session.restore(sessionName)
104
+ return result
105
+ })
106
+ return recorder.add(
107
+ 'result',
108
+ () => {
109
+ store.hopeThat = undefined
110
+ return result
111
+ },
112
+ true,
113
+ false,
114
+ )
115
+ },
116
+ false,
117
+ false,
118
+ )
119
+ }
120
+
121
+ module.exports = {
122
+ hopeThat,
123
+ }
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();