codeceptjs 4.0.0-beta.4 → 4.0.0-beta.6.esm-aria
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 +89 -119
- package/bin/codecept.js +53 -54
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +70 -102
- package/lib/ai.js +131 -121
- package/lib/assert/empty.js +11 -12
- package/lib/assert/equal.js +16 -21
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +11 -15
- package/lib/assert/throws.js +3 -5
- package/lib/assert/truth.js +10 -7
- package/lib/assert.js +18 -18
- package/lib/codecept.js +112 -101
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +206 -0
- package/lib/command/configMigrate.js +13 -14
- package/lib/command/definitions.js +24 -36
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +38 -39
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +76 -74
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +49 -15
- package/lib/command/init.js +41 -37
- package/lib/command/interactive.js +22 -13
- package/lib/command/list.js +11 -10
- package/lib/command/run-multiple/chunk.js +50 -47
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +27 -47
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +15 -66
- package/lib/command/run.js +8 -8
- package/lib/command/utils.js +22 -21
- package/lib/command/workers/runTests.js +131 -241
- package/lib/config.js +111 -49
- package/lib/container.js +589 -244
- package/lib/data/context.js +16 -18
- package/lib/data/dataScenarioConfig.js +9 -9
- package/lib/data/dataTableArgument.js +7 -7
- package/lib/data/table.js +6 -12
- package/lib/effects.js +307 -0
- package/lib/els.js +160 -0
- package/lib/event.js +24 -19
- package/lib/globals.js +141 -0
- package/lib/heal.js +89 -81
- package/lib/helper/AI.js +3 -2
- package/lib/helper/ApiDataFactory.js +19 -19
- package/lib/helper/Appium.js +47 -51
- package/lib/helper/FileSystem.js +35 -15
- package/lib/helper/GraphQL.js +1 -1
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +72 -45
- package/lib/helper/Mochawesome.js +14 -11
- package/lib/helper/Playwright.js +832 -434
- package/lib/helper/Puppeteer.js +393 -292
- package/lib/helper/REST.js +32 -27
- package/lib/helper/WebDriver.js +320 -219
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/extras/React.js +29 -30
- package/lib/helper/network/actions.js +33 -48
- package/lib/helper/network/utils.js +76 -83
- package/lib/helper/scripts/blurElement.js +6 -6
- package/lib/helper/scripts/focusElement.js +6 -6
- package/lib/helper/scripts/highlightElement.js +9 -9
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -1
- package/lib/history.js +23 -20
- package/lib/hooks.js +10 -10
- package/lib/html.js +90 -100
- package/lib/index.js +48 -21
- package/lib/listener/config.js +8 -9
- package/lib/listener/emptyRun.js +54 -0
- package/lib/listener/exit.js +10 -12
- package/lib/listener/{retry.js → globalRetry.js} +10 -10
- package/lib/listener/globalTimeout.js +166 -0
- package/lib/listener/helpers.js +43 -24
- package/lib/listener/mocha.js +4 -5
- package/lib/listener/result.js +11 -0
- package/lib/listener/steps.js +26 -23
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +264 -0
- package/lib/mocha/bdd.js +167 -0
- package/lib/mocha/cli.js +341 -0
- package/lib/mocha/factory.js +160 -0
- package/lib/{interfaces → mocha}/featureConfig.js +33 -13
- package/lib/{interfaces → mocha}/gherkin.js +75 -45
- package/lib/mocha/hooks.js +121 -0
- package/lib/mocha/index.js +21 -0
- package/lib/mocha/inject.js +46 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +32 -8
- package/lib/mocha/suite.js +89 -0
- package/lib/mocha/test.js +178 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +229 -0
- package/lib/output.js +86 -64
- package/lib/parser.js +44 -44
- package/lib/pause.js +160 -139
- package/lib/plugin/analyze.js +403 -0
- package/lib/plugin/{autoLogin.js → auth.js} +137 -43
- package/lib/plugin/autoDelay.js +19 -15
- package/lib/plugin/coverage.js +22 -27
- package/lib/plugin/customLocator.js +5 -5
- package/lib/plugin/customReporter.js +53 -0
- package/lib/plugin/heal.js +49 -17
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +60 -19
- package/lib/plugin/screenshotOnFail.js +80 -83
- package/lib/plugin/stepByStepReport.js +70 -31
- package/lib/plugin/stepTimeout.js +7 -13
- package/lib/plugin/subtitles.js +10 -9
- package/lib/recorder.js +167 -126
- package/lib/rerun.js +94 -50
- package/lib/result.js +161 -0
- package/lib/secret.js +18 -17
- package/lib/session.js +95 -89
- 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 +18 -332
- package/lib/steps.js +54 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/timeout.js +60 -0
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils.js +354 -250
- package/lib/workerStorage.js +16 -16
- package/lib/workers.js +366 -282
- package/package.json +107 -95
- package/translations/de-DE.js +5 -4
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +23 -9
- package/translations/it-IT.js +5 -4
- package/translations/ja-JP.js +5 -4
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +5 -4
- package/translations/pt-BR.js +5 -4
- package/translations/ru-RU.js +5 -4
- package/translations/utils.js +18 -0
- package/translations/zh-CN.js +5 -4
- package/translations/zh-TW.js +5 -4
- package/typings/index.d.ts +177 -186
- package/typings/promiseBasedTypes.d.ts +3573 -5941
- package/typings/types.d.ts +4042 -6370
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/Nightmare.js +0 -1504
- package/lib/helper/Protractor.js +0 -1863
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/helper/TestCafe.js +0 -1414
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -62
- package/lib/interfaces/bdd.js +0 -81
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/commentStep.js +0 -136
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -127
- package/lib/plugin/selenoid.js +0 -384
- package/lib/plugin/standardActingHelpers.js +0 -3
- package/lib/plugin/tryTo.js +0 -115
- package/lib/plugin/wdio.js +0 -249
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
- package/lib/within.js +0 -70
package/lib/result.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { serializeTest } from './mocha/test.js'
|
|
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
|
+
export default Result
|
package/lib/secret.js
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
import { deepClone } from './utils.js'
|
|
2
2
|
|
|
3
|
-
const maskedString = '*****'
|
|
3
|
+
const maskedString = '*****'
|
|
4
4
|
|
|
5
5
|
/** @param {string} secret */
|
|
6
6
|
class Secret {
|
|
7
7
|
constructor(secret) {
|
|
8
|
-
this._secret = secret
|
|
8
|
+
this._secret = secret
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/** @returns {string} */
|
|
12
12
|
toString() {
|
|
13
|
-
return this._secret
|
|
13
|
+
return this._secret
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
getMasked() {
|
|
17
|
-
return maskedString
|
|
17
|
+
return maskedString
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -23,11 +23,11 @@ class Secret {
|
|
|
23
23
|
*/
|
|
24
24
|
static secret(secret) {
|
|
25
25
|
if (typeof secret === 'object') {
|
|
26
|
-
const fields = Array.from(arguments)
|
|
27
|
-
fields.shift()
|
|
28
|
-
return secretObject(secret, fields)
|
|
26
|
+
const fields = Array.from(arguments)
|
|
27
|
+
fields.shift()
|
|
28
|
+
return secretObject(secret, fields)
|
|
29
29
|
}
|
|
30
|
-
return new Secret(secret)
|
|
30
|
+
return new Secret(secret)
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -36,16 +36,17 @@ function secretObject(obj, fieldsToHide = []) {
|
|
|
36
36
|
get(obj, prop) {
|
|
37
37
|
if (prop === 'toString') {
|
|
38
38
|
return function () {
|
|
39
|
-
const maskedObject = deepClone(obj)
|
|
40
|
-
fieldsToHide.forEach(f => maskedObject[f] = maskedString)
|
|
41
|
-
return JSON.stringify(maskedObject)
|
|
42
|
-
}
|
|
39
|
+
const maskedObject = deepClone(obj)
|
|
40
|
+
fieldsToHide.forEach(f => (maskedObject[f] = maskedString))
|
|
41
|
+
return JSON.stringify(maskedObject)
|
|
42
|
+
}
|
|
43
43
|
}
|
|
44
|
-
return fieldsToHide.includes(prop) ? new Secret(obj[prop]) : obj[prop]
|
|
44
|
+
return fieldsToHide.includes(prop) ? new Secret(obj[prop]) : obj[prop]
|
|
45
45
|
},
|
|
46
|
-
}
|
|
46
|
+
}
|
|
47
47
|
|
|
48
|
-
return new Proxy(obj, handler)
|
|
48
|
+
return new Proxy(obj, handler)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
export default Secret
|
|
52
|
+
export const secret = Secret.secret
|
package/lib/session.js
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const sessionColors = [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
'red',
|
|
12
|
-
'magenta',
|
|
13
|
-
'yellow',
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
const savedSessions = {};
|
|
1
|
+
import recorder from './recorder.js'
|
|
2
|
+
import container from './container.js'
|
|
3
|
+
import event from './event.js'
|
|
4
|
+
import output from './output.js'
|
|
5
|
+
import store from './store.js'
|
|
6
|
+
import { isAsyncFunction } from './utils.js'
|
|
7
|
+
|
|
8
|
+
const sessionColors = ['cyan', 'blue', 'red', 'magenta', 'yellow']
|
|
9
|
+
|
|
10
|
+
const savedSessions = {}
|
|
17
11
|
|
|
18
12
|
/**
|
|
19
13
|
* @param {CodeceptJS.LocatorOrString} sessionName
|
|
@@ -24,109 +18,121 @@ const savedSessions = {};
|
|
|
24
18
|
function session(sessionName, config, fn) {
|
|
25
19
|
if (typeof config === 'function') {
|
|
26
20
|
if (typeof fn === 'function') {
|
|
27
|
-
config = config()
|
|
21
|
+
config = config()
|
|
28
22
|
} else {
|
|
29
23
|
// no config but with function
|
|
30
|
-
fn = config
|
|
31
|
-
config = {}
|
|
24
|
+
fn = config
|
|
25
|
+
config = {}
|
|
32
26
|
}
|
|
33
27
|
}
|
|
34
28
|
// session helpers won't use beforeSuite and afterSuite hooks...
|
|
35
29
|
// restart: false options are not allowed as well
|
|
36
30
|
// but those helpers should be already started so inside listener/helpers.js the `_init` method should already be called
|
|
37
31
|
|
|
38
|
-
const helpers = container.helpers()
|
|
32
|
+
const helpers = container.helpers()
|
|
39
33
|
|
|
40
34
|
if (!savedSessions[sessionName]) {
|
|
41
35
|
for (const helper in helpers) {
|
|
42
|
-
if (!helpers[helper]._session) continue
|
|
36
|
+
if (!helpers[helper]._session) continue
|
|
43
37
|
savedSessions[sessionName] = {
|
|
44
38
|
start: () => null,
|
|
45
39
|
stop: () => null,
|
|
46
40
|
loadVars: () => null,
|
|
47
41
|
restoreVars: () => null,
|
|
48
42
|
...(store.dryRun ? {} : helpers[helper]._session()),
|
|
49
|
-
}
|
|
50
|
-
break
|
|
43
|
+
}
|
|
44
|
+
break
|
|
51
45
|
}
|
|
52
46
|
|
|
53
47
|
const closeBrowsers = () => {
|
|
54
|
-
const session = savedSessions[sessionName]
|
|
55
|
-
if (!session) return
|
|
56
|
-
session.stop(session.vars)
|
|
57
|
-
delete savedSessions[sessionName]
|
|
58
|
-
}
|
|
48
|
+
const session = savedSessions[sessionName]
|
|
49
|
+
if (!session) return
|
|
50
|
+
session.stop(session.vars)
|
|
51
|
+
delete savedSessions[sessionName]
|
|
52
|
+
}
|
|
59
53
|
|
|
60
54
|
event.dispatcher.once(event.test.after, () => {
|
|
61
|
-
recorder.add('close session browsers', closeBrowsers)
|
|
62
|
-
})
|
|
55
|
+
recorder.add('close session browsers', closeBrowsers)
|
|
56
|
+
})
|
|
63
57
|
|
|
64
58
|
if (!savedSessions[sessionName]) {
|
|
65
|
-
throw new Error('Configured helpers do not support starting sessions. Please use a helper with session support.')
|
|
59
|
+
throw new Error('Configured helpers do not support starting sessions. Please use a helper with session support.')
|
|
66
60
|
}
|
|
67
61
|
|
|
68
62
|
recorder.add('save vars', async () => {
|
|
69
|
-
savedSessions[sessionName].vars = await savedSessions[sessionName].start(sessionName, config)
|
|
70
|
-
})
|
|
63
|
+
savedSessions[sessionName].vars = await savedSessions[sessionName].start(sessionName, config)
|
|
64
|
+
})
|
|
71
65
|
}
|
|
72
66
|
|
|
73
67
|
// pick random color
|
|
74
|
-
const color = sessionColors[Object.keys(savedSessions).indexOf(sessionName) % sessionColors.length]
|
|
75
|
-
|
|
76
|
-
const addContextToStep = (step) => {
|
|
77
|
-
step.actor = `${output.colors[color](sessionName)}: I`;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
if (!fn) return; // no current session steps
|
|
81
|
-
|
|
82
|
-
return recorder.add('register session wrapper', async () => {
|
|
83
|
-
const session = savedSessions[sessionName];
|
|
84
|
-
recorder.session.start(`session:${sessionName}`);
|
|
85
|
-
event.dispatcher.on(event.step.after, addContextToStep);
|
|
86
|
-
recorder.add('switch to browser', () => {
|
|
87
|
-
const session = savedSessions[sessionName];
|
|
88
|
-
return session.loadVars(session.vars);
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const finalize = () => {
|
|
92
|
-
recorder.add('Finalize session', async () => {
|
|
93
|
-
output.stepShift = 0;
|
|
94
|
-
event.dispatcher.removeListener(event.step.after, addContextToStep);
|
|
95
|
-
await session.restoreVars();
|
|
96
|
-
recorder.session.restore(`session:${sessionName}`);
|
|
97
|
-
});
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// Indicate when executing this function that we are in a session
|
|
101
|
-
if (isAsyncFunction(fn)) {
|
|
102
|
-
return fn.apply(null).then((res) => {
|
|
103
|
-
finalize();
|
|
104
|
-
return recorder.promise().then(() => res);
|
|
105
|
-
}).catch((e) => {
|
|
106
|
-
output.stepShift = 0;
|
|
107
|
-
session.restoreVars(sessionName);
|
|
108
|
-
event.dispatcher.removeListener(event.step.after, addContextToStep);
|
|
109
|
-
recorder.throw(e);
|
|
110
|
-
return recorder.promise();
|
|
111
|
-
});
|
|
112
|
-
}
|
|
68
|
+
const color = sessionColors[Object.keys(savedSessions).indexOf(sessionName) % sessionColors.length]
|
|
113
69
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
70
|
+
const addContextToStep = step => {
|
|
71
|
+
step.actor = `${output.colors[color](sessionName)}: I`
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!fn) return // no current session steps
|
|
75
|
+
|
|
76
|
+
return recorder.add(
|
|
77
|
+
'register session wrapper',
|
|
78
|
+
async () => {
|
|
79
|
+
const session = savedSessions[sessionName]
|
|
80
|
+
if (!session) {
|
|
81
|
+
throw new Error(`Session "${sessionName}" not found. It may have been closed already.`)
|
|
82
|
+
}
|
|
83
|
+
recorder.session.start(`session:${sessionName}`)
|
|
84
|
+
event.dispatcher.on(event.step.after, addContextToStep)
|
|
85
|
+
recorder.add('switch to browser', () => {
|
|
86
|
+
const session = savedSessions[sessionName]
|
|
87
|
+
return session.loadVars(session.vars)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const finalize = () => {
|
|
91
|
+
recorder.add('Finalize session', async () => {
|
|
92
|
+
output.stepShift = 0
|
|
93
|
+
event.dispatcher.removeListener(event.step.after, addContextToStep)
|
|
94
|
+
await session.restoreVars()
|
|
95
|
+
recorder.session.restore(`session:${sessionName}`)
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Indicate when executing this function that we are in a session
|
|
100
|
+
if (isAsyncFunction(fn)) {
|
|
101
|
+
return fn
|
|
102
|
+
.apply(null)
|
|
103
|
+
.then(async res => {
|
|
104
|
+
finalize()
|
|
105
|
+
await recorder.promise()
|
|
106
|
+
return res
|
|
107
|
+
})
|
|
108
|
+
.catch(e => {
|
|
109
|
+
output.stepShift = 0
|
|
110
|
+
session.restoreVars(sessionName)
|
|
111
|
+
event.dispatcher.removeListener(event.step.after, addContextToStep)
|
|
112
|
+
recorder.throw(e)
|
|
113
|
+
return recorder.promise()
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
let res
|
|
118
|
+
try {
|
|
119
|
+
res = fn.apply(null)
|
|
120
|
+
} catch (err) {
|
|
121
|
+
recorder.throw(err)
|
|
122
|
+
} finally {
|
|
123
|
+
recorder.catch(e => {
|
|
124
|
+
session.restoreVars(sessionName)
|
|
125
|
+
output.stepShift = 0
|
|
126
|
+
event.dispatcher.removeListener(event.step.after, addContextToStep)
|
|
127
|
+
throw e
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
finalize()
|
|
131
|
+
return recorder.promise().then(() => res)
|
|
132
|
+
},
|
|
133
|
+
false,
|
|
134
|
+
false,
|
|
135
|
+
)
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
|
|
138
|
+
export default session
|
package/lib/step/base.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import color from 'chalk'
|
|
2
|
+
import Secret from '../secret.js'
|
|
3
|
+
import { getCurrentTimeout } from '../timeout.js'
|
|
4
|
+
import { ucfirst, humanizeString, serializeError } from '../utils.js'
|
|
5
|
+
import recordStep from './record.js'
|
|
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
|
+
export default Step
|