codeceptjs 3.7.0-beta.6 → 3.7.0-beta.8
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.
- package/README.md +7 -7
- package/lib/actor.js +1 -2
- package/lib/ai.js +130 -121
- package/lib/codecept.js +4 -4
- package/lib/command/check.js +4 -0
- package/lib/command/run-workers.js +1 -53
- package/lib/command/workers/runTests.js +25 -189
- package/lib/container.js +16 -0
- package/lib/event.js +18 -17
- package/lib/helper/Appium.js +4 -6
- package/lib/helper/WebDriver.js +20 -7
- package/lib/listener/exit.js +5 -8
- package/lib/listener/globalTimeout.js +26 -9
- package/lib/listener/result.js +12 -0
- package/lib/listener/steps.js +0 -6
- package/lib/mocha/asyncWrapper.js +12 -2
- package/lib/mocha/cli.js +50 -24
- package/lib/mocha/hooks.js +32 -3
- package/lib/mocha/suite.js +27 -1
- package/lib/mocha/test.js +91 -7
- package/lib/mocha/types.d.ts +5 -0
- package/lib/output.js +1 -0
- package/lib/pause.js +0 -3
- package/lib/plugin/analyze.js +351 -0
- package/lib/plugin/commentStep.js +5 -0
- package/lib/plugin/customReporter.js +52 -0
- package/lib/plugin/heal.js +2 -2
- package/lib/plugin/pageInfo.js +143 -0
- package/lib/plugin/retryTo.js +10 -2
- package/lib/plugin/screenshotOnFail.js +4 -6
- package/lib/plugin/stepTimeout.js +1 -1
- package/lib/plugin/tryTo.js +9 -1
- package/lib/recorder.js +4 -4
- package/lib/rerun.js +43 -42
- package/lib/result.js +161 -0
- package/lib/step/base.js +52 -4
- package/lib/step/helper.js +3 -0
- package/lib/step/meta.js +9 -1
- package/lib/step/record.js +5 -5
- package/lib/step/section.js +55 -0
- package/lib/steps.js +28 -1
- package/lib/{step/timeout.js → timeout.js} +24 -0
- package/lib/utils.js +35 -0
- package/lib/workers.js +28 -38
- package/package.json +12 -12
- package/typings/promiseBasedTypes.d.ts +6 -520
- package/typings/types.d.ts +115 -562
- package/lib/listener/artifacts.js +0 -19
- package/lib/plugin/debugErrors.js +0 -67
package/lib/rerun.js
CHANGED
|
@@ -1,81 +1,82 @@
|
|
|
1
|
-
const fsPath = require('path')
|
|
2
|
-
const container = require('./container')
|
|
3
|
-
const event = require('./event')
|
|
4
|
-
const BaseCodecept = require('./codecept')
|
|
5
|
-
const output = require('./output')
|
|
1
|
+
const fsPath = require('path')
|
|
2
|
+
const container = require('./container')
|
|
3
|
+
const event = require('./event')
|
|
4
|
+
const BaseCodecept = require('./codecept')
|
|
5
|
+
const output = require('./output')
|
|
6
6
|
|
|
7
7
|
class CodeceptRerunner extends BaseCodecept {
|
|
8
8
|
runOnce(test) {
|
|
9
9
|
return new Promise((resolve, reject) => {
|
|
10
10
|
// @ts-ignore
|
|
11
|
-
container.createMocha()
|
|
12
|
-
const mocha = container.mocha()
|
|
13
|
-
this.testFiles.forEach(
|
|
14
|
-
delete require.cache[file]
|
|
15
|
-
})
|
|
16
|
-
mocha.files = this.testFiles
|
|
11
|
+
container.createMocha()
|
|
12
|
+
const mocha = container.mocha()
|
|
13
|
+
this.testFiles.forEach(file => {
|
|
14
|
+
delete require.cache[file]
|
|
15
|
+
})
|
|
16
|
+
mocha.files = this.testFiles
|
|
17
17
|
if (test) {
|
|
18
18
|
if (!fsPath.isAbsolute(test)) {
|
|
19
|
-
test = fsPath.join(global.codecept_dir, test)
|
|
19
|
+
test = fsPath.join(global.codecept_dir, test)
|
|
20
20
|
}
|
|
21
|
-
mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test)
|
|
21
|
+
mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test)
|
|
22
22
|
}
|
|
23
23
|
try {
|
|
24
|
-
mocha.run(
|
|
24
|
+
mocha.run(failures => {
|
|
25
25
|
if (failures === 0) {
|
|
26
|
-
resolve()
|
|
26
|
+
resolve()
|
|
27
27
|
} else {
|
|
28
|
-
reject(new Error(`${failures} tests fail`))
|
|
28
|
+
reject(new Error(`${failures} tests fail`))
|
|
29
29
|
}
|
|
30
|
-
})
|
|
30
|
+
})
|
|
31
31
|
} catch (e) {
|
|
32
|
-
reject(e)
|
|
32
|
+
reject(e)
|
|
33
33
|
}
|
|
34
|
-
})
|
|
34
|
+
})
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async runTests(test) {
|
|
38
|
-
const configRerun = this.config.rerun || {}
|
|
39
|
-
const minSuccess = configRerun.minSuccess || 1
|
|
40
|
-
const maxReruns = configRerun.maxReruns || 1
|
|
38
|
+
const configRerun = this.config.rerun || {}
|
|
39
|
+
const minSuccess = configRerun.minSuccess || 1
|
|
40
|
+
const maxReruns = configRerun.maxReruns || 1
|
|
41
41
|
if (minSuccess > maxReruns) {
|
|
42
|
-
process.exitCode = 1
|
|
43
|
-
throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`)
|
|
42
|
+
process.exitCode = 1
|
|
43
|
+
throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`)
|
|
44
44
|
}
|
|
45
45
|
if (maxReruns === 1) {
|
|
46
|
-
await this.runOnce(test)
|
|
47
|
-
return
|
|
46
|
+
await this.runOnce(test)
|
|
47
|
+
return
|
|
48
48
|
}
|
|
49
|
-
let successCounter = 0
|
|
50
|
-
let rerunsCounter = 0
|
|
49
|
+
let successCounter = 0
|
|
50
|
+
let rerunsCounter = 0
|
|
51
51
|
while (rerunsCounter < maxReruns && successCounter < minSuccess) {
|
|
52
|
-
|
|
52
|
+
container.result().reset() // reset result
|
|
53
|
+
rerunsCounter++
|
|
53
54
|
try {
|
|
54
|
-
await this.runOnce(test)
|
|
55
|
-
successCounter
|
|
56
|
-
output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`)
|
|
55
|
+
await this.runOnce(test)
|
|
56
|
+
successCounter++
|
|
57
|
+
output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`)
|
|
57
58
|
} catch (e) {
|
|
58
|
-
output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`)
|
|
59
|
-
console.error(e)
|
|
59
|
+
output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`)
|
|
60
|
+
console.error(e)
|
|
60
61
|
}
|
|
61
62
|
}
|
|
62
63
|
if (successCounter < minSuccess) {
|
|
63
|
-
throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`)
|
|
64
|
+
throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`)
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
async run(test) {
|
|
68
|
-
event.emit(event.all.before, this)
|
|
69
|
+
event.emit(event.all.before, this)
|
|
69
70
|
try {
|
|
70
|
-
await this.runTests(test)
|
|
71
|
+
await this.runTests(test)
|
|
71
72
|
} catch (e) {
|
|
72
|
-
output.error(e.stack)
|
|
73
|
-
throw e
|
|
73
|
+
output.error(e.stack)
|
|
74
|
+
throw e
|
|
74
75
|
} finally {
|
|
75
|
-
event.emit(event.all.result, this)
|
|
76
|
-
event.emit(event.all.after, this)
|
|
76
|
+
event.emit(event.all.result, this)
|
|
77
|
+
event.emit(event.all.after, this)
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
module.exports = CodeceptRerunner
|
|
82
|
+
module.exports = CodeceptRerunner
|
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/step/base.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const color = require('chalk')
|
|
2
2
|
const Secret = require('../secret')
|
|
3
|
-
const { getCurrentTimeout } = require('
|
|
4
|
-
const { ucfirst, humanizeString } = require('../utils')
|
|
3
|
+
const { getCurrentTimeout } = require('../timeout')
|
|
4
|
+
const { ucfirst, humanizeString, serializeError } = require('../utils')
|
|
5
5
|
|
|
6
|
-
const STACK_LINE =
|
|
6
|
+
const STACK_LINE = 5
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Each command in test executed through `I.` object is wrapped in Step.
|
|
@@ -37,6 +37,9 @@ class Step {
|
|
|
37
37
|
/** @member {string} */
|
|
38
38
|
this.stack = ''
|
|
39
39
|
|
|
40
|
+
this.startTime = 0
|
|
41
|
+
this.endTime = 0
|
|
42
|
+
|
|
40
43
|
this.setTrace()
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -159,6 +162,51 @@ class Step {
|
|
|
159
162
|
return this.constructor.name === 'MetaStep'
|
|
160
163
|
}
|
|
161
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
|
+
|
|
162
210
|
/** @return {boolean} */
|
|
163
211
|
hasBDDAncestor() {
|
|
164
212
|
let hasBDD = false
|
|
@@ -166,7 +214,7 @@ class Step {
|
|
|
166
214
|
processingStep = this
|
|
167
215
|
|
|
168
216
|
while (processingStep.metaStep) {
|
|
169
|
-
if (processingStep.metaStep.actor
|
|
217
|
+
if (processingStep.metaStep.actor?.match(/^(Given|When|Then|And)/)) {
|
|
170
218
|
hasBDD = true
|
|
171
219
|
break
|
|
172
220
|
} else {
|
package/lib/step/helper.js
CHANGED
|
@@ -16,6 +16,7 @@ class HelperStep extends Step {
|
|
|
16
16
|
*/
|
|
17
17
|
run() {
|
|
18
18
|
this.args = Array.prototype.slice.call(arguments)
|
|
19
|
+
this.startTime = +Date.now()
|
|
19
20
|
|
|
20
21
|
if (store.dryRun) {
|
|
21
22
|
this.setStatus('success')
|
|
@@ -27,7 +28,9 @@ class HelperStep extends Step {
|
|
|
27
28
|
result = this.helper[this.helperMethod].apply(this.helper, this.args)
|
|
28
29
|
}
|
|
29
30
|
this.setStatus('success')
|
|
31
|
+
this.endTime = +Date.now()
|
|
30
32
|
} catch (err) {
|
|
33
|
+
this.endTime = +Date.now()
|
|
31
34
|
this.setStatus('failed')
|
|
32
35
|
throw err
|
|
33
36
|
}
|
package/lib/step/meta.js
CHANGED
|
@@ -6,6 +6,10 @@ class MetaStep extends Step {
|
|
|
6
6
|
constructor(actor, method) {
|
|
7
7
|
if (!method) method = ''
|
|
8
8
|
super(method)
|
|
9
|
+
|
|
10
|
+
/** @member {boolean} collsapsed hide children steps from output */
|
|
11
|
+
this.collapsed = false
|
|
12
|
+
|
|
9
13
|
this.actor = actor
|
|
10
14
|
}
|
|
11
15
|
|
|
@@ -32,7 +36,11 @@ class MetaStep extends Step {
|
|
|
32
36
|
return `${this.prefix}${actorText} ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
|
|
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()
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
humanize() {
|
package/lib/step/record.js
CHANGED
|
@@ -3,7 +3,7 @@ const recorder = require('../recorder')
|
|
|
3
3
|
const StepConfig = require('./config')
|
|
4
4
|
const { debug } = require('../output')
|
|
5
5
|
const store = require('../store')
|
|
6
|
-
const { TIMEOUT_ORDER } = require('
|
|
6
|
+
const { TIMEOUT_ORDER } = require('../timeout')
|
|
7
7
|
const retryStep = require('./retry')
|
|
8
8
|
function recordStep(step, args) {
|
|
9
9
|
step.status = 'queued'
|
|
@@ -40,7 +40,7 @@ function recordStep(step, args) {
|
|
|
40
40
|
if (!step.startTime) {
|
|
41
41
|
// step can be retries
|
|
42
42
|
event.emit(event.step.started, step)
|
|
43
|
-
step.startTime = Date.now()
|
|
43
|
+
step.startTime = +Date.now()
|
|
44
44
|
}
|
|
45
45
|
return (val = step.run(...args))
|
|
46
46
|
},
|
|
@@ -52,15 +52,15 @@ function recordStep(step, args) {
|
|
|
52
52
|
event.emit(event.step.after, step)
|
|
53
53
|
|
|
54
54
|
recorder.add('step passed', () => {
|
|
55
|
-
step.endTime = Date.now()
|
|
55
|
+
step.endTime = +Date.now()
|
|
56
56
|
event.emit(event.step.passed, step, val)
|
|
57
57
|
event.emit(event.step.finished, step)
|
|
58
58
|
})
|
|
59
59
|
|
|
60
60
|
recorder.catchWithoutStop(err => {
|
|
61
61
|
step.status = 'failed'
|
|
62
|
-
step.endTime = Date.now()
|
|
63
|
-
event.emit(event.step.failed, step)
|
|
62
|
+
step.endTime = +Date.now()
|
|
63
|
+
event.emit(event.step.failed, step, err)
|
|
64
64
|
event.emit(event.step.finished, step)
|
|
65
65
|
throw err
|
|
66
66
|
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const MetaStep = require('./meta')
|
|
2
|
+
const event = require('../event')
|
|
3
|
+
|
|
4
|
+
let currentSection
|
|
5
|
+
|
|
6
|
+
class Section {
|
|
7
|
+
constructor(name = '') {
|
|
8
|
+
this.name = name
|
|
9
|
+
|
|
10
|
+
this.metaStep = new MetaStep(null, name)
|
|
11
|
+
|
|
12
|
+
this.attachMetaStep = step => {
|
|
13
|
+
if (currentSection !== this) return
|
|
14
|
+
if (!step) return
|
|
15
|
+
const metaStep = getRootMetaStep(step)
|
|
16
|
+
|
|
17
|
+
if (metaStep !== this.metaStep) {
|
|
18
|
+
metaStep.metaStep = this.metaStep
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
hidden() {
|
|
24
|
+
this.metaStep.collapsed = true
|
|
25
|
+
return this
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
start() {
|
|
29
|
+
if (currentSection) currentSection.end()
|
|
30
|
+
currentSection = this
|
|
31
|
+
event.dispatcher.prependListener(event.step.before, this.attachMetaStep)
|
|
32
|
+
event.dispatcher.once(event.test.finished, () => this.end())
|
|
33
|
+
return this
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
end() {
|
|
37
|
+
currentSection = null
|
|
38
|
+
event.dispatcher.off(event.step.started, this.attachMetaStep)
|
|
39
|
+
return this
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @returns {Section}
|
|
44
|
+
*/
|
|
45
|
+
static current() {
|
|
46
|
+
return currentSection
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function getRootMetaStep(step) {
|
|
51
|
+
if (step.metaStep) return getRootMetaStep(step.metaStep)
|
|
52
|
+
return step
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = Section
|
package/lib/steps.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const StepConfig = require('./step/config')
|
|
2
|
-
|
|
2
|
+
const Section = require('./step/section')
|
|
3
3
|
function stepOpts(opts = {}) {
|
|
4
4
|
return new StepConfig(opts)
|
|
5
5
|
}
|
|
@@ -12,12 +12,39 @@ function stepRetry(retry) {
|
|
|
12
12
|
return new StepConfig().retry(retry)
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
function section(name) {
|
|
16
|
+
if (!name) return endSection()
|
|
17
|
+
return new Section(name).start()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function endSection() {
|
|
21
|
+
return Section.current().end()
|
|
22
|
+
}
|
|
23
|
+
|
|
15
24
|
// Section function to be added here
|
|
16
25
|
|
|
17
26
|
const step = {
|
|
27
|
+
// steps.opts syntax
|
|
18
28
|
opts: stepOpts,
|
|
19
29
|
timeout: stepTimeout,
|
|
20
30
|
retry: stepRetry,
|
|
31
|
+
|
|
32
|
+
// one-function syntax
|
|
33
|
+
stepTimeout,
|
|
34
|
+
stepRetry,
|
|
35
|
+
stepOpts,
|
|
36
|
+
|
|
37
|
+
// sections
|
|
38
|
+
section,
|
|
39
|
+
endSection,
|
|
40
|
+
|
|
41
|
+
Section: section,
|
|
42
|
+
EndSection: endSection,
|
|
43
|
+
|
|
44
|
+
// shortcuts
|
|
45
|
+
Given: () => section('Given'),
|
|
46
|
+
When: () => section('When'),
|
|
47
|
+
Then: () => section('Then'),
|
|
21
48
|
}
|
|
22
49
|
|
|
23
50
|
module.exports = step
|
|
@@ -36,7 +36,31 @@ function getCurrentTimeout(timeouts) {
|
|
|
36
36
|
return totalTimeout
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
class TimeoutError extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message)
|
|
42
|
+
this.name = 'TimeoutError'
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class TestTimeoutError extends TimeoutError {
|
|
47
|
+
constructor(timeout) {
|
|
48
|
+
super(`Timeout ${timeout}s exceeded (with Before hook)`)
|
|
49
|
+
this.name = 'TestTimeoutError'
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
class StepTimeoutError extends TimeoutError {
|
|
54
|
+
constructor(timeout, step) {
|
|
55
|
+
super(`Step ${step.toCode().trim()} timed out after ${timeout}s`)
|
|
56
|
+
this.name = 'StepTimeoutError'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
39
60
|
module.exports = {
|
|
40
61
|
TIMEOUT_ORDER,
|
|
41
62
|
getCurrentTimeout,
|
|
63
|
+
TimeoutError,
|
|
64
|
+
TestTimeoutError,
|
|
65
|
+
StepTimeoutError,
|
|
42
66
|
}
|
package/lib/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const os = require('os')
|
|
3
3
|
const path = require('path')
|
|
4
|
+
const chalk = require('chalk')
|
|
4
5
|
const getFunctionArguments = require('fn-args')
|
|
5
6
|
const deepClone = require('lodash.clonedeep')
|
|
6
7
|
const { convertColorToRGBA, isColorProperty } = require('./colorUtils')
|
|
@@ -542,3 +543,37 @@ module.exports.humanizeString = function (string) {
|
|
|
542
543
|
_result[0] = _result[0] === 'i' ? this.ucfirst(_result[0]) : _result[0]
|
|
543
544
|
return _result.join(' ').trim()
|
|
544
545
|
}
|
|
546
|
+
|
|
547
|
+
module.exports.serializeError = function (error) {
|
|
548
|
+
if (error) {
|
|
549
|
+
const { stack, uncaught, message, actual, expected } = error
|
|
550
|
+
return { stack, uncaught, message, actual, expected }
|
|
551
|
+
}
|
|
552
|
+
return null
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
module.exports.base64EncodeFile = function (filePath) {
|
|
556
|
+
return Buffer.from(fs.readFileSync(filePath)).toString('base64')
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
module.exports.markdownToAnsi = function (markdown) {
|
|
560
|
+
return (
|
|
561
|
+
markdown
|
|
562
|
+
// Headers (# Text) - make blue and bold
|
|
563
|
+
.replace(/^(#{1,6})\s+(.+)$/gm, (_, hashes, text) => {
|
|
564
|
+
return chalk.bold.blue(`${hashes} ${text}`)
|
|
565
|
+
})
|
|
566
|
+
// Bullet points - replace with yellow bullet character
|
|
567
|
+
.replace(/^[-*]\s+(.+)$/gm, (_, text) => {
|
|
568
|
+
return `${chalk.yellow('•')} ${text}`
|
|
569
|
+
})
|
|
570
|
+
// Bold (**text**) - make bold
|
|
571
|
+
.replace(/\*\*(.+?)\*\*/g, (_, text) => {
|
|
572
|
+
return chalk.bold(text)
|
|
573
|
+
})
|
|
574
|
+
// Italic (*text*) - make italic (dim in terminals)
|
|
575
|
+
.replace(/\*(.+?)\*/g, (_, text) => {
|
|
576
|
+
return chalk.italic(text)
|
|
577
|
+
})
|
|
578
|
+
)
|
|
579
|
+
}
|