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.
Files changed (155) hide show
  1. package/README.md +134 -119
  2. package/bin/codecept.js +12 -2
  3. package/bin/test-server.js +53 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/lib/actor.js +66 -102
  6. package/lib/ai.js +130 -121
  7. package/lib/assert/empty.js +3 -5
  8. package/lib/assert/equal.js +4 -7
  9. package/lib/assert/include.js +4 -6
  10. package/lib/assert/throws.js +2 -4
  11. package/lib/assert/truth.js +2 -2
  12. package/lib/codecept.js +141 -86
  13. package/lib/command/check.js +201 -0
  14. package/lib/command/configMigrate.js +2 -4
  15. package/lib/command/definitions.js +8 -26
  16. package/lib/command/dryRun.js +30 -35
  17. package/lib/command/generate.js +10 -14
  18. package/lib/command/gherkin/snippets.js +75 -73
  19. package/lib/command/gherkin/steps.js +1 -1
  20. package/lib/command/info.js +42 -8
  21. package/lib/command/init.js +13 -12
  22. package/lib/command/interactive.js +10 -2
  23. package/lib/command/list.js +1 -1
  24. package/lib/command/run-multiple/chunk.js +48 -45
  25. package/lib/command/run-multiple.js +12 -35
  26. package/lib/command/run-workers.js +21 -58
  27. package/lib/command/utils.js +5 -6
  28. package/lib/command/workers/runTests.js +263 -222
  29. package/lib/container.js +386 -238
  30. package/lib/data/context.js +10 -13
  31. package/lib/data/dataScenarioConfig.js +8 -8
  32. package/lib/data/dataTableArgument.js +6 -6
  33. package/lib/data/table.js +5 -11
  34. package/lib/effects.js +223 -0
  35. package/lib/element/WebElement.js +327 -0
  36. package/lib/els.js +158 -0
  37. package/lib/event.js +21 -17
  38. package/lib/heal.js +88 -80
  39. package/lib/helper/AI.js +2 -1
  40. package/lib/helper/ApiDataFactory.js +4 -7
  41. package/lib/helper/Appium.js +50 -57
  42. package/lib/helper/FileSystem.js +3 -3
  43. package/lib/helper/GraphQLDataFactory.js +4 -4
  44. package/lib/helper/JSONResponse.js +75 -37
  45. package/lib/helper/Mochawesome.js +31 -9
  46. package/lib/helper/Nightmare.js +37 -58
  47. package/lib/helper/Playwright.js +267 -272
  48. package/lib/helper/Protractor.js +56 -87
  49. package/lib/helper/Puppeteer.js +247 -264
  50. package/lib/helper/REST.js +29 -17
  51. package/lib/helper/TestCafe.js +22 -47
  52. package/lib/helper/WebDriver.js +157 -368
  53. package/lib/helper/extras/PlaywrightPropEngine.js +2 -2
  54. package/lib/helper/extras/Popup.js +22 -22
  55. package/lib/helper/network/utils.js +1 -1
  56. package/lib/helper/testcafe/testcafe-utils.js +27 -28
  57. package/lib/listener/emptyRun.js +55 -0
  58. package/lib/listener/exit.js +7 -10
  59. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  60. package/lib/listener/globalTimeout.js +165 -0
  61. package/lib/listener/helpers.js +15 -15
  62. package/lib/listener/mocha.js +1 -1
  63. package/lib/listener/result.js +12 -0
  64. package/lib/listener/retryEnhancer.js +85 -0
  65. package/lib/listener/steps.js +32 -18
  66. package/lib/listener/store.js +20 -0
  67. package/lib/locator.js +1 -1
  68. package/lib/mocha/asyncWrapper.js +231 -0
  69. package/lib/{interfaces → mocha}/bdd.js +3 -3
  70. package/lib/mocha/cli.js +308 -0
  71. package/lib/mocha/factory.js +104 -0
  72. package/lib/{interfaces → mocha}/featureConfig.js +32 -12
  73. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  74. package/lib/mocha/hooks.js +112 -0
  75. package/lib/mocha/index.js +12 -0
  76. package/lib/mocha/inject.js +29 -0
  77. package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
  78. package/lib/mocha/suite.js +82 -0
  79. package/lib/mocha/test.js +181 -0
  80. package/lib/mocha/types.d.ts +42 -0
  81. package/lib/mocha/ui.js +232 -0
  82. package/lib/output.js +93 -65
  83. package/lib/pause.js +160 -138
  84. package/lib/plugin/analyze.js +396 -0
  85. package/lib/plugin/auth.js +435 -0
  86. package/lib/plugin/autoDelay.js +8 -8
  87. package/lib/plugin/autoLogin.js +3 -338
  88. package/lib/plugin/commentStep.js +6 -1
  89. package/lib/plugin/coverage.js +10 -22
  90. package/lib/plugin/customLocator.js +3 -3
  91. package/lib/plugin/customReporter.js +52 -0
  92. package/lib/plugin/eachElement.js +1 -1
  93. package/lib/plugin/fakerTransform.js +1 -1
  94. package/lib/plugin/heal.js +36 -9
  95. package/lib/plugin/htmlReporter.js +1947 -0
  96. package/lib/plugin/pageInfo.js +140 -0
  97. package/lib/plugin/retryFailedStep.js +17 -18
  98. package/lib/plugin/retryTo.js +2 -113
  99. package/lib/plugin/screenshotOnFail.js +17 -58
  100. package/lib/plugin/selenoid.js +15 -35
  101. package/lib/plugin/standardActingHelpers.js +4 -1
  102. package/lib/plugin/stepByStepReport.js +56 -17
  103. package/lib/plugin/stepTimeout.js +5 -12
  104. package/lib/plugin/subtitles.js +4 -4
  105. package/lib/plugin/tryTo.js +3 -102
  106. package/lib/plugin/wdio.js +8 -10
  107. package/lib/recorder.js +155 -124
  108. package/lib/rerun.js +43 -42
  109. package/lib/result.js +161 -0
  110. package/lib/secret.js +1 -2
  111. package/lib/step/base.js +239 -0
  112. package/lib/step/comment.js +10 -0
  113. package/lib/step/config.js +50 -0
  114. package/lib/step/func.js +46 -0
  115. package/lib/step/helper.js +50 -0
  116. package/lib/step/meta.js +99 -0
  117. package/lib/step/record.js +74 -0
  118. package/lib/step/retry.js +11 -0
  119. package/lib/step/section.js +55 -0
  120. package/lib/step.js +21 -332
  121. package/lib/steps.js +50 -0
  122. package/lib/store.js +37 -5
  123. package/lib/template/heal.js +2 -11
  124. package/lib/test-server.js +323 -0
  125. package/lib/timeout.js +66 -0
  126. package/lib/utils.js +351 -218
  127. package/lib/within.js +75 -55
  128. package/lib/workerStorage.js +2 -1
  129. package/lib/workers.js +386 -277
  130. package/package.json +81 -75
  131. package/translations/de-DE.js +5 -3
  132. package/translations/fr-FR.js +5 -4
  133. package/translations/index.js +1 -0
  134. package/translations/it-IT.js +4 -3
  135. package/translations/ja-JP.js +4 -3
  136. package/translations/nl-NL.js +76 -0
  137. package/translations/pl-PL.js +4 -3
  138. package/translations/pt-BR.js +4 -3
  139. package/translations/ru-RU.js +4 -3
  140. package/translations/utils.js +9 -0
  141. package/translations/zh-CN.js +4 -3
  142. package/translations/zh-TW.js +4 -3
  143. package/typings/index.d.ts +197 -187
  144. package/typings/promiseBasedTypes.d.ts +53 -903
  145. package/typings/types.d.ts +372 -1042
  146. package/lib/cli.js +0 -257
  147. package/lib/helper/ExpectHelper.js +0 -391
  148. package/lib/helper/MockServer.js +0 -221
  149. package/lib/helper/SoftExpectHelper.js +0 -381
  150. package/lib/listener/artifacts.js +0 -19
  151. package/lib/listener/timeout.js +0 -109
  152. package/lib/mochaFactory.js +0 -113
  153. package/lib/plugin/debugErrors.js +0 -67
  154. package/lib/scenario.js +0 -224
  155. 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 = [...require('../plugin/standardActingHelpers'), 'REST']
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((k) => {
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}`
@@ -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.toLowerCase()
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 ? process.env.grep.toLowerCase() : undefined
63
+ const filterBy = process.env.grep
64
64
 
65
+ let filterRegex
65
66
  if (filterBy) {
66
- for (const suite of mocha.suite.suites) {
67
- const currentSuite = suite.title
68
- if (suite.title.toLowerCase().includes(filterBy)) {
69
- outputString += `${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')} -- ${mocha.suite.suites.length} tests\n`
70
- numOfSuites++
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
- numOfSuites = countSuites(outputString)
82
- } else {
83
- for (const suite of mocha.suite.suites) {
84
- output.print(
85
- `${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')} -- ${mocha.suite.suites.length} tests`,
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
- output.print(` ${output.styles.scenario(figures.checkboxOff)} ${test.title}`)
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
- const resultString = uniqueLines.join('\n')
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
  }
@@ -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: (val) => !!val,
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((result) => {
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: (val) => !!val,
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: (answers) => `./${kind}s/${answers.name}.${extension}`,
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((result) => {
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: (val) => !!val,
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: (answers) => `./${answers.name.toLowerCase()}_helper.${extension}`,
246
+ default: answers => `./${answers.name.toLowerCase()}_helper.${extension}`,
251
247
  },
252
248
  ])
253
- .then((result) => {
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 glob = require('glob');
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('../../interfaces/bdd');
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
- glob.sync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach((file) => {
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 = (steps) => {
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 = (file) => {
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; // skip scenario outline
93
- parseSteps(child.scenario.steps).map((step) => {
94
- return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) });
95
- }).map((step) => newSteps.set(`${step.type}(${step})`, step));
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((step) => {
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'); // eslint-disable-line
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('../../interfaces/bdd');
4
+ const { getSteps } = require('../../mocha/bdd');
5
5
 
6
6
  module.exports = function (genPath, options) {
7
7
  const configFile = options.config || genPath;
@@ -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:-\n')
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.chromeInfo = await envinfo.helpers.getChromeInfo()
20
- info.edgeInfo = await envinfo.helpers.getEdgeInfo()
21
- info.firefoxInfo = await envinfo.helpers.getFirefoxInfo()
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('***************************************')
@@ -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: (answers) => `./*_test.${answers.typescript ? 'ts' : 'js'}`,
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: (answers) => ['GraphQL', 'REST'].includes(answers.helper) === true,
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((result) => {
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((config) => {
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 (helperResult) => {
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((key) => {
345
+ Object.keys(helperResult).forEach(key => {
345
346
  const parts = key.split('_')
346
347
  const helperName = parts[0]
347
348
  const configName = parts[1]