codeceptjs 3.6.10 → 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 (127) hide show
  1. package/README.md +89 -119
  2. package/bin/codecept.js +9 -2
  3. package/docs/webapi/clearCookie.mustache +1 -1
  4. package/lib/actor.js +66 -102
  5. package/lib/ai.js +130 -121
  6. package/lib/assert/empty.js +3 -5
  7. package/lib/assert/equal.js +4 -7
  8. package/lib/assert/include.js +4 -6
  9. package/lib/assert/throws.js +2 -4
  10. package/lib/assert/truth.js +2 -2
  11. package/lib/codecept.js +87 -83
  12. package/lib/command/check.js +186 -0
  13. package/lib/command/configMigrate.js +2 -4
  14. package/lib/command/definitions.js +8 -26
  15. package/lib/command/generate.js +10 -14
  16. package/lib/command/gherkin/snippets.js +10 -8
  17. package/lib/command/gherkin/steps.js +1 -1
  18. package/lib/command/info.js +1 -3
  19. package/lib/command/init.js +8 -12
  20. package/lib/command/interactive.js +2 -2
  21. package/lib/command/list.js +1 -1
  22. package/lib/command/run-multiple.js +12 -35
  23. package/lib/command/run-workers.js +5 -57
  24. package/lib/command/utils.js +5 -6
  25. package/lib/command/workers/runTests.js +68 -232
  26. package/lib/container.js +354 -237
  27. package/lib/data/context.js +10 -13
  28. package/lib/data/dataScenarioConfig.js +8 -8
  29. package/lib/data/dataTableArgument.js +6 -6
  30. package/lib/data/table.js +5 -11
  31. package/lib/effects.js +218 -0
  32. package/lib/els.js +158 -0
  33. package/lib/event.js +19 -17
  34. package/lib/heal.js +88 -80
  35. package/lib/helper/AI.js +2 -1
  36. package/lib/helper/ApiDataFactory.js +3 -6
  37. package/lib/helper/Appium.js +45 -51
  38. package/lib/helper/FileSystem.js +3 -3
  39. package/lib/helper/GraphQLDataFactory.js +3 -3
  40. package/lib/helper/JSONResponse.js +57 -37
  41. package/lib/helper/Nightmare.js +35 -53
  42. package/lib/helper/Playwright.js +211 -252
  43. package/lib/helper/Protractor.js +54 -77
  44. package/lib/helper/Puppeteer.js +139 -232
  45. package/lib/helper/REST.js +5 -17
  46. package/lib/helper/TestCafe.js +21 -44
  47. package/lib/helper/WebDriver.js +131 -169
  48. package/lib/helper/testcafe/testcafe-utils.js +26 -27
  49. package/lib/listener/emptyRun.js +55 -0
  50. package/lib/listener/exit.js +7 -10
  51. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  52. package/lib/listener/globalTimeout.js +165 -0
  53. package/lib/listener/helpers.js +15 -15
  54. package/lib/listener/mocha.js +1 -1
  55. package/lib/listener/result.js +12 -0
  56. package/lib/listener/steps.js +20 -18
  57. package/lib/listener/store.js +20 -0
  58. package/lib/mocha/asyncWrapper.js +216 -0
  59. package/lib/{interfaces → mocha}/bdd.js +3 -3
  60. package/lib/mocha/cli.js +308 -0
  61. package/lib/mocha/factory.js +104 -0
  62. package/lib/{interfaces → mocha}/featureConfig.js +24 -12
  63. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  64. package/lib/mocha/hooks.js +112 -0
  65. package/lib/mocha/index.js +12 -0
  66. package/lib/mocha/inject.js +29 -0
  67. package/lib/{interfaces → mocha}/scenarioConfig.js +21 -6
  68. package/lib/mocha/suite.js +81 -0
  69. package/lib/mocha/test.js +159 -0
  70. package/lib/mocha/types.d.ts +42 -0
  71. package/lib/mocha/ui.js +219 -0
  72. package/lib/output.js +82 -62
  73. package/lib/pause.js +155 -138
  74. package/lib/plugin/analyze.js +349 -0
  75. package/lib/plugin/autoDelay.js +6 -6
  76. package/lib/plugin/autoLogin.js +6 -7
  77. package/lib/plugin/commentStep.js +6 -1
  78. package/lib/plugin/coverage.js +10 -19
  79. package/lib/plugin/customLocator.js +3 -3
  80. package/lib/plugin/customReporter.js +52 -0
  81. package/lib/plugin/eachElement.js +1 -1
  82. package/lib/plugin/fakerTransform.js +1 -1
  83. package/lib/plugin/heal.js +36 -9
  84. package/lib/plugin/pageInfo.js +140 -0
  85. package/lib/plugin/retryFailedStep.js +4 -4
  86. package/lib/plugin/retryTo.js +18 -118
  87. package/lib/plugin/screenshotOnFail.js +17 -49
  88. package/lib/plugin/selenoid.js +15 -35
  89. package/lib/plugin/standardActingHelpers.js +4 -1
  90. package/lib/plugin/stepByStepReport.js +56 -17
  91. package/lib/plugin/stepTimeout.js +5 -12
  92. package/lib/plugin/subtitles.js +4 -4
  93. package/lib/plugin/tryTo.js +17 -107
  94. package/lib/plugin/wdio.js +8 -10
  95. package/lib/recorder.js +146 -125
  96. package/lib/rerun.js +43 -42
  97. package/lib/result.js +161 -0
  98. package/lib/secret.js +1 -1
  99. package/lib/step/base.js +228 -0
  100. package/lib/step/config.js +50 -0
  101. package/lib/step/func.js +46 -0
  102. package/lib/step/helper.js +50 -0
  103. package/lib/step/meta.js +99 -0
  104. package/lib/step/record.js +74 -0
  105. package/lib/step/retry.js +11 -0
  106. package/lib/step/section.js +55 -0
  107. package/lib/step.js +21 -332
  108. package/lib/steps.js +50 -0
  109. package/lib/store.js +10 -2
  110. package/lib/template/heal.js +2 -11
  111. package/lib/timeout.js +66 -0
  112. package/lib/utils.js +317 -216
  113. package/lib/within.js +73 -55
  114. package/lib/workers.js +259 -275
  115. package/package.json +56 -54
  116. package/typings/index.d.ts +175 -186
  117. package/typings/promiseBasedTypes.d.ts +164 -17
  118. package/typings/types.d.ts +284 -115
  119. package/lib/cli.js +0 -256
  120. package/lib/helper/ExpectHelper.js +0 -391
  121. package/lib/helper/SoftExpectHelper.js +0 -381
  122. package/lib/listener/artifacts.js +0 -19
  123. package/lib/listener/timeout.js +0 -109
  124. package/lib/mochaFactory.js +0 -113
  125. package/lib/plugin/debugErrors.js +0 -67
  126. package/lib/scenario.js +0 -224
  127. package/lib/ui.js +0 -236
@@ -0,0 +1,216 @@
1
+ const promiseRetry = require('promise-retry')
2
+ const event = require('../event')
3
+ const recorder = require('../recorder')
4
+ const assertThrown = require('../assert/throws')
5
+ const { ucfirst, isAsyncFunction } = require('../utils')
6
+ const { getInjectedArguments } = require('./inject')
7
+ const { fireHook } = require('./hooks')
8
+
9
+ const injectHook = function (inject, suite) {
10
+ try {
11
+ inject()
12
+ } catch (err) {
13
+ recorder.throw(err)
14
+ }
15
+ recorder.catch(err => {
16
+ suiteTestFailedHookError(suite, err)
17
+ throw err
18
+ })
19
+ return recorder.promise()
20
+ }
21
+
22
+ function suiteTestFailedHookError(suite, err, hookName) {
23
+ suite.eachTest(test => {
24
+ test.err = err
25
+ event.emit(event.test.failed, test, err, ucfirst(hookName))
26
+ })
27
+ }
28
+
29
+ function makeDoneCallableOnce(done) {
30
+ let called = false
31
+ return function (err) {
32
+ if (called) {
33
+ return
34
+ }
35
+ called = true
36
+ return done(err)
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Wraps test function, injects support objects from container,
42
+ * starts promise chain with recorder, performs before/after hooks
43
+ * through event system.
44
+ */
45
+ module.exports.test = test => {
46
+ const testFn = test.fn
47
+ if (!testFn) {
48
+ return test
49
+ }
50
+
51
+ test.timeout(0)
52
+ test.async = true
53
+
54
+ test.fn = function (done) {
55
+ const doneFn = makeDoneCallableOnce(done)
56
+ recorder.errHandler(err => {
57
+ recorder.session.start('teardown')
58
+ recorder.cleanAsyncErr()
59
+ if (test.throws) {
60
+ // check that test should actually fail
61
+ try {
62
+ assertThrown(err, test.throws)
63
+ event.emit(event.test.passed, test)
64
+ event.emit(event.test.finished, test)
65
+ recorder.add(doneFn)
66
+ return
67
+ } catch (newErr) {
68
+ err = newErr
69
+ }
70
+ }
71
+ test.err = err
72
+ event.emit(event.test.failed, test, err)
73
+ event.emit(event.test.finished, test)
74
+ recorder.add(() => doneFn(err))
75
+ })
76
+
77
+ if (isAsyncFunction(testFn)) {
78
+ event.emit(event.test.started, test)
79
+ testFn
80
+ .call(test, getInjectedArguments(testFn, test))
81
+ .then(() => {
82
+ recorder.add('fire test.passed', () => {
83
+ event.emit(event.test.passed, test)
84
+ event.emit(event.test.finished, test)
85
+ })
86
+ recorder.add('finish test', doneFn)
87
+ })
88
+ .catch(err => {
89
+ recorder.throw(err)
90
+ })
91
+ .finally(() => {
92
+ recorder.catch()
93
+ })
94
+ return
95
+ }
96
+
97
+ try {
98
+ event.emit(event.test.started, test)
99
+ testFn.call(test, getInjectedArguments(testFn, test))
100
+ } catch (err) {
101
+ recorder.throw(err)
102
+ } finally {
103
+ recorder.add('fire test.passed', () => {
104
+ event.emit(event.test.passed, test)
105
+ event.emit(event.test.finished, test)
106
+ })
107
+ recorder.add('finish test', doneFn)
108
+ recorder.catch()
109
+ }
110
+ }
111
+ return test
112
+ }
113
+
114
+ /**
115
+ * Injects arguments to function from controller
116
+ */
117
+ module.exports.injected = function (fn, suite, hookName) {
118
+ return function (done) {
119
+ const doneFn = makeDoneCallableOnce(done)
120
+ const errHandler = err => {
121
+ recorder.session.start('teardown')
122
+ recorder.cleanAsyncErr()
123
+ if (hookName == 'before' || hookName == 'beforeSuite') suiteTestFailedHookError(suite, err, hookName)
124
+ if (hookName === 'after') event.emit(event.test.after, suite)
125
+ if (hookName === 'afterSuite') event.emit(event.suite.after, suite)
126
+ recorder.add(() => doneFn(err))
127
+ }
128
+
129
+ recorder.errHandler(err => {
130
+ errHandler(err)
131
+ })
132
+
133
+ if (!fn) throw new Error('fn is not defined')
134
+
135
+ fireHook(event.hook.started, suite)
136
+
137
+ this.test.body = fn.toString()
138
+
139
+ if (!recorder.isRunning()) {
140
+ recorder.errHandler(err => {
141
+ errHandler(err)
142
+ })
143
+ }
144
+
145
+ const opts = suite.opts || {}
146
+ const retries = opts[`retry${ucfirst(hookName)}`] || 0
147
+
148
+ const currentTest = hookName === 'before' || hookName === 'after' ? suite?.ctx?.currentTest : null
149
+
150
+ promiseRetry(
151
+ async (retry, number) => {
152
+ try {
153
+ recorder.startUnlessRunning()
154
+ await fn.call(this, { ...getInjectedArguments(fn), suite, test: currentTest })
155
+ await recorder.promise().catch(err => retry(err))
156
+ } catch (err) {
157
+ retry(err)
158
+ } finally {
159
+ if (number < retries) {
160
+ recorder.stop()
161
+ recorder.start()
162
+ }
163
+ }
164
+ },
165
+ { retries },
166
+ )
167
+ .then(() => {
168
+ recorder.add('fire hook.passed', () => fireHook(event.hook.passed, suite))
169
+ recorder.add('fire hook.finished', () => fireHook(event.hook.finished, suite))
170
+ recorder.add(`finish ${hookName} hook`, doneFn)
171
+ recorder.catch()
172
+ })
173
+ .catch(e => {
174
+ recorder.throw(e)
175
+ recorder.catch(e => {
176
+ const err = recorder.getAsyncErr() === null ? e : recorder.getAsyncErr()
177
+ errHandler(err)
178
+ })
179
+ recorder.add('fire hook.failed', () => fireHook(event.hook.failed, suite, e))
180
+ recorder.add('fire hook.finished', () => fireHook(event.hook.finished, suite))
181
+ })
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Starts promise chain, so helpers could enqueue their hooks
187
+ */
188
+ module.exports.setup = function (suite) {
189
+ return injectHook(() => {
190
+ recorder.startUnlessRunning()
191
+ event.emit(event.test.before, suite && suite.ctx && suite.ctx.currentTest)
192
+ }, suite)
193
+ }
194
+
195
+ module.exports.teardown = function (suite) {
196
+ return injectHook(() => {
197
+ recorder.startUnlessRunning()
198
+ event.emit(event.test.after, suite && suite.ctx && suite.ctx.currentTest)
199
+ }, suite)
200
+ }
201
+
202
+ module.exports.suiteSetup = function (suite) {
203
+ return injectHook(() => {
204
+ recorder.startUnlessRunning()
205
+ event.emit(event.suite.before, suite)
206
+ }, suite)
207
+ }
208
+
209
+ module.exports.suiteTeardown = function (suite) {
210
+ return injectHook(() => {
211
+ recorder.startUnlessRunning()
212
+ event.emit(event.suite.after, suite)
213
+ }, suite)
214
+ }
215
+
216
+ module.exports.getInjectedArguments = getInjectedArguments
@@ -27,7 +27,7 @@ const addStep = (step, fn) => {
27
27
 
28
28
  const parameterTypeRegistry = new ParameterTypeRegistry()
29
29
 
30
- const matchStep = (step) => {
30
+ const matchStep = step => {
31
31
  for (const stepName in steps) {
32
32
  if (stepName.indexOf('/') === 0) {
33
33
  const regExpArr = stepName.match(/^\/(.*?)\/([gimy]*)$/) || []
@@ -43,7 +43,7 @@ const matchStep = (step) => {
43
43
  const res = expression.match(step)
44
44
  if (res) {
45
45
  const fn = steps[stepName]
46
- fn.params = res.map((arg) => arg.getValue())
46
+ fn.params = res.map(arg => arg.getValue())
47
47
  return fn
48
48
  }
49
49
  }
@@ -58,7 +58,7 @@ const getSteps = () => {
58
58
  return steps
59
59
  }
60
60
 
61
- const defineParameterType = (options) => {
61
+ const defineParameterType = options => {
62
62
  const parameterType = buildParameterType(options)
63
63
  parameterTypeRegistry.defineParameterType(parameterType)
64
64
  }
@@ -0,0 +1,308 @@
1
+ const {
2
+ reporters: { Base },
3
+ } = require('mocha')
4
+ const ms = require('ms')
5
+ const figures = require('figures')
6
+ const event = require('../event')
7
+ const AssertionFailedError = require('../assert/error')
8
+ const output = require('../output')
9
+ const { cloneTest } = require('./test')
10
+ const cursor = Base.cursor
11
+ let currentMetaStep = []
12
+ let codeceptjsEventDispatchersRegistered = false
13
+
14
+ class Cli extends Base {
15
+ constructor(runner, opts) {
16
+ super(runner)
17
+ let level = 0
18
+ this.loadedTests = []
19
+ opts = opts.reporterOptions || opts
20
+ if (opts.steps) level = 1
21
+ if (opts.debug) level = 2
22
+ if (opts.verbose) level = 3
23
+ output.level(level)
24
+ output.print(`CodeceptJS v${require('../codecept').version()} ${output.standWithUkraine()}`)
25
+ output.print(`Using test root "${global.codecept_dir}"`)
26
+
27
+ const showSteps = level >= 1
28
+
29
+ if (level >= 2) {
30
+ const Containter = require('../container')
31
+ output.print(output.styles.debug(`Helpers: ${Object.keys(Containter.helpers()).join(', ')}`))
32
+ output.print(output.styles.debug(`Plugins: ${Object.keys(Containter.plugins()).join(', ')}`))
33
+ }
34
+
35
+ if (level >= 3) {
36
+ process.on('warning', warning => {
37
+ console.log('\nWarning Details:')
38
+ console.log('Name:', warning.name)
39
+ console.log('Message:', warning.message)
40
+ console.log('Stack:', warning.stack)
41
+ console.log('-------------------')
42
+ })
43
+ }
44
+
45
+ runner.on('start', () => {
46
+ console.log()
47
+ })
48
+
49
+ runner.on('suite', suite => {
50
+ output.suite.started(suite)
51
+ })
52
+
53
+ runner.on('fail', test => {
54
+ if (test.ctx.currentTest) {
55
+ this.loadedTests.push(test.ctx.currentTest.uid)
56
+ }
57
+ if (showSteps && test.steps) {
58
+ return output.scenario.failed(test)
59
+ }
60
+ cursor.CR()
61
+ output.test.failed(test)
62
+ })
63
+
64
+ runner.on('pending', test => {
65
+ if (test.parent && test.parent.pending) {
66
+ const suite = test.parent
67
+ const skipInfo = suite.opts.skipInfo || {}
68
+ skipTestConfig(test, skipInfo.message)
69
+ } else {
70
+ skipTestConfig(test, null)
71
+ }
72
+ this.loadedTests.push(test.uid)
73
+ cursor.CR()
74
+ output.test.skipped(test)
75
+ })
76
+
77
+ runner.on('pass', test => {
78
+ if (showSteps && test.steps) {
79
+ return output.scenario.passed(test)
80
+ }
81
+ cursor.CR()
82
+ output.test.passed(test)
83
+ })
84
+
85
+ if (showSteps) {
86
+ runner.on('test', test => {
87
+ currentMetaStep = []
88
+ if (test.steps) {
89
+ output.test.started(test)
90
+ }
91
+ })
92
+
93
+ if (!codeceptjsEventDispatchersRegistered) {
94
+ codeceptjsEventDispatchersRegistered = true
95
+
96
+ event.dispatcher.on(event.bddStep.started, step => {
97
+ output.stepShift = 2
98
+ output.step(step)
99
+ })
100
+
101
+ event.dispatcher.on(event.step.started, step => {
102
+ let processingStep = step
103
+ const metaSteps = []
104
+ let isHidden = false
105
+ while (processingStep.metaStep) {
106
+ metaSteps.unshift(processingStep.metaStep)
107
+ processingStep = processingStep.metaStep
108
+ if (processingStep.collapsed) isHidden = true
109
+ }
110
+ const shift = metaSteps.length
111
+
112
+ for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
113
+ if (currentMetaStep[i] !== metaSteps[i]) {
114
+ output.stepShift = 3 + 2 * i
115
+ if (!metaSteps[i]) continue
116
+ // bdd steps are handled by bddStep.started
117
+ if (metaSteps[i].isBDD()) continue
118
+ output.step(metaSteps[i])
119
+ }
120
+ }
121
+ currentMetaStep = metaSteps
122
+ if (isHidden) return
123
+ output.stepShift = 3 + 2 * shift
124
+ output.step(step)
125
+ })
126
+
127
+ event.dispatcher.on(event.step.finished, () => {
128
+ output.stepShift = 0
129
+ })
130
+ }
131
+ }
132
+
133
+ runner.on('suite end', suite => {
134
+ let skippedCount = 0
135
+ const grep = runner._grep
136
+ for (const test of suite.tests) {
137
+ if (!test.state && !this.loadedTests.includes(test.uid)) {
138
+ if (matchTest(grep, test.title)) {
139
+ if (!test.opts) {
140
+ test.opts = {}
141
+ }
142
+ if (!test.opts.skipInfo) {
143
+ test.opts.skipInfo = {}
144
+ }
145
+ skipTestConfig(test, "Skipped due to failure in 'before' hook")
146
+ output.test.skipped(test)
147
+ skippedCount += 1
148
+ }
149
+ }
150
+ }
151
+
152
+ const container = require('../container')
153
+ container.result().addStats({ pending: skippedCount, tests: skippedCount })
154
+ })
155
+
156
+ runner.on('end', this.result.bind(this))
157
+ }
158
+
159
+ result() {
160
+ const container = require('../container')
161
+ container.result().addStats(this.stats)
162
+ container.result().finish()
163
+
164
+ const stats = container.result().stats
165
+ console.log()
166
+
167
+ // passes
168
+ if (stats.failures) {
169
+ output.print(output.styles.bold('-- FAILURES:'))
170
+ }
171
+
172
+ const failuresLog = []
173
+
174
+ // failures
175
+ if (stats.failures) {
176
+ // append step traces
177
+ this.failures = this.failures.map(test => {
178
+ // we will change the stack trace, so we need to clone the test
179
+ const err = test.err
180
+
181
+ let log = ''
182
+ let originalMessage = err.message
183
+
184
+ if (err instanceof AssertionFailedError) {
185
+ err.message = err.inspect()
186
+ }
187
+
188
+ // multi-line error messages (for Playwright)
189
+ if (err.message && err.message.includes('\n')) {
190
+ const lines = err.message.split('\n')
191
+ const truncatedLines = lines.slice(0, 5)
192
+ if (lines.length > 5) {
193
+ truncatedLines.push('...')
194
+ }
195
+ err.message = truncatedLines.join('\n').replace(/^/gm, ' ').trim()
196
+ }
197
+
198
+ // add new line before the message
199
+ err.message = '\n ' + err.message
200
+
201
+ // explicitly show file with error
202
+ if (test.file) {
203
+ log += `\n${output.styles.basic(figures.circle)} ${output.styles.section('File:')} ${output.styles.basic(test.file)}\n`
204
+ }
205
+
206
+ const steps = test.steps || (test.ctx && test.ctx.test.steps)
207
+
208
+ if (steps && steps.length) {
209
+ let scenarioTrace = ''
210
+ steps
211
+ .reverse()
212
+ .slice(0, 10)
213
+ .forEach(step => {
214
+ const hasFailed = step.status === 'failed'
215
+ let line = `${hasFailed ? output.styles.bold(figures.cross) : figures.tick} ${step.toCode()} ${step.line()}`
216
+ if (hasFailed) line = output.styles.bold(line)
217
+ scenarioTrace += `\n${line}`
218
+ })
219
+ log += `\n${output.styles.basic(figures.circle)} ${output.styles.section('Scenario Steps')}:${scenarioTrace}\n`
220
+ }
221
+
222
+ // display artifacts in debug mode
223
+ if (test?.artifacts && Object.keys(test.artifacts).length) {
224
+ log += `\n${output.styles.basic(figures.circle)} ${output.styles.section('Artifacts:')}`
225
+ for (const artifact of Object.keys(test.artifacts)) {
226
+ log += `\n- ${artifact}: ${test.artifacts[artifact]}`
227
+ }
228
+ }
229
+
230
+ // display metadata
231
+ if (test.meta && Object.keys(test.meta).length) {
232
+ log += `\n\n${output.styles.basic(figures.circle)} ${output.styles.section('Metadata:')}`
233
+ for (const [key, value] of Object.entries(test.meta)) {
234
+ log += `\n- ${key}: ${value}`
235
+ }
236
+ }
237
+
238
+ try {
239
+ let stack = err.stack
240
+ stack = (stack || '').replace(originalMessage, '')
241
+ stack = stack ? stack.split('\n') : []
242
+
243
+ if (stack[0] && stack[0].includes(err.message)) {
244
+ stack.shift()
245
+ }
246
+
247
+ if (stack[0] && stack[0].trim() == 'Error:') {
248
+ stack.shift()
249
+ }
250
+
251
+ if (output.level() < 3) {
252
+ stack = stack.slice(0, 3)
253
+ }
254
+
255
+ err.stack = `${stack.join('\n')}\n\n${output.colors.blue(log)}`
256
+ } catch (e) {
257
+ console.error(e)
258
+ }
259
+
260
+ // we will change the stack trace, so we need to clone the test
261
+ test = cloneTest(test)
262
+ test.err = err
263
+ return test
264
+ })
265
+
266
+ const originalLog = Base.consoleLog
267
+ Base.consoleLog = (...data) => {
268
+ failuresLog.push([...data])
269
+ originalLog(...data)
270
+ }
271
+ Base.list(this.failures)
272
+ Base.consoleLog = originalLog
273
+ console.log()
274
+ }
275
+
276
+ container.result().addFailures(failuresLog)
277
+
278
+ output.result(stats.passes, stats.failures, stats.pending, ms(stats.duration), stats.failedHooks)
279
+
280
+ if (stats.failures && output.level() < 3) {
281
+ output.print(output.styles.debug('Run with --verbose flag to see complete NodeJS stacktrace'))
282
+ }
283
+ }
284
+ }
285
+
286
+ function matchTest(grep, test) {
287
+ if (grep) {
288
+ return grep.test(test)
289
+ }
290
+ return true
291
+ }
292
+
293
+ function skipTestConfig(test, message) {
294
+ if (!test.opts) {
295
+ test.opts = {}
296
+ }
297
+ if (!test.opts.skipInfo) {
298
+ test.opts.skipInfo = {}
299
+ }
300
+ test.opts.skipInfo.message = test.opts.skipInfo.message || message
301
+ test.opts.skipInfo.isFastSkipped = true
302
+ event.emit(event.test.skipped, test)
303
+ test.state = 'skipped'
304
+ }
305
+
306
+ module.exports = function (runner, opts) {
307
+ return new Cli(runner, opts)
308
+ }
@@ -0,0 +1,104 @@
1
+ const Mocha = require('mocha')
2
+ const fsPath = require('path')
3
+ const fs = require('fs')
4
+ const reporter = require('./cli')
5
+ const gherkinParser = require('./gherkin')
6
+ const output = require('../output')
7
+ const ConnectionRefused = require('../helper/errors/ConnectionRefused')
8
+
9
+ const scenarioUi = fsPath.join(__dirname, './ui.js')
10
+
11
+ let mocha
12
+
13
+ class MochaFactory {
14
+ static create(config, opts) {
15
+ mocha = new Mocha(Object.assign(config, opts))
16
+ output.process(opts.child)
17
+ mocha.ui(scenarioUi)
18
+
19
+ Mocha.Runner.prototype.uncaught = function (err) {
20
+ if (err) {
21
+ if (err.toString().indexOf('ECONNREFUSED') >= 0) {
22
+ err = new ConnectionRefused(err)
23
+ }
24
+ output.error(err)
25
+ output.print(err.stack)
26
+ process.exit(1)
27
+ }
28
+ output.error('Uncaught undefined exception')
29
+ process.exit(1)
30
+ }
31
+
32
+ mocha.loadFiles = fn => {
33
+ // load features
34
+ if (mocha.suite.suites.length === 0) {
35
+ mocha.files.filter(file => file.match(/\.feature$/)).forEach(file => mocha.suite.addSuite(gherkinParser(fs.readFileSync(file, 'utf8'), file)))
36
+
37
+ // remove feature files
38
+ mocha.files = mocha.files.filter(file => !file.match(/\.feature$/))
39
+
40
+ Mocha.prototype.loadFiles.call(mocha, fn)
41
+
42
+ // add ids for each test and check uniqueness
43
+ const dupes = []
44
+ let missingFeatureInFile = []
45
+ const seenTests = []
46
+ mocha.suite.eachTest(test => {
47
+ const name = test.fullTitle()
48
+ if (seenTests.includes(test.uid)) {
49
+ dupes.push(name)
50
+ }
51
+ seenTests.push(test.uid)
52
+
53
+ if (name.slice(0, name.indexOf(':')) === '') {
54
+ missingFeatureInFile.push(test.file)
55
+ }
56
+ })
57
+ if (dupes.length) {
58
+ // ideally this should be no-op and throw (breaking change)...
59
+ output.error(`Duplicate test names detected - Feature + Scenario name should be unique:\n${dupes.join('\n')}`)
60
+ }
61
+
62
+ if (missingFeatureInFile.length) {
63
+ missingFeatureInFile = [...new Set(missingFeatureInFile)]
64
+ output.error(`Missing Feature section in:\n${missingFeatureInFile.join('\n')}`)
65
+ }
66
+ }
67
+ }
68
+
69
+ const presetReporter = opts.reporter || config.reporter
70
+ // use standard reporter
71
+ if (!presetReporter) {
72
+ mocha.reporter(reporter, opts)
73
+ return mocha
74
+ }
75
+
76
+ // load custom reporter with options
77
+ const reporterOptions = Object.assign(config.reporterOptions || {})
78
+
79
+ if (opts.reporterOptions !== undefined) {
80
+ opts.reporterOptions.split(',').forEach(opt => {
81
+ const L = opt.split('=')
82
+ if (L.length > 2 || L.length === 0) {
83
+ throw new Error(`invalid reporter option '${opt}'`)
84
+ } else if (L.length === 2) {
85
+ reporterOptions[L[0]] = L[1]
86
+ } else {
87
+ reporterOptions[L[0]] = true
88
+ }
89
+ })
90
+ }
91
+
92
+ const attributes = Object.getOwnPropertyDescriptor(reporterOptions, 'codeceptjs-cli-reporter')
93
+ if (reporterOptions['codeceptjs-cli-reporter'] && attributes) {
94
+ Object.defineProperty(reporterOptions, 'codeceptjs/lib/mocha/cli', attributes)
95
+ delete reporterOptions['codeceptjs-cli-reporter']
96
+ }
97
+
98
+ // custom reporters
99
+ mocha.reporter(presetReporter, reporterOptions)
100
+ return mocha
101
+ }
102
+ }
103
+
104
+ module.exports = MochaFactory