codeceptjs 4.0.0-beta.4 → 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 (150) 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 +139 -87
  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/generate.js +10 -14
  17. package/lib/command/gherkin/snippets.js +75 -73
  18. package/lib/command/gherkin/steps.js +1 -1
  19. package/lib/command/info.js +42 -8
  20. package/lib/command/init.js +13 -12
  21. package/lib/command/interactive.js +10 -2
  22. package/lib/command/list.js +1 -1
  23. package/lib/command/run-multiple/chunk.js +48 -45
  24. package/lib/command/run-multiple.js +12 -35
  25. package/lib/command/run-workers.js +21 -58
  26. package/lib/command/utils.js +5 -6
  27. package/lib/command/workers/runTests.js +262 -220
  28. package/lib/container.js +386 -238
  29. package/lib/data/context.js +10 -13
  30. package/lib/data/dataScenarioConfig.js +8 -8
  31. package/lib/data/dataTableArgument.js +6 -6
  32. package/lib/data/table.js +5 -11
  33. package/lib/effects.js +223 -0
  34. package/lib/element/WebElement.js +327 -0
  35. package/lib/els.js +158 -0
  36. package/lib/event.js +21 -17
  37. package/lib/heal.js +88 -80
  38. package/lib/helper/AI.js +2 -1
  39. package/lib/helper/ApiDataFactory.js +3 -6
  40. package/lib/helper/Appium.js +47 -51
  41. package/lib/helper/FileSystem.js +3 -3
  42. package/lib/helper/GraphQLDataFactory.js +3 -3
  43. package/lib/helper/JSONResponse.js +75 -37
  44. package/lib/helper/Mochawesome.js +31 -9
  45. package/lib/helper/Nightmare.js +35 -53
  46. package/lib/helper/Playwright.js +262 -267
  47. package/lib/helper/Protractor.js +54 -77
  48. package/lib/helper/Puppeteer.js +246 -260
  49. package/lib/helper/REST.js +5 -17
  50. package/lib/helper/TestCafe.js +21 -44
  51. package/lib/helper/WebDriver.js +151 -170
  52. package/lib/helper/extras/Popup.js +22 -22
  53. package/lib/helper/testcafe/testcafe-utils.js +26 -27
  54. package/lib/listener/emptyRun.js +55 -0
  55. package/lib/listener/exit.js +7 -10
  56. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  57. package/lib/listener/globalTimeout.js +165 -0
  58. package/lib/listener/helpers.js +15 -15
  59. package/lib/listener/mocha.js +1 -1
  60. package/lib/listener/result.js +12 -0
  61. package/lib/listener/retryEnhancer.js +85 -0
  62. package/lib/listener/steps.js +32 -18
  63. package/lib/listener/store.js +20 -0
  64. package/lib/mocha/asyncWrapper.js +231 -0
  65. package/lib/{interfaces → mocha}/bdd.js +3 -3
  66. package/lib/mocha/cli.js +308 -0
  67. package/lib/mocha/factory.js +104 -0
  68. package/lib/{interfaces → mocha}/featureConfig.js +32 -12
  69. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  70. package/lib/mocha/hooks.js +112 -0
  71. package/lib/mocha/index.js +12 -0
  72. package/lib/mocha/inject.js +29 -0
  73. package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
  74. package/lib/mocha/suite.js +82 -0
  75. package/lib/mocha/test.js +181 -0
  76. package/lib/mocha/types.d.ts +42 -0
  77. package/lib/mocha/ui.js +232 -0
  78. package/lib/output.js +82 -62
  79. package/lib/pause.js +160 -138
  80. package/lib/plugin/analyze.js +396 -0
  81. package/lib/plugin/auth.js +435 -0
  82. package/lib/plugin/autoDelay.js +8 -8
  83. package/lib/plugin/autoLogin.js +3 -338
  84. package/lib/plugin/commentStep.js +6 -1
  85. package/lib/plugin/coverage.js +10 -19
  86. package/lib/plugin/customLocator.js +3 -3
  87. package/lib/plugin/customReporter.js +52 -0
  88. package/lib/plugin/eachElement.js +1 -1
  89. package/lib/plugin/fakerTransform.js +1 -1
  90. package/lib/plugin/heal.js +36 -9
  91. package/lib/plugin/htmlReporter.js +1947 -0
  92. package/lib/plugin/pageInfo.js +140 -0
  93. package/lib/plugin/retryFailedStep.js +17 -18
  94. package/lib/plugin/retryTo.js +2 -113
  95. package/lib/plugin/screenshotOnFail.js +17 -58
  96. package/lib/plugin/selenoid.js +15 -35
  97. package/lib/plugin/standardActingHelpers.js +4 -1
  98. package/lib/plugin/stepByStepReport.js +56 -17
  99. package/lib/plugin/stepTimeout.js +5 -12
  100. package/lib/plugin/subtitles.js +4 -4
  101. package/lib/plugin/tryTo.js +3 -102
  102. package/lib/plugin/wdio.js +8 -10
  103. package/lib/recorder.js +155 -124
  104. package/lib/rerun.js +43 -42
  105. package/lib/result.js +161 -0
  106. package/lib/secret.js +1 -1
  107. package/lib/step/base.js +239 -0
  108. package/lib/step/comment.js +10 -0
  109. package/lib/step/config.js +50 -0
  110. package/lib/step/func.js +46 -0
  111. package/lib/step/helper.js +50 -0
  112. package/lib/step/meta.js +99 -0
  113. package/lib/step/record.js +74 -0
  114. package/lib/step/retry.js +11 -0
  115. package/lib/step/section.js +55 -0
  116. package/lib/step.js +21 -332
  117. package/lib/steps.js +50 -0
  118. package/lib/store.js +37 -5
  119. package/lib/template/heal.js +2 -11
  120. package/lib/test-server.js +323 -0
  121. package/lib/timeout.js +66 -0
  122. package/lib/utils.js +351 -218
  123. package/lib/within.js +75 -55
  124. package/lib/workerStorage.js +2 -1
  125. package/lib/workers.js +386 -276
  126. package/package.json +76 -70
  127. package/translations/de-DE.js +4 -3
  128. package/translations/fr-FR.js +4 -3
  129. package/translations/index.js +1 -0
  130. package/translations/it-IT.js +4 -3
  131. package/translations/ja-JP.js +4 -3
  132. package/translations/nl-NL.js +76 -0
  133. package/translations/pl-PL.js +4 -3
  134. package/translations/pt-BR.js +4 -3
  135. package/translations/ru-RU.js +4 -3
  136. package/translations/utils.js +9 -0
  137. package/translations/zh-CN.js +4 -3
  138. package/translations/zh-TW.js +4 -3
  139. package/typings/index.d.ts +188 -186
  140. package/typings/promiseBasedTypes.d.ts +18 -705
  141. package/typings/types.d.ts +301 -804
  142. package/lib/cli.js +0 -256
  143. package/lib/helper/ExpectHelper.js +0 -391
  144. package/lib/helper/SoftExpectHelper.js +0 -381
  145. package/lib/listener/artifacts.js +0 -19
  146. package/lib/listener/timeout.js +0 -109
  147. package/lib/mochaFactory.js +0 -113
  148. package/lib/plugin/debugErrors.js +0 -67
  149. package/lib/scenario.js +0 -224
  150. package/lib/ui.js +0 -236
@@ -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,30 @@
1
- /** @class */
1
+ /**
2
+ * Configuration for a Feature.
3
+ * Can inject values and add custom configuration.
4
+ */
2
5
  class FeatureConfig {
6
+ /**
7
+ * @param {CodeceptJS.Suite} suite
8
+ */
3
9
  constructor(suite) {
4
10
  this.suite = suite
5
11
  }
6
12
 
7
13
  /**
8
- * Retry this suite for x times
14
+ * Set metadata for this suite
15
+ * @param {string} key
16
+ * @param {string} value
17
+ * @returns {this}
18
+ */
19
+ meta(key, value) {
20
+ this.suite.tests.forEach(test => {
21
+ test.meta[key] = value
22
+ })
23
+ return this
24
+ }
25
+
26
+ /**
27
+ * Retry this test for number of times
9
28
  *
10
29
  * @param {number} retries
11
30
  * @returns {this}
@@ -16,24 +35,26 @@ class FeatureConfig {
16
35
  }
17
36
 
18
37
  /**
19
- * Set timeout for this suite
38
+ * Set timeout for this test
20
39
  * @param {number} timeout
21
40
  * @returns {this}
22
- * @deprecated
23
41
  */
24
42
  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
43
  this.suite.timeout(timeout)
29
44
  return this
30
45
  }
31
46
 
47
+ /**
48
+ * @callback FeatureConfigCallback
49
+ * @param {CodeceptJS.Suite} suite
50
+ * @returns {Object<string, any>}
51
+ */
52
+
32
53
  /**
33
54
  * Configures a helper.
34
55
  * Helper name can be omitted and values will be applied to first helper.
35
- * @param {string | Object<string, *>} helper
36
- * @param {Object<string, *>} [obj]
56
+ * @param {string | Object<string, any> | FeatureConfigCallback} helper
57
+ * @param {Object<string, any>} [obj]
37
58
  * @returns {this}
38
59
  */
39
60
  config(helper, obj) {
@@ -57,9 +78,8 @@ class FeatureConfig {
57
78
  * @returns {this}
58
79
  */
59
80
  tag(tagName) {
60
- if (tagName[0] !== '@') {
61
- tagName = `@${tagName}`
62
- }
81
+ if (tagName[0] !== '@') tagName = `@${tagName}`
82
+ if (!this.suite.tags) this.suite.tags = []
63
83
  this.suite.tags.push(tagName)
64
84
  this.suite.title = `${this.suite.title.trim()} ${tagName}`
65
85
  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 ? currentLanguage.contexts.ScenarioOutline === child.scenario.keyword : 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,112 @@
1
+ const event = require('../event')
2
+ const { serializeError } = require('../utils')
3
+ // const { serializeTest } = require('./test')
4
+
5
+ /**
6
+ * Represents a test hook in the testing framework
7
+ * @class
8
+ * @property {Object} suite - The test suite this hook belongs to
9
+ * @property {Object} test - The test object associated with this hook
10
+ * @property {Object} runnable - The current test being executed
11
+ * @property {Object} ctx - The context object
12
+ * @property {Error|null} err - The error that occurred during hook execution, if any
13
+ */
14
+ class Hook {
15
+ /**
16
+ * Creates a new Hook instance
17
+ * @param {Object} context - The context object containing suite and test information
18
+ * @param {Object} context.suite - The test suite
19
+ * @param {Object} context.test - The test object
20
+ * @param {Object} context.ctx - The context object
21
+ * @param {Error} error - The error object if hook execution failed
22
+ */
23
+ constructor(context, error) {
24
+ this.suite = context.suite
25
+ this.test = context.test
26
+ this.runnable = context?.ctx?.test
27
+ this.ctx = context.ctx
28
+ this.err = error
29
+ }
30
+
31
+ get hookName() {
32
+ return this.constructor.name.replace('Hook', '')
33
+ }
34
+
35
+ simplify() {
36
+ return {
37
+ hookName: this.hookName,
38
+ title: this.title,
39
+ // test: this.test ? serializeTest(this.test) : null,
40
+ // suite: this.suite ? serializeSuite(this.suite) : null,
41
+ error: this.err ? serializeError(this.err) : null,
42
+ }
43
+ }
44
+
45
+ toString() {
46
+ return this.hookName
47
+ }
48
+
49
+ toCode() {
50
+ return this.toString() + '()'
51
+ }
52
+
53
+ retry(n) {
54
+ this.suite.opts[`retry${this.hookName}`] = n
55
+ }
56
+
57
+ get title() {
58
+ return this.ctx?.test?.title || this.name
59
+ }
60
+
61
+ get name() {
62
+ return this.constructor.name
63
+ }
64
+ }
65
+
66
+ class BeforeHook extends Hook {}
67
+
68
+ class AfterHook extends Hook {}
69
+
70
+ class BeforeSuiteHook extends Hook {}
71
+
72
+ class AfterSuiteHook extends Hook {}
73
+
74
+ function fireHook(eventType, suite, error) {
75
+ const hook = suite.ctx?.test?.title?.match(/"([^"]*)"/)[1]
76
+ switch (hook) {
77
+ case 'before each':
78
+ event.emit(eventType, new BeforeHook(suite, error))
79
+ break
80
+ case 'after each':
81
+ event.emit(eventType, new AfterHook(suite, error))
82
+ break
83
+ case 'before all':
84
+ event.emit(eventType, new BeforeSuiteHook(suite, error))
85
+ break
86
+ case 'after all':
87
+ event.emit(eventType, new AfterSuiteHook(suite, error))
88
+ break
89
+ default:
90
+ event.emit(eventType, suite, error)
91
+ }
92
+ }
93
+
94
+ class HookConfig {
95
+ constructor(hook) {
96
+ this.hook = hook
97
+ }
98
+
99
+ retry(n) {
100
+ this.hook.retry(n)
101
+ return this
102
+ }
103
+ }
104
+
105
+ module.exports = {
106
+ BeforeHook,
107
+ AfterHook,
108
+ BeforeSuiteHook,
109
+ AfterSuiteHook,
110
+ fireHook,
111
+ HookConfig,
112
+ }
@@ -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
+ }
@@ -0,0 +1,29 @@
1
+ const parser = require('../parser')
2
+
3
+ const getInjectedArguments = (fn, test) => {
4
+ const container = require('../container')
5
+ const testArgs = {}
6
+ const params = parser.getParams(fn) || []
7
+ const objects = container.support()
8
+
9
+ for (const key of params) {
10
+ testArgs[key] = {}
11
+ if (test && test.inject && test.inject[key]) {
12
+ // @FIX: need fix got inject
13
+ testArgs[key] = test.inject[key]
14
+ continue
15
+ }
16
+ if (!objects[key]) {
17
+ throw new Error(`Object of type ${key} is not defined in container`)
18
+ }
19
+ testArgs[key] = container.support(key)
20
+ }
21
+
22
+ if (test) {
23
+ testArgs.suite = test?.parent
24
+ testArgs.test = test
25
+ }
26
+ return testArgs
27
+ }
28
+
29
+ module.exports.getInjectedArguments = getInjectedArguments
@@ -1,5 +1,10 @@
1
+ const { isAsyncFunction } = require('../utils')
2
+
1
3
  /** @class */
2
4
  class ScenarioConfig {
5
+ /**
6
+ * @param {CodeceptJS.Test} test
7
+ */
3
8
  constructor(test) {
4
9
  this.test = test
5
10
  }
@@ -40,6 +45,17 @@ class ScenarioConfig {
40
45
  return this
41
46
  }
42
47
 
48
+ /**
49
+ * Set metadata for this test
50
+ * @param {string} key
51
+ * @param {string} value
52
+ * @returns {this}
53
+ */
54
+ meta(key, value) {
55
+ this.test.meta[key] = value
56
+ return this
57
+ }
58
+
43
59
  /**
44
60
  * Set timeout for this test
45
61
  * @param {number} timeout
@@ -64,24 +80,32 @@ class ScenarioConfig {
64
80
  return this
65
81
  }
66
82
 
83
+ /**
84
+ * @callback ScenarioConfigCallback
85
+ * @param {CodeceptJS.Test} test
86
+ * @returns {Object<string, any>}
87
+ */
88
+
67
89
  /**
68
90
  * Configures a helper.
69
91
  * Helper name can be omitted and values will be applied to first helper.
70
- * @param {string | Object<string, any>} helper
92
+ * @param {string | Object<string, any> | ScenarioConfigCallback} helper
71
93
  * @param {Object<string, any>} [obj]
72
94
  * @returns {this}
73
95
  */
74
- async config(helper, obj) {
96
+ config(helper, obj) {
75
97
  if (!obj) {
76
98
  obj = helper
77
99
  helper = 0
78
100
  }
79
101
  if (typeof obj === 'function') {
80
- obj = await obj(this.test)
81
- }
82
- if (!this.test.config) {
83
- this.test.config = {}
102
+ if (isAsyncFunction(obj)) {
103
+ obj(this.test).then(res => (this.test.config[helper] = res))
104
+ return this
105
+ }
106
+ obj = obj(this.test)
84
107
  }
108
+
85
109
  this.test.config[helper] = obj
86
110
  return this
87
111
  }
@@ -104,7 +128,7 @@ class ScenarioConfig {
104
128
  * @returns {this}
105
129
  */
106
130
  injectDependencies(dependencies) {
107
- Object.keys(dependencies).forEach((key) => {
131
+ Object.keys(dependencies).forEach(key => {
108
132
  this.test.inject[key] = dependencies[key]
109
133
  })
110
134
  return this
@@ -0,0 +1,82 @@
1
+ const MochaSuite = require('mocha/lib/suite')
2
+ /**
3
+ * @typedef {import('mocha')} Mocha
4
+ */
5
+
6
+ /**
7
+ * Enhances MochaSuite with CodeceptJS specific functionality using composition
8
+ */
9
+ function enhanceMochaSuite(suite) {
10
+ if (!suite) suite = new MochaSuite('Suite', null, false)
11
+ // already enhanced
12
+ if (suite.codeceptjs) return suite
13
+
14
+ suite.codeceptjs = true
15
+ // Add properties
16
+ suite.tags = suite.title.match(/(\@[a-zA-Z0-9-_]+)/g) || []
17
+ suite.opts = {}
18
+ // suite.totalTimeout = undefined
19
+
20
+ // Override fullTitle method
21
+ suite.fullTitle = () => `${suite.title}:`
22
+
23
+ // Add new methods
24
+ suite.applyOptions = function (opts) {
25
+ if (!opts) opts = {}
26
+ suite.opts = opts
27
+
28
+ if (opts.retries) suite.retries(opts.retries)
29
+ if (opts.timeout) suite.totalTimeout = opts.timeout
30
+
31
+ if (opts.skipInfo && opts.skipInfo.skipped) {
32
+ suite.pending = true
33
+ suite.opts = { ...this.opts, skipInfo: opts.skipInfo }
34
+ }
35
+ }
36
+
37
+ suite.simplify = function () {
38
+ return serializeSuite(this)
39
+ }
40
+
41
+ return suite
42
+ }
43
+
44
+ /**
45
+ * Factory function to create enhanced suites
46
+ * @param {Mocha.Suite} parent - Parent suite
47
+ * @param {string} title - Suite title
48
+ * @returns {CodeceptJS.Suite & Mocha.Suite} New enhanced suite instance
49
+ */
50
+ function createSuite(parent, title) {
51
+ const suite = MochaSuite.create(parent, title)
52
+ suite.timeout(0)
53
+ return enhanceMochaSuite(suite)
54
+ }
55
+
56
+ function serializeSuite(suite) {
57
+ suite = { ...suite }
58
+
59
+ return {
60
+ opts: suite.opts || {},
61
+ tags: suite.tags || [],
62
+ retries: suite._retries,
63
+ title: suite.title,
64
+ status: suite.status,
65
+ notes: suite.notes || [],
66
+ meta: suite.meta || {},
67
+ duration: suite.duration || 0,
68
+ }
69
+ }
70
+
71
+ function deserializeSuite(suite) {
72
+ suite = Object.assign(new MochaSuite(suite.title), suite)
73
+ enhanceMochaSuite(suite)
74
+ return suite
75
+ }
76
+
77
+ module.exports = {
78
+ createSuite,
79
+ enhanceMochaSuite,
80
+ serializeSuite,
81
+ deserializeSuite,
82
+ }