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.
Files changed (150) 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 +139 -87
  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/generate.js +10 -14
  17. package/lib/command/gherkin/snippets.js +75 -73
  18. package/lib/command/gherkin/steps.js +1 -1
  19. package/lib/command/info.js +42 -8
  20. package/lib/command/init.js +13 -12
  21. package/lib/command/interactive.js +10 -2
  22. package/lib/command/list.js +1 -1
  23. package/lib/command/run-multiple/chunk.js +48 -45
  24. package/lib/command/run-multiple.js +12 -35
  25. package/lib/command/run-workers.js +21 -58
  26. package/lib/command/utils.js +5 -6
  27. package/lib/command/workers/runTests.js +262 -220
  28. package/lib/container.js +386 -238
  29. package/lib/data/context.js +10 -13
  30. package/lib/data/dataScenarioConfig.js +8 -8
  31. package/lib/data/dataTableArgument.js +6 -6
  32. package/lib/data/table.js +5 -11
  33. package/lib/effects.js +223 -0
  34. package/lib/element/WebElement.js +327 -0
  35. package/lib/els.js +158 -0
  36. package/lib/event.js +21 -17
  37. package/lib/heal.js +88 -80
  38. package/lib/helper/AI.js +2 -1
  39. package/lib/helper/ApiDataFactory.js +3 -6
  40. package/lib/helper/Appium.js +47 -51
  41. package/lib/helper/FileSystem.js +3 -3
  42. package/lib/helper/GraphQLDataFactory.js +3 -3
  43. package/lib/helper/JSONResponse.js +75 -37
  44. package/lib/helper/Mochawesome.js +31 -9
  45. package/lib/helper/Nightmare.js +35 -53
  46. package/lib/helper/Playwright.js +262 -267
  47. package/lib/helper/Protractor.js +54 -77
  48. package/lib/helper/Puppeteer.js +246 -260
  49. package/lib/helper/REST.js +5 -17
  50. package/lib/helper/TestCafe.js +21 -44
  51. package/lib/helper/WebDriver.js +151 -170
  52. package/lib/helper/extras/Popup.js +22 -22
  53. package/lib/helper/testcafe/testcafe-utils.js +26 -27
  54. package/lib/listener/emptyRun.js +55 -0
  55. package/lib/listener/exit.js +7 -10
  56. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  57. package/lib/listener/globalTimeout.js +165 -0
  58. package/lib/listener/helpers.js +15 -15
  59. package/lib/listener/mocha.js +1 -1
  60. package/lib/listener/result.js +12 -0
  61. package/lib/listener/retryEnhancer.js +85 -0
  62. package/lib/listener/steps.js +32 -18
  63. package/lib/listener/store.js +20 -0
  64. package/lib/mocha/asyncWrapper.js +231 -0
  65. package/lib/{interfaces → mocha}/bdd.js +3 -3
  66. package/lib/mocha/cli.js +308 -0
  67. package/lib/mocha/factory.js +104 -0
  68. package/lib/{interfaces → mocha}/featureConfig.js +32 -12
  69. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  70. package/lib/mocha/hooks.js +112 -0
  71. package/lib/mocha/index.js +12 -0
  72. package/lib/mocha/inject.js +29 -0
  73. package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
  74. package/lib/mocha/suite.js +82 -0
  75. package/lib/mocha/test.js +181 -0
  76. package/lib/mocha/types.d.ts +42 -0
  77. package/lib/mocha/ui.js +232 -0
  78. package/lib/output.js +82 -62
  79. package/lib/pause.js +160 -138
  80. package/lib/plugin/analyze.js +396 -0
  81. package/lib/plugin/auth.js +435 -0
  82. package/lib/plugin/autoDelay.js +8 -8
  83. package/lib/plugin/autoLogin.js +3 -338
  84. package/lib/plugin/commentStep.js +6 -1
  85. package/lib/plugin/coverage.js +10 -19
  86. package/lib/plugin/customLocator.js +3 -3
  87. package/lib/plugin/customReporter.js +52 -0
  88. package/lib/plugin/eachElement.js +1 -1
  89. package/lib/plugin/fakerTransform.js +1 -1
  90. package/lib/plugin/heal.js +36 -9
  91. package/lib/plugin/htmlReporter.js +1947 -0
  92. package/lib/plugin/pageInfo.js +140 -0
  93. package/lib/plugin/retryFailedStep.js +17 -18
  94. package/lib/plugin/retryTo.js +2 -113
  95. package/lib/plugin/screenshotOnFail.js +17 -58
  96. package/lib/plugin/selenoid.js +15 -35
  97. package/lib/plugin/standardActingHelpers.js +4 -1
  98. package/lib/plugin/stepByStepReport.js +56 -17
  99. package/lib/plugin/stepTimeout.js +5 -12
  100. package/lib/plugin/subtitles.js +4 -4
  101. package/lib/plugin/tryTo.js +3 -102
  102. package/lib/plugin/wdio.js +8 -10
  103. package/lib/recorder.js +155 -124
  104. package/lib/rerun.js +43 -42
  105. package/lib/result.js +161 -0
  106. package/lib/secret.js +1 -1
  107. package/lib/step/base.js +239 -0
  108. package/lib/step/comment.js +10 -0
  109. package/lib/step/config.js +50 -0
  110. package/lib/step/func.js +46 -0
  111. package/lib/step/helper.js +50 -0
  112. package/lib/step/meta.js +99 -0
  113. package/lib/step/record.js +74 -0
  114. package/lib/step/retry.js +11 -0
  115. package/lib/step/section.js +55 -0
  116. package/lib/step.js +21 -332
  117. package/lib/steps.js +50 -0
  118. package/lib/store.js +37 -5
  119. package/lib/template/heal.js +2 -11
  120. package/lib/test-server.js +323 -0
  121. package/lib/timeout.js +66 -0
  122. package/lib/utils.js +351 -218
  123. package/lib/within.js +75 -55
  124. package/lib/workerStorage.js +2 -1
  125. package/lib/workers.js +386 -276
  126. package/package.json +76 -70
  127. package/translations/de-DE.js +4 -3
  128. package/translations/fr-FR.js +4 -3
  129. package/translations/index.js +1 -0
  130. package/translations/it-IT.js +4 -3
  131. package/translations/ja-JP.js +4 -3
  132. package/translations/nl-NL.js +76 -0
  133. package/translations/pl-PL.js +4 -3
  134. package/translations/pt-BR.js +4 -3
  135. package/translations/ru-RU.js +4 -3
  136. package/translations/utils.js +9 -0
  137. package/translations/zh-CN.js +4 -3
  138. package/translations/zh-TW.js +4 -3
  139. package/typings/index.d.ts +188 -186
  140. package/typings/promiseBasedTypes.d.ts +18 -705
  141. package/typings/types.d.ts +301 -804
  142. package/lib/cli.js +0 -256
  143. package/lib/helper/ExpectHelper.js +0 -391
  144. package/lib/helper/SoftExpectHelper.js +0 -381
  145. package/lib/listener/artifacts.js +0 -19
  146. package/lib/listener/timeout.js +0 -109
  147. package/lib/mochaFactory.js +0 -113
  148. package/lib/plugin/debugErrors.js +0 -67
  149. package/lib/scenario.js +0 -224
  150. 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}`
@@ -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');
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]
@@ -4,7 +4,7 @@ const Codecept = require('../codecept')
4
4
  const Container = require('../container')
5
5
  const event = require('../event')
6
6
  const output = require('../output')
7
- const webHelpers = require('../plugin/standardActingHelpers')
7
+ const webHelpers = Container.STANDARD_ACTING_HELPERS
8
8
 
9
9
  module.exports = async function (path, options) {
10
10
  // Backward compatibility for --profile
@@ -23,15 +23,23 @@ module.exports = async function (path, options) {
23
23
 
24
24
  if (options.verbose) output.level(3)
25
25
 
26
+ let addGlobalRetries
27
+
28
+ if (config.retry) {
29
+ addGlobalRetries = function retries() {}
30
+ }
31
+
26
32
  output.print('Starting interactive shell for current suite...')
27
33
  recorder.start()
28
34
  event.emit(event.suite.before, {
29
35
  fullTitle: () => 'Interactive Shell',
30
36
  tests: [],
37
+ retries: addGlobalRetries,
31
38
  })
32
39
  event.emit(event.test.before, {
33
40
  title: '',
34
41
  artifacts: {},
42
+ retries: addGlobalRetries,
35
43
  })
36
44
 
37
45
  const enabledHelpers = Container.helpers()
@@ -39,7 +47,7 @@ module.exports = async function (path, options) {
39
47
  if (webHelpers.includes(helperName)) {
40
48
  const I = enabledHelpers[helperName]
41
49
  recorder.add(() => I.amOnPage('/'))
42
- recorder.catchWithoutStop((e) => output.print(`Error while loading home page: ${e.message}}`))
50
+ recorder.catchWithoutStop(e => output.print(`Error while loading home page: ${e.message}}`))
43
51
  break
44
52
  }
45
53
  }
@@ -17,7 +17,7 @@ module.exports = function (path) {
17
17
  const actions = []
18
18
  for (const name in helpers) {
19
19
  const helper = helpers[name]
20
- methodsOfObject(helper).forEach((action) => {
20
+ methodsOfObject(helper).forEach(action => {
21
21
  const params = getParamsToString(helper[action])
22
22
  actions[action] = 1
23
23
  output.print(` ${output.colors.grey(name)} I.${output.colors.bold(action)}(${params})`)