codeceptjs 3.6.10 → 3.7.0-beta.1

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 (105) hide show
  1. package/README.md +81 -110
  2. package/bin/codecept.js +2 -2
  3. package/docs/webapi/clearCookie.mustache +1 -1
  4. package/lib/actor.js +46 -36
  5. package/lib/assert/empty.js +3 -5
  6. package/lib/assert/equal.js +4 -7
  7. package/lib/assert/include.js +4 -6
  8. package/lib/assert/throws.js +2 -4
  9. package/lib/assert/truth.js +2 -2
  10. package/lib/codecept.js +87 -83
  11. package/lib/command/configMigrate.js +2 -4
  12. package/lib/command/definitions.js +5 -25
  13. package/lib/command/generate.js +10 -14
  14. package/lib/command/gherkin/snippets.js +10 -8
  15. package/lib/command/gherkin/steps.js +1 -1
  16. package/lib/command/info.js +1 -3
  17. package/lib/command/init.js +8 -12
  18. package/lib/command/interactive.js +1 -1
  19. package/lib/command/list.js +1 -1
  20. package/lib/command/run-multiple.js +12 -35
  21. package/lib/command/run-workers.js +10 -10
  22. package/lib/command/utils.js +5 -6
  23. package/lib/command/workers/runTests.js +14 -17
  24. package/lib/container.js +327 -237
  25. package/lib/data/context.js +10 -13
  26. package/lib/data/dataScenarioConfig.js +8 -8
  27. package/lib/data/dataTableArgument.js +6 -6
  28. package/lib/data/table.js +5 -11
  29. package/lib/els.js +177 -0
  30. package/lib/event.js +1 -0
  31. package/lib/heal.js +78 -80
  32. package/lib/helper/ApiDataFactory.js +3 -6
  33. package/lib/helper/Appium.js +15 -30
  34. package/lib/helper/FileSystem.js +3 -3
  35. package/lib/helper/GraphQLDataFactory.js +3 -3
  36. package/lib/helper/JSONResponse.js +57 -37
  37. package/lib/helper/Nightmare.js +35 -53
  38. package/lib/helper/Playwright.js +189 -251
  39. package/lib/helper/Protractor.js +54 -77
  40. package/lib/helper/Puppeteer.js +134 -232
  41. package/lib/helper/REST.js +5 -17
  42. package/lib/helper/TestCafe.js +21 -44
  43. package/lib/helper/WebDriver.js +103 -162
  44. package/lib/helper/testcafe/testcafe-utils.js +26 -27
  45. package/lib/listener/artifacts.js +2 -2
  46. package/lib/listener/emptyRun.js +58 -0
  47. package/lib/listener/exit.js +4 -4
  48. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  49. package/lib/listener/{timeout.js → globalTimeout.js} +8 -8
  50. package/lib/listener/helpers.js +15 -15
  51. package/lib/listener/mocha.js +1 -1
  52. package/lib/listener/steps.js +17 -12
  53. package/lib/listener/store.js +12 -0
  54. package/lib/mocha/asyncWrapper.js +204 -0
  55. package/lib/{interfaces → mocha}/bdd.js +3 -3
  56. package/lib/mocha/cli.js +257 -0
  57. package/lib/mocha/factory.js +104 -0
  58. package/lib/{interfaces → mocha}/featureConfig.js +11 -12
  59. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  60. package/lib/mocha/hooks.js +83 -0
  61. package/lib/mocha/index.js +12 -0
  62. package/lib/mocha/inject.js +24 -0
  63. package/lib/{interfaces → mocha}/scenarioConfig.js +10 -6
  64. package/lib/mocha/suite.js +55 -0
  65. package/lib/mocha/test.js +60 -0
  66. package/lib/mocha/types.d.ts +31 -0
  67. package/lib/mocha/ui.js +219 -0
  68. package/lib/output.js +28 -10
  69. package/lib/pause.js +159 -135
  70. package/lib/plugin/autoDelay.js +4 -4
  71. package/lib/plugin/autoLogin.js +6 -7
  72. package/lib/plugin/commentStep.js +1 -1
  73. package/lib/plugin/coverage.js +10 -19
  74. package/lib/plugin/customLocator.js +3 -3
  75. package/lib/plugin/debugErrors.js +2 -2
  76. package/lib/plugin/eachElement.js +1 -1
  77. package/lib/plugin/fakerTransform.js +1 -1
  78. package/lib/plugin/heal.js +6 -9
  79. package/lib/plugin/retryFailedStep.js +4 -4
  80. package/lib/plugin/retryTo.js +2 -2
  81. package/lib/plugin/screenshotOnFail.js +9 -36
  82. package/lib/plugin/selenoid.js +15 -35
  83. package/lib/plugin/stepByStepReport.js +51 -13
  84. package/lib/plugin/stepTimeout.js +4 -11
  85. package/lib/plugin/subtitles.js +4 -4
  86. package/lib/plugin/tryTo.js +1 -1
  87. package/lib/plugin/wdio.js +8 -10
  88. package/lib/recorder.js +142 -121
  89. package/lib/secret.js +1 -1
  90. package/lib/step.js +160 -144
  91. package/lib/store.js +6 -2
  92. package/lib/template/heal.js +2 -11
  93. package/lib/utils.js +224 -216
  94. package/lib/within.js +73 -55
  95. package/lib/workers.js +265 -261
  96. package/package.json +46 -47
  97. package/typings/index.d.ts +172 -184
  98. package/typings/promiseBasedTypes.d.ts +53 -516
  99. package/typings/types.d.ts +127 -587
  100. package/lib/cli.js +0 -256
  101. package/lib/helper/ExpectHelper.js +0 -391
  102. package/lib/helper/SoftExpectHelper.js +0 -381
  103. package/lib/mochaFactory.js +0 -113
  104. package/lib/scenario.js +0 -224
  105. package/lib/ui.js +0 -236
@@ -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,257 @@
1
+ const {
2
+ reporters: { Base },
3
+ } = require('mocha')
4
+ const ms = require('ms')
5
+ const event = require('../event')
6
+ const AssertionFailedError = require('../assert/error')
7
+ const output = require('../output')
8
+
9
+ const cursor = Base.cursor
10
+ let currentMetaStep = []
11
+ let codeceptjsEventDispatchersRegistered = false
12
+
13
+ class Cli extends Base {
14
+ constructor(runner, opts) {
15
+ super(runner)
16
+ let level = 0
17
+ this.loadedTests = []
18
+ opts = opts.reporterOptions || opts
19
+ if (opts.steps) level = 1
20
+ if (opts.debug) level = 2
21
+ if (opts.verbose) level = 3
22
+ output.level(level)
23
+ output.print(`CodeceptJS v${require('../codecept').version()} ${output.standWithUkraine()}`)
24
+ output.print(`Using test root "${global.codecept_dir}"`)
25
+
26
+ const showSteps = level >= 1
27
+
28
+ if (level >= 2) {
29
+ const Containter = require('../container')
30
+ output.print(output.styles.debug(`Helpers: ${Object.keys(Containter.helpers()).join(', ')}`))
31
+ output.print(output.styles.debug(`Plugins: ${Object.keys(Containter.plugins()).join(', ')}`))
32
+ }
33
+
34
+ runner.on('start', () => {
35
+ console.log()
36
+ })
37
+
38
+ runner.on('suite', suite => {
39
+ output.suite.started(suite)
40
+ })
41
+
42
+ runner.on('fail', test => {
43
+ if (test.ctx.currentTest) {
44
+ this.loadedTests.push(test.ctx.currentTest.uid)
45
+ }
46
+ if (showSteps && test.steps) {
47
+ return output.scenario.failed(test)
48
+ }
49
+ cursor.CR()
50
+ output.test.failed(test)
51
+ })
52
+
53
+ runner.on('pending', test => {
54
+ if (test.parent && test.parent.pending) {
55
+ const suite = test.parent
56
+ const skipInfo = suite.opts.skipInfo || {}
57
+ skipTestConfig(test, skipInfo.message)
58
+ } else {
59
+ skipTestConfig(test, null)
60
+ }
61
+ this.loadedTests.push(test.uid)
62
+ cursor.CR()
63
+ output.test.skipped(test)
64
+ })
65
+
66
+ runner.on('pass', test => {
67
+ if (showSteps && test.steps) {
68
+ return output.scenario.passed(test)
69
+ }
70
+ cursor.CR()
71
+ output.test.passed(test)
72
+ })
73
+
74
+ if (showSteps) {
75
+ runner.on('test', test => {
76
+ currentMetaStep = []
77
+ if (test.steps) {
78
+ output.test.started(test)
79
+ }
80
+ })
81
+
82
+ if (!codeceptjsEventDispatchersRegistered) {
83
+ codeceptjsEventDispatchersRegistered = true
84
+
85
+ event.dispatcher.on(event.bddStep.started, step => {
86
+ output.stepShift = 2
87
+ output.step(step)
88
+ })
89
+
90
+ event.dispatcher.on(event.step.started, step => {
91
+ let processingStep = step
92
+ const metaSteps = []
93
+ while (processingStep.metaStep) {
94
+ metaSteps.unshift(processingStep.metaStep)
95
+ processingStep = processingStep.metaStep
96
+ }
97
+ const shift = metaSteps.length
98
+
99
+ for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
100
+ if (currentMetaStep[i] !== metaSteps[i]) {
101
+ output.stepShift = 3 + 2 * i
102
+ if (!metaSteps[i]) continue
103
+ // bdd steps are handled by bddStep.started
104
+ if (metaSteps[i].isBDD()) continue
105
+ output.step(metaSteps[i])
106
+ }
107
+ }
108
+ currentMetaStep = metaSteps
109
+ output.stepShift = 3 + 2 * shift
110
+ if (step.helper.constructor.name !== 'ExpectHelper') {
111
+ output.step(step)
112
+ }
113
+ })
114
+
115
+ event.dispatcher.on(event.step.finished, () => {
116
+ output.stepShift = 0
117
+ })
118
+ }
119
+ }
120
+
121
+ runner.on('suite end', suite => {
122
+ let skippedCount = 0
123
+ const grep = runner._grep
124
+ for (const test of suite.tests) {
125
+ if (!test.state && !this.loadedTests.includes(test.uid)) {
126
+ if (matchTest(grep, test.title)) {
127
+ if (!test.opts) {
128
+ test.opts = {}
129
+ }
130
+ if (!test.opts.skipInfo) {
131
+ test.opts.skipInfo = {}
132
+ }
133
+ skipTestConfig(test, "Skipped due to failure in 'before' hook")
134
+ output.test.skipped(test)
135
+ skippedCount += 1
136
+ }
137
+ }
138
+ }
139
+
140
+ this.stats.pending += skippedCount
141
+ this.stats.tests += skippedCount
142
+ })
143
+
144
+ runner.on('end', this.result.bind(this))
145
+ }
146
+
147
+ result() {
148
+ const stats = this.stats
149
+ stats.failedHooks = 0
150
+ console.log()
151
+
152
+ // passes
153
+ if (stats.failures) {
154
+ output.print(output.styles.bold('-- FAILURES:'))
155
+ }
156
+
157
+ const failuresLog = []
158
+
159
+ // failures
160
+ if (stats.failures) {
161
+ // append step traces
162
+ this.failures.map(test => {
163
+ const err = test.err
164
+
165
+ let log = ''
166
+
167
+ if (err instanceof AssertionFailedError) {
168
+ err.message = err.inspect()
169
+ }
170
+
171
+ const steps = test.steps || (test.ctx && test.ctx.test.steps)
172
+
173
+ if (steps && steps.length) {
174
+ let scenarioTrace = ''
175
+ steps.reverse().forEach(step => {
176
+ const line = `- ${step.toCode()} ${step.line()}`
177
+ // if (step.status === 'failed') line = '' + line;
178
+ scenarioTrace += `\n${line}`
179
+ })
180
+ log += `${output.styles.bold('Scenario Steps')}:${scenarioTrace}\n`
181
+ }
182
+
183
+ // display artifacts in debug mode
184
+ if (test?.artifacts && Object.keys(test.artifacts).length) {
185
+ log += `\n${output.styles.bold('Artifacts:')}`
186
+ for (const artifact of Object.keys(test.artifacts)) {
187
+ log += `\n- ${artifact}: ${test.artifacts[artifact]}`
188
+ }
189
+ }
190
+
191
+ try {
192
+ let stack = err.stack ? err.stack.split('\n') : []
193
+ if (stack[0] && stack[0].includes(err.message)) {
194
+ stack.shift()
195
+ }
196
+
197
+ if (output.level() < 3) {
198
+ stack = stack.slice(0, 3)
199
+ }
200
+
201
+ err.stack = `${stack.join('\n')}\n\n${output.colors.blue(log)}`
202
+
203
+ // clone err object so stack trace adjustments won't affect test other reports
204
+ test.err = err
205
+ return test
206
+ } catch (e) {
207
+ throw Error(e)
208
+ }
209
+ })
210
+
211
+ const originalLog = Base.consoleLog
212
+ Base.consoleLog = (...data) => {
213
+ failuresLog.push([...data])
214
+ originalLog(...data)
215
+ }
216
+ Base.list(this.failures)
217
+ Base.consoleLog = originalLog
218
+ console.log()
219
+ }
220
+
221
+ this.failures.forEach(failure => {
222
+ if (failure.constructor.name === 'Hook') {
223
+ stats.failedHooks += 1
224
+ }
225
+ })
226
+ event.emit(event.all.failures, { failuresLog, stats })
227
+ output.result(stats.passes, stats.failures, stats.pending, ms(stats.duration), stats.failedHooks)
228
+
229
+ if (stats.failures && output.level() < 3) {
230
+ output.print(output.styles.debug('Run with --verbose flag to see complete NodeJS stacktrace'))
231
+ }
232
+ }
233
+ }
234
+
235
+ function matchTest(grep, test) {
236
+ if (grep) {
237
+ return grep.test(test)
238
+ }
239
+ return true
240
+ }
241
+
242
+ function skipTestConfig(test, message) {
243
+ if (!test.opts) {
244
+ test.opts = {}
245
+ }
246
+ if (!test.opts.skipInfo) {
247
+ test.opts.skipInfo = {}
248
+ }
249
+ test.opts.skipInfo.message = test.opts.skipInfo.message || message
250
+ test.opts.skipInfo.isFastSkipped = true
251
+ event.emit(event.test.skipped, test)
252
+ test.state = 'skipped'
253
+ }
254
+
255
+ module.exports = function (runner, opts) {
256
+ return new Cli(runner, opts)
257
+ }
@@ -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
@@ -1,11 +1,14 @@
1
- /** @class */
1
+ /**
2
+ * Configuration for a Feature.
3
+ * Can inject values and add custom configuration.
4
+ */
2
5
  class FeatureConfig {
3
6
  constructor(suite) {
4
7
  this.suite = suite
5
8
  }
6
9
 
7
10
  /**
8
- * Retry this suite for x times
11
+ * Retry this test for number of times
9
12
  *
10
13
  * @param {number} retries
11
14
  * @returns {this}
@@ -16,15 +19,11 @@ class FeatureConfig {
16
19
  }
17
20
 
18
21
  /**
19
- * Set timeout for this suite
22
+ * Set timeout for this test
20
23
  * @param {number} timeout
21
24
  * @returns {this}
22
- * @deprecated
23
25
  */
24
26
  timeout(timeout) {
25
- console.log(`Feature('${this.suite.title}').timeout(${timeout}) is deprecated!`)
26
- console.log(`Please use Feature('${this.suite.title}', { timeout: ${timeout / 1000} }) instead`)
27
- console.log('Timeout should be set in seconds')
28
27
  this.suite.timeout(timeout)
29
28
  return this
30
29
  }
@@ -32,8 +31,9 @@ class FeatureConfig {
32
31
  /**
33
32
  * Configures a helper.
34
33
  * Helper name can be omitted and values will be applied to first helper.
35
- * @param {string | Object<string, *>} helper
36
- * @param {Object<string, *>} [obj]
34
+ *
35
+ * @param {string|number} helper
36
+ * @param {*} obj
37
37
  * @returns {this}
38
38
  */
39
39
  config(helper, obj) {
@@ -57,9 +57,8 @@ class FeatureConfig {
57
57
  * @returns {this}
58
58
  */
59
59
  tag(tagName) {
60
- if (tagName[0] !== '@') {
61
- tagName = `@${tagName}`
62
- }
60
+ if (tagName[0] !== '@') tagName = `@${tagName}`
61
+ if (!this.suite.tags) this.suite.tags = []
63
62
  this.suite.tags.push(tagName)
64
63
  this.suite.title = `${this.suite.title.trim()} ${tagName}`
65
64
  return this
@@ -1,11 +1,13 @@
1
1
  const Gherkin = require('@cucumber/gherkin')
2
2
  const Messages = require('@cucumber/messages')
3
- const { Context, Suite, Test } = require('mocha')
3
+ const { Context, Suite } = require('mocha')
4
4
  const debug = require('debug')('codeceptjs:bdd')
5
5
 
6
+ const { enhanceMochaSuite } = require('./suite')
7
+ const { createTest } = require('./test')
6
8
  const { matchStep } = require('./bdd')
7
9
  const event = require('../event')
8
- const scenario = require('../scenario')
10
+ const { injected, setup, teardown, suiteSetup, suiteTeardown } = require('./asyncWrapper')
9
11
  const Step = require('../step')
10
12
  const DataTableArgument = require('../data/dataTableArgument')
11
13
  const transform = require('../transform')
@@ -28,7 +30,8 @@ module.exports = (text, file) => {
28
30
  throw new Error(`No 'Features' available in Gherkin '${file}' provided!`)
29
31
  }
30
32
  const suite = new Suite(ast.feature.name, new Context())
31
- const tags = ast.feature.tags.map((t) => t.name)
33
+ enhanceMochaSuite(suite)
34
+ const tags = ast.feature.tags.map(t => t.name)
32
35
  suite.title = `${suite.title} ${tags.join(' ')}`.trim()
33
36
  suite.tags = tags || []
34
37
  suite.comment = ast.feature.description
@@ -36,17 +39,17 @@ module.exports = (text, file) => {
36
39
  suite.file = file
37
40
  suite.timeout(0)
38
41
 
39
- suite.beforeEach('codeceptjs.before', () => scenario.setup(suite))
40
- suite.afterEach('codeceptjs.after', () => scenario.teardown(suite))
41
- suite.beforeAll('codeceptjs.beforeSuite', () => scenario.suiteSetup(suite))
42
- suite.afterAll('codeceptjs.afterSuite', () => scenario.suiteTeardown(suite))
42
+ suite.beforeEach('codeceptjs.before', () => setup(suite))
43
+ suite.afterEach('codeceptjs.after', () => teardown(suite))
44
+ suite.beforeAll('codeceptjs.beforeSuite', () => suiteSetup(suite))
45
+ suite.afterAll('codeceptjs.afterSuite', () => suiteTeardown(suite))
43
46
 
44
- const runSteps = async (steps) => {
47
+ const runSteps = async steps => {
45
48
  for (const step of steps) {
46
49
  const metaStep = new Step.MetaStep(null, step.text)
47
50
  metaStep.actor = step.keyword.trim()
48
51
  let helperStep
49
- const setMetaStep = (step) => {
52
+ const setMetaStep = step => {
50
53
  helperStep = step
51
54
  if (step.metaStep) {
52
55
  if (step.metaStep === metaStep) {
@@ -100,18 +103,13 @@ module.exports = (text, file) => {
100
103
  if (child.background) {
101
104
  suite.beforeEach(
102
105
  'Before',
103
- scenario.injected(async () => runSteps(child.background.steps), suite, 'before'),
106
+ injected(async () => runSteps(child.background.steps), suite, 'before'),
104
107
  )
105
108
  continue
106
109
  }
107
- if (
108
- child.scenario &&
109
- (currentLanguage
110
- ? child.scenario.keyword === currentLanguage.contexts.ScenarioOutline
111
- : child.scenario.keyword === 'Scenario Outline')
112
- ) {
110
+ if (child.scenario && (currentLanguage ? child.scenario.keyword === currentLanguage.contexts.ScenarioOutline : child.scenario.keyword === 'Scenario Outline')) {
113
111
  for (const examples of child.scenario.examples) {
114
- const fields = examples.tableHeader.cells.map((c) => c.value)
112
+ const fields = examples.tableHeader.cells.map(c => c.value)
115
113
  for (const example of examples.tableBody) {
116
114
  let exampleSteps = [...child.scenario.steps]
117
115
  const current = {}
@@ -120,13 +118,13 @@ module.exports = (text, file) => {
120
118
  const value = transform('gherkin.examples', example.cells[index].value)
121
119
  example.cells[index].value = value
122
120
  current[placeholder] = value
123
- exampleSteps = exampleSteps.map((step) => {
121
+ exampleSteps = exampleSteps.map(step => {
124
122
  step = { ...step }
125
123
  step.text = step.text.split(`<${placeholder}>`).join(value)
126
124
  return step
127
125
  })
128
126
  }
129
- const tags = child.scenario.tags.map((t) => t.name).concat(examples.tags.map((t) => t.name))
127
+ const tags = child.scenario.tags.map(t => t.name).concat(examples.tags.map(t => t.name))
130
128
  let title = `${child.scenario.name} ${JSON.stringify(current)} ${tags.join(' ')}`.trim()
131
129
 
132
130
  for (const [key, value] of Object.entries(current)) {
@@ -135,22 +133,22 @@ module.exports = (text, file) => {
135
133
  }
136
134
  }
137
135
 
138
- const test = new Test(title, async () => runSteps(addExampleInTable(exampleSteps, current)))
136
+ const test = createTest(title, async () => runSteps(addExampleInTable(exampleSteps, current)))
137
+ test.addToSuite(suite)
139
138
  test.tags = suite.tags.concat(tags)
140
139
  test.file = file
141
- suite.addTest(scenario.test(test))
142
140
  }
143
141
  }
144
142
  continue
145
143
  }
146
144
 
147
145
  if (child.scenario) {
148
- const tags = child.scenario.tags.map((t) => t.name)
146
+ const tags = child.scenario.tags.map(t => t.name)
149
147
  const title = `${child.scenario.name} ${tags.join(' ')}`.trim()
150
- const test = new Test(title, async () => runSteps(child.scenario.steps))
148
+ const test = createTest(title, async () => runSteps(child.scenario.steps))
149
+ test.addToSuite(suite)
151
150
  test.tags = suite.tags.concat(tags)
152
151
  test.file = file
153
- suite.addTest(scenario.test(test))
154
152
  }
155
153
  }
156
154
 
@@ -162,8 +160,8 @@ function transformTable(table) {
162
160
  for (const id in table.rows) {
163
161
  const cells = table.rows[id].cells
164
162
  str += cells
165
- .map((c) => c.value)
166
- .map((c) => c.padEnd(15))
163
+ .map(c => c.value)
164
+ .map(c => c.padEnd(15))
167
165
  .join(' | ')
168
166
  str += '\n'
169
167
  }
@@ -172,12 +170,12 @@ function transformTable(table) {
172
170
  function addExampleInTable(exampleSteps, placeholders) {
173
171
  const steps = JSON.parse(JSON.stringify(exampleSteps))
174
172
  for (const placeholder in placeholders) {
175
- steps.map((step) => {
173
+ steps.map(step => {
176
174
  step = { ...step }
177
175
  if (step.dataTable) {
178
176
  for (const id in step.dataTable.rows) {
179
177
  const cells = step.dataTable.rows[id].cells
180
- cells.map((c) => (c.value = c.value.replace(`<${placeholder}>`, placeholders[placeholder])))
178
+ cells.map(c => (c.value = c.value.replace(`<${placeholder}>`, placeholders[placeholder])))
181
179
  }
182
180
  }
183
181
  return step
@@ -0,0 +1,83 @@
1
+ const event = require('../event')
2
+
3
+ class Hook {
4
+ constructor(context, error) {
5
+ this.suite = context.suite
6
+ this.test = context.test
7
+ this.runnable = context?.ctx?.test
8
+ this.ctx = context.ctx
9
+ this.error = error
10
+ }
11
+
12
+ get hookName() {
13
+ return this.constructor.name.replace('Hook', '')
14
+ }
15
+
16
+ toString() {
17
+ return this.hookName
18
+ }
19
+
20
+ toCode() {
21
+ return this.toString() + '()'
22
+ }
23
+
24
+ retry(n) {
25
+ this.suite.opts[`retry${this.hookName}`] = n
26
+ }
27
+
28
+ get title() {
29
+ return this.ctx?.test?.title || this.name
30
+ }
31
+
32
+ get name() {
33
+ return this.constructor.name
34
+ }
35
+ }
36
+
37
+ class BeforeHook extends Hook {}
38
+
39
+ class AfterHook extends Hook {}
40
+
41
+ class BeforeSuiteHook extends Hook {}
42
+
43
+ class AfterSuiteHook extends Hook {}
44
+
45
+ function fireHook(eventType, suite, error) {
46
+ const hook = suite.ctx?.test?.title?.match(/"([^"]*)"/)[1]
47
+ switch (hook) {
48
+ case 'before each':
49
+ event.emit(eventType, new BeforeHook(suite))
50
+ break
51
+ case 'after each':
52
+ event.emit(eventType, new AfterHook(suite, error))
53
+ break
54
+ case 'before all':
55
+ event.emit(eventType, new BeforeSuiteHook(suite))
56
+ break
57
+ case 'after all':
58
+ event.emit(eventType, new AfterSuiteHook(suite, error))
59
+ break
60
+ default:
61
+ event.emit(eventType, suite, error)
62
+ }
63
+ }
64
+
65
+ class HookConfig {
66
+ constructor(hook) {
67
+ this.hook = hook
68
+ }
69
+
70
+ retry(n) {
71
+ this.hook.retry(n)
72
+ return this
73
+ }
74
+ }
75
+
76
+ module.exports = {
77
+ BeforeHook,
78
+ AfterHook,
79
+ BeforeSuiteHook,
80
+ AfterSuiteHook,
81
+ fireHook,
82
+ HookConfig,
83
+ }
@@ -0,0 +1,12 @@
1
+ const Suite = require('mocha/lib/suite')
2
+ const Test = require('mocha/lib/test')
3
+ const { BeforeHook, AfterHook, BeforeSuiteHook, AfterSuiteHook } = require('./hooks')
4
+
5
+ module.exports = {
6
+ Suite,
7
+ Test,
8
+ BeforeHook,
9
+ AfterHook,
10
+ BeforeSuiteHook,
11
+ AfterSuiteHook,
12
+ }