codeceptjs 4.0.0-beta.3 → 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.
- package/README.md +134 -119
- package/bin/codecept.js +12 -2
- package/bin/test-server.js +53 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +66 -102
- package/lib/ai.js +130 -121
- package/lib/assert/empty.js +3 -5
- package/lib/assert/equal.js +4 -7
- package/lib/assert/include.js +4 -6
- package/lib/assert/throws.js +2 -4
- package/lib/assert/truth.js +2 -2
- package/lib/codecept.js +141 -86
- package/lib/command/check.js +201 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- package/lib/command/dryRun.js +30 -35
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +75 -73
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +42 -8
- package/lib/command/init.js +13 -12
- package/lib/command/interactive.js +10 -2
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple/chunk.js +48 -45
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +21 -58
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +263 -222
- package/lib/container.js +386 -238
- package/lib/data/context.js +10 -13
- package/lib/data/dataScenarioConfig.js +8 -8
- package/lib/data/dataTableArgument.js +6 -6
- package/lib/data/table.js +5 -11
- package/lib/effects.js +223 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +158 -0
- package/lib/event.js +21 -17
- package/lib/heal.js +88 -80
- package/lib/helper/AI.js +2 -1
- package/lib/helper/ApiDataFactory.js +4 -7
- package/lib/helper/Appium.js +50 -57
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +75 -37
- package/lib/helper/Mochawesome.js +31 -9
- package/lib/helper/Nightmare.js +37 -58
- package/lib/helper/Playwright.js +267 -272
- package/lib/helper/Protractor.js +56 -87
- package/lib/helper/Puppeteer.js +247 -264
- package/lib/helper/REST.js +29 -17
- package/lib/helper/TestCafe.js +22 -47
- package/lib/helper/WebDriver.js +157 -368
- package/lib/helper/extras/PlaywrightPropEngine.js +2 -2
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/network/utils.js +1 -1
- package/lib/helper/testcafe/testcafe-utils.js +27 -28
- package/lib/listener/emptyRun.js +55 -0
- package/lib/listener/exit.js +7 -10
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/globalTimeout.js +165 -0
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/result.js +12 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +32 -18
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +1 -1
- package/lib/mocha/asyncWrapper.js +231 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +308 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +32 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +112 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +29 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
- package/lib/mocha/suite.js +82 -0
- package/lib/mocha/test.js +181 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +232 -0
- package/lib/output.js +93 -65
- package/lib/pause.js +160 -138
- package/lib/plugin/analyze.js +396 -0
- package/lib/plugin/auth.js +435 -0
- package/lib/plugin/autoDelay.js +8 -8
- package/lib/plugin/autoLogin.js +3 -338
- package/lib/plugin/commentStep.js +6 -1
- package/lib/plugin/coverage.js +10 -22
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +52 -0
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +36 -9
- package/lib/plugin/htmlReporter.js +1947 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/retryFailedStep.js +17 -18
- package/lib/plugin/retryTo.js +2 -113
- package/lib/plugin/screenshotOnFail.js +17 -58
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/standardActingHelpers.js +4 -1
- package/lib/plugin/stepByStepReport.js +56 -17
- package/lib/plugin/stepTimeout.js +5 -12
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +3 -102
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +155 -124
- package/lib/rerun.js +43 -42
- package/lib/result.js +161 -0
- package/lib/secret.js +1 -2
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +21 -332
- package/lib/steps.js +50 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/test-server.js +323 -0
- package/lib/timeout.js +66 -0
- package/lib/utils.js +351 -218
- package/lib/within.js +75 -55
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +386 -277
- package/package.json +81 -75
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +1 -0
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +9 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +197 -187
- package/typings/promiseBasedTypes.d.ts +53 -903
- package/typings/types.d.ts +372 -1042
- package/lib/cli.js +0 -257
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/MockServer.js +0 -221
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/scenario.js +0 -224
- 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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/* eslint-disable max-classes-per-file */
|
|
2
1
|
const { deepClone } = require('./utils');
|
|
3
2
|
|
|
4
3
|
const maskedString = '*****';
|
|
@@ -38,7 +37,7 @@ function secretObject(obj, fieldsToHide = []) {
|
|
|
38
37
|
if (prop === 'toString') {
|
|
39
38
|
return function () {
|
|
40
39
|
const maskedObject = deepClone(obj);
|
|
41
|
-
fieldsToHide.forEach(f => maskedObject[f] = maskedString);
|
|
40
|
+
fieldsToHide.forEach(f => (maskedObject[f] = maskedString));
|
|
42
41
|
return JSON.stringify(maskedObject);
|
|
43
42
|
};
|
|
44
43
|
}
|
package/lib/step/base.js
ADDED
|
@@ -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,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
|
package/lib/step/func.js
ADDED
|
@@ -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
|
+
}
|
package/lib/step/meta.js
ADDED
|
@@ -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
|