codeceptjs 4.0.0-beta.3 → 4.0.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 (155) hide show
  1. package/README.md +134 -119
  2. package/bin/codecept.js +12 -2
  3. package/bin/test-server.js +53 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/lib/actor.js +66 -102
  6. package/lib/ai.js +130 -121
  7. package/lib/assert/empty.js +3 -5
  8. package/lib/assert/equal.js +4 -7
  9. package/lib/assert/include.js +4 -6
  10. package/lib/assert/throws.js +2 -4
  11. package/lib/assert/truth.js +2 -2
  12. package/lib/codecept.js +141 -86
  13. package/lib/command/check.js +201 -0
  14. package/lib/command/configMigrate.js +2 -4
  15. package/lib/command/definitions.js +8 -26
  16. package/lib/command/dryRun.js +30 -35
  17. package/lib/command/generate.js +10 -14
  18. package/lib/command/gherkin/snippets.js +75 -73
  19. package/lib/command/gherkin/steps.js +1 -1
  20. package/lib/command/info.js +42 -8
  21. package/lib/command/init.js +13 -12
  22. package/lib/command/interactive.js +10 -2
  23. package/lib/command/list.js +1 -1
  24. package/lib/command/run-multiple/chunk.js +48 -45
  25. package/lib/command/run-multiple.js +12 -35
  26. package/lib/command/run-workers.js +21 -58
  27. package/lib/command/utils.js +5 -6
  28. package/lib/command/workers/runTests.js +263 -222
  29. package/lib/container.js +386 -238
  30. package/lib/data/context.js +10 -13
  31. package/lib/data/dataScenarioConfig.js +8 -8
  32. package/lib/data/dataTableArgument.js +6 -6
  33. package/lib/data/table.js +5 -11
  34. package/lib/effects.js +223 -0
  35. package/lib/element/WebElement.js +327 -0
  36. package/lib/els.js +158 -0
  37. package/lib/event.js +21 -17
  38. package/lib/heal.js +88 -80
  39. package/lib/helper/AI.js +2 -1
  40. package/lib/helper/ApiDataFactory.js +4 -7
  41. package/lib/helper/Appium.js +50 -57
  42. package/lib/helper/FileSystem.js +3 -3
  43. package/lib/helper/GraphQLDataFactory.js +4 -4
  44. package/lib/helper/JSONResponse.js +75 -37
  45. package/lib/helper/Mochawesome.js +31 -9
  46. package/lib/helper/Nightmare.js +37 -58
  47. package/lib/helper/Playwright.js +267 -272
  48. package/lib/helper/Protractor.js +56 -87
  49. package/lib/helper/Puppeteer.js +247 -264
  50. package/lib/helper/REST.js +29 -17
  51. package/lib/helper/TestCafe.js +22 -47
  52. package/lib/helper/WebDriver.js +157 -368
  53. package/lib/helper/extras/PlaywrightPropEngine.js +2 -2
  54. package/lib/helper/extras/Popup.js +22 -22
  55. package/lib/helper/network/utils.js +1 -1
  56. package/lib/helper/testcafe/testcafe-utils.js +27 -28
  57. package/lib/listener/emptyRun.js +55 -0
  58. package/lib/listener/exit.js +7 -10
  59. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  60. package/lib/listener/globalTimeout.js +165 -0
  61. package/lib/listener/helpers.js +15 -15
  62. package/lib/listener/mocha.js +1 -1
  63. package/lib/listener/result.js +12 -0
  64. package/lib/listener/retryEnhancer.js +85 -0
  65. package/lib/listener/steps.js +32 -18
  66. package/lib/listener/store.js +20 -0
  67. package/lib/locator.js +1 -1
  68. package/lib/mocha/asyncWrapper.js +231 -0
  69. package/lib/{interfaces → mocha}/bdd.js +3 -3
  70. package/lib/mocha/cli.js +308 -0
  71. package/lib/mocha/factory.js +104 -0
  72. package/lib/{interfaces → mocha}/featureConfig.js +32 -12
  73. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  74. package/lib/mocha/hooks.js +112 -0
  75. package/lib/mocha/index.js +12 -0
  76. package/lib/mocha/inject.js +29 -0
  77. package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
  78. package/lib/mocha/suite.js +82 -0
  79. package/lib/mocha/test.js +181 -0
  80. package/lib/mocha/types.d.ts +42 -0
  81. package/lib/mocha/ui.js +232 -0
  82. package/lib/output.js +93 -65
  83. package/lib/pause.js +160 -138
  84. package/lib/plugin/analyze.js +396 -0
  85. package/lib/plugin/auth.js +435 -0
  86. package/lib/plugin/autoDelay.js +8 -8
  87. package/lib/plugin/autoLogin.js +3 -338
  88. package/lib/plugin/commentStep.js +6 -1
  89. package/lib/plugin/coverage.js +10 -22
  90. package/lib/plugin/customLocator.js +3 -3
  91. package/lib/plugin/customReporter.js +52 -0
  92. package/lib/plugin/eachElement.js +1 -1
  93. package/lib/plugin/fakerTransform.js +1 -1
  94. package/lib/plugin/heal.js +36 -9
  95. package/lib/plugin/htmlReporter.js +1947 -0
  96. package/lib/plugin/pageInfo.js +140 -0
  97. package/lib/plugin/retryFailedStep.js +17 -18
  98. package/lib/plugin/retryTo.js +2 -113
  99. package/lib/plugin/screenshotOnFail.js +17 -58
  100. package/lib/plugin/selenoid.js +15 -35
  101. package/lib/plugin/standardActingHelpers.js +4 -1
  102. package/lib/plugin/stepByStepReport.js +56 -17
  103. package/lib/plugin/stepTimeout.js +5 -12
  104. package/lib/plugin/subtitles.js +4 -4
  105. package/lib/plugin/tryTo.js +3 -102
  106. package/lib/plugin/wdio.js +8 -10
  107. package/lib/recorder.js +155 -124
  108. package/lib/rerun.js +43 -42
  109. package/lib/result.js +161 -0
  110. package/lib/secret.js +1 -2
  111. package/lib/step/base.js +239 -0
  112. package/lib/step/comment.js +10 -0
  113. package/lib/step/config.js +50 -0
  114. package/lib/step/func.js +46 -0
  115. package/lib/step/helper.js +50 -0
  116. package/lib/step/meta.js +99 -0
  117. package/lib/step/record.js +74 -0
  118. package/lib/step/retry.js +11 -0
  119. package/lib/step/section.js +55 -0
  120. package/lib/step.js +21 -332
  121. package/lib/steps.js +50 -0
  122. package/lib/store.js +37 -5
  123. package/lib/template/heal.js +2 -11
  124. package/lib/test-server.js +323 -0
  125. package/lib/timeout.js +66 -0
  126. package/lib/utils.js +351 -218
  127. package/lib/within.js +75 -55
  128. package/lib/workerStorage.js +2 -1
  129. package/lib/workers.js +386 -277
  130. package/package.json +81 -75
  131. package/translations/de-DE.js +5 -3
  132. package/translations/fr-FR.js +5 -4
  133. package/translations/index.js +1 -0
  134. package/translations/it-IT.js +4 -3
  135. package/translations/ja-JP.js +4 -3
  136. package/translations/nl-NL.js +76 -0
  137. package/translations/pl-PL.js +4 -3
  138. package/translations/pt-BR.js +4 -3
  139. package/translations/ru-RU.js +4 -3
  140. package/translations/utils.js +9 -0
  141. package/translations/zh-CN.js +4 -3
  142. package/translations/zh-TW.js +4 -3
  143. package/typings/index.d.ts +197 -187
  144. package/typings/promiseBasedTypes.d.ts +53 -903
  145. package/typings/types.d.ts +372 -1042
  146. package/lib/cli.js +0 -257
  147. package/lib/helper/ExpectHelper.js +0 -391
  148. package/lib/helper/MockServer.js +0 -221
  149. package/lib/helper/SoftExpectHelper.js +0 -381
  150. package/lib/listener/artifacts.js +0 -19
  151. package/lib/listener/timeout.js +0 -109
  152. package/lib/mochaFactory.js +0 -113
  153. package/lib/plugin/debugErrors.js +0 -67
  154. package/lib/scenario.js +0 -224
  155. package/lib/ui.js +0 -236
@@ -10,7 +10,7 @@ class InclusionAssertion extends Assertion {
10
10
  params.jar = params.jar || 'string'
11
11
  const comparator = function (needle, haystack) {
12
12
  if (Array.isArray(haystack)) {
13
- return haystack.filter((part) => part.indexOf(needle) >= 0).length > 0
13
+ return haystack.filter(part => part.indexOf(needle) >= 0).length > 0
14
14
  }
15
15
  return haystack.indexOf(needle) >= 0
16
16
  }
@@ -28,9 +28,7 @@ class InclusionAssertion extends Assertion {
28
28
  this.params.haystack = this.params.haystack.join('\n___(next element)___\n')
29
29
  }
30
30
  err.cliMessage = function () {
31
- const msg = this.template
32
- .replace('{{jar}}', output.colors.bold('{{jar}}'))
33
- .replace('{{needle}}', output.colors.bold('{{needle}}'))
31
+ const msg = this.template.replace('{{jar}}', output.colors.bold('{{jar}}')).replace('{{needle}}', output.colors.bold('{{needle}}'))
34
32
  return template(msg, this.params)
35
33
  }
36
34
  return err
@@ -66,11 +64,11 @@ class InclusionAssertion extends Assertion {
66
64
 
67
65
  module.exports = {
68
66
  Assertion: InclusionAssertion,
69
- includes: (needleType) => {
67
+ includes: needleType => {
70
68
  needleType = needleType || 'string'
71
69
  return new InclusionAssertion({ jar: needleType })
72
70
  },
73
- fileIncludes: (file) => new InclusionAssertion({ file, jar: 'file {{file}}' }),
71
+ fileIncludes: file => new InclusionAssertion({ file, jar: 'file {{file}}' }),
74
72
  }
75
73
 
76
74
  function escapeRegExp(str) {
@@ -11,10 +11,8 @@ function errorThrown(actual, expected) {
11
11
  throw new Error(`Expected error to be thrown with message ${expected} while '${msg}' caught`)
12
12
  }
13
13
  if (typeof expected === 'object') {
14
- if (actual.constructor.name !== expected.constructor.name)
15
- throw new Error(`Expected ${expected} error to be thrown but ${actual} was caught`)
16
- if (expected.message && expected.message !== msg)
17
- throw new Error(`Expected error to be thrown with message ${expected.message} while '${msg}' caught`)
14
+ if (actual.constructor.name !== expected.constructor.name) throw new Error(`Expected ${expected} error to be thrown but ${actual} was caught`)
15
+ if (expected.message && expected.message !== msg) throw new Error(`Expected error to be thrown with message ${expected.message} while '${msg}' caught`)
18
16
  }
19
17
  return null
20
18
  }
@@ -5,9 +5,9 @@ const output = require('../output')
5
5
 
6
6
  class TruthAssertion extends Assertion {
7
7
  constructor(params) {
8
- super((value) => {
8
+ super(value => {
9
9
  if (Array.isArray(value)) {
10
- return value.filter((val) => !!val).length > 0
10
+ return value.filter(val => !!val).length > 0
11
11
  }
12
12
  return !!value
13
13
  }, params)
package/lib/codecept.js CHANGED
@@ -1,14 +1,15 @@
1
- const { existsSync, readFileSync } = require('fs');
2
- const glob = require('glob');
3
- const fsPath = require('path');
4
- const { resolve } = require('path');
5
-
6
- const container = require('./container');
7
- const Config = require('./config');
8
- const event = require('./event');
9
- const runHook = require('./hooks');
10
- const output = require('./output');
11
- const { emptyFolder } = require('./utils');
1
+ const { existsSync, readFileSync } = require('fs')
2
+ const { globSync } = require('glob')
3
+ const shuffle = require('lodash.shuffle')
4
+ const fsPath = require('path')
5
+ const { resolve } = require('path')
6
+
7
+ const container = require('./container')
8
+ const Config = require('./config')
9
+ const event = require('./event')
10
+ const runHook = require('./hooks')
11
+ const output = require('./output')
12
+ const { emptyFolder } = require('./utils')
12
13
 
13
14
  /**
14
15
  * CodeceptJS runner
@@ -22,10 +23,10 @@ class Codecept {
22
23
  * @param {*} opts
23
24
  */
24
25
  constructor(config, opts) {
25
- this.config = Config.create(config);
26
- this.opts = opts;
27
- this.testFiles = new Array(0);
28
- this.requireModules(config.require);
26
+ this.config = Config.create(config)
27
+ this.opts = opts
28
+ this.testFiles = new Array(0)
29
+ this.requireModules(config.require)
29
30
  }
30
31
 
31
32
  /**
@@ -35,13 +36,13 @@ class Codecept {
35
36
  */
36
37
  requireModules(requiringModules) {
37
38
  if (requiringModules) {
38
- requiringModules.forEach((requiredModule) => {
39
- const isLocalFile = existsSync(requiredModule) || existsSync(`${requiredModule}.js`);
39
+ requiringModules.forEach(requiredModule => {
40
+ const isLocalFile = existsSync(requiredModule) || existsSync(`${requiredModule}.js`)
40
41
  if (isLocalFile) {
41
- requiredModule = resolve(requiredModule);
42
+ requiredModule = resolve(requiredModule)
42
43
  }
43
- require(requiredModule);
44
- });
44
+ require(requiredModule)
45
+ })
45
46
  }
46
47
  }
47
48
 
@@ -52,10 +53,10 @@ class Codecept {
52
53
  * @param {string} dir
53
54
  */
54
55
  init(dir) {
55
- this.initGlobals(dir);
56
+ this.initGlobals(dir)
56
57
  // initializing listeners
57
- container.create(this.config, this.opts);
58
- this.runHooks();
58
+ container.create(this.config, this.opts)
59
+ this.runHooks()
59
60
  }
60
61
 
61
62
  /**
@@ -64,34 +65,37 @@ class Codecept {
64
65
  * @param {string} dir
65
66
  */
66
67
  initGlobals(dir) {
67
- global.codecept_dir = dir;
68
- global.output_dir = fsPath.resolve(dir, this.config.output);
68
+ global.codecept_dir = dir
69
+ global.output_dir = fsPath.resolve(dir, this.config.output)
69
70
 
70
- if (this.config.emptyOutputFolder) emptyFolder(global.output_dir);
71
+ if (this.config.emptyOutputFolder) emptyFolder(global.output_dir)
71
72
 
72
73
  if (!this.config.noGlobals) {
73
- global.Helper = global.codecept_helper = require('@codeceptjs/helper');
74
- global.actor = global.codecept_actor = require('./actor');
75
- global.pause = require('./pause');
76
- global.within = require('./within');
77
- global.session = require('./session');
78
- global.DataTable = require('./data/table');
79
- global.locate = locator => require('./locator').build(locator);
80
- global.inject = container.support;
81
- global.share = container.share;
82
- global.secret = require('./secret').secret;
83
- global.codecept_debug = output.debug;
84
- global.codeceptjs = require('./index'); // load all objects
74
+ global.Helper = global.codecept_helper = require('@codeceptjs/helper')
75
+ global.actor = global.codecept_actor = require('./actor')
76
+ global.pause = require('./pause')
77
+ global.within = require('./within')
78
+ global.session = require('./session')
79
+ global.DataTable = require('./data/table')
80
+ global.locate = locator => require('./locator').build(locator)
81
+ global.inject = container.support
82
+ global.share = container.share
83
+ global.secret = require('./secret').secret
84
+ global.codecept_debug = output.debug
85
+ global.codeceptjs = require('./index') // load all objects
85
86
 
86
87
  // BDD
87
- const stepDefinitions = require('./interfaces/bdd');
88
- global.Given = stepDefinitions.Given;
89
- global.When = stepDefinitions.When;
90
- global.Then = stepDefinitions.Then;
91
- global.DefineParameterType = stepDefinitions.defineParameterType;
88
+ const stepDefinitions = require('./mocha/bdd')
89
+ global.Given = stepDefinitions.Given
90
+ global.When = stepDefinitions.When
91
+ global.Then = stepDefinitions.Then
92
+ global.DefineParameterType = stepDefinitions.defineParameterType
92
93
 
93
94
  // debug mode
94
- global.debugMode = false;
95
+ global.debugMode = false
96
+
97
+ // mask sensitive data
98
+ global.maskSensitiveData = this.config.maskSensitiveData || false
95
99
  }
96
100
  }
97
101
 
@@ -100,16 +104,19 @@ class Codecept {
100
104
  */
101
105
  runHooks() {
102
106
  // default hooks
103
- runHook(require('./listener/steps'));
104
- runHook(require('./listener/artifacts'));
105
- runHook(require('./listener/config'));
106
- runHook(require('./listener/helpers'));
107
- runHook(require('./listener/retry'));
108
- runHook(require('./listener/timeout'));
109
- runHook(require('./listener/exit'));
107
+ runHook(require('./listener/store'))
108
+ runHook(require('./listener/steps'))
109
+ runHook(require('./listener/config'))
110
+ runHook(require('./listener/result'))
111
+ runHook(require('./listener/helpers'))
112
+ runHook(require('./listener/globalTimeout'))
113
+ runHook(require('./listener/globalRetry'))
114
+ runHook(require('./listener/retryEnhancer'))
115
+ runHook(require('./listener/exit'))
116
+ runHook(require('./listener/emptyRun'))
110
117
 
111
118
  // custom hooks (previous iteration of plugins)
112
- this.config.hooks.forEach(hook => runHook(hook));
119
+ this.config.hooks.forEach(hook => runHook(hook))
113
120
  }
114
121
 
115
122
  /**
@@ -117,7 +124,7 @@ class Codecept {
117
124
  *
118
125
  */
119
126
  async bootstrap() {
120
- return runHook(this.config.bootstrap, 'bootstrap');
127
+ return runHook(this.config.bootstrap, 'bootstrap')
121
128
  }
122
129
 
123
130
  /**
@@ -125,7 +132,7 @@ class Codecept {
125
132
 
126
133
  */
127
134
  async teardown() {
128
- return runHook(this.config.teardown, 'teardown');
135
+ return runHook(this.config.teardown, 'teardown')
129
136
  }
130
137
 
131
138
  /**
@@ -136,45 +143,91 @@ class Codecept {
136
143
  loadTests(pattern) {
137
144
  const options = {
138
145
  cwd: global.codecept_dir,
139
- };
146
+ }
140
147
 
141
- let patterns = [pattern];
148
+ let patterns = [pattern]
142
149
  if (!pattern) {
143
- patterns = [];
150
+ patterns = []
144
151
 
145
152
  // If the user wants to test a specific set of test files as an array or string.
146
153
  if (this.config.tests && !this.opts.features) {
147
154
  if (Array.isArray(this.config.tests)) {
148
- patterns.push(...this.config.tests);
155
+ patterns.push(...this.config.tests)
149
156
  } else {
150
- patterns.push(this.config.tests);
157
+ patterns.push(this.config.tests)
151
158
  }
152
159
  }
153
160
 
154
161
  if (this.config.gherkin.features && !this.opts.tests) {
155
162
  if (Array.isArray(this.config.gherkin.features)) {
156
163
  this.config.gherkin.features.forEach(feature => {
157
- patterns.push(feature);
158
- });
164
+ patterns.push(feature)
165
+ })
159
166
  } else {
160
- patterns.push(this.config.gherkin.features);
167
+ patterns.push(this.config.gherkin.features)
161
168
  }
162
169
  }
163
170
  }
164
171
 
165
172
  for (pattern of patterns) {
166
- glob.sync(pattern, options).forEach((file) => {
167
- if (file.includes('node_modules')) return;
168
- if (!fsPath.isAbsolute(file)) {
169
- file = fsPath.join(global.codecept_dir, file);
170
- }
171
- if (!this.testFiles.includes(fsPath.resolve(file))) {
172
- this.testFiles.push(fsPath.resolve(file));
173
- }
174
- });
173
+ if (pattern) {
174
+ globSync(pattern, options).forEach(file => {
175
+ if (file.includes('node_modules')) return
176
+ if (!fsPath.isAbsolute(file)) {
177
+ file = fsPath.join(global.codecept_dir, file)
178
+ }
179
+ if (!this.testFiles.includes(fsPath.resolve(file))) {
180
+ this.testFiles.push(fsPath.resolve(file))
181
+ }
182
+ })
183
+ }
184
+ }
185
+
186
+ if (this.opts.shuffle) {
187
+ this.testFiles = shuffle(this.testFiles)
188
+ }
189
+
190
+ if (this.opts.shard) {
191
+ this.testFiles = this._applySharding(this.testFiles, this.opts.shard)
175
192
  }
176
193
  }
177
194
 
195
+ /**
196
+ * Apply sharding to test files based on shard configuration
197
+ *
198
+ * @param {Array<string>} testFiles - Array of test file paths
199
+ * @param {string} shardConfig - Shard configuration in format "index/total" (e.g., "1/4")
200
+ * @returns {Array<string>} - Filtered array of test files for this shard
201
+ */
202
+ _applySharding(testFiles, shardConfig) {
203
+ const shardMatch = shardConfig.match(/^(\d+)\/(\d+)$/)
204
+ if (!shardMatch) {
205
+ throw new Error('Invalid shard format. Expected format: "index/total" (e.g., "1/4")')
206
+ }
207
+
208
+ const shardIndex = parseInt(shardMatch[1], 10)
209
+ const shardTotal = parseInt(shardMatch[2], 10)
210
+
211
+ if (shardTotal < 1) {
212
+ throw new Error('Shard total must be at least 1')
213
+ }
214
+
215
+ if (shardIndex < 1 || shardIndex > shardTotal) {
216
+ throw new Error(`Shard index ${shardIndex} must be between 1 and ${shardTotal}`)
217
+ }
218
+
219
+ if (testFiles.length === 0) {
220
+ return testFiles
221
+ }
222
+
223
+ // Calculate which tests belong to this shard
224
+ const shardSize = Math.ceil(testFiles.length / shardTotal)
225
+ const startIndex = (shardIndex - 1) * shardSize
226
+ const endIndex = Math.min(startIndex + shardSize, testFiles.length)
227
+
228
+ return testFiles.slice(startIndex, endIndex)
229
+ }
230
+
178
231
  /**
179
232
  * Run a specific test or all loaded tests.
180
233
  *
@@ -182,34 +235,36 @@ class Codecept {
182
235
  * @returns {Promise<void>}
183
236
  */
184
237
  async run(test) {
238
+ await container.started()
239
+
185
240
  return new Promise((resolve, reject) => {
186
- const mocha = container.mocha();
187
- mocha.files = this.testFiles;
241
+ const mocha = container.mocha()
242
+ mocha.files = this.testFiles
188
243
  if (test) {
189
244
  if (!fsPath.isAbsolute(test)) {
190
- test = fsPath.join(global.codecept_dir, test);
245
+ test = fsPath.join(global.codecept_dir, test)
191
246
  }
192
- mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test);
247
+ mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test)
193
248
  }
194
249
  const done = () => {
195
- event.emit(event.all.result, this);
196
- event.emit(event.all.after, this);
197
- resolve();
198
- };
250
+ event.emit(event.all.result, container.result())
251
+ event.emit(event.all.after, this)
252
+ resolve()
253
+ }
199
254
 
200
255
  try {
201
- event.emit(event.all.before, this);
202
- mocha.run(() => done());
256
+ event.emit(event.all.before, this)
257
+ mocha.run(() => done())
203
258
  } catch (e) {
204
- output.error(e.stack);
205
- reject(e);
259
+ output.error(e.stack)
260
+ reject(e)
206
261
  }
207
- });
262
+ })
208
263
  }
209
264
 
210
265
  static version() {
211
- return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version;
266
+ return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version
212
267
  }
213
268
  }
214
269
 
215
- module.exports = Codecept;
270
+ module.exports = Codecept
@@ -0,0 +1,201 @@
1
+ const { getConfig, getTestRoot } = require('./utils')
2
+ const Codecept = require('../codecept')
3
+ const output = require('../output')
4
+ const store = require('../store')
5
+ const container = require('../container')
6
+ const figures = require('figures')
7
+ const chalk = require('chalk')
8
+ const { createTest } = require('../mocha/test')
9
+ const { getMachineInfo } = require('./info')
10
+ const definitions = require('./definitions')
11
+
12
+ module.exports = async function (options) {
13
+ const configFile = options.config
14
+
15
+ setTimeout(() => {
16
+ output.error("Something went wrong. Checks didn't pass and timed out. Please check your config and helpers.")
17
+ process.exit(1)
18
+ }, options.timeout || 50000)
19
+
20
+ const checks = {
21
+ config: false,
22
+ container: false,
23
+ pageObjects: false,
24
+ plugins: false,
25
+ ai: true, // we don't need to check AI
26
+ helpers: false,
27
+ setup: false,
28
+ teardown: false,
29
+ tests: false,
30
+ def: false,
31
+ }
32
+
33
+ const testRoot = getTestRoot(configFile)
34
+ let config = getConfig(configFile)
35
+
36
+ try {
37
+ config = getConfig(configFile)
38
+ checks['config'] = true
39
+ } catch (err) {
40
+ checks['config'] = err
41
+ }
42
+
43
+ printCheck('config', checks['config'], config.name)
44
+
45
+ let codecept
46
+ try {
47
+ codecept = new Codecept(config, options)
48
+ codecept.init(testRoot)
49
+ await container.started()
50
+ checks.container = true
51
+ } catch (err) {
52
+ checks.container = err
53
+ }
54
+
55
+ const standardActingHelpers = container.STANDARD_ACTING_HELPERS
56
+
57
+ printCheck('container', checks['container'])
58
+
59
+ if (codecept) {
60
+ try {
61
+ if (options.bootstrap) await codecept.bootstrap()
62
+ checks.bootstrap = true
63
+ } catch (err) {
64
+ checks.bootstrap = err
65
+ }
66
+ printCheck('bootstrap', checks['bootstrap'], options.bootstrap ? 'Bootstrap was executed' : 'No bootstrap command')
67
+ }
68
+
69
+ let numTests = 0
70
+ if (codecept) {
71
+ try {
72
+ codecept.loadTests()
73
+ const mocha = container.mocha()
74
+ mocha.files = codecept.testFiles
75
+ mocha.loadFiles()
76
+ mocha.suite.suites.forEach(suite => {
77
+ numTests += suite.tests.length
78
+ })
79
+ if (numTests > 0) {
80
+ checks.tests = true
81
+ } else {
82
+ throw new Error('No tests found')
83
+ }
84
+ } catch (err) {
85
+ checks.tests = err
86
+ }
87
+ }
88
+
89
+ if (config?.ai?.request) {
90
+ checks.ai = true
91
+ printCheck('ai', checks['ai'], 'Configuration is enabled, request function is set')
92
+ } else {
93
+ printCheck('ai', checks['ai'], 'Disabled')
94
+ }
95
+
96
+ printCheck('tests', checks['tests'], `Total: ${numTests} tests`)
97
+
98
+ store.dryRun = true
99
+
100
+ const helpers = container.helpers()
101
+
102
+ try {
103
+ if (!Object.keys(helpers).length) throw new Error('No helpers found')
104
+ // load helpers
105
+ for (const helper of Object.values(helpers)) {
106
+ if (helper._init) helper._init()
107
+ }
108
+ checks.helpers = true
109
+ } catch (err) {
110
+ checks.helpers = err
111
+ }
112
+
113
+ printCheck('helpers', checks['helpers'], `${Object.keys(helpers).join(', ')}`)
114
+
115
+ const pageObjects = container.support()
116
+
117
+ try {
118
+ if (Object.keys(pageObjects).length) {
119
+ for (const pageObject of Object.values(pageObjects)) {
120
+ pageObject.name
121
+ }
122
+ }
123
+ checks.pageObjects = true
124
+ } catch (err) {
125
+ checks.pageObjects = err
126
+ }
127
+ printCheck('page objects', checks['pageObjects'], `Total: ${Object.keys(pageObjects).length} support objects`)
128
+
129
+ checks.plugins = true // how to check plugins?
130
+ printCheck('plugins', checks['plugins'], Object.keys(container.plugins()).join(', '))
131
+
132
+ if (Object.keys(helpers).length) {
133
+ const suite = container.mocha().suite
134
+ const test = createTest('test', () => {})
135
+ checks.setup = true
136
+ for (const helper of Object.values(helpers)) {
137
+ try {
138
+ if (helper._beforeSuite) await helper._beforeSuite(suite)
139
+ if (helper._before) await helper._before(test)
140
+ } catch (err) {
141
+ err.message = `${helper.constructor.name} helper: ${err.message}`
142
+ if (checks.setup instanceof Error) err.message = `${err.message}\n\n${checks.setup?.message || ''}`.trim()
143
+ checks.setup = err
144
+ }
145
+ }
146
+
147
+ printCheck('Helpers Before', checks['setup'], standardActingHelpers.some(h => Object.keys(helpers).includes(h)) ? 'Initializing browser' : '')
148
+
149
+ checks.teardown = true
150
+ for (const helper of Object.values(helpers).reverse()) {
151
+ try {
152
+ if (helper._passed) await helper._passed(test)
153
+ if (helper._after) await helper._after(test)
154
+ if (helper._finishTest) await helper._finishTest(suite)
155
+ if (helper._afterSuite) await helper._afterSuite(suite)
156
+ } catch (err) {
157
+ err.message = `${helper.constructor.name} helper: ${err.message}`
158
+ if (checks.teardown instanceof Error) err.message = `${err.message}\n\n${checks.teardown?.message || ''}`.trim()
159
+ checks.teardown = err
160
+ }
161
+ }
162
+
163
+ printCheck('Helpers After', checks['teardown'], standardActingHelpers.some(h => Object.keys(helpers).includes(h)) ? 'Closing browser' : '')
164
+ }
165
+
166
+ try {
167
+ definitions(configFile, { dryRun: true })
168
+ checks.def = true
169
+ } catch (err) {
170
+ checks.def = err
171
+ }
172
+
173
+ printCheck('TypeScript Definitions', checks['def'])
174
+
175
+ output.print('')
176
+
177
+ if (!Object.values(checks).every(check => check === true)) {
178
+ output.error("Something went wrong. Checks didn't pass.")
179
+ output.print()
180
+ await getMachineInfo()
181
+ process.exit(1)
182
+ }
183
+
184
+ output.print(output.styles.success('All checks passed'.toUpperCase()), 'Ready to run your tests 🚀')
185
+ process.exit(0)
186
+ }
187
+
188
+ function printCheck(name, value, comment = '') {
189
+ let status = ''
190
+ if (value == true) {
191
+ status += chalk.bold.green(figures.tick)
192
+ } else {
193
+ status += chalk.bold.red(figures.cross)
194
+ }
195
+
196
+ if (value instanceof Error) {
197
+ comment = `${comment} ${chalk.red(value.message)}`.trim()
198
+ }
199
+
200
+ output.print(status, name.toUpperCase(), chalk.dim(comment))
201
+ }
@@ -14,9 +14,7 @@ module.exports = function (initPath) {
14
14
 
15
15
  print()
16
16
  print(` Welcome to ${colors.magenta.bold('CodeceptJS')} configuration migration tool`)
17
- print(
18
- ` It will help you switch from ${colors.cyan.bold('.json')} to ${colors.magenta.bold('.js')} config format at ease`,
19
- )
17
+ print(` It will help you switch from ${colors.cyan.bold('.json')} to ${colors.magenta.bold('.js')} config format at ease`)
20
18
  print()
21
19
 
22
20
  if (!path) {
@@ -53,7 +51,7 @@ module.exports = function (initPath) {
53
51
  default: true,
54
52
  },
55
53
  ])
56
- .then((result) => {
54
+ .then(result => {
57
55
  if (result.configFile) {
58
56
  const jsonConfigFile = path.join(testsPath, 'codecept.js')
59
57
  const config = JSON.parse(fs.readFileSync(jsonConfigFile, 'utf8'))