codeceptjs 3.7.0-beta.1 → 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 (73) hide show
  1. package/README.md +9 -10
  2. package/bin/codecept.js +7 -0
  3. package/lib/actor.js +46 -92
  4. package/lib/ai.js +130 -121
  5. package/lib/codecept.js +2 -2
  6. package/lib/command/check.js +186 -0
  7. package/lib/command/definitions.js +3 -1
  8. package/lib/command/interactive.js +1 -1
  9. package/lib/command/run-workers.js +2 -54
  10. package/lib/command/workers/runTests.js +64 -225
  11. package/lib/container.js +27 -0
  12. package/lib/effects.js +218 -0
  13. package/lib/els.js +87 -106
  14. package/lib/event.js +18 -17
  15. package/lib/heal.js +10 -0
  16. package/lib/helper/AI.js +2 -1
  17. package/lib/helper/Appium.js +31 -22
  18. package/lib/helper/Playwright.js +22 -1
  19. package/lib/helper/Puppeteer.js +5 -0
  20. package/lib/helper/WebDriver.js +29 -8
  21. package/lib/listener/emptyRun.js +2 -5
  22. package/lib/listener/exit.js +5 -8
  23. package/lib/listener/globalTimeout.js +66 -10
  24. package/lib/listener/result.js +12 -0
  25. package/lib/listener/steps.js +3 -6
  26. package/lib/listener/store.js +9 -1
  27. package/lib/mocha/asyncWrapper.js +15 -3
  28. package/lib/mocha/cli.js +79 -28
  29. package/lib/mocha/featureConfig.js +13 -0
  30. package/lib/mocha/hooks.js +32 -3
  31. package/lib/mocha/inject.js +5 -0
  32. package/lib/mocha/scenarioConfig.js +11 -0
  33. package/lib/mocha/suite.js +27 -1
  34. package/lib/mocha/test.js +102 -3
  35. package/lib/mocha/types.d.ts +11 -0
  36. package/lib/output.js +75 -73
  37. package/lib/pause.js +3 -10
  38. package/lib/plugin/analyze.js +349 -0
  39. package/lib/plugin/autoDelay.js +2 -2
  40. package/lib/plugin/commentStep.js +5 -0
  41. package/lib/plugin/customReporter.js +52 -0
  42. package/lib/plugin/heal.js +30 -0
  43. package/lib/plugin/pageInfo.js +140 -0
  44. package/lib/plugin/retryTo.js +18 -118
  45. package/lib/plugin/screenshotOnFail.js +12 -17
  46. package/lib/plugin/standardActingHelpers.js +4 -1
  47. package/lib/plugin/stepByStepReport.js +6 -5
  48. package/lib/plugin/stepTimeout.js +1 -1
  49. package/lib/plugin/tryTo.js +17 -107
  50. package/lib/recorder.js +5 -5
  51. package/lib/rerun.js +43 -42
  52. package/lib/result.js +161 -0
  53. package/lib/step/base.js +228 -0
  54. package/lib/step/config.js +50 -0
  55. package/lib/step/func.js +46 -0
  56. package/lib/step/helper.js +50 -0
  57. package/lib/step/meta.js +99 -0
  58. package/lib/step/record.js +74 -0
  59. package/lib/step/retry.js +11 -0
  60. package/lib/step/section.js +55 -0
  61. package/lib/step.js +20 -347
  62. package/lib/steps.js +50 -0
  63. package/lib/store.js +4 -0
  64. package/lib/timeout.js +66 -0
  65. package/lib/utils.js +93 -0
  66. package/lib/within.js +2 -2
  67. package/lib/workers.js +29 -49
  68. package/package.json +23 -20
  69. package/typings/index.d.ts +5 -4
  70. package/typings/promiseBasedTypes.d.ts +617 -7
  71. package/typings/types.d.ts +663 -34
  72. package/lib/listener/artifacts.js +0 -19
  73. package/lib/plugin/debugErrors.js +0 -67
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
@@ -0,0 +1,228 @@
1
+ const color = require('chalk')
2
+ const Secret = require('../secret')
3
+ const { getCurrentTimeout } = require('../timeout')
4
+ const { ucfirst, humanizeString, serializeError } = require('../utils')
5
+
6
+ const STACK_LINE = 5
7
+
8
+ /**
9
+ * Each command in test executed through `I.` object is wrapped in Step.
10
+ * Step allows logging executed commands and triggers hook before and after step execution.
11
+ * @param {string} name
12
+ */
13
+ class Step {
14
+ constructor(name) {
15
+ /** @member {string} */
16
+ this.name = name
17
+ /** @member {Map<number, number>} */
18
+ this.timeouts = new Map()
19
+
20
+ /** @member {Array<*>} */
21
+ this.args = []
22
+
23
+ /** @member {Record<string,any>} */
24
+ this.opts = {}
25
+ /** @member {string} */
26
+ this.actor = 'I' // I = actor
27
+ /** @member {string} */
28
+ this.helperMethod = name // helper method
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
+ this.startTime = 0
41
+ this.endTime = 0
42
+
43
+ this.setTrace()
44
+ }
45
+
46
+ setMetaStep(metaStep) {
47
+ this.metaStep = metaStep
48
+ }
49
+
50
+ run() {
51
+ throw new Error('Not implemented')
52
+ }
53
+
54
+ /**
55
+ * @returns {number|undefined}
56
+ */
57
+ get timeout() {
58
+ return getCurrentTimeout(this.timeouts)
59
+ }
60
+
61
+ /**
62
+ * @param {number} timeout - timeout in milliseconds or 0 if no timeout
63
+ * @param {number} order - order defines the priority of timeout, timeouts set with lower order override those set with higher order.
64
+ * When order below 0 value of timeout only override if new value is lower
65
+ */
66
+ setTimeout(timeout, order) {
67
+ this.timeouts.set(order, timeout)
68
+ }
69
+
70
+ /** @function */
71
+ setTrace() {
72
+ Error.captureStackTrace(this)
73
+ }
74
+
75
+ /** @param {Array<*>} args */
76
+ setArguments(args) {
77
+ this.args = args
78
+ }
79
+
80
+ setActor(actor) {
81
+ this.actor = actor || ''
82
+ }
83
+
84
+ /** @param {string} status */
85
+ setStatus(status) {
86
+ this.status = status
87
+ if (this.metaStep) {
88
+ this.metaStep.setStatus(status)
89
+ }
90
+ }
91
+
92
+ /** @return {string} */
93
+ humanize() {
94
+ return humanizeString(this.name)
95
+ }
96
+
97
+ /** @return {string} */
98
+ humanizeArgs() {
99
+ return this.args
100
+ .map(arg => {
101
+ if (!arg) {
102
+ return ''
103
+ }
104
+ if (typeof arg === 'string') {
105
+ return `"${arg}"`
106
+ }
107
+ if (Array.isArray(arg)) {
108
+ try {
109
+ const res = JSON.stringify(arg)
110
+ return res
111
+ } catch (err) {
112
+ return `[${arg.toString()}]`
113
+ }
114
+ } else if (typeof arg === 'function') {
115
+ return arg.toString()
116
+ } else if (typeof arg === 'undefined') {
117
+ return `${arg}`
118
+ } else if (arg instanceof Secret) {
119
+ return arg.getMasked()
120
+ } else if (arg.toString && arg.toString() !== '[object Object]') {
121
+ return arg.toString()
122
+ } else if (typeof arg === 'object') {
123
+ const returnedArg = {}
124
+ for (const [key, value] of Object.entries(arg)) {
125
+ returnedArg[key] = value
126
+ if (value instanceof Secret) returnedArg[key] = value.getMasked()
127
+ }
128
+ return JSON.stringify(returnedArg)
129
+ }
130
+ return arg
131
+ })
132
+ .join(', ')
133
+ }
134
+
135
+ /** @return {string} */
136
+ line() {
137
+ const lines = this.stack.split('\n')
138
+ if (lines[STACK_LINE]) {
139
+ return lines[STACK_LINE].trim()
140
+ .replace(global.codecept_dir || '', '.')
141
+ .trim()
142
+ }
143
+ return ''
144
+ }
145
+
146
+ /** @return {string} */
147
+ toString() {
148
+ return ucfirst(`${this.prefix}${this.actor} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`).trim()
149
+ }
150
+
151
+ /** @return {string} */
152
+ toCliStyled() {
153
+ return `${this.prefix}${this.actor} ${color.italic(this.humanize())} ${color.yellow(this.humanizeArgs())}${this.suffix}`
154
+ }
155
+
156
+ /** @return {string} */
157
+ toCode() {
158
+ return `${this.prefix}${this.actor}.${this.name}(${this.humanizeArgs()})${this.suffix}`
159
+ }
160
+
161
+ isMetaStep() {
162
+ return this.constructor.name === 'MetaStep'
163
+ }
164
+
165
+ get duration() {
166
+ if (!this.startTime || !this.endTime) return 0
167
+ return this.endTime - this.startTime
168
+ }
169
+
170
+ simplify() {
171
+ const step = this
172
+
173
+ const parent = {}
174
+ if (step.metaStep) {
175
+ parent.title = step.metaStep.actor
176
+ }
177
+
178
+ if (step.opts) {
179
+ Object.keys(step.opts).forEach(k => {
180
+ if (typeof step.opts[k] === 'object') delete step.opts[k]
181
+ if (typeof step.opts[k] === 'function') delete step.opts[k]
182
+ })
183
+ }
184
+
185
+ const args = []
186
+ if (step.args) {
187
+ for (const arg of step.args) {
188
+ // check if arg is a JOI object
189
+ if (arg && typeof arg === 'function') {
190
+ args.push(arg.name)
191
+ } else if (typeof arg == 'string') {
192
+ args.push(arg)
193
+ } else {
194
+ args.push(JSON.stringify(arg).slice(0, 300))
195
+ }
196
+ }
197
+ }
198
+
199
+ return {
200
+ opts: step.opts || {},
201
+ title: step.name,
202
+ args: args,
203
+ status: step.status,
204
+ startTime: step.startTime,
205
+ endTime: step.endTime,
206
+ parent,
207
+ }
208
+ }
209
+
210
+ /** @return {boolean} */
211
+ hasBDDAncestor() {
212
+ let hasBDD = false
213
+ let processingStep
214
+ processingStep = this
215
+
216
+ while (processingStep.metaStep) {
217
+ if (processingStep.metaStep.actor?.match(/^(Given|When|Then|And)/)) {
218
+ hasBDD = true
219
+ break
220
+ } else {
221
+ processingStep = processingStep.metaStep
222
+ }
223
+ }
224
+ return hasBDD
225
+ }
226
+ }
227
+
228
+ module.exports = Step
@@ -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
@@ -0,0 +1,74 @@
1
+ const event = require('../event')
2
+ const recorder = require('../recorder')
3
+ const StepConfig = require('./config')
4
+ const { debug } = require('../output')
5
+ const store = require('../store')
6
+ const { TIMEOUT_ORDER } = require('../timeout')
7
+ const retryStep = require('./retry')
8
+ function recordStep(step, args) {
9
+ step.status = 'queued'
10
+
11
+ // apply step configuration
12
+ const lastArg = args[args.length - 1]
13
+ if (lastArg instanceof StepConfig) {
14
+ const stepConfig = args.pop()
15
+ const { opts, timeout, retry } = stepConfig.getConfig()
16
+
17
+ if (opts) {
18
+ debug(`Step ${step.name}: options applied ${JSON.stringify(opts)}`)
19
+ store.stepOptions = opts
20
+ step.opts = opts
21
+ }
22
+ if (timeout) {
23
+ debug(`Step ${step.name} timeout ${timeout}s`)
24
+ step.setTimeout(timeout * 1000, TIMEOUT_ORDER.codeLimitTime)
25
+ }
26
+ if (retry) retryStep(retry)
27
+ }
28
+
29
+ step.setArguments(args)
30
+ // run async before step hooks
31
+ event.emit(event.step.before, step)
32
+
33
+ const task = `${step.name}: ${step.humanizeArgs()}`
34
+ let val
35
+
36
+ // run step inside promise
37
+ recorder.add(
38
+ task,
39
+ () => {
40
+ if (!step.startTime) {
41
+ // step can be retries
42
+ event.emit(event.step.started, step)
43
+ step.startTime = +Date.now()
44
+ }
45
+ return (val = step.run(...args))
46
+ },
47
+ false,
48
+ undefined,
49
+ step.timeout,
50
+ )
51
+
52
+ event.emit(event.step.after, step)
53
+
54
+ recorder.add('step passed', () => {
55
+ step.endTime = +Date.now()
56
+ event.emit(event.step.passed, step, val)
57
+ event.emit(event.step.finished, step)
58
+ })
59
+
60
+ recorder.catchWithoutStop(err => {
61
+ step.status = 'failed'
62
+ step.endTime = +Date.now()
63
+ event.emit(event.step.failed, step, err)
64
+ event.emit(event.step.finished, step)
65
+ throw err
66
+ })
67
+
68
+ recorder.add('return result', () => val)
69
+ // run async after step hooks
70
+
71
+ return recorder.promise()
72
+ }
73
+
74
+ module.exports = recordStep