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
package/lib/result.js ADDED
@@ -0,0 +1,161 @@
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const { serializeTest } = require('./mocha/test')
4
+
5
+ /**
6
+ * Result of the test run
7
+ *
8
+ * @typedef {Object} Stats
9
+ * @property {number} passes
10
+ * @property {number} failures
11
+ * @property {number} tests
12
+ * @property {number} pending
13
+ * @property {number} failedHooks
14
+ * @property {Date} start
15
+ * @property {Date} end
16
+ * @property {number} duration
17
+ */
18
+ class Result {
19
+ /**
20
+ * Create Result of the test run
21
+ */
22
+ constructor() {
23
+ this._startTime = new Date()
24
+ this._endTime = null
25
+
26
+ this.reset()
27
+ this.start()
28
+ }
29
+
30
+ reset() {
31
+ this._stats = {
32
+ passes: 0,
33
+ failures: 0,
34
+ tests: 0,
35
+ pending: 0,
36
+ failedHooks: 0,
37
+ start: null,
38
+ end: null,
39
+ duration: 0,
40
+ }
41
+
42
+ /** @type {CodeceptJS.Test[]} */
43
+ this._tests = []
44
+
45
+ /** @type {String[]} */
46
+ this._failures = []
47
+ }
48
+
49
+ start() {
50
+ this._startTime = new Date()
51
+ }
52
+
53
+ finish() {
54
+ this._endTime = new Date()
55
+ }
56
+
57
+ get hasFailed() {
58
+ return this._stats.failures > 0
59
+ }
60
+
61
+ get tests() {
62
+ return this._tests
63
+ }
64
+
65
+ get failures() {
66
+ return this._failures.filter(f => f && (!Array.isArray(f) || f.length > 0))
67
+ }
68
+
69
+ get stats() {
70
+ return this._stats
71
+ }
72
+
73
+ get startTime() {
74
+ return this._startTime
75
+ }
76
+
77
+ /**
78
+ * Add test to result
79
+ *
80
+ * @param {CodeceptJS.Test} test
81
+ */
82
+ addTest(test) {
83
+ const existingTestIndex = this._tests.findIndex(t => !!t.uid && t.uid === test.uid)
84
+ if (existingTestIndex >= 0) {
85
+ this._tests[existingTestIndex] = test
86
+ return
87
+ }
88
+
89
+ this._tests.push(test)
90
+ }
91
+
92
+ /**
93
+ * Add failures to result
94
+ *
95
+ * @param {String[]} newFailures
96
+ */
97
+ addFailures(newFailures) {
98
+ this._failures.push(...newFailures)
99
+ }
100
+
101
+ get hasFailures() {
102
+ return this.stats.failures > 0
103
+ }
104
+
105
+ get duration() {
106
+ return this._endTime ? +this._endTime - +this._startTime : 0
107
+ }
108
+
109
+ get failedTests() {
110
+ return this._tests.filter(test => test.state === 'failed')
111
+ }
112
+
113
+ get passedTests() {
114
+ return this._tests.filter(test => test.state === 'passed')
115
+ }
116
+
117
+ get skippedTests() {
118
+ return this._tests.filter(test => test.state === 'skipped' || test.state === 'pending')
119
+ }
120
+
121
+ simplify() {
122
+ return {
123
+ hasFailed: this.hasFailed,
124
+ stats: this.stats,
125
+ duration: this.duration,
126
+ tests: this._tests.map(test => serializeTest(test)),
127
+ failures: this._failures,
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Save result to json file
133
+ *
134
+ * @param {string} fileName
135
+ */
136
+ save(fileName) {
137
+ if (!fileName) fileName = 'result.json'
138
+ fs.writeFileSync(path.join(global.output_dir, fileName), JSON.stringify(this.simplify(), null, 2))
139
+ }
140
+
141
+ /**
142
+ * Add stats to result
143
+ *
144
+ * @param {object} newStats
145
+ */
146
+ addStats(newStats = {}) {
147
+ this._stats.passes += newStats.passes || 0
148
+ this._stats.failures += newStats.failures || 0
149
+ this._stats.tests += newStats.tests || 0
150
+ this._stats.pending += newStats.pending || 0
151
+ this._stats.failedHooks += newStats.failedHooks || 0
152
+
153
+ // do not override start time
154
+ this._stats.start = this._stats.start || newStats.start
155
+
156
+ this._stats.end = newStats.end || this._stats.end
157
+ this._stats.duration = newStats.duration
158
+ }
159
+ }
160
+
161
+ module.exports = Result
package/lib/secret.js CHANGED
@@ -37,7 +37,7 @@ function secretObject(obj, fieldsToHide = []) {
37
37
  if (prop === 'toString') {
38
38
  return function () {
39
39
  const maskedObject = deepClone(obj);
40
- fieldsToHide.forEach(f => maskedObject[f] = maskedString);
40
+ fieldsToHide.forEach(f => (maskedObject[f] = maskedString));
41
41
  return JSON.stringify(maskedObject);
42
42
  };
43
43
  }
@@ -0,0 +1,239 @@
1
+ const color = require('chalk')
2
+ const Secret = require('../secret')
3
+ const { getCurrentTimeout } = require('../timeout')
4
+ const { ucfirst, humanizeString, serializeError } = require('../utils')
5
+ const recordStep = require('./record')
6
+
7
+ const STACK_LINE = 5
8
+
9
+ /**
10
+ * Each command in test executed through `I.` object is wrapped in Step.
11
+ * Step allows logging executed commands and triggers hook before and after step execution.
12
+ * @param {string} name
13
+ */
14
+ class Step {
15
+ constructor(name) {
16
+ /** @member {string} */
17
+ this.name = name
18
+ /** @member {Map<number, number>} */
19
+ this.timeouts = new Map()
20
+
21
+ /** @member {Array<*>} */
22
+ this.args = []
23
+
24
+ /** @member {Record<string,any>} */
25
+ this.opts = {}
26
+ /** @member {string} */
27
+ this.actor = 'I' // I = actor
28
+
29
+ /** @member {string} */
30
+ this.status = 'pending'
31
+ /** @member {string} */
32
+ this.prefix = this.suffix = ''
33
+ /** @member {string} */
34
+ this.comment = ''
35
+ /** @member {any} */
36
+ this.metaStep = undefined
37
+ /** @member {string} */
38
+ this.stack = ''
39
+
40
+ // These are part of HelperStep class
41
+ // but left here for types compatibility
42
+ /** @member {any} */
43
+ this.helper = null
44
+ /** @member {string} */
45
+ this.helperMethod = name
46
+
47
+ this.startTime = 0
48
+ this.endTime = 0
49
+
50
+ this.setTrace()
51
+ }
52
+
53
+ setMetaStep(metaStep) {
54
+ this.metaStep = metaStep
55
+ }
56
+
57
+ run() {
58
+ throw new Error('Not implemented')
59
+ }
60
+
61
+ addToRecorder(args) {
62
+ return recordStep(this, args)
63
+ }
64
+
65
+ /**
66
+ * @returns {number|undefined}
67
+ */
68
+ get timeout() {
69
+ return getCurrentTimeout(this.timeouts)
70
+ }
71
+
72
+ /**
73
+ * @param {number} timeout - timeout in milliseconds or 0 if no timeout
74
+ * @param {number} order - order defines the priority of timeout, timeouts set with lower order override those set with higher order.
75
+ * When order below 0 value of timeout only override if new value is lower
76
+ */
77
+ setTimeout(timeout, order) {
78
+ this.timeouts.set(order, timeout)
79
+ }
80
+
81
+ /** @function */
82
+ setTrace() {
83
+ Error.captureStackTrace(this)
84
+ }
85
+
86
+ /** @param {Array<*>} args */
87
+ setArguments(args) {
88
+ this.args = args
89
+ }
90
+
91
+ setActor(actor) {
92
+ this.actor = actor || ''
93
+ }
94
+
95
+ /** @param {string} status */
96
+ setStatus(status) {
97
+ this.status = status
98
+ if (this.metaStep) {
99
+ this.metaStep.setStatus(status)
100
+ }
101
+ }
102
+
103
+ /** @return {string} */
104
+ humanize() {
105
+ return humanizeString(this.name)
106
+ }
107
+
108
+ /** @return {string} */
109
+ humanizeArgs() {
110
+ return this.args
111
+ .map(arg => {
112
+ if (!arg) {
113
+ return ''
114
+ }
115
+ if (typeof arg === 'string') {
116
+ return `"${arg}"`
117
+ }
118
+ if (Array.isArray(arg)) {
119
+ try {
120
+ const res = JSON.stringify(arg)
121
+ return res
122
+ } catch (err) {
123
+ return `[${arg.toString()}]`
124
+ }
125
+ } else if (typeof arg === 'function') {
126
+ return arg.toString()
127
+ } else if (typeof arg === 'undefined') {
128
+ return `${arg}`
129
+ } else if (arg instanceof Secret) {
130
+ return arg.getMasked()
131
+ } else if (arg.toString && arg.toString() !== '[object Object]') {
132
+ return arg.toString()
133
+ } else if (typeof arg === 'object') {
134
+ const returnedArg = {}
135
+ for (const [key, value] of Object.entries(arg)) {
136
+ returnedArg[key] = value
137
+ if (value instanceof Secret) returnedArg[key] = value.getMasked()
138
+ }
139
+ return JSON.stringify(returnedArg)
140
+ }
141
+ return arg
142
+ })
143
+ .join(', ')
144
+ }
145
+
146
+ /** @return {string} */
147
+ line() {
148
+ const lines = this.stack.split('\n')
149
+ if (lines[STACK_LINE]) {
150
+ return lines[STACK_LINE].trim()
151
+ .replace(global.codecept_dir || '', '.')
152
+ .trim()
153
+ }
154
+ return ''
155
+ }
156
+
157
+ /** @return {string} */
158
+ toString() {
159
+ return ucfirst(`${this.prefix}${this.actor} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`).trim()
160
+ }
161
+
162
+ /** @return {string} */
163
+ toCliStyled() {
164
+ return `${this.prefix}${this.actor} ${color.italic(this.humanize())} ${color.yellow(this.humanizeArgs())}${this.suffix}`
165
+ }
166
+
167
+ /** @return {string} */
168
+ toCode() {
169
+ return `${this.prefix}${this.actor}.${this.name}(${this.humanizeArgs()})${this.suffix}`
170
+ }
171
+
172
+ isMetaStep() {
173
+ return this.constructor.name === 'MetaStep'
174
+ }
175
+
176
+ get duration() {
177
+ if (!this.startTime || !this.endTime) return 0
178
+ return this.endTime - this.startTime
179
+ }
180
+
181
+ simplify() {
182
+ const step = this
183
+
184
+ const parent = {}
185
+ if (step.metaStep) {
186
+ parent.title = step.metaStep.actor
187
+ }
188
+
189
+ if (step.opts) {
190
+ Object.keys(step.opts).forEach(k => {
191
+ if (typeof step.opts[k] === 'object') delete step.opts[k]
192
+ if (typeof step.opts[k] === 'function') delete step.opts[k]
193
+ })
194
+ }
195
+
196
+ const args = []
197
+ if (step.args) {
198
+ for (const arg of step.args) {
199
+ // check if arg is a JOI object
200
+ if (arg && typeof arg === 'function') {
201
+ args.push(arg.name)
202
+ } else if (typeof arg == 'string') {
203
+ args.push(arg)
204
+ } else if (arg) {
205
+ args.push((JSON.stringify(arg) || '').slice(0, 300))
206
+ }
207
+ }
208
+ }
209
+
210
+ return {
211
+ opts: step.opts || {},
212
+ title: step.name,
213
+ args: args,
214
+ status: step.status,
215
+ startTime: step.startTime,
216
+ endTime: step.endTime,
217
+ parent,
218
+ }
219
+ }
220
+
221
+ /** @return {boolean} */
222
+ hasBDDAncestor() {
223
+ let hasBDD = false
224
+ let processingStep
225
+ processingStep = this
226
+
227
+ while (processingStep.metaStep) {
228
+ if (processingStep.metaStep.actor?.match(/^(Given|When|Then|And)/)) {
229
+ hasBDD = true
230
+ break
231
+ } else {
232
+ processingStep = processingStep.metaStep
233
+ }
234
+ }
235
+ return hasBDD
236
+ }
237
+ }
238
+
239
+ module.exports = Step
@@ -0,0 +1,10 @@
1
+ const FuncStep = require('./func')
2
+
3
+ class CommentStep extends FuncStep {
4
+ constructor(name, comment) {
5
+ super(name)
6
+ this.fn = () => {}
7
+ }
8
+ }
9
+
10
+ module.exports = CommentStep
@@ -0,0 +1,50 @@
1
+ /**
2
+ * StepConfig is a configuration object for a step.
3
+ * It is used to create a new step that is a combination of other steps.
4
+ */
5
+ class StepConfig {
6
+ constructor(opts = {}) {
7
+ /** @member {{ opts: Record<string, any>, timeout: number|undefined, retry: number|undefined }} */
8
+ this.config = {
9
+ opts,
10
+ timeout: undefined,
11
+ retry: undefined,
12
+ }
13
+ }
14
+
15
+ /**
16
+ * Set the options for the step.
17
+ * @param {object} opts - The options for the step.
18
+ * @returns {StepConfig} - The step configuration object.
19
+ */
20
+ opts(opts) {
21
+ this.config.opts = opts
22
+ return this
23
+ }
24
+
25
+ /**
26
+ * Set the timeout for the step.
27
+ * @param {number} timeout - The timeout for the step.
28
+ * @returns {StepConfig} - The step configuration object.
29
+ */
30
+ timeout(timeout) {
31
+ this.config.timeout = timeout
32
+ return this
33
+ }
34
+
35
+ /**
36
+ * Set the retry for the step.
37
+ * @param {number} retry - The retry for the step.
38
+ * @returns {StepConfig} - The step configuration object.
39
+ */
40
+ retry(retry) {
41
+ this.config.retry = retry
42
+ return this
43
+ }
44
+
45
+ getConfig() {
46
+ return this.config
47
+ }
48
+ }
49
+
50
+ module.exports = StepConfig
@@ -0,0 +1,46 @@
1
+ const BaseStep = require('./base')
2
+ const store = require('../store')
3
+
4
+ /**
5
+ * Function executed as a step
6
+ */
7
+ class FuncStep extends BaseStep {
8
+ // this is actual function that should be executed within step
9
+ setCallable(fn) {
10
+ this.fn = fn
11
+ }
12
+
13
+ // helper is optional, if we need to allow step to access helper methods
14
+ setHelper(helper) {
15
+ this.helper = helper
16
+ }
17
+
18
+ run() {
19
+ if (!this.fn) throw new Error('Function is not set')
20
+
21
+ // we wrap that function to track time and status
22
+ // and disable it in dry run mode
23
+ this.args = Array.prototype.slice.call(arguments)
24
+ this.startTime = +Date.now()
25
+
26
+ if (store.dryRun) {
27
+ this.setStatus('success')
28
+ // should we add Proxy and dry run resolver here?
29
+ return Promise.resolve(true)
30
+ }
31
+
32
+ let result
33
+ try {
34
+ result = this.fn.apply(this.helper, this.args)
35
+ this.setStatus('success')
36
+ this.endTime = +Date.now()
37
+ } catch (err) {
38
+ this.endTime = +Date.now()
39
+ this.setStatus('failed')
40
+ throw err
41
+ }
42
+ return result
43
+ }
44
+ }
45
+
46
+ module.exports = FuncStep
@@ -0,0 +1,50 @@
1
+ const Step = require('./base')
2
+ const store = require('../store')
3
+
4
+ class HelperStep extends Step {
5
+ constructor(helper, name) {
6
+ super(name)
7
+ /** @member {CodeceptJS.Helper} helper corresponding helper */
8
+ this.helper = helper
9
+ /** @member {string} helperMethod name of method to be executed */
10
+ this.helperMethod = name
11
+ }
12
+
13
+ /**
14
+ * @param {...any} args
15
+ * @return {*}
16
+ */
17
+ run() {
18
+ this.args = Array.prototype.slice.call(arguments)
19
+ this.startTime = +Date.now()
20
+
21
+ if (store.dryRun) {
22
+ this.setStatus('success')
23
+ return Promise.resolve(new Proxy({}, dryRunResolver()))
24
+ }
25
+ let result
26
+ try {
27
+ if (this.helperMethod !== 'say') {
28
+ result = this.helper[this.helperMethod].apply(this.helper, this.args)
29
+ }
30
+ this.setStatus('success')
31
+ this.endTime = +Date.now()
32
+ } catch (err) {
33
+ this.endTime = +Date.now()
34
+ this.setStatus('failed')
35
+ throw err
36
+ }
37
+ return result
38
+ }
39
+ }
40
+
41
+ module.exports = HelperStep
42
+
43
+ function dryRunResolver() {
44
+ return {
45
+ get(target, prop) {
46
+ if (prop === 'toString') return () => '<VALUE>'
47
+ return new Proxy({}, dryRunResolver())
48
+ },
49
+ }
50
+ }
@@ -0,0 +1,99 @@
1
+ const Step = require('./base')
2
+ const event = require('../event')
3
+ const { humanizeString } = require('../utils')
4
+
5
+ class MetaStep extends Step {
6
+ constructor(actor, method) {
7
+ if (!method) method = ''
8
+ super(method)
9
+
10
+ /** @member {boolean} collsapsed hide children steps from output */
11
+ this.collapsed = false
12
+
13
+ this.actor = actor
14
+ }
15
+
16
+ /** @return {boolean} */
17
+ isBDD() {
18
+ if (this.actor && this.actor.match && this.actor.match(/^(Given|When|Then|And)/)) {
19
+ return true
20
+ }
21
+ return false
22
+ }
23
+
24
+ toCliStyled() {
25
+ return this.toString()
26
+ }
27
+
28
+ toString() {
29
+ const actorText = this.actor
30
+
31
+ if (this.isBDD()) {
32
+ return `${this.prefix}${actorText} ${this.name} "${this.humanizeArgs()}${this.suffix}"`
33
+ }
34
+
35
+ if (actorText === 'I') {
36
+ return `${this.prefix}${actorText} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`
37
+ }
38
+
39
+ if (!this.actor) {
40
+ return `${this.name} ${this.humanizeArgs()}${this.suffix}`.trim()
41
+ }
42
+
43
+ return `On ${this.prefix}${actorText}: ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`.trim()
44
+ }
45
+
46
+ humanize() {
47
+ return humanizeString(this.name)
48
+ }
49
+
50
+ setTrace() {}
51
+
52
+ setContext(context) {
53
+ this.context = context
54
+ }
55
+
56
+ /** @return {*} */
57
+ run(fn) {
58
+ this.status = 'queued'
59
+ this.setArguments(Array.from(arguments).slice(1))
60
+ let result
61
+
62
+ const registerStep = step => {
63
+ this.setMetaStep(null)
64
+ step.setMetaStep(this)
65
+ }
66
+ event.dispatcher.prependListener(event.step.before, registerStep)
67
+ // Handle async and sync methods.
68
+ if (fn.constructor.name === 'AsyncFunction') {
69
+ result = fn
70
+ .apply(this.context, this.args)
71
+ .then(result => {
72
+ return result
73
+ })
74
+ .catch(error => {
75
+ this.setStatus('failed')
76
+ throw error
77
+ })
78
+ .finally(() => {
79
+ this.endTime = Date.now()
80
+ event.dispatcher.removeListener(event.step.before, registerStep)
81
+ })
82
+ } else {
83
+ try {
84
+ this.startTime = Date.now()
85
+ result = fn.apply(this.context, this.args)
86
+ } catch (error) {
87
+ this.setStatus('failed')
88
+ throw error
89
+ } finally {
90
+ this.endTime = Date.now()
91
+ event.dispatcher.removeListener(event.step.before, registerStep)
92
+ }
93
+ }
94
+
95
+ return result
96
+ }
97
+ }
98
+
99
+ module.exports = MetaStep