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
package/README.md CHANGED
@@ -3,16 +3,15 @@
3
3
  [<img src="https://img.shields.io/badge/slack-@codeceptjs-purple.svg?logo=slack">](https://join.slack.com/t/codeceptjs/shared_invite/enQtMzA5OTM4NDM2MzA4LWE4MThhN2NmYTgxNTU5MTc4YzAyYWMwY2JkMmZlYWI5MWQ2MDM5MmRmYzZmYmNiNmY5NTAzM2EwMGIwOTNhOGQ) [<img src="https://img.shields.io/badge/discourse-codeceptjs-purple">](https://codecept.discourse.group) [![NPM version][npm-image]][npm-url] [<img src="https://img.shields.io/badge/dockerhub-images-blue.svg?logo=codeceptjs">](https://hub.docker.com/r/codeceptjs/codeceptjs)
4
4
  [![AI features](https://img.shields.io/badge/AI-features?logo=openai&logoColor=white)](https://github.com/codeceptjs/CodeceptJS/edit/3.x/docs/ai.md) [![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
5
5
 
6
- Build Status:
6
+ ## Build Status
7
7
 
8
- Appium Helper:
9
- [![Appium V2 Tests - Android](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_Android.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_Android.yml)
10
-
11
- Web Helper:
12
- [![Playwright Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml)
13
- [![Puppeteer Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml)
14
- [![WebDriver Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml)
15
- [![TestCafe Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml)
8
+ | Type | Engine | Status |
9
+ | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
10
+ | 🌐 Web | Playwright | [![Playwright Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml) |
11
+ | 🌐 Web | Puppeteer | [![Puppeteer Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml) |
12
+ | 🌐 Web | WebDriver | [![WebDriver Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml) |
13
+ | 🌐 Web | TestCafe | [![TestCafe Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml) |
14
+ | 📱 Mobile | Appium | [![Appium V2 Tests - Android](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_Android.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_Android.yml) |
16
15
 
17
16
  # CodeceptJS [![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua)
18
17
 
@@ -292,7 +291,7 @@ When using Typescript, replace `module.exports` with `export` for autocompletion
292
291
 
293
292
  Thanks to our awesome contributors! 🎉
294
293
  <a href="https://github.com/codeceptjs/codeceptjs/graphs/contributors">
295
- <img src="https://contrib.rocks/image?repo=codeceptjs/codeceptjs" />
294
+ <img src="https://contrib.rocks/image?repo=codeceptjs/codeceptjs" />
296
295
  </a>
297
296
 
298
297
  Made with [contrib.rocks](https://contrib.rocks).
package/bin/codecept.js CHANGED
@@ -58,6 +58,13 @@ program
58
58
  .description('Creates dummy config in current dir or [path]')
59
59
  .action(errorHandler(require('../lib/command/init')))
60
60
 
61
+ program
62
+ .command('check')
63
+ .option(commandFlags.config.flag, commandFlags.config.description)
64
+ .description('Checks configuration and environment before running tests')
65
+ .option('-t, --timeout [ms]', 'timeout for checks in ms, 20000 by default')
66
+ .action(errorHandler(require('../lib/command/check')))
67
+
61
68
  program
62
69
  .command('migrate [path]')
63
70
  .description('Migrate json config to js config in current dir or [path]')
package/lib/actor.js CHANGED
@@ -1,11 +1,13 @@
1
- const Step = require('./step');
2
- const { MetaStep } = require('./step');
3
- const container = require('./container');
4
- const { methodsOfObject } = require('./utils');
5
- const recorder = require('./recorder');
6
- const event = require('./event');
7
- const store = require('./store');
8
- const output = require('./output');
1
+ const Step = require('./step')
2
+ const MetaStep = require('./step/meta')
3
+ const recordStep = require('./step/record')
4
+ const container = require('./container')
5
+ const { methodsOfObject } = require('./utils')
6
+ const { TIMEOUT_ORDER } = require('./step/timeout')
7
+ const recorder = require('./recorder')
8
+ const event = require('./event')
9
+ const store = require('./store')
10
+ const output = require('./output')
9
11
 
10
12
  /**
11
13
  * @interface
@@ -21,13 +23,13 @@ class Actor {
21
23
  * ⚠️ returns a promise which is synchronized internally by recorder
22
24
  */
23
25
  async say(msg, color = 'cyan') {
24
- const step = new Step('say', 'say');
25
- step.status = 'passed';
26
+ const step = new Step('say', 'say')
27
+ step.status = 'passed'
26
28
  return recordStep(step, [msg]).then(() => {
27
29
  // this is backward compatibility as this event may be used somewhere
28
- event.emit(event.step.comment, msg);
29
- output.say(msg, `${color}`);
30
- });
30
+ event.emit(event.step.comment, msg)
31
+ output.say(msg, `${color}`)
32
+ })
31
33
  }
32
34
 
33
35
  /**
@@ -38,14 +40,16 @@ class Actor {
38
40
  * @inner
39
41
  */
40
42
  limitTime(timeout) {
41
- if (!store.timeouts) return this;
43
+ if (!store.timeouts) return this
44
+
45
+ console.log('I.limitTime() is deprecated, use step.timeout() instead')
42
46
 
43
47
  event.dispatcher.prependOnceListener(event.step.before, step => {
44
- output.log(`Timeout to ${step}: ${timeout}s`);
45
- step.setTimeout(timeout * 1000, Step.TIMEOUT_ORDER.codeLimitTime);
46
- });
48
+ output.log(`Timeout to ${step}: ${timeout}s`)
49
+ step.setTimeout(timeout * 1000, TIMEOUT_ORDER.codeLimitTime)
50
+ })
47
51
 
48
- return this;
52
+ return this
49
53
  }
50
54
 
51
55
  /**
@@ -55,11 +59,10 @@ class Actor {
55
59
  * @inner
56
60
  */
57
61
  retry(opts) {
58
- if (opts === undefined) opts = 1;
59
- recorder.retry(opts);
60
- // remove retry once the step passed
61
- recorder.add(() => event.dispatcher.once(event.step.finished, () => recorder.retries.pop()));
62
- return this;
62
+ console.log('I.retry() is deprecated, use step.retry() instead')
63
+ const retryStep = require('./step/retry')
64
+ retryStep(opts)
65
+ return this
63
66
  }
64
67
  }
65
68
 
@@ -70,102 +73,54 @@ class Actor {
70
73
  * @ignore
71
74
  */
72
75
  module.exports = function (obj = {}) {
73
- const actor = container.actor() || new Actor();
76
+ const actor = container.actor() || new Actor()
74
77
 
75
78
  // load all helpers once container initialized
76
79
  container.started(() => {
77
- const translation = container.translation();
78
- const helpers = container.helpers();
80
+ const translation = container.translation()
81
+ const helpers = container.helpers()
79
82
 
80
83
  // add methods from enabled helpers
81
84
  Object.values(helpers).forEach(helper => {
82
85
  methodsOfObject(helper, 'Helper')
83
86
  .filter(method => method !== 'constructor' && method[0] !== '_')
84
87
  .forEach(action => {
85
- const actionAlias = translation.actionAliasFor(action);
88
+ const actionAlias = translation.actionAliasFor(action)
86
89
  if (!actor[action]) {
87
90
  actor[action] = actor[actionAlias] = function () {
88
- const step = new Step(helper, action);
91
+ const step = new Step(helper, action)
89
92
  if (translation.loaded) {
90
- step.name = actionAlias;
91
- step.actor = translation.I;
93
+ step.name = actionAlias
94
+ step.actor = translation.I
92
95
  }
93
96
  // add methods to promise chain
94
- return recordStep(step, Array.from(arguments));
95
- };
97
+ return recordStep(step, Array.from(arguments))
98
+ }
96
99
  }
97
- });
98
- });
100
+ })
101
+ })
99
102
 
100
103
  // add translated custom steps from actor
101
104
  Object.keys(obj).forEach(key => {
102
- const actionAlias = translation.actionAliasFor(key);
105
+ const actionAlias = translation.actionAliasFor(key)
103
106
  if (!actor[actionAlias]) {
104
- actor[actionAlias] = actor[key];
107
+ actor[actionAlias] = actor[key]
105
108
  }
106
- });
109
+ })
107
110
 
108
111
  container.append({
109
112
  support: {
110
113
  I: actor,
111
114
  },
112
- });
113
- });
115
+ })
116
+ })
114
117
  // store.actor = actor;
115
118
  // add custom steps from actor
116
119
  Object.keys(obj).forEach(key => {
117
- const ms = new MetaStep('I', key);
118
- ms.setContext(actor);
119
- actor[key] = ms.run.bind(ms, obj[key]);
120
- });
121
-
122
- return actor;
123
- };
124
-
125
- function recordStep(step, args) {
126
- step.status = 'queued';
127
- step.setArguments(args);
128
-
129
- // run async before step hooks
130
- event.emit(event.step.before, step);
131
-
132
- const task = `${step.name}: ${step.humanizeArgs()}`;
133
- let val;
134
-
135
- // run step inside promise
136
- recorder.add(
137
- task,
138
- () => {
139
- if (!step.startTime) {
140
- // step can be retries
141
- event.emit(event.step.started, step);
142
- step.startTime = Date.now();
143
- }
144
- return (val = step.run(...args));
145
- },
146
- false,
147
- undefined,
148
- step.getTimeout(),
149
- );
150
-
151
- event.emit(event.step.after, step);
152
-
153
- recorder.add('step passed', () => {
154
- step.endTime = Date.now();
155
- event.emit(event.step.passed, step, val);
156
- event.emit(event.step.finished, step);
157
- });
158
-
159
- recorder.catchWithoutStop(err => {
160
- step.status = 'failed';
161
- step.endTime = Date.now();
162
- event.emit(event.step.failed, step);
163
- event.emit(event.step.finished, step);
164
- throw err;
165
- });
166
-
167
- recorder.add('return result', () => val);
168
- // run async after step hooks
120
+ const ms = new MetaStep('I', key)
121
+ ms.setContext(actor)
122
+ actor[key] = ms.run.bind(ms, obj[key])
123
+ })
169
124
 
170
- return recorder.promise();
125
+ return actor
171
126
  }
@@ -0,0 +1,173 @@
1
+ const { getConfig, getTestRoot } = require('./utils')
2
+ const Codecept = require('../codecept')
3
+ const output = require('../output')
4
+ const standardActingHelpers = require('../plugin/standardActingHelpers')
5
+ const store = require('../store')
6
+ const container = require('../container')
7
+ const figures = require('figures')
8
+ const chalk = require('chalk')
9
+ const { createTest } = require('../mocha/test')
10
+ const { getMachineInfo } = require('./info')
11
+ const definitions = require('./definitions')
12
+
13
+ module.exports = async function (options) {
14
+ const configFile = options.config
15
+
16
+ setTimeout(() => {
17
+ output.error("Something went wrong. Checks didn't pass and timed out. Please check your config and helpers.")
18
+ process.exit(1)
19
+ }, options.timeout || 50000)
20
+
21
+ const checks = {
22
+ config: false,
23
+ container: false,
24
+ pageObjects: false,
25
+ helpers: false,
26
+ setup: false,
27
+ tests: false,
28
+ def: false,
29
+ }
30
+
31
+ const testRoot = getTestRoot(configFile)
32
+ let config = getConfig(configFile)
33
+
34
+ try {
35
+ config = getConfig(configFile)
36
+ checks['config'] = true
37
+ } catch (err) {
38
+ checks['config'] = err
39
+ }
40
+
41
+ printCheck('config', checks['config'], config.name)
42
+
43
+ let codecept
44
+ try {
45
+ codecept = new Codecept(config, options)
46
+ codecept.init(testRoot)
47
+ await container.started()
48
+ checks.container = true
49
+ } catch (err) {
50
+ checks.container = err
51
+ }
52
+
53
+ printCheck('container', checks['container'])
54
+
55
+ if (codecept) {
56
+ try {
57
+ if (options.bootstrap) await codecept.bootstrap()
58
+ checks.bootstrap = true
59
+ } catch (err) {
60
+ checks.bootstrap = err
61
+ }
62
+ printCheck('bootstrap', checks['bootstrap'], options.bootstrap ? 'Bootstrap was executed' : 'No bootstrap command')
63
+ }
64
+
65
+ let numTests = 0
66
+ if (codecept) {
67
+ try {
68
+ codecept.loadTests()
69
+ const mocha = container.mocha()
70
+ mocha.files = codecept.testFiles
71
+ mocha.loadFiles()
72
+ mocha.suite.suites.forEach(suite => {
73
+ numTests += suite.tests.length
74
+ })
75
+ if (numTests > 0) {
76
+ checks.tests = true
77
+ } else {
78
+ throw new Error('No tests found')
79
+ }
80
+ } catch (err) {
81
+ checks.tests = err
82
+ }
83
+ }
84
+
85
+ printCheck('tests', checks['tests'], `Total: ${numTests} tests`)
86
+
87
+ store.dryRun = true
88
+
89
+ const helpers = container.helpers()
90
+
91
+ try {
92
+ if (!Object.keys(helpers).length) throw new Error('No helpers found')
93
+ // load helpers
94
+ for (const helper of Object.values(helpers)) {
95
+ if (helper._init) helper._init()
96
+ }
97
+ checks.helpers = true
98
+ } catch (err) {
99
+ checks.helpers = err
100
+ }
101
+
102
+ printCheck('helpers', checks['helpers'], `${Object.keys(helpers).join(', ')}`)
103
+
104
+ const pageObjects = container.support()
105
+
106
+ try {
107
+ if (Object.keys(pageObjects).length) {
108
+ for (const pageObject of Object.values(pageObjects)) {
109
+ pageObject.name
110
+ }
111
+ }
112
+ checks.pageObjects = true
113
+ } catch (err) {
114
+ checks.pageObjects = err
115
+ }
116
+ printCheck('page objects', checks['pageObjects'], `Total: ${Object.keys(pageObjects).length} support objects`)
117
+
118
+ if (Object.keys(helpers).length) {
119
+ const suite = container.mocha().suite
120
+ const test = createTest('test', () => {})
121
+ try {
122
+ for (const helper of Object.values(helpers)) {
123
+ if (helper._beforeSuite) await helper._beforeSuite(suite)
124
+ if (helper._before) await helper._before(test)
125
+ if (helper._passed) await helper._passed(test)
126
+ if (helper._after) await helper._after(test)
127
+ if (helper._finishTest) await helper._finishTest(suite)
128
+ if (helper._afterSuite) await helper._afterSuite(suite)
129
+ }
130
+ checks.setup = true
131
+ } catch (err) {
132
+ checks.setup = err
133
+ }
134
+ }
135
+
136
+ printCheck('Helpers Before/After', checks['setup'], standardActingHelpers.some(h => Object.keys(helpers).includes(h)) ? 'Initializing and closing browser' : '')
137
+
138
+ try {
139
+ definitions(configFile, { dryRun: true })
140
+ checks.def = true
141
+ } catch (err) {
142
+ checks.def = err
143
+ }
144
+
145
+ printCheck('TypeScript Definitions', checks['def'])
146
+
147
+ output.print('')
148
+
149
+ if (!Object.values(checks).every(check => check === true)) {
150
+ output.error("Something went wrong. Checks didn't pass.")
151
+ output.print()
152
+ await getMachineInfo()
153
+ process.exit(1)
154
+ }
155
+
156
+ output.print(output.styles.success('All checks passed'.toUpperCase()), 'Ready to run your tests 🚀')
157
+ process.exit(0)
158
+ }
159
+
160
+ function printCheck(name, value, comment = '') {
161
+ let status = ''
162
+ if (value == true) {
163
+ status += chalk.bold.green(figures.tick)
164
+ } else {
165
+ status += chalk.bold.red(figures.cross)
166
+ }
167
+
168
+ if (value instanceof Error) {
169
+ comment = `${comment} ${chalk.red.italic(value.message)}`.trim()
170
+ }
171
+
172
+ output.print(status, name.toUpperCase(), chalk.dim(comment))
173
+ }
@@ -185,6 +185,8 @@ module.exports = function (genPath, options) {
185
185
  definitionsFileContent += `\n${translationAliases.join('\n')}`
186
186
  }
187
187
 
188
+ if (options.dryRun) return
189
+
188
190
  fs.writeFileSync(path.join(targetFolderPath, 'steps.d.ts'), definitionsFileContent)
189
191
  output.print('TypeScript Definitions provide autocompletion in Visual Studio Code and other IDEs')
190
192
  output.print('Definitions were generated in steps.d.ts')
@@ -30,6 +30,7 @@ module.exports = async function (workerCount, selectedRuns, options) {
30
30
  output.print(`CodeceptJS v${require('../codecept').version()} ${output.standWithUkraine()}`)
31
31
  output.print(`Running tests in ${output.styles.bold(numberOfWorkers)} workers...`)
32
32
  output.print()
33
+ store.hasWorkers = true
33
34
 
34
35
  const workers = new Workers(numberOfWorkers, config)
35
36
  workers.overrideConfig(overrideConfigs)
@@ -100,7 +101,6 @@ module.exports = async function (workerCount, selectedRuns, options) {
100
101
  if (options.verbose || options.debug) store.debugMode = true
101
102
 
102
103
  if (options.verbose) {
103
- global.debugMode = true
104
104
  const { getMachineInfo } = require('./info')
105
105
  await getMachineInfo()
106
106
  }