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
|
@@ -5,7 +5,7 @@ const { getConfig, getTestRoot } = require('./utils')
|
|
|
5
5
|
const Codecept = require('../codecept')
|
|
6
6
|
const container = require('../container')
|
|
7
7
|
const output = require('../output')
|
|
8
|
-
const actingHelpers = [...
|
|
8
|
+
const actingHelpers = [...container.STANDARD_ACTING_HELPERS, 'REST']
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Prepare data and generate content of definitions file
|
|
@@ -21,14 +21,7 @@ const actingHelpers = [...require('../plugin/standardActingHelpers'), 'REST']
|
|
|
21
21
|
*
|
|
22
22
|
* @returns {string}
|
|
23
23
|
*/
|
|
24
|
-
const getDefinitionsFileContent = ({
|
|
25
|
-
hasCustomHelper,
|
|
26
|
-
hasCustomStepsFile,
|
|
27
|
-
helperNames,
|
|
28
|
-
supportObject,
|
|
29
|
-
importPaths,
|
|
30
|
-
translations,
|
|
31
|
-
}) => {
|
|
24
|
+
const getDefinitionsFileContent = ({ hasCustomHelper, hasCustomStepsFile, helperNames, supportObject, importPaths, translations }) => {
|
|
32
25
|
const getHelperListFragment = ({ hasCustomHelper, hasCustomStepsFile }) => {
|
|
33
26
|
if (hasCustomHelper && hasCustomStepsFile) {
|
|
34
27
|
return `${['ReturnType<steps_file>', 'WithTranslation<Methods>'].join(', ')}`
|
|
@@ -73,13 +66,7 @@ const getDefinitionsFileContent = ({
|
|
|
73
66
|
*
|
|
74
67
|
* @returns {string}
|
|
75
68
|
*/
|
|
76
|
-
const generateDefinitionsContent = ({
|
|
77
|
-
importPathsFragment,
|
|
78
|
-
supportObjectsTypeFragment,
|
|
79
|
-
methodsTypeFragment,
|
|
80
|
-
helpersListFragment,
|
|
81
|
-
translatedActionsFragment,
|
|
82
|
-
}) => {
|
|
69
|
+
const generateDefinitionsContent = ({ importPathsFragment, supportObjectsTypeFragment, methodsTypeFragment, helpersListFragment, translatedActionsFragment }) => {
|
|
83
70
|
return `/// <reference types='codeceptjs' />
|
|
84
71
|
${importPathsFragment}
|
|
85
72
|
|
|
@@ -185,15 +172,12 @@ module.exports = function (genPath, options) {
|
|
|
185
172
|
namespaceTranslationAliases.push(`interface ${translations.vocabulary.I} extends WithTranslation<Methods> {}`)
|
|
186
173
|
|
|
187
174
|
namespaceTranslationAliases.push(' namespace Translation {')
|
|
188
|
-
definitionsFileContent = definitionsFileContent.replace(
|
|
189
|
-
'namespace Translation {',
|
|
190
|
-
namespaceTranslationAliases.join('\n'),
|
|
191
|
-
)
|
|
175
|
+
definitionsFileContent = definitionsFileContent.replace('namespace Translation {', namespaceTranslationAliases.join('\n'))
|
|
192
176
|
|
|
193
177
|
const translationAliases = []
|
|
194
178
|
|
|
195
179
|
if (translations.vocabulary.contexts) {
|
|
196
|
-
Object.keys(translations.vocabulary.contexts).forEach(
|
|
180
|
+
Object.keys(translations.vocabulary.contexts).forEach(k => {
|
|
197
181
|
translationAliases.push(`declare const ${translations.vocabulary.contexts[k]}: typeof ${k};`)
|
|
198
182
|
})
|
|
199
183
|
}
|
|
@@ -201,6 +185,8 @@ module.exports = function (genPath, options) {
|
|
|
201
185
|
definitionsFileContent += `\n${translationAliases.join('\n')}`
|
|
202
186
|
}
|
|
203
187
|
|
|
188
|
+
if (options.dryRun) return
|
|
189
|
+
|
|
204
190
|
fs.writeFileSync(path.join(targetFolderPath, 'steps.d.ts'), definitionsFileContent)
|
|
205
191
|
output.print('TypeScript Definitions provide autocompletion in Visual Studio Code and other IDEs')
|
|
206
192
|
output.print('Definitions were generated in steps.d.ts')
|
|
@@ -222,11 +208,7 @@ function getPath(originalPath, targetFolderPath, testsPath) {
|
|
|
222
208
|
if (!parsedPath.dir.startsWith('.')) return path.posix.join(parsedPath.dir, parsedPath.base)
|
|
223
209
|
const relativePath = path.posix.relative(
|
|
224
210
|
targetFolderPath.split(path.sep).join(path.posix.sep),
|
|
225
|
-
path.posix.join(
|
|
226
|
-
testsPath.split(path.sep).join(path.posix.sep),
|
|
227
|
-
parsedPath.dir.split(path.sep).join(path.posix.sep),
|
|
228
|
-
parsedPath.base.split(path.sep).join(path.posix.sep),
|
|
229
|
-
),
|
|
211
|
+
path.posix.join(testsPath.split(path.sep).join(path.posix.sep), parsedPath.dir.split(path.sep).join(path.posix.sep), parsedPath.base.split(path.sep).join(path.posix.sep)),
|
|
230
212
|
)
|
|
231
213
|
|
|
232
214
|
return relativePath.startsWith('.') ? relativePath : `./${relativePath}`
|
package/lib/command/dryRun.js
CHANGED
|
@@ -7,7 +7,7 @@ const store = require('../store')
|
|
|
7
7
|
const Container = require('../container')
|
|
8
8
|
|
|
9
9
|
module.exports = async function (test, options) {
|
|
10
|
-
if (options.grep) process.env.grep = options.grep
|
|
10
|
+
if (options.grep) process.env.grep = options.grep
|
|
11
11
|
const configFile = options.config
|
|
12
12
|
let codecept
|
|
13
13
|
|
|
@@ -60,35 +60,40 @@ function printTests(files) {
|
|
|
60
60
|
let numOfTests = 0
|
|
61
61
|
let numOfSuites = 0
|
|
62
62
|
let outputString = ''
|
|
63
|
-
const filterBy = process.env.grep
|
|
63
|
+
const filterBy = process.env.grep
|
|
64
64
|
|
|
65
|
+
let filterRegex
|
|
65
66
|
if (filterBy) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
for (test of suite.tests) {
|
|
74
|
-
if (test.title.toLowerCase().includes(filterBy)) {
|
|
75
|
-
numOfTests++
|
|
76
|
-
outputString += `${colors.white.bold(test.parent.title)} -- ${output.styles.log(test.parent.file || '')} -- ${mocha.suite.suites.length} tests\n`
|
|
77
|
-
outputString += ` ${output.styles.scenario(figures.checkboxOff)} ${test.title}\n`
|
|
78
|
-
}
|
|
79
|
-
}
|
|
67
|
+
try {
|
|
68
|
+
filterRegex = new RegExp(filterBy, 'i') // Case-insensitive matching
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error(`Invalid grep pattern: ${filterBy}`)
|
|
71
|
+
process.exit(1)
|
|
80
72
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const suite of mocha.suite.suites) {
|
|
76
|
+
const suiteMatches = filterRegex ? filterRegex.test(suite.title) : true
|
|
77
|
+
let suiteHasMatchingTests = false
|
|
78
|
+
|
|
79
|
+
if (suiteMatches) {
|
|
80
|
+
outputString += `${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')}\n`
|
|
81
|
+
suiteHasMatchingTests = true
|
|
87
82
|
numOfSuites++
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const test of suite.tests) {
|
|
86
|
+
const testMatches = filterRegex ? filterRegex.test(test.title) : true
|
|
87
|
+
|
|
88
|
+
if (testMatches) {
|
|
89
|
+
if (!suiteMatches && !suiteHasMatchingTests) {
|
|
90
|
+
outputString += `${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')}\n`
|
|
91
|
+
suiteHasMatchingTests = true
|
|
92
|
+
numOfSuites++
|
|
93
|
+
}
|
|
88
94
|
|
|
89
|
-
for (test of suite.tests) {
|
|
90
95
|
numOfTests++
|
|
91
|
-
|
|
96
|
+
outputString += ` ${output.styles.scenario(figures.checkboxOff)} ${test.title}\n`
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
}
|
|
@@ -108,15 +113,5 @@ function printFooter() {
|
|
|
108
113
|
function removeDuplicates(inputString) {
|
|
109
114
|
const array = inputString.split('\n')
|
|
110
115
|
const uniqueLines = [...new Set(array)]
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return resultString
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function countSuites(inputString) {
|
|
117
|
-
const array = inputString.split('\n')
|
|
118
|
-
|
|
119
|
-
const uniqueLines = [...new Set(array)]
|
|
120
|
-
const res = uniqueLines.filter((item) => item.includes('-- '))
|
|
121
|
-
return res.length
|
|
116
|
+
return uniqueLines.join('\n')
|
|
122
117
|
}
|
package/lib/command/generate.js
CHANGED
|
@@ -35,7 +35,7 @@ module.exports.test = function (genPath) {
|
|
|
35
35
|
type: 'input',
|
|
36
36
|
name: 'feature',
|
|
37
37
|
message: 'Feature which is being tested (ex: account, login, etc)',
|
|
38
|
-
validate:
|
|
38
|
+
validate: val => !!val,
|
|
39
39
|
},
|
|
40
40
|
{
|
|
41
41
|
type: 'input',
|
|
@@ -46,7 +46,7 @@ module.exports.test = function (genPath) {
|
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
48
|
])
|
|
49
|
-
.then(
|
|
49
|
+
.then(result => {
|
|
50
50
|
const testFilePath = path.dirname(path.join(testsPath, config.tests)).replace(/\*\*$/, '')
|
|
51
51
|
let testFile = path.join(testFilePath, result.filename)
|
|
52
52
|
const ext = path.extname(testFile)
|
|
@@ -63,9 +63,7 @@ module.exports.test = function (genPath) {
|
|
|
63
63
|
testContent = testContent.replace('{{actor}}', container.translation().I)
|
|
64
64
|
if (vocabulary.contexts.Feature) testContent = testContent.replace('Feature', vocabulary.contexts.Feature)
|
|
65
65
|
if (vocabulary.contexts.Scenario) testContent = testContent.replace('Scenario', vocabulary.contexts.Scenario)
|
|
66
|
-
output.print(
|
|
67
|
-
`Test was created in ${colors.bold(config.translation)} localization. See: https://codecept.io/translation/`,
|
|
68
|
-
)
|
|
66
|
+
output.print(`Test was created in ${colors.bold(config.translation)} localization. See: https://codecept.io/translation/`)
|
|
69
67
|
} else {
|
|
70
68
|
testContent = testContent.replace('{{actor}}', 'I')
|
|
71
69
|
}
|
|
@@ -128,13 +126,13 @@ module.exports.pageObject = function (genPath, opts) {
|
|
|
128
126
|
type: 'input',
|
|
129
127
|
name: 'name',
|
|
130
128
|
message: `Name of a ${kind} object`,
|
|
131
|
-
validate:
|
|
129
|
+
validate: val => !!val,
|
|
132
130
|
},
|
|
133
131
|
{
|
|
134
132
|
type: 'input',
|
|
135
133
|
name: 'filename',
|
|
136
134
|
message: 'Where should it be stored',
|
|
137
|
-
default:
|
|
135
|
+
default: answers => `./${kind}s/${answers.name}.${extension}`,
|
|
138
136
|
},
|
|
139
137
|
{
|
|
140
138
|
type: 'list',
|
|
@@ -144,7 +142,7 @@ module.exports.pageObject = function (genPath, opts) {
|
|
|
144
142
|
default: 'module',
|
|
145
143
|
},
|
|
146
144
|
])
|
|
147
|
-
.then(
|
|
145
|
+
.then(result => {
|
|
148
146
|
const pageObjectFile = path.join(testsPath, result.filename)
|
|
149
147
|
const dir = path.dirname(pageObjectFile)
|
|
150
148
|
if (!fileExists(dir)) fs.mkdirSync(dir)
|
|
@@ -194,9 +192,7 @@ module.exports.pageObject = function (genPath, opts) {
|
|
|
194
192
|
try {
|
|
195
193
|
generateDefinitions(testsPath, {})
|
|
196
194
|
} catch (_err) {
|
|
197
|
-
output.print(
|
|
198
|
-
`Run ${colors.green('npx codeceptjs def')} to update your types to get auto-completion for object.`,
|
|
199
|
-
)
|
|
195
|
+
output.print(`Run ${colors.green('npx codeceptjs def')} to update your types to get auto-completion for object.`)
|
|
200
196
|
}
|
|
201
197
|
})
|
|
202
198
|
}
|
|
@@ -241,16 +237,16 @@ module.exports.helper = function (genPath) {
|
|
|
241
237
|
type: 'input',
|
|
242
238
|
name: 'name',
|
|
243
239
|
message: 'Name of a Helper',
|
|
244
|
-
validate:
|
|
240
|
+
validate: val => !!val,
|
|
245
241
|
},
|
|
246
242
|
{
|
|
247
243
|
type: 'input',
|
|
248
244
|
name: 'filename',
|
|
249
245
|
message: 'Where should it be stored',
|
|
250
|
-
default:
|
|
246
|
+
default: answers => `./${answers.name.toLowerCase()}_helper.${extension}`,
|
|
251
247
|
},
|
|
252
248
|
])
|
|
253
|
-
.then(
|
|
249
|
+
.then(result => {
|
|
254
250
|
const name = ucfirst(result.name)
|
|
255
251
|
const helperFile = path.join(testsPath, result.filename)
|
|
256
252
|
const dir = path.dirname(helperFile)
|
|
@@ -1,132 +1,134 @@
|
|
|
1
|
-
const escapeStringRegexp = require('escape-string-regexp')
|
|
2
|
-
const fs = require('fs')
|
|
3
|
-
const Gherkin = require('@cucumber/gherkin')
|
|
4
|
-
const Messages = require('@cucumber/messages')
|
|
5
|
-
const
|
|
6
|
-
const fsPath = require('path')
|
|
1
|
+
const escapeStringRegexp = require('escape-string-regexp')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const Gherkin = require('@cucumber/gherkin')
|
|
4
|
+
const Messages = require('@cucumber/messages')
|
|
5
|
+
const { globSync } = require('glob')
|
|
6
|
+
const fsPath = require('path')
|
|
7
7
|
|
|
8
|
-
const { getConfig, getTestRoot } = require('../utils')
|
|
9
|
-
const Codecept = require('../../codecept')
|
|
10
|
-
const output = require('../../output')
|
|
11
|
-
const { matchStep } = require('../../
|
|
8
|
+
const { getConfig, getTestRoot } = require('../utils')
|
|
9
|
+
const Codecept = require('../../codecept')
|
|
10
|
+
const output = require('../../output')
|
|
11
|
+
const { matchStep } = require('../../mocha/bdd')
|
|
12
12
|
|
|
13
|
-
const uuidFn = Messages.IdGenerator.uuid()
|
|
14
|
-
const builder = new Gherkin.AstBuilder(uuidFn)
|
|
15
|
-
const matcher = new Gherkin.GherkinClassicTokenMatcher()
|
|
16
|
-
const parser = new Gherkin.Parser(builder, matcher)
|
|
17
|
-
parser.stopAtFirstError = false
|
|
13
|
+
const uuidFn = Messages.IdGenerator.uuid()
|
|
14
|
+
const builder = new Gherkin.AstBuilder(uuidFn)
|
|
15
|
+
const matcher = new Gherkin.GherkinClassicTokenMatcher()
|
|
16
|
+
const parser = new Gherkin.Parser(builder, matcher)
|
|
17
|
+
parser.stopAtFirstError = false
|
|
18
18
|
|
|
19
19
|
module.exports = function (genPath, options) {
|
|
20
|
-
const configFile = options.config || genPath
|
|
21
|
-
const testsPath = getTestRoot(configFile)
|
|
22
|
-
const config = getConfig(configFile)
|
|
23
|
-
if (!config) return
|
|
20
|
+
const configFile = options.config || genPath
|
|
21
|
+
const testsPath = getTestRoot(configFile)
|
|
22
|
+
const config = getConfig(configFile)
|
|
23
|
+
if (!config) return
|
|
24
24
|
|
|
25
|
-
const codecept = new Codecept(config, {})
|
|
26
|
-
codecept.init(testsPath)
|
|
25
|
+
const codecept = new Codecept(config, {})
|
|
26
|
+
codecept.init(testsPath)
|
|
27
27
|
|
|
28
28
|
if (!config.gherkin) {
|
|
29
|
-
output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it')
|
|
30
|
-
process.exit(1)
|
|
29
|
+
output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it')
|
|
30
|
+
process.exit(1)
|
|
31
31
|
}
|
|
32
32
|
if (!config.gherkin.steps || !config.gherkin.steps[0]) {
|
|
33
|
-
output.error('No gherkin steps defined in config. Exiting')
|
|
34
|
-
process.exit(1)
|
|
33
|
+
output.error('No gherkin steps defined in config. Exiting')
|
|
34
|
+
process.exit(1)
|
|
35
35
|
}
|
|
36
36
|
if (!options.feature && !config.gherkin.features) {
|
|
37
|
-
output.error('No gherkin features defined in config. Exiting')
|
|
38
|
-
process.exit(1)
|
|
37
|
+
output.error('No gherkin features defined in config. Exiting')
|
|
38
|
+
process.exit(1)
|
|
39
39
|
}
|
|
40
40
|
if (options.path && !config.gherkin.steps.includes(options.path)) {
|
|
41
|
-
output.error(`You must include ${options.path} to the gherkin steps in your config file`)
|
|
42
|
-
process.exit(1)
|
|
41
|
+
output.error(`You must include ${options.path} to the gherkin steps in your config file`)
|
|
42
|
+
process.exit(1)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
const files = []
|
|
46
|
-
|
|
45
|
+
const files = []
|
|
46
|
+
globSync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach(file => {
|
|
47
47
|
if (!fsPath.isAbsolute(file)) {
|
|
48
|
-
file = fsPath.join(global.codecept_dir, file)
|
|
48
|
+
file = fsPath.join(global.codecept_dir, file)
|
|
49
49
|
}
|
|
50
|
-
files.push(fsPath.resolve(file))
|
|
51
|
-
})
|
|
52
|
-
output.print(`Loaded ${files.length} files`)
|
|
50
|
+
files.push(fsPath.resolve(file))
|
|
51
|
+
})
|
|
52
|
+
output.print(`Loaded ${files.length} files`)
|
|
53
53
|
|
|
54
|
-
const newSteps = new Map()
|
|
54
|
+
const newSteps = new Map()
|
|
55
55
|
|
|
56
|
-
const parseSteps =
|
|
57
|
-
const newSteps = []
|
|
58
|
-
let currentKeyword = ''
|
|
56
|
+
const parseSteps = steps => {
|
|
57
|
+
const newSteps = []
|
|
58
|
+
let currentKeyword = ''
|
|
59
59
|
for (const step of steps) {
|
|
60
60
|
if (step.keyword.trim() === 'And') {
|
|
61
|
-
if (!currentKeyword) throw new Error(`There is no active keyword for step '${step.text}'`)
|
|
62
|
-
step.keyword = currentKeyword
|
|
61
|
+
if (!currentKeyword) throw new Error(`There is no active keyword for step '${step.text}'`)
|
|
62
|
+
step.keyword = currentKeyword
|
|
63
63
|
}
|
|
64
|
-
currentKeyword = step.keyword
|
|
64
|
+
currentKeyword = step.keyword
|
|
65
65
|
try {
|
|
66
|
-
matchStep(step.text)
|
|
66
|
+
matchStep(step.text)
|
|
67
67
|
} catch (err) {
|
|
68
|
-
let stepLine
|
|
68
|
+
let stepLine
|
|
69
69
|
if (/[{}()/]/.test(step.text)) {
|
|
70
70
|
stepLine = escapeStringRegexp(step.text)
|
|
71
71
|
.replace(/\//g, '\\/')
|
|
72
72
|
.replace(/\"(.*?)\"/g, '"(.*?)"')
|
|
73
73
|
.replace(/(\d+\\\.\d+)/, '(\\d+\\.\\d+)')
|
|
74
|
-
.replace(/ (\d+) /, ' (\\d+) ')
|
|
75
|
-
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true })
|
|
74
|
+
.replace(/ (\d+) /, ' (\\d+) ')
|
|
75
|
+
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true })
|
|
76
76
|
} else {
|
|
77
77
|
stepLine = step.text
|
|
78
78
|
.replace(/\"(.*?)\"/g, '{string}')
|
|
79
79
|
.replace(/(\d+\.\d+)/, '{float}')
|
|
80
|
-
.replace(/ (\d+) /, ' {int} ')
|
|
81
|
-
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false })
|
|
80
|
+
.replace(/ (\d+) /, ' {int} ')
|
|
81
|
+
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false })
|
|
82
82
|
}
|
|
83
|
-
newSteps.push(stepLine)
|
|
83
|
+
newSteps.push(stepLine)
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
-
return newSteps
|
|
87
|
-
}
|
|
86
|
+
return newSteps
|
|
87
|
+
}
|
|
88
88
|
|
|
89
|
-
const parseFile =
|
|
90
|
-
const ast = parser.parse(fs.readFileSync(file).toString())
|
|
89
|
+
const parseFile = file => {
|
|
90
|
+
const ast = parser.parse(fs.readFileSync(file).toString())
|
|
91
91
|
for (const child of ast.feature.children) {
|
|
92
|
-
if (child.scenario.keyword === 'Scenario Outline') continue
|
|
93
|
-
parseSteps(child.scenario.steps)
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
if (child.scenario.keyword === 'Scenario Outline') continue // skip scenario outline
|
|
93
|
+
parseSteps(child.scenario.steps)
|
|
94
|
+
.map(step => {
|
|
95
|
+
return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) })
|
|
96
|
+
})
|
|
97
|
+
.map(step => newSteps.set(`${step.type}(${step})`, step))
|
|
96
98
|
}
|
|
97
|
-
}
|
|
99
|
+
}
|
|
98
100
|
|
|
99
|
-
files.forEach(file => parseFile(file))
|
|
101
|
+
files.forEach(file => parseFile(file))
|
|
100
102
|
|
|
101
|
-
let stepFile = options.path || config.gherkin.steps[0]
|
|
103
|
+
let stepFile = options.path || config.gherkin.steps[0]
|
|
102
104
|
if (!fs.existsSync(stepFile)) {
|
|
103
|
-
output.error(`Please enter a valid step file path ${stepFile}`)
|
|
104
|
-
process.exit(1)
|
|
105
|
+
output.error(`Please enter a valid step file path ${stepFile}`)
|
|
106
|
+
process.exit(1)
|
|
105
107
|
}
|
|
106
108
|
|
|
107
109
|
if (!fsPath.isAbsolute(stepFile)) {
|
|
108
|
-
stepFile = fsPath.join(global.codecept_dir, stepFile)
|
|
110
|
+
stepFile = fsPath.join(global.codecept_dir, stepFile)
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
const snippets = [...newSteps.values()]
|
|
112
114
|
.filter((value, index, self) => self.indexOf(value) === index)
|
|
113
|
-
.map(
|
|
115
|
+
.map(step => {
|
|
114
116
|
return `
|
|
115
117
|
${step.type}(${step.regexp ? '/^' : "'"}${step}${step.regexp ? '$/' : "'"}, () => {
|
|
116
118
|
// From "${step.file}" ${JSON.stringify(step.location)}
|
|
117
119
|
throw new Error('Not implemented yet');
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
+
});`
|
|
121
|
+
})
|
|
120
122
|
|
|
121
123
|
if (!snippets.length) {
|
|
122
|
-
output.print('No new snippets found')
|
|
123
|
-
return
|
|
124
|
+
output.print('No new snippets found')
|
|
125
|
+
return
|
|
124
126
|
}
|
|
125
|
-
output.success(`Snippets generated: ${snippets.length}`)
|
|
126
|
-
output.print(snippets.join('\n'))
|
|
127
|
+
output.success(`Snippets generated: ${snippets.length}`)
|
|
128
|
+
output.print(snippets.join('\n'))
|
|
127
129
|
|
|
128
130
|
if (!options.dryRun) {
|
|
129
|
-
output.success(`Snippets added to ${output.colors.bold(stepFile)}`)
|
|
130
|
-
fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n')
|
|
131
|
+
output.success(`Snippets added to ${output.colors.bold(stepFile)}`)
|
|
132
|
+
fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n')
|
|
131
133
|
}
|
|
132
|
-
}
|
|
134
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { getConfig, getTestRoot } = require('../utils');
|
|
2
2
|
const Codecept = require('../../codecept');
|
|
3
3
|
const output = require('../../output');
|
|
4
|
-
const { getSteps } = require('../../
|
|
4
|
+
const { getSteps } = require('../../mocha/bdd');
|
|
5
5
|
|
|
6
6
|
module.exports = function (genPath, options) {
|
|
7
7
|
const configFile = options.config || genPath;
|
package/lib/command/info.js
CHANGED
|
@@ -3,6 +3,42 @@ const envinfo = require('envinfo')
|
|
|
3
3
|
const { getConfig, getTestRoot } = require('./utils')
|
|
4
4
|
const Codecept = require('../codecept')
|
|
5
5
|
const output = require('../output')
|
|
6
|
+
const { execSync } = require('child_process')
|
|
7
|
+
|
|
8
|
+
async function getPlaywrightBrowsers() {
|
|
9
|
+
try {
|
|
10
|
+
const regex = /(chromium|firefox|webkit)\s+version\s+([\d.]+)/gi
|
|
11
|
+
let versions = []
|
|
12
|
+
|
|
13
|
+
const info = execSync('npx playwright install --dry-run').toString().trim()
|
|
14
|
+
|
|
15
|
+
const matches = [...info.matchAll(regex)]
|
|
16
|
+
|
|
17
|
+
matches.forEach(match => {
|
|
18
|
+
const browser = match[1]
|
|
19
|
+
const version = match[2]
|
|
20
|
+
versions.push(`${browser}: ${version}`)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
return versions.join(', ')
|
|
24
|
+
} catch (err) {
|
|
25
|
+
return 'Playwright not installed'
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function getOsBrowsers() {
|
|
30
|
+
const chromeInfo = await envinfo.helpers.getChromeInfo()
|
|
31
|
+
const edgeInfo = await envinfo.helpers.getEdgeInfo()
|
|
32
|
+
const firefoxInfo = await envinfo.helpers.getFirefoxInfo()
|
|
33
|
+
const safariInfo = await envinfo.helpers.getSafariInfo()
|
|
34
|
+
|
|
35
|
+
return [
|
|
36
|
+
`chrome: ${chromeInfo ? chromeInfo[1] : 'not installed'}`,
|
|
37
|
+
`edge: ${edgeInfo ? edgeInfo[1] : 'not installed'}`,
|
|
38
|
+
`firefox: ${firefoxInfo ? firefoxInfo[1] : 'not installed'}`,
|
|
39
|
+
`safari: ${safariInfo ? safariInfo[1] : 'not installed'}`,
|
|
40
|
+
].join(', ')
|
|
41
|
+
}
|
|
6
42
|
|
|
7
43
|
module.exports = async function (path) {
|
|
8
44
|
const testsPath = getTestRoot(path)
|
|
@@ -10,16 +46,15 @@ module.exports = async function (path) {
|
|
|
10
46
|
const codecept = new Codecept(config, {})
|
|
11
47
|
codecept.init(testsPath)
|
|
12
48
|
|
|
13
|
-
output.print('\n Environment information
|
|
49
|
+
output.print('\n Environment information: \n')
|
|
14
50
|
const info = {}
|
|
15
51
|
info.codeceptVersion = Codecept.version()
|
|
16
52
|
info.nodeInfo = await envinfo.helpers.getNodeInfo()
|
|
17
53
|
info.osInfo = await envinfo.helpers.getOSInfo()
|
|
18
54
|
info.cpuInfo = await envinfo.helpers.getCPUInfo()
|
|
19
|
-
info.
|
|
20
|
-
info.
|
|
21
|
-
|
|
22
|
-
info.safariInfo = await envinfo.helpers.getSafariInfo()
|
|
55
|
+
info.osBrowsers = await getOsBrowsers()
|
|
56
|
+
info.playwrightBrowsers = await getPlaywrightBrowsers()
|
|
57
|
+
|
|
23
58
|
const { helpers, plugins } = config
|
|
24
59
|
info.helpers = helpers || "You don't use any helpers"
|
|
25
60
|
info.plugins = plugins || "You don't have any enabled plugins"
|
|
@@ -34,9 +69,7 @@ module.exports = async function (path) {
|
|
|
34
69
|
output.print('***************************************')
|
|
35
70
|
output.print('If you have questions ask them in our Slack: http://bit.ly/chat-codeceptjs')
|
|
36
71
|
output.print('Or ask them on our discussion board: https://codecept.discourse.group/')
|
|
37
|
-
output.print(
|
|
38
|
-
'Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues',
|
|
39
|
-
)
|
|
72
|
+
output.print('Please copy environment info when you report issues on GitHub: https://github.com/Codeception/CodeceptJS/issues')
|
|
40
73
|
output.print('***************************************')
|
|
41
74
|
}
|
|
42
75
|
|
|
@@ -49,6 +82,7 @@ module.exports.getMachineInfo = async () => {
|
|
|
49
82
|
edgeInfo: await envinfo.helpers.getEdgeInfo(),
|
|
50
83
|
firefoxInfo: await envinfo.helpers.getFirefoxInfo(),
|
|
51
84
|
safariInfo: await envinfo.helpers.getSafariInfo(),
|
|
85
|
+
playwrightBrowsers: await getPlaywrightBrowsers(),
|
|
52
86
|
}
|
|
53
87
|
|
|
54
88
|
output.print('***************************************')
|
package/lib/command/init.js
CHANGED
|
@@ -18,6 +18,11 @@ const defaultConfig = {
|
|
|
18
18
|
output: '',
|
|
19
19
|
helpers: {},
|
|
20
20
|
include: {},
|
|
21
|
+
plugins: {
|
|
22
|
+
htmlReporter: {
|
|
23
|
+
enabled: true,
|
|
24
|
+
},
|
|
25
|
+
},
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
const helpers = ['Playwright', 'WebDriver', 'Puppeteer', 'REST', 'GraphQL', 'Appium', 'TestCafe']
|
|
@@ -117,7 +122,7 @@ module.exports = function (initPath) {
|
|
|
117
122
|
{
|
|
118
123
|
name: 'tests',
|
|
119
124
|
type: 'input',
|
|
120
|
-
default:
|
|
125
|
+
default: answers => `./*_test.${answers.typescript ? 'ts' : 'js'}`,
|
|
121
126
|
message: 'Where are your tests located?',
|
|
122
127
|
},
|
|
123
128
|
{
|
|
@@ -132,7 +137,7 @@ module.exports = function (initPath) {
|
|
|
132
137
|
type: 'confirm',
|
|
133
138
|
default: true,
|
|
134
139
|
message: 'Do you want to use JSONResponse helper for assertions on JSON responses? http://bit.ly/3ASVPy9',
|
|
135
|
-
when:
|
|
140
|
+
when: answers => ['GraphQL', 'REST'].includes(answers.helper) === true,
|
|
136
141
|
},
|
|
137
142
|
{
|
|
138
143
|
name: 'output',
|
|
@@ -146,7 +151,7 @@ module.exports = function (initPath) {
|
|
|
146
151
|
choices: translations,
|
|
147
152
|
},
|
|
148
153
|
])
|
|
149
|
-
.then(
|
|
154
|
+
.then(result => {
|
|
150
155
|
if (result.typescript === true) {
|
|
151
156
|
isTypeScript = true
|
|
152
157
|
extension = isTypeScript === true ? 'ts' : 'js'
|
|
@@ -189,7 +194,7 @@ module.exports = function (initPath) {
|
|
|
189
194
|
|
|
190
195
|
if (!Helper._config()) return
|
|
191
196
|
helperConfigs = helperConfigs.concat(
|
|
192
|
-
Helper._config().map(
|
|
197
|
+
Helper._config().map(config => {
|
|
193
198
|
config.message = `[${helperName}] ${config.message}`
|
|
194
199
|
config.name = `${helperName}_${config.name}`
|
|
195
200
|
config.type = config.type || 'input'
|
|
@@ -225,9 +230,7 @@ module.exports = function (initPath) {
|
|
|
225
230
|
fs.writeFileSync(typeScriptconfigFile, configSource, 'utf-8')
|
|
226
231
|
print(`Config created at ${typeScriptconfigFile}`)
|
|
227
232
|
} else {
|
|
228
|
-
configSource = beautify(
|
|
229
|
-
`/** @type {CodeceptJS.MainConfig} */\nexports.config = ${inspect(config, false, 4, false)}`,
|
|
230
|
-
)
|
|
233
|
+
configSource = beautify(`/** @type {CodeceptJS.MainConfig} */\nexports.config = ${inspect(config, false, 4, false)}`)
|
|
231
234
|
|
|
232
235
|
if (hasConfigure) configSource = requireCodeceptConfigure + configHeader + configSource
|
|
233
236
|
|
|
@@ -286,9 +289,7 @@ module.exports = function (initPath) {
|
|
|
286
289
|
}
|
|
287
290
|
}
|
|
288
291
|
|
|
289
|
-
const generateDefinitionsManually = colors.bold(
|
|
290
|
-
`To get auto-completion support, please generate type definitions: ${colors.green('npx codeceptjs def')}`,
|
|
291
|
-
)
|
|
292
|
+
const generateDefinitionsManually = colors.bold(`To get auto-completion support, please generate type definitions: ${colors.green('npx codeceptjs def')}`)
|
|
292
293
|
|
|
293
294
|
if (packages) {
|
|
294
295
|
try {
|
|
@@ -330,7 +331,7 @@ module.exports = function (initPath) {
|
|
|
330
331
|
}
|
|
331
332
|
|
|
332
333
|
print('Configure helpers...')
|
|
333
|
-
inquirer.prompt(helperConfigs).then(async
|
|
334
|
+
inquirer.prompt(helperConfigs).then(async helperResult => {
|
|
334
335
|
if (helperResult.Playwright_browser === 'electron') {
|
|
335
336
|
delete helperResult.Playwright_url
|
|
336
337
|
delete helperResult.Playwright_show
|
|
@@ -341,7 +342,7 @@ module.exports = function (initPath) {
|
|
|
341
342
|
}
|
|
342
343
|
}
|
|
343
344
|
|
|
344
|
-
Object.keys(helperResult).forEach(
|
|
345
|
+
Object.keys(helperResult).forEach(key => {
|
|
345
346
|
const parts = key.split('_')
|
|
346
347
|
const helperName = parts[0]
|
|
347
348
|
const configName = parts[1]
|