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/assert/include.js
CHANGED
|
@@ -10,7 +10,7 @@ class InclusionAssertion extends Assertion {
|
|
|
10
10
|
params.jar = params.jar || 'string'
|
|
11
11
|
const comparator = function (needle, haystack) {
|
|
12
12
|
if (Array.isArray(haystack)) {
|
|
13
|
-
return haystack.filter(
|
|
13
|
+
return haystack.filter(part => part.indexOf(needle) >= 0).length > 0
|
|
14
14
|
}
|
|
15
15
|
return haystack.indexOf(needle) >= 0
|
|
16
16
|
}
|
|
@@ -28,9 +28,7 @@ class InclusionAssertion extends Assertion {
|
|
|
28
28
|
this.params.haystack = this.params.haystack.join('\n___(next element)___\n')
|
|
29
29
|
}
|
|
30
30
|
err.cliMessage = function () {
|
|
31
|
-
const msg = this.template
|
|
32
|
-
.replace('{{jar}}', output.colors.bold('{{jar}}'))
|
|
33
|
-
.replace('{{needle}}', output.colors.bold('{{needle}}'))
|
|
31
|
+
const msg = this.template.replace('{{jar}}', output.colors.bold('{{jar}}')).replace('{{needle}}', output.colors.bold('{{needle}}'))
|
|
34
32
|
return template(msg, this.params)
|
|
35
33
|
}
|
|
36
34
|
return err
|
|
@@ -66,11 +64,11 @@ class InclusionAssertion extends Assertion {
|
|
|
66
64
|
|
|
67
65
|
module.exports = {
|
|
68
66
|
Assertion: InclusionAssertion,
|
|
69
|
-
includes:
|
|
67
|
+
includes: needleType => {
|
|
70
68
|
needleType = needleType || 'string'
|
|
71
69
|
return new InclusionAssertion({ jar: needleType })
|
|
72
70
|
},
|
|
73
|
-
fileIncludes:
|
|
71
|
+
fileIncludes: file => new InclusionAssertion({ file, jar: 'file {{file}}' }),
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
function escapeRegExp(str) {
|
package/lib/assert/throws.js
CHANGED
|
@@ -11,10 +11,8 @@ function errorThrown(actual, expected) {
|
|
|
11
11
|
throw new Error(`Expected error to be thrown with message ${expected} while '${msg}' caught`)
|
|
12
12
|
}
|
|
13
13
|
if (typeof expected === 'object') {
|
|
14
|
-
if (actual.constructor.name !== expected.constructor.name)
|
|
15
|
-
|
|
16
|
-
if (expected.message && expected.message !== msg)
|
|
17
|
-
throw new Error(`Expected error to be thrown with message ${expected.message} while '${msg}' caught`)
|
|
14
|
+
if (actual.constructor.name !== expected.constructor.name) throw new Error(`Expected ${expected} error to be thrown but ${actual} was caught`)
|
|
15
|
+
if (expected.message && expected.message !== msg) throw new Error(`Expected error to be thrown with message ${expected.message} while '${msg}' caught`)
|
|
18
16
|
}
|
|
19
17
|
return null
|
|
20
18
|
}
|
package/lib/assert/truth.js
CHANGED
|
@@ -5,9 +5,9 @@ const output = require('../output')
|
|
|
5
5
|
|
|
6
6
|
class TruthAssertion extends Assertion {
|
|
7
7
|
constructor(params) {
|
|
8
|
-
super(
|
|
8
|
+
super(value => {
|
|
9
9
|
if (Array.isArray(value)) {
|
|
10
|
-
return value.filter(
|
|
10
|
+
return value.filter(val => !!val).length > 0
|
|
11
11
|
}
|
|
12
12
|
return !!value
|
|
13
13
|
}, params)
|
package/lib/codecept.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
const { existsSync, readFileSync } = require('fs')
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
1
|
+
const { existsSync, readFileSync } = require('fs')
|
|
2
|
+
const { globSync } = require('glob')
|
|
3
|
+
const shuffle = require('lodash.shuffle')
|
|
4
|
+
const fsPath = require('path')
|
|
5
|
+
const { resolve } = require('path')
|
|
6
|
+
|
|
7
|
+
const container = require('./container')
|
|
8
|
+
const Config = require('./config')
|
|
9
|
+
const event = require('./event')
|
|
10
|
+
const runHook = require('./hooks')
|
|
11
|
+
const output = require('./output')
|
|
12
|
+
const { emptyFolder } = require('./utils')
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* CodeceptJS runner
|
|
@@ -22,10 +23,10 @@ class Codecept {
|
|
|
22
23
|
* @param {*} opts
|
|
23
24
|
*/
|
|
24
25
|
constructor(config, opts) {
|
|
25
|
-
this.config = Config.create(config)
|
|
26
|
-
this.opts = opts
|
|
27
|
-
this.testFiles = new Array(0)
|
|
28
|
-
this.requireModules(config.require)
|
|
26
|
+
this.config = Config.create(config)
|
|
27
|
+
this.opts = opts
|
|
28
|
+
this.testFiles = new Array(0)
|
|
29
|
+
this.requireModules(config.require)
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -35,13 +36,13 @@ class Codecept {
|
|
|
35
36
|
*/
|
|
36
37
|
requireModules(requiringModules) {
|
|
37
38
|
if (requiringModules) {
|
|
38
|
-
requiringModules.forEach(
|
|
39
|
-
const isLocalFile = existsSync(requiredModule) || existsSync(`${requiredModule}.js`)
|
|
39
|
+
requiringModules.forEach(requiredModule => {
|
|
40
|
+
const isLocalFile = existsSync(requiredModule) || existsSync(`${requiredModule}.js`)
|
|
40
41
|
if (isLocalFile) {
|
|
41
|
-
requiredModule = resolve(requiredModule)
|
|
42
|
+
requiredModule = resolve(requiredModule)
|
|
42
43
|
}
|
|
43
|
-
require(requiredModule)
|
|
44
|
-
})
|
|
44
|
+
require(requiredModule)
|
|
45
|
+
})
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
@@ -52,10 +53,10 @@ class Codecept {
|
|
|
52
53
|
* @param {string} dir
|
|
53
54
|
*/
|
|
54
55
|
init(dir) {
|
|
55
|
-
this.initGlobals(dir)
|
|
56
|
+
this.initGlobals(dir)
|
|
56
57
|
// initializing listeners
|
|
57
|
-
container.create(this.config, this.opts)
|
|
58
|
-
this.runHooks()
|
|
58
|
+
container.create(this.config, this.opts)
|
|
59
|
+
this.runHooks()
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
/**
|
|
@@ -64,34 +65,37 @@ class Codecept {
|
|
|
64
65
|
* @param {string} dir
|
|
65
66
|
*/
|
|
66
67
|
initGlobals(dir) {
|
|
67
|
-
global.codecept_dir = dir
|
|
68
|
-
global.output_dir = fsPath.resolve(dir, this.config.output)
|
|
68
|
+
global.codecept_dir = dir
|
|
69
|
+
global.output_dir = fsPath.resolve(dir, this.config.output)
|
|
69
70
|
|
|
70
|
-
if (this.config.emptyOutputFolder) emptyFolder(global.output_dir)
|
|
71
|
+
if (this.config.emptyOutputFolder) emptyFolder(global.output_dir)
|
|
71
72
|
|
|
72
73
|
if (!this.config.noGlobals) {
|
|
73
|
-
global.Helper = global.codecept_helper = require('@codeceptjs/helper')
|
|
74
|
-
global.actor = global.codecept_actor = require('./actor')
|
|
75
|
-
global.pause = require('./pause')
|
|
76
|
-
global.within = require('./within')
|
|
77
|
-
global.session = require('./session')
|
|
78
|
-
global.DataTable = require('./data/table')
|
|
79
|
-
global.locate = locator => require('./locator').build(locator)
|
|
80
|
-
global.inject = container.support
|
|
81
|
-
global.share = container.share
|
|
82
|
-
global.secret = require('./secret').secret
|
|
83
|
-
global.codecept_debug = output.debug
|
|
84
|
-
global.codeceptjs = require('./index')
|
|
74
|
+
global.Helper = global.codecept_helper = require('@codeceptjs/helper')
|
|
75
|
+
global.actor = global.codecept_actor = require('./actor')
|
|
76
|
+
global.pause = require('./pause')
|
|
77
|
+
global.within = require('./within')
|
|
78
|
+
global.session = require('./session')
|
|
79
|
+
global.DataTable = require('./data/table')
|
|
80
|
+
global.locate = locator => require('./locator').build(locator)
|
|
81
|
+
global.inject = container.support
|
|
82
|
+
global.share = container.share
|
|
83
|
+
global.secret = require('./secret').secret
|
|
84
|
+
global.codecept_debug = output.debug
|
|
85
|
+
global.codeceptjs = require('./index') // load all objects
|
|
85
86
|
|
|
86
87
|
// BDD
|
|
87
|
-
const stepDefinitions = require('./
|
|
88
|
-
global.Given = stepDefinitions.Given
|
|
89
|
-
global.When = stepDefinitions.When
|
|
90
|
-
global.Then = stepDefinitions.Then
|
|
91
|
-
global.DefineParameterType = stepDefinitions.defineParameterType
|
|
88
|
+
const stepDefinitions = require('./mocha/bdd')
|
|
89
|
+
global.Given = stepDefinitions.Given
|
|
90
|
+
global.When = stepDefinitions.When
|
|
91
|
+
global.Then = stepDefinitions.Then
|
|
92
|
+
global.DefineParameterType = stepDefinitions.defineParameterType
|
|
92
93
|
|
|
93
94
|
// debug mode
|
|
94
|
-
global.debugMode = false
|
|
95
|
+
global.debugMode = false
|
|
96
|
+
|
|
97
|
+
// mask sensitive data
|
|
98
|
+
global.maskSensitiveData = this.config.maskSensitiveData || false
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -100,16 +104,19 @@ class Codecept {
|
|
|
100
104
|
*/
|
|
101
105
|
runHooks() {
|
|
102
106
|
// default hooks
|
|
103
|
-
runHook(require('./listener/
|
|
104
|
-
runHook(require('./listener/
|
|
105
|
-
runHook(require('./listener/config'))
|
|
106
|
-
runHook(require('./listener/
|
|
107
|
-
runHook(require('./listener/
|
|
108
|
-
runHook(require('./listener/
|
|
109
|
-
runHook(require('./listener/
|
|
107
|
+
runHook(require('./listener/store'))
|
|
108
|
+
runHook(require('./listener/steps'))
|
|
109
|
+
runHook(require('./listener/config'))
|
|
110
|
+
runHook(require('./listener/result'))
|
|
111
|
+
runHook(require('./listener/helpers'))
|
|
112
|
+
runHook(require('./listener/globalTimeout'))
|
|
113
|
+
runHook(require('./listener/globalRetry'))
|
|
114
|
+
runHook(require('./listener/retryEnhancer'))
|
|
115
|
+
runHook(require('./listener/exit'))
|
|
116
|
+
runHook(require('./listener/emptyRun'))
|
|
110
117
|
|
|
111
118
|
// custom hooks (previous iteration of plugins)
|
|
112
|
-
this.config.hooks.forEach(hook => runHook(hook))
|
|
119
|
+
this.config.hooks.forEach(hook => runHook(hook))
|
|
113
120
|
}
|
|
114
121
|
|
|
115
122
|
/**
|
|
@@ -117,7 +124,7 @@ class Codecept {
|
|
|
117
124
|
*
|
|
118
125
|
*/
|
|
119
126
|
async bootstrap() {
|
|
120
|
-
return runHook(this.config.bootstrap, 'bootstrap')
|
|
127
|
+
return runHook(this.config.bootstrap, 'bootstrap')
|
|
121
128
|
}
|
|
122
129
|
|
|
123
130
|
/**
|
|
@@ -125,7 +132,7 @@ class Codecept {
|
|
|
125
132
|
|
|
126
133
|
*/
|
|
127
134
|
async teardown() {
|
|
128
|
-
return runHook(this.config.teardown, 'teardown')
|
|
135
|
+
return runHook(this.config.teardown, 'teardown')
|
|
129
136
|
}
|
|
130
137
|
|
|
131
138
|
/**
|
|
@@ -136,45 +143,91 @@ class Codecept {
|
|
|
136
143
|
loadTests(pattern) {
|
|
137
144
|
const options = {
|
|
138
145
|
cwd: global.codecept_dir,
|
|
139
|
-
}
|
|
146
|
+
}
|
|
140
147
|
|
|
141
|
-
let patterns = [pattern]
|
|
148
|
+
let patterns = [pattern]
|
|
142
149
|
if (!pattern) {
|
|
143
|
-
patterns = []
|
|
150
|
+
patterns = []
|
|
144
151
|
|
|
145
152
|
// If the user wants to test a specific set of test files as an array or string.
|
|
146
153
|
if (this.config.tests && !this.opts.features) {
|
|
147
154
|
if (Array.isArray(this.config.tests)) {
|
|
148
|
-
patterns.push(...this.config.tests)
|
|
155
|
+
patterns.push(...this.config.tests)
|
|
149
156
|
} else {
|
|
150
|
-
patterns.push(this.config.tests)
|
|
157
|
+
patterns.push(this.config.tests)
|
|
151
158
|
}
|
|
152
159
|
}
|
|
153
160
|
|
|
154
161
|
if (this.config.gherkin.features && !this.opts.tests) {
|
|
155
162
|
if (Array.isArray(this.config.gherkin.features)) {
|
|
156
163
|
this.config.gherkin.features.forEach(feature => {
|
|
157
|
-
patterns.push(feature)
|
|
158
|
-
})
|
|
164
|
+
patterns.push(feature)
|
|
165
|
+
})
|
|
159
166
|
} else {
|
|
160
|
-
patterns.push(this.config.gherkin.features)
|
|
167
|
+
patterns.push(this.config.gherkin.features)
|
|
161
168
|
}
|
|
162
169
|
}
|
|
163
170
|
}
|
|
164
171
|
|
|
165
172
|
for (pattern of patterns) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
this.testFiles.
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
if (pattern) {
|
|
174
|
+
globSync(pattern, options).forEach(file => {
|
|
175
|
+
if (file.includes('node_modules')) return
|
|
176
|
+
if (!fsPath.isAbsolute(file)) {
|
|
177
|
+
file = fsPath.join(global.codecept_dir, file)
|
|
178
|
+
}
|
|
179
|
+
if (!this.testFiles.includes(fsPath.resolve(file))) {
|
|
180
|
+
this.testFiles.push(fsPath.resolve(file))
|
|
181
|
+
}
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (this.opts.shuffle) {
|
|
187
|
+
this.testFiles = shuffle(this.testFiles)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (this.opts.shard) {
|
|
191
|
+
this.testFiles = this._applySharding(this.testFiles, this.opts.shard)
|
|
175
192
|
}
|
|
176
193
|
}
|
|
177
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Apply sharding to test files based on shard configuration
|
|
197
|
+
*
|
|
198
|
+
* @param {Array<string>} testFiles - Array of test file paths
|
|
199
|
+
* @param {string} shardConfig - Shard configuration in format "index/total" (e.g., "1/4")
|
|
200
|
+
* @returns {Array<string>} - Filtered array of test files for this shard
|
|
201
|
+
*/
|
|
202
|
+
_applySharding(testFiles, shardConfig) {
|
|
203
|
+
const shardMatch = shardConfig.match(/^(\d+)\/(\d+)$/)
|
|
204
|
+
if (!shardMatch) {
|
|
205
|
+
throw new Error('Invalid shard format. Expected format: "index/total" (e.g., "1/4")')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const shardIndex = parseInt(shardMatch[1], 10)
|
|
209
|
+
const shardTotal = parseInt(shardMatch[2], 10)
|
|
210
|
+
|
|
211
|
+
if (shardTotal < 1) {
|
|
212
|
+
throw new Error('Shard total must be at least 1')
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (shardIndex < 1 || shardIndex > shardTotal) {
|
|
216
|
+
throw new Error(`Shard index ${shardIndex} must be between 1 and ${shardTotal}`)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (testFiles.length === 0) {
|
|
220
|
+
return testFiles
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Calculate which tests belong to this shard
|
|
224
|
+
const shardSize = Math.ceil(testFiles.length / shardTotal)
|
|
225
|
+
const startIndex = (shardIndex - 1) * shardSize
|
|
226
|
+
const endIndex = Math.min(startIndex + shardSize, testFiles.length)
|
|
227
|
+
|
|
228
|
+
return testFiles.slice(startIndex, endIndex)
|
|
229
|
+
}
|
|
230
|
+
|
|
178
231
|
/**
|
|
179
232
|
* Run a specific test or all loaded tests.
|
|
180
233
|
*
|
|
@@ -182,34 +235,36 @@ class Codecept {
|
|
|
182
235
|
* @returns {Promise<void>}
|
|
183
236
|
*/
|
|
184
237
|
async run(test) {
|
|
238
|
+
await container.started()
|
|
239
|
+
|
|
185
240
|
return new Promise((resolve, reject) => {
|
|
186
|
-
const mocha = container.mocha()
|
|
187
|
-
mocha.files = this.testFiles
|
|
241
|
+
const mocha = container.mocha()
|
|
242
|
+
mocha.files = this.testFiles
|
|
188
243
|
if (test) {
|
|
189
244
|
if (!fsPath.isAbsolute(test)) {
|
|
190
|
-
test = fsPath.join(global.codecept_dir, test)
|
|
245
|
+
test = fsPath.join(global.codecept_dir, test)
|
|
191
246
|
}
|
|
192
|
-
mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test)
|
|
247
|
+
mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test)
|
|
193
248
|
}
|
|
194
249
|
const done = () => {
|
|
195
|
-
event.emit(event.all.result,
|
|
196
|
-
event.emit(event.all.after, this)
|
|
197
|
-
resolve()
|
|
198
|
-
}
|
|
250
|
+
event.emit(event.all.result, container.result())
|
|
251
|
+
event.emit(event.all.after, this)
|
|
252
|
+
resolve()
|
|
253
|
+
}
|
|
199
254
|
|
|
200
255
|
try {
|
|
201
|
-
event.emit(event.all.before, this)
|
|
202
|
-
mocha.run(() => done())
|
|
256
|
+
event.emit(event.all.before, this)
|
|
257
|
+
mocha.run(() => done())
|
|
203
258
|
} catch (e) {
|
|
204
|
-
output.error(e.stack)
|
|
205
|
-
reject(e)
|
|
259
|
+
output.error(e.stack)
|
|
260
|
+
reject(e)
|
|
206
261
|
}
|
|
207
|
-
})
|
|
262
|
+
})
|
|
208
263
|
}
|
|
209
264
|
|
|
210
265
|
static version() {
|
|
211
|
-
return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version
|
|
266
|
+
return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version
|
|
212
267
|
}
|
|
213
268
|
}
|
|
214
269
|
|
|
215
|
-
module.exports = Codecept
|
|
270
|
+
module.exports = Codecept
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
const { getConfig, getTestRoot } = require('./utils')
|
|
2
|
+
const Codecept = require('../codecept')
|
|
3
|
+
const output = require('../output')
|
|
4
|
+
const store = require('../store')
|
|
5
|
+
const container = require('../container')
|
|
6
|
+
const figures = require('figures')
|
|
7
|
+
const chalk = require('chalk')
|
|
8
|
+
const { createTest } = require('../mocha/test')
|
|
9
|
+
const { getMachineInfo } = require('./info')
|
|
10
|
+
const definitions = require('./definitions')
|
|
11
|
+
|
|
12
|
+
module.exports = async function (options) {
|
|
13
|
+
const configFile = options.config
|
|
14
|
+
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
output.error("Something went wrong. Checks didn't pass and timed out. Please check your config and helpers.")
|
|
17
|
+
process.exit(1)
|
|
18
|
+
}, options.timeout || 50000)
|
|
19
|
+
|
|
20
|
+
const checks = {
|
|
21
|
+
config: false,
|
|
22
|
+
container: false,
|
|
23
|
+
pageObjects: false,
|
|
24
|
+
plugins: false,
|
|
25
|
+
ai: true, // we don't need to check AI
|
|
26
|
+
helpers: false,
|
|
27
|
+
setup: false,
|
|
28
|
+
teardown: false,
|
|
29
|
+
tests: false,
|
|
30
|
+
def: false,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const testRoot = getTestRoot(configFile)
|
|
34
|
+
let config = getConfig(configFile)
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
config = getConfig(configFile)
|
|
38
|
+
checks['config'] = true
|
|
39
|
+
} catch (err) {
|
|
40
|
+
checks['config'] = err
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
printCheck('config', checks['config'], config.name)
|
|
44
|
+
|
|
45
|
+
let codecept
|
|
46
|
+
try {
|
|
47
|
+
codecept = new Codecept(config, options)
|
|
48
|
+
codecept.init(testRoot)
|
|
49
|
+
await container.started()
|
|
50
|
+
checks.container = true
|
|
51
|
+
} catch (err) {
|
|
52
|
+
checks.container = err
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const standardActingHelpers = container.STANDARD_ACTING_HELPERS
|
|
56
|
+
|
|
57
|
+
printCheck('container', checks['container'])
|
|
58
|
+
|
|
59
|
+
if (codecept) {
|
|
60
|
+
try {
|
|
61
|
+
if (options.bootstrap) await codecept.bootstrap()
|
|
62
|
+
checks.bootstrap = true
|
|
63
|
+
} catch (err) {
|
|
64
|
+
checks.bootstrap = err
|
|
65
|
+
}
|
|
66
|
+
printCheck('bootstrap', checks['bootstrap'], options.bootstrap ? 'Bootstrap was executed' : 'No bootstrap command')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let numTests = 0
|
|
70
|
+
if (codecept) {
|
|
71
|
+
try {
|
|
72
|
+
codecept.loadTests()
|
|
73
|
+
const mocha = container.mocha()
|
|
74
|
+
mocha.files = codecept.testFiles
|
|
75
|
+
mocha.loadFiles()
|
|
76
|
+
mocha.suite.suites.forEach(suite => {
|
|
77
|
+
numTests += suite.tests.length
|
|
78
|
+
})
|
|
79
|
+
if (numTests > 0) {
|
|
80
|
+
checks.tests = true
|
|
81
|
+
} else {
|
|
82
|
+
throw new Error('No tests found')
|
|
83
|
+
}
|
|
84
|
+
} catch (err) {
|
|
85
|
+
checks.tests = err
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (config?.ai?.request) {
|
|
90
|
+
checks.ai = true
|
|
91
|
+
printCheck('ai', checks['ai'], 'Configuration is enabled, request function is set')
|
|
92
|
+
} else {
|
|
93
|
+
printCheck('ai', checks['ai'], 'Disabled')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
printCheck('tests', checks['tests'], `Total: ${numTests} tests`)
|
|
97
|
+
|
|
98
|
+
store.dryRun = true
|
|
99
|
+
|
|
100
|
+
const helpers = container.helpers()
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
if (!Object.keys(helpers).length) throw new Error('No helpers found')
|
|
104
|
+
// load helpers
|
|
105
|
+
for (const helper of Object.values(helpers)) {
|
|
106
|
+
if (helper._init) helper._init()
|
|
107
|
+
}
|
|
108
|
+
checks.helpers = true
|
|
109
|
+
} catch (err) {
|
|
110
|
+
checks.helpers = err
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
printCheck('helpers', checks['helpers'], `${Object.keys(helpers).join(', ')}`)
|
|
114
|
+
|
|
115
|
+
const pageObjects = container.support()
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
if (Object.keys(pageObjects).length) {
|
|
119
|
+
for (const pageObject of Object.values(pageObjects)) {
|
|
120
|
+
pageObject.name
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
checks.pageObjects = true
|
|
124
|
+
} catch (err) {
|
|
125
|
+
checks.pageObjects = err
|
|
126
|
+
}
|
|
127
|
+
printCheck('page objects', checks['pageObjects'], `Total: ${Object.keys(pageObjects).length} support objects`)
|
|
128
|
+
|
|
129
|
+
checks.plugins = true // how to check plugins?
|
|
130
|
+
printCheck('plugins', checks['plugins'], Object.keys(container.plugins()).join(', '))
|
|
131
|
+
|
|
132
|
+
if (Object.keys(helpers).length) {
|
|
133
|
+
const suite = container.mocha().suite
|
|
134
|
+
const test = createTest('test', () => {})
|
|
135
|
+
checks.setup = true
|
|
136
|
+
for (const helper of Object.values(helpers)) {
|
|
137
|
+
try {
|
|
138
|
+
if (helper._beforeSuite) await helper._beforeSuite(suite)
|
|
139
|
+
if (helper._before) await helper._before(test)
|
|
140
|
+
} catch (err) {
|
|
141
|
+
err.message = `${helper.constructor.name} helper: ${err.message}`
|
|
142
|
+
if (checks.setup instanceof Error) err.message = `${err.message}\n\n${checks.setup?.message || ''}`.trim()
|
|
143
|
+
checks.setup = err
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
printCheck('Helpers Before', checks['setup'], standardActingHelpers.some(h => Object.keys(helpers).includes(h)) ? 'Initializing browser' : '')
|
|
148
|
+
|
|
149
|
+
checks.teardown = true
|
|
150
|
+
for (const helper of Object.values(helpers).reverse()) {
|
|
151
|
+
try {
|
|
152
|
+
if (helper._passed) await helper._passed(test)
|
|
153
|
+
if (helper._after) await helper._after(test)
|
|
154
|
+
if (helper._finishTest) await helper._finishTest(suite)
|
|
155
|
+
if (helper._afterSuite) await helper._afterSuite(suite)
|
|
156
|
+
} catch (err) {
|
|
157
|
+
err.message = `${helper.constructor.name} helper: ${err.message}`
|
|
158
|
+
if (checks.teardown instanceof Error) err.message = `${err.message}\n\n${checks.teardown?.message || ''}`.trim()
|
|
159
|
+
checks.teardown = err
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
printCheck('Helpers After', checks['teardown'], standardActingHelpers.some(h => Object.keys(helpers).includes(h)) ? 'Closing browser' : '')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
definitions(configFile, { dryRun: true })
|
|
168
|
+
checks.def = true
|
|
169
|
+
} catch (err) {
|
|
170
|
+
checks.def = err
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
printCheck('TypeScript Definitions', checks['def'])
|
|
174
|
+
|
|
175
|
+
output.print('')
|
|
176
|
+
|
|
177
|
+
if (!Object.values(checks).every(check => check === true)) {
|
|
178
|
+
output.error("Something went wrong. Checks didn't pass.")
|
|
179
|
+
output.print()
|
|
180
|
+
await getMachineInfo()
|
|
181
|
+
process.exit(1)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
output.print(output.styles.success('All checks passed'.toUpperCase()), 'Ready to run your tests 🚀')
|
|
185
|
+
process.exit(0)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function printCheck(name, value, comment = '') {
|
|
189
|
+
let status = ''
|
|
190
|
+
if (value == true) {
|
|
191
|
+
status += chalk.bold.green(figures.tick)
|
|
192
|
+
} else {
|
|
193
|
+
status += chalk.bold.red(figures.cross)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (value instanceof Error) {
|
|
197
|
+
comment = `${comment} ${chalk.red(value.message)}`.trim()
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
output.print(status, name.toUpperCase(), chalk.dim(comment))
|
|
201
|
+
}
|
|
@@ -14,9 +14,7 @@ module.exports = function (initPath) {
|
|
|
14
14
|
|
|
15
15
|
print()
|
|
16
16
|
print(` Welcome to ${colors.magenta.bold('CodeceptJS')} configuration migration tool`)
|
|
17
|
-
print(
|
|
18
|
-
` It will help you switch from ${colors.cyan.bold('.json')} to ${colors.magenta.bold('.js')} config format at ease`,
|
|
19
|
-
)
|
|
17
|
+
print(` It will help you switch from ${colors.cyan.bold('.json')} to ${colors.magenta.bold('.js')} config format at ease`)
|
|
20
18
|
print()
|
|
21
19
|
|
|
22
20
|
if (!path) {
|
|
@@ -53,7 +51,7 @@ module.exports = function (initPath) {
|
|
|
53
51
|
default: true,
|
|
54
52
|
},
|
|
55
53
|
])
|
|
56
|
-
.then(
|
|
54
|
+
.then(result => {
|
|
57
55
|
if (result.configFile) {
|
|
58
56
|
const jsonConfigFile = path.join(testsPath, 'codecept.js')
|
|
59
57
|
const config = JSON.parse(fs.readFileSync(jsonConfigFile, 'utf8'))
|