codeceptjs 3.7.0-beta.1 → 3.7.0-beta.10

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 +27 -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 +617 -7
  71. package/typings/types.d.ts +663 -34
  72. package/lib/listener/artifacts.js +0 -19
  73. package/lib/plugin/debugErrors.js +0 -67
@@ -1,123 +1,23 @@
1
- const recorder = require('../recorder')
2
- const { debug } = require('../output')
1
+ const { retryTo } = require('../effects')
3
2
 
4
- const defaultConfig = {
5
- registerGlobal: true,
6
- pollInterval: 200,
7
- }
8
-
9
- /**
10
- *
11
- *
12
- * Adds global `retryTo` which retries steps a few times before failing.
13
- *
14
- * Enable this plugin in `codecept.conf.js` (enabled by default for new setups):
15
- *
16
- * ```js
17
- * plugins: {
18
- * retryTo: {
19
- * enabled: true
20
- * }
21
- * }
22
- * ```
23
- *
24
- * Use it in your tests:
25
- *
26
- * ```js
27
- * // retry these steps 5 times before failing
28
- * await retryTo((tryNum) => {
29
- * I.switchTo('#editor frame');
30
- * I.click('Open');
31
- * I.see('Opened')
32
- * }, 5);
33
- * ```
34
- * Set polling interval as 3rd argument (200ms by default):
35
- *
36
- * ```js
37
- * // retry these steps 5 times before failing
38
- * await retryTo((tryNum) => {
39
- * I.switchTo('#editor frame');
40
- * I.click('Open');
41
- * I.see('Opened')
42
- * }, 5, 100);
43
- * ```
44
- *
45
- * Default polling interval can be changed in a config:
46
- *
47
- * ```js
48
- * plugins: {
49
- * retryTo: {
50
- * enabled: true,
51
- * pollInterval: 500,
52
- * }
53
- * }
54
- * ```
55
- *
56
- * Disables retryFailedStep plugin for steps inside a block;
57
- *
58
- * Use this plugin if:
59
- *
60
- * * you need repeat a set of actions in flaky tests
61
- * * iframe was not rendered and you need to retry switching to it
62
- *
63
- *
64
- * #### Configuration
65
- *
66
- * * `pollInterval` - default interval between retries in ms. 200 by default.
67
- * * `registerGlobal` - to register `retryTo` function globally, true by default
68
- *
69
- * If `registerGlobal` is false you can use retryTo from the plugin:
70
- *
71
- * ```js
72
- * const retryTo = codeceptjs.container.plugins('retryTo');
73
- * ```
74
- *
75
- */
76
3
  module.exports = function (config) {
77
- config = Object.assign(defaultConfig, config)
78
- function retryTo(callback, maxTries, pollInterval = config.pollInterval) {
79
- return new Promise((done, reject) => {
80
- let tries = 1
81
-
82
- function handleRetryException(err) {
83
- recorder.throw(err)
84
- reject(err)
85
- }
86
-
87
- const tryBlock = async () => {
88
- tries++
89
- recorder.session.start(`retryTo ${tries}`)
90
- try {
91
- await callback(tries)
92
- } catch (err) {
93
- handleRetryException(err)
94
- }
95
-
96
- // Call done if no errors
97
- recorder.add(() => {
98
- recorder.session.restore(`retryTo ${tries}`)
99
- done(null)
100
- })
101
-
102
- // Catch errors and retry
103
- recorder.session.catch(err => {
104
- recorder.session.restore(`retryTo ${tries}`)
105
- if (tries <= maxTries) {
106
- debug(`Error ${err}... Retrying`)
107
- recorder.add(`retryTo ${tries}`, () => setTimeout(tryBlock, pollInterval))
108
- } else {
109
- // if maxTries reached
110
- handleRetryException(err)
111
- }
112
- })
113
- }
114
-
115
- recorder.add('retryTo', tryBlock).catch(err => {
116
- console.error('An error occurred:', err)
117
- done(null)
118
- })
119
- })
120
- }
4
+ console.log(`
5
+ Deprecation Warning: 'retryTo' has been moved to the effects module.
6
+ You should update your tests to use it as follows:
7
+
8
+ \`\`\`javascript
9
+ const { retryTo } = require('codeceptjs/effects');
10
+
11
+ // Example: Retry these steps 5 times before failing
12
+ await retryTo((tryNum) => {
13
+ I.switchTo('#editor frame');
14
+ I.click('Open');
15
+ I.see('Opened');
16
+ }, 5);
17
+ \`\`\`
18
+
19
+ For more details, refer to the documentation.
20
+ `)
121
21
 
122
22
  if (config.registerGlobal) {
123
23
  global.retryTo = retryTo
@@ -5,8 +5,9 @@ const Container = require('../container')
5
5
  const recorder = require('../recorder')
6
6
  const event = require('../event')
7
7
  const output = require('../output')
8
- const { fileExists, clearString } = require('../utils')
8
+ const { fileExists } = require('../utils')
9
9
  const Codeceptjs = require('../index')
10
+ const { testToFileName } = require('../mocha/test')
10
11
 
11
12
  const defaultConfig = {
12
13
  uniqueScreenshotNames: false,
@@ -14,7 +15,7 @@ const defaultConfig = {
14
15
  fullPageScreenshots: false,
15
16
  }
16
17
 
17
- const supportedHelpers = require('./standardActingHelpers')
18
+ const supportedHelpers = Container.STANDARD_ACTING_HELPERS
18
19
 
19
20
  /**
20
21
  * Creates screenshot on failure. Screenshot is saved into `output` directory.
@@ -63,7 +64,7 @@ module.exports = function (config) {
63
64
  }
64
65
 
65
66
  if (Codeceptjs.container.mocha()) {
66
- options.reportDir = Codeceptjs.container.mocha().options.reporterOptions && Codeceptjs.container.mocha().options.reporterOptions.reportDir
67
+ options.reportDir = Codeceptjs.container.mocha()?.options?.reporterOptions && Codeceptjs.container.mocha()?.options?.reporterOptions?.reportDir
67
68
  }
68
69
 
69
70
  if (options.disableScreenshots) {
@@ -71,21 +72,19 @@ module.exports = function (config) {
71
72
  return
72
73
  }
73
74
 
74
- event.dispatcher.on(event.test.failed, test => {
75
- if (test.ctx?._runnable.title.includes('hook: ')) {
76
- output.plugin('screenshotOnFail', 'BeforeSuite/AfterSuite do not have any access to the browser, hence it could not take screenshot.')
75
+ event.dispatcher.on(event.test.failed, (test, _err, hookName) => {
76
+ if (hookName === 'BeforeSuite' || hookName === 'AfterSuite') {
77
+ // no browser here
77
78
  return
78
79
  }
80
+
79
81
  recorder.add(
80
82
  'screenshot of failed test',
81
83
  async () => {
82
- let fileName = clearString(test.title)
83
84
  const dataType = 'image/png'
84
85
  // This prevents data driven to be included in the failed screenshot file name
85
- if (fileName.indexOf('{') !== -1) {
86
- fileName = fileName.substr(0, fileName.indexOf('{') - 3).trim()
87
- }
88
- if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`)
86
+ let fileName = testToFileName(test)
87
+
89
88
  if (options.uniqueScreenshotNames && test) {
90
89
  const uuid = _getUUID(test)
91
90
  fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`
@@ -141,12 +140,8 @@ module.exports = function (config) {
141
140
  })
142
141
 
143
142
  function _getUUID(test) {
144
- if (test.uuid) {
145
- return test.uuid
146
- }
147
-
148
- if (test.ctx && test.ctx.test.uuid) {
149
- return test.ctx.test.uuid
143
+ if (test.uid) {
144
+ return test.uid
150
145
  }
151
146
 
152
147
  return Math.floor(new Date().getTime() / 1000)
@@ -1,3 +1,6 @@
1
- const standardActingHelpers = ['Playwright', 'WebDriver', 'Puppeteer', 'Appium', 'TestCafe']
1
+ const Container = require('../container')
2
+ // due to using this in internal tooling we won't post deprecation warning
3
+ // but please switch to Container.STANDARD_ACTING_HELPERS
4
+ const standardActingHelpers = Container.STANDARD_ACTING_HELPERS
2
5
 
3
6
  module.exports = standardActingHelpers
@@ -12,7 +12,7 @@ const event = require('../event')
12
12
  const output = require('../output')
13
13
  const { template, deleteDir } = require('../utils')
14
14
 
15
- const supportedHelpers = require('./standardActingHelpers')
15
+ const supportedHelpers = Container.STANDARD_ACTING_HELPERS
16
16
 
17
17
  const defaultConfig = {
18
18
  deleteSuccessful: true,
@@ -121,12 +121,13 @@ module.exports = function (config) {
121
121
  deleteDir(dir)
122
122
  })
123
123
 
124
- event.dispatcher.on(event.test.failed, (test, err) => {
125
- if (test.ctx._runnable.title.includes('hook: ')) {
126
- output.plugin('stepByStepReport', 'BeforeSuite/AfterSuite do not have any access to the browser, hence it could not take screenshot.')
124
+ event.dispatcher.on(event.test.failed, (test, _err, hookName) => {
125
+ if (hookName === 'BeforeSuite' || hookName === 'AfterSuite') {
126
+ // no browser here
127
127
  return
128
128
  }
129
- persist(test, err)
129
+
130
+ persist(test)
130
131
  })
131
132
 
132
133
  event.dispatcher.on(event.all.result, () => {
@@ -1,5 +1,5 @@
1
1
  const event = require('../event')
2
- const TIMEOUT_ORDER = require('../step').TIMEOUT_ORDER
2
+ const { TIMEOUT_ORDER } = require('../timeout')
3
3
 
4
4
  const defaultConfig = {
5
5
  timeout: 150,
@@ -1,115 +1,25 @@
1
- const recorder = require('../recorder')
2
- const { debug } = require('../output')
1
+ const { tryTo } = require('../effects')
3
2
 
4
- const defaultConfig = {
5
- registerGlobal: true,
6
- }
7
-
8
- /**
9
- *
10
- *
11
- * Adds global `tryTo` function in which all failed steps won't fail a test but will return true/false.
12
- *
13
- * Enable this plugin in `codecept.conf.js` (enabled by default for new setups):
14
- *
15
- * ```js
16
- * plugins: {
17
- * tryTo: {
18
- * enabled: true
19
- * }
20
- * }
21
- * ```
22
- * Use it in your tests:
23
- *
24
- * ```js
25
- * const result = await tryTo(() => I.see('Welcome'));
26
- *
27
- * // if text "Welcome" is on page, result => true
28
- * // if text "Welcome" is not on page, result => false
29
- * ```
30
- *
31
- * Disables retryFailedStep plugin for steps inside a block;
32
- *
33
- * Use this plugin if:
34
- *
35
- * * you need to perform multiple assertions inside a test
36
- * * there is A/B testing on a website you test
37
- * * there is "Accept Cookie" banner which may surprisingly appear on a page.
38
- *
39
- * #### Usage
40
- *
41
- * #### Multiple Conditional Assertions
42
- *
43
- * ```js
44
- *
45
- * Add assert requires first:
46
- * ```js
47
- * const assert = require('assert');
48
- * ```
49
- * Then use the assertion:
50
- * const result1 = await tryTo(() => I.see('Hello, user'));
51
- * const result2 = await tryTo(() => I.seeElement('.welcome'));
52
- * assert.ok(result1 && result2, 'Assertions were not succesful');
53
- * ```
54
- *
55
- * ##### Optional click
56
- *
57
- * ```js
58
- * I.amOnPage('/');
59
- * tryTo(() => I.click('Agree', '.cookies'));
60
- * ```
61
- *
62
- * #### Configuration
63
- *
64
- * * `registerGlobal` - to register `tryTo` function globally, true by default
65
- *
66
- * If `registerGlobal` is false you can use tryTo from the plugin:
67
- *
68
- * ```js
69
- * const tryTo = codeceptjs.container.plugins('tryTo');
70
- * ```
71
- *
72
- */
73
3
  module.exports = function (config) {
74
- config = Object.assign(defaultConfig, config)
4
+ console.log(`
5
+ Deprecated Warning: 'tryTo' has been moved to the effects module.
6
+ You should update your tests to use it as follows:
7
+
8
+ \`\`\`javascript
9
+ const { tryTo } = require('codeceptjs/effects');
10
+
11
+ // Example: failed step won't fail a test but will return true/false
12
+ await tryTo(() => {
13
+ I.switchTo('#editor frame');
14
+ });
15
+ \`\`\`
16
+
17
+ For more details, refer to the documentation.
18
+ `)
75
19
 
76
20
  if (config.registerGlobal) {
77
21
  global.tryTo = tryTo
78
22
  }
79
- return tryTo
80
- }
81
23
 
82
- function tryTo(callback) {
83
- let result = false
84
- return recorder.add(
85
- 'tryTo',
86
- () => {
87
- recorder.session.start('tryTo')
88
- process.env.TRY_TO = 'true'
89
- callback()
90
- recorder.add(() => {
91
- result = true
92
- recorder.session.restore('tryTo')
93
- return result
94
- })
95
- recorder.session.catch(err => {
96
- result = false
97
- const msg = err.inspect ? err.inspect() : err.toString()
98
- debug(`Unsuccessful try > ${msg}`)
99
- recorder.session.restore('tryTo')
100
- return result
101
- })
102
- return recorder.add(
103
- 'result',
104
- () => {
105
- process.env.TRY_TO = undefined
106
- return result
107
- },
108
- true,
109
- false,
110
- )
111
- },
112
- false,
113
- false,
114
- )
24
+ return tryTo
115
25
  }
package/lib/recorder.js CHANGED
@@ -3,7 +3,7 @@ const promiseRetry = require('promise-retry')
3
3
  const chalk = require('chalk')
4
4
  const { printObjectProperties } = require('./utils')
5
5
  const { log } = require('./output')
6
-
6
+ const { TimeoutError } = require('./timeout')
7
7
  const MAX_TASKS = 100
8
8
 
9
9
  let promise
@@ -179,7 +179,7 @@ module.exports = {
179
179
  }
180
180
  if (retry === undefined) retry = true
181
181
  if (!running && !force) {
182
- return
182
+ return Promise.resolve()
183
183
  }
184
184
  tasks.push(taskName)
185
185
  debug(chalk.gray(`${currentQueue()} Queued | ${taskName}`))
@@ -191,13 +191,13 @@ module.exports = {
191
191
  .slice(-1)
192
192
  .pop()
193
193
  // no retries or unnamed tasks
194
+ debug(`${currentQueue()} Running | ${taskName} | Timeout: ${timeout || 'None'}`)
195
+
194
196
  if (!retryOpts || !taskName || !retry) {
195
197
  const [promise, timer] = getTimeoutPromise(timeout, taskName)
196
198
  return Promise.race([promise, Promise.resolve(res).then(fn)]).finally(() => clearTimeout(timer))
197
199
  }
198
200
 
199
- debug(`${currentQueue()} Running | ${taskName}`)
200
-
201
201
  const retryRules = this.retries.slice().reverse()
202
202
  return promiseRetry(Object.assign(defaultRetryOptions, retryOpts), (retry, number) => {
203
203
  if (number > 1) log(`${currentQueue()}Retrying... Attempt #${number}`)
@@ -386,7 +386,7 @@ function getTimeoutPromise(timeoutMs, taskName) {
386
386
  return [
387
387
  new Promise((done, reject) => {
388
388
  timer = setTimeout(() => {
389
- reject(new Error(`Action ${taskName} was interrupted on step timeout ${timeoutMs}ms`))
389
+ reject(new TimeoutError(`Action ${taskName} was interrupted on timeout ${timeoutMs}ms`))
390
390
  }, timeoutMs || 2e9)
391
391
  }),
392
392
  timer,
package/lib/rerun.js CHANGED
@@ -1,81 +1,82 @@
1
- const fsPath = require('path');
2
- const container = require('./container');
3
- const event = require('./event');
4
- const BaseCodecept = require('./codecept');
5
- const output = require('./output');
1
+ const fsPath = require('path')
2
+ const container = require('./container')
3
+ const event = require('./event')
4
+ const BaseCodecept = require('./codecept')
5
+ const output = require('./output')
6
6
 
7
7
  class CodeceptRerunner extends BaseCodecept {
8
8
  runOnce(test) {
9
9
  return new Promise((resolve, reject) => {
10
10
  // @ts-ignore
11
- container.createMocha();
12
- const mocha = container.mocha();
13
- this.testFiles.forEach((file) => {
14
- delete require.cache[file];
15
- });
16
- mocha.files = this.testFiles;
11
+ container.createMocha()
12
+ const mocha = container.mocha()
13
+ this.testFiles.forEach(file => {
14
+ delete require.cache[file]
15
+ })
16
+ mocha.files = this.testFiles
17
17
  if (test) {
18
18
  if (!fsPath.isAbsolute(test)) {
19
- test = fsPath.join(global.codecept_dir, test);
19
+ test = fsPath.join(global.codecept_dir, test)
20
20
  }
21
- mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test);
21
+ mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test)
22
22
  }
23
23
  try {
24
- mocha.run((failures) => {
24
+ mocha.run(failures => {
25
25
  if (failures === 0) {
26
- resolve();
26
+ resolve()
27
27
  } else {
28
- reject(new Error(`${failures} tests fail`));
28
+ reject(new Error(`${failures} tests fail`))
29
29
  }
30
- });
30
+ })
31
31
  } catch (e) {
32
- reject(e);
32
+ reject(e)
33
33
  }
34
- });
34
+ })
35
35
  }
36
36
 
37
37
  async runTests(test) {
38
- const configRerun = this.config.rerun || {};
39
- const minSuccess = configRerun.minSuccess || 1;
40
- const maxReruns = configRerun.maxReruns || 1;
38
+ const configRerun = this.config.rerun || {}
39
+ const minSuccess = configRerun.minSuccess || 1
40
+ const maxReruns = configRerun.maxReruns || 1
41
41
  if (minSuccess > maxReruns) {
42
- process.exitCode = 1;
43
- throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`);
42
+ process.exitCode = 1
43
+ throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`)
44
44
  }
45
45
  if (maxReruns === 1) {
46
- await this.runOnce(test);
47
- return;
46
+ await this.runOnce(test)
47
+ return
48
48
  }
49
- let successCounter = 0;
50
- let rerunsCounter = 0;
49
+ let successCounter = 0
50
+ let rerunsCounter = 0
51
51
  while (rerunsCounter < maxReruns && successCounter < minSuccess) {
52
- rerunsCounter++;
52
+ container.result().reset() // reset result
53
+ rerunsCounter++
53
54
  try {
54
- await this.runOnce(test);
55
- successCounter++;
56
- output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`);
55
+ await this.runOnce(test)
56
+ successCounter++
57
+ output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`)
57
58
  } catch (e) {
58
- output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`);
59
- console.error(e);
59
+ output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`)
60
+ console.error(e)
60
61
  }
61
62
  }
62
63
  if (successCounter < minSuccess) {
63
- throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`);
64
+ throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`)
64
65
  }
65
66
  }
66
67
 
67
68
  async run(test) {
68
- event.emit(event.all.before, this);
69
+ event.emit(event.all.before, this)
69
70
  try {
70
- await this.runTests(test);
71
+ await this.runTests(test)
71
72
  } catch (e) {
72
- output.error(e.stack);
73
- throw e;
73
+ output.error(e.stack)
74
+ throw e
74
75
  } finally {
75
- event.emit(event.all.result, this);
76
- event.emit(event.all.after, this);
76
+ event.emit(event.all.result, this)
77
+ event.emit(event.all.after, this)
77
78
  }
78
79
  }
79
80
  }
80
81
 
81
- module.exports = CodeceptRerunner;
82
+ module.exports = CodeceptRerunner