codeceptjs 4.0.0-beta.4 → 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 +139 -87
- package/lib/command/check.js +201 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- 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 +262 -220
- 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 +3 -6
- package/lib/helper/Appium.js +47 -51
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +3 -3
- package/lib/helper/JSONResponse.js +75 -37
- package/lib/helper/Mochawesome.js +31 -9
- package/lib/helper/Nightmare.js +35 -53
- package/lib/helper/Playwright.js +262 -267
- package/lib/helper/Protractor.js +54 -77
- package/lib/helper/Puppeteer.js +246 -260
- package/lib/helper/REST.js +5 -17
- package/lib/helper/TestCafe.js +21 -44
- package/lib/helper/WebDriver.js +151 -170
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/testcafe/testcafe-utils.js +26 -27
- 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/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 +82 -62
- 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 -19
- 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 -1
- 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 -276
- package/package.json +76 -70
- package/translations/de-DE.js +4 -3
- package/translations/fr-FR.js +4 -3
- 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 +188 -186
- package/typings/promiseBasedTypes.d.ts +18 -705
- package/typings/types.d.ts +301 -804
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- 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,37 +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
|
|
95
96
|
|
|
96
97
|
// mask sensitive data
|
|
97
|
-
global.maskSensitiveData = this.config.maskSensitiveData || false
|
|
98
|
+
global.maskSensitiveData = this.config.maskSensitiveData || false
|
|
98
99
|
}
|
|
99
100
|
}
|
|
100
101
|
|
|
@@ -103,16 +104,19 @@ class Codecept {
|
|
|
103
104
|
*/
|
|
104
105
|
runHooks() {
|
|
105
106
|
// default hooks
|
|
106
|
-
runHook(require('./listener/
|
|
107
|
-
runHook(require('./listener/
|
|
108
|
-
runHook(require('./listener/config'))
|
|
109
|
-
runHook(require('./listener/
|
|
110
|
-
runHook(require('./listener/
|
|
111
|
-
runHook(require('./listener/
|
|
112
|
-
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'))
|
|
113
117
|
|
|
114
118
|
// custom hooks (previous iteration of plugins)
|
|
115
|
-
this.config.hooks.forEach(hook => runHook(hook))
|
|
119
|
+
this.config.hooks.forEach(hook => runHook(hook))
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
/**
|
|
@@ -120,7 +124,7 @@ class Codecept {
|
|
|
120
124
|
*
|
|
121
125
|
*/
|
|
122
126
|
async bootstrap() {
|
|
123
|
-
return runHook(this.config.bootstrap, 'bootstrap')
|
|
127
|
+
return runHook(this.config.bootstrap, 'bootstrap')
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
/**
|
|
@@ -128,7 +132,7 @@ class Codecept {
|
|
|
128
132
|
|
|
129
133
|
*/
|
|
130
134
|
async teardown() {
|
|
131
|
-
return runHook(this.config.teardown, 'teardown')
|
|
135
|
+
return runHook(this.config.teardown, 'teardown')
|
|
132
136
|
}
|
|
133
137
|
|
|
134
138
|
/**
|
|
@@ -139,43 +143,89 @@ class Codecept {
|
|
|
139
143
|
loadTests(pattern) {
|
|
140
144
|
const options = {
|
|
141
145
|
cwd: global.codecept_dir,
|
|
142
|
-
}
|
|
146
|
+
}
|
|
143
147
|
|
|
144
|
-
let patterns = [pattern]
|
|
148
|
+
let patterns = [pattern]
|
|
145
149
|
if (!pattern) {
|
|
146
|
-
patterns = []
|
|
150
|
+
patterns = []
|
|
147
151
|
|
|
148
152
|
// If the user wants to test a specific set of test files as an array or string.
|
|
149
153
|
if (this.config.tests && !this.opts.features) {
|
|
150
154
|
if (Array.isArray(this.config.tests)) {
|
|
151
|
-
patterns.push(...this.config.tests)
|
|
155
|
+
patterns.push(...this.config.tests)
|
|
152
156
|
} else {
|
|
153
|
-
patterns.push(this.config.tests)
|
|
157
|
+
patterns.push(this.config.tests)
|
|
154
158
|
}
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
if (this.config.gherkin.features && !this.opts.tests) {
|
|
158
162
|
if (Array.isArray(this.config.gherkin.features)) {
|
|
159
163
|
this.config.gherkin.features.forEach(feature => {
|
|
160
|
-
patterns.push(feature)
|
|
161
|
-
})
|
|
164
|
+
patterns.push(feature)
|
|
165
|
+
})
|
|
162
166
|
} else {
|
|
163
|
-
patterns.push(this.config.gherkin.features)
|
|
167
|
+
patterns.push(this.config.gherkin.features)
|
|
164
168
|
}
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
171
|
|
|
168
172
|
for (pattern of patterns) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
this.testFiles.
|
|
176
|
-
|
|
177
|
-
|
|
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)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
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}`)
|
|
178
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)
|
|
179
229
|
}
|
|
180
230
|
|
|
181
231
|
/**
|
|
@@ -185,34 +235,36 @@ class Codecept {
|
|
|
185
235
|
* @returns {Promise<void>}
|
|
186
236
|
*/
|
|
187
237
|
async run(test) {
|
|
238
|
+
await container.started()
|
|
239
|
+
|
|
188
240
|
return new Promise((resolve, reject) => {
|
|
189
|
-
const mocha = container.mocha()
|
|
190
|
-
mocha.files = this.testFiles
|
|
241
|
+
const mocha = container.mocha()
|
|
242
|
+
mocha.files = this.testFiles
|
|
191
243
|
if (test) {
|
|
192
244
|
if (!fsPath.isAbsolute(test)) {
|
|
193
|
-
test = fsPath.join(global.codecept_dir, test)
|
|
245
|
+
test = fsPath.join(global.codecept_dir, test)
|
|
194
246
|
}
|
|
195
|
-
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)
|
|
196
248
|
}
|
|
197
249
|
const done = () => {
|
|
198
|
-
event.emit(event.all.result,
|
|
199
|
-
event.emit(event.all.after, this)
|
|
200
|
-
resolve()
|
|
201
|
-
}
|
|
250
|
+
event.emit(event.all.result, container.result())
|
|
251
|
+
event.emit(event.all.after, this)
|
|
252
|
+
resolve()
|
|
253
|
+
}
|
|
202
254
|
|
|
203
255
|
try {
|
|
204
|
-
event.emit(event.all.before, this)
|
|
205
|
-
mocha.run(() => done())
|
|
256
|
+
event.emit(event.all.before, this)
|
|
257
|
+
mocha.run(() => done())
|
|
206
258
|
} catch (e) {
|
|
207
|
-
output.error(e.stack)
|
|
208
|
-
reject(e)
|
|
259
|
+
output.error(e.stack)
|
|
260
|
+
reject(e)
|
|
209
261
|
}
|
|
210
|
-
})
|
|
262
|
+
})
|
|
211
263
|
}
|
|
212
264
|
|
|
213
265
|
static version() {
|
|
214
|
-
return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version
|
|
266
|
+
return JSON.parse(readFileSync(`${__dirname}/../package.json`, 'utf8')).version
|
|
215
267
|
}
|
|
216
268
|
}
|
|
217
269
|
|
|
218
|
-
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'))
|