codeceptjs 3.7.0-beta.9 → 3.7.0

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 (50) hide show
  1. package/bin/codecept.js +1 -1
  2. package/lib/codecept.js +14 -12
  3. package/lib/command/check.js +33 -9
  4. package/lib/command/definitions.js +1 -1
  5. package/lib/command/gherkin/snippets.js +69 -69
  6. package/lib/command/interactive.js +1 -1
  7. package/lib/command/run-multiple/chunk.js +48 -45
  8. package/lib/container.js +14 -7
  9. package/lib/effects.js +7 -2
  10. package/lib/event.js +2 -0
  11. package/lib/helper/AI.js +2 -1
  12. package/lib/helper/Playwright.js +1 -1
  13. package/lib/helper/Puppeteer.js +1 -1
  14. package/lib/helper/extras/Popup.js +22 -22
  15. package/lib/mocha/asyncWrapper.js +3 -1
  16. package/lib/mocha/gherkin.js +1 -1
  17. package/lib/mocha/inject.js +5 -0
  18. package/lib/mocha/test.js +5 -2
  19. package/lib/plugin/analyze.js +50 -3
  20. package/lib/plugin/auth.js +435 -0
  21. package/lib/plugin/autoDelay.js +2 -2
  22. package/lib/plugin/autoLogin.js +3 -337
  23. package/lib/plugin/pageInfo.js +1 -1
  24. package/lib/plugin/retryFailedStep.js +13 -14
  25. package/lib/plugin/retryTo.js +6 -17
  26. package/lib/plugin/screenshotOnFail.js +4 -5
  27. package/lib/plugin/standardActingHelpers.js +4 -1
  28. package/lib/plugin/stepByStepReport.js +1 -1
  29. package/lib/plugin/tryTo.js +6 -15
  30. package/lib/recorder.js +1 -0
  31. package/lib/step/base.js +15 -4
  32. package/lib/step/comment.js +10 -0
  33. package/lib/store.js +29 -5
  34. package/lib/utils.js +1 -1
  35. package/lib/within.js +2 -0
  36. package/package.json +18 -18
  37. package/translations/de-DE.js +4 -3
  38. package/translations/fr-FR.js +4 -3
  39. package/translations/index.js +1 -0
  40. package/translations/it-IT.js +4 -3
  41. package/translations/ja-JP.js +4 -3
  42. package/translations/nl-NL.js +76 -0
  43. package/translations/pl-PL.js +4 -3
  44. package/translations/pt-BR.js +4 -3
  45. package/translations/ru-RU.js +4 -3
  46. package/translations/utils.js +9 -0
  47. package/translations/zh-CN.js +4 -3
  48. package/translations/zh-TW.js +4 -3
  49. package/typings/promiseBasedTypes.d.ts +0 -652
  50. package/typings/types.d.ts +99 -655
package/bin/codecept.js CHANGED
@@ -62,7 +62,7 @@ program
62
62
  .command('check')
63
63
  .option(commandFlags.config.flag, commandFlags.config.description)
64
64
  .description('Checks configuration and environment before running tests')
65
- .option('-t, --timeout [ms]', 'timeout for checks in ms, 20000 by default')
65
+ .option('-t, --timeout [ms]', 'timeout for checks in ms, 50000 by default')
66
66
  .action(errorHandler(require('../lib/command/check')))
67
67
 
68
68
  program
package/lib/codecept.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { existsSync, readFileSync } = require('fs')
2
- const glob = require('glob')
2
+ const { globSync } = require('glob')
3
3
  const fsPath = require('path')
4
4
  const { resolve } = require('path')
5
5
 
@@ -168,15 +168,17 @@ class Codecept {
168
168
  }
169
169
 
170
170
  for (pattern of patterns) {
171
- glob.sync(pattern, options).forEach(file => {
172
- if (file.includes('node_modules')) return
173
- if (!fsPath.isAbsolute(file)) {
174
- file = fsPath.join(global.codecept_dir, file)
175
- }
176
- if (!this.testFiles.includes(fsPath.resolve(file))) {
177
- this.testFiles.push(fsPath.resolve(file))
178
- }
179
- })
171
+ if (pattern) {
172
+ globSync(pattern, options).forEach(file => {
173
+ if (file.includes('node_modules')) return
174
+ if (!fsPath.isAbsolute(file)) {
175
+ file = fsPath.join(global.codecept_dir, file)
176
+ }
177
+ if (!this.testFiles.includes(fsPath.resolve(file))) {
178
+ this.testFiles.push(fsPath.resolve(file))
179
+ }
180
+ })
181
+ }
180
182
  }
181
183
  }
182
184
 
@@ -200,12 +202,12 @@ class Codecept {
200
202
  }
201
203
  const done = () => {
202
204
  event.emit(event.all.result, container.result())
203
- event.emit(event.all.after)
205
+ event.emit(event.all.after, this)
204
206
  resolve()
205
207
  }
206
208
 
207
209
  try {
208
- event.emit(event.all.before)
210
+ event.emit(event.all.before, this)
209
211
  mocha.run(() => done())
210
212
  } catch (e) {
211
213
  output.error(e.stack)
@@ -1,7 +1,6 @@
1
1
  const { getConfig, getTestRoot } = require('./utils')
2
2
  const Codecept = require('../codecept')
3
3
  const output = require('../output')
4
- const standardActingHelpers = require('../plugin/standardActingHelpers')
5
4
  const store = require('../store')
6
5
  const container = require('../container')
7
6
  const figures = require('figures')
@@ -23,8 +22,10 @@ module.exports = async function (options) {
23
22
  container: false,
24
23
  pageObjects: false,
25
24
  plugins: false,
25
+ ai: true, // we don't need to check AI
26
26
  helpers: false,
27
27
  setup: false,
28
+ teardown: false,
28
29
  tests: false,
29
30
  def: false,
30
31
  }
@@ -51,6 +52,8 @@ module.exports = async function (options) {
51
52
  checks.container = err
52
53
  }
53
54
 
55
+ const standardActingHelpers = container.STANDARD_ACTING_HELPERS
56
+
54
57
  printCheck('container', checks['container'])
55
58
 
56
59
  if (codecept) {
@@ -83,6 +86,13 @@ module.exports = async function (options) {
83
86
  }
84
87
  }
85
88
 
89
+ if (config?.ai?.request) {
90
+ checks.ai = true
91
+ printCheck('ai', checks['ai'], 'Configuration is enabled, request function is set')
92
+ } else {
93
+ printCheck('ai', checks['ai'], 'Disabled')
94
+ }
95
+
86
96
  printCheck('tests', checks['tests'], `Total: ${numTests} tests`)
87
97
 
88
98
  store.dryRun = true
@@ -122,22 +132,36 @@ module.exports = async function (options) {
122
132
  if (Object.keys(helpers).length) {
123
133
  const suite = container.mocha().suite
124
134
  const test = createTest('test', () => {})
125
- try {
126
- for (const helper of Object.values(helpers)) {
135
+ checks.setup = true
136
+ for (const helper of Object.values(helpers)) {
137
+ try {
127
138
  if (helper._beforeSuite) await helper._beforeSuite(suite)
128
139
  if (helper._before) await helper._before(test)
140
+ } catch (err) {
141
+ err.message = `${helper.constructor.name} helper: ${err.message}`
142
+ if (checks.setup instanceof Error) err.message = `${err.message}\n\n${checks.setup?.message || ''}`.trim()
143
+ checks.setup = err
144
+ }
145
+ }
146
+
147
+ printCheck('Helpers Before', checks['setup'], standardActingHelpers.some(h => Object.keys(helpers).includes(h)) ? 'Initializing browser' : '')
148
+
149
+ checks.teardown = true
150
+ for (const helper of Object.values(helpers).reverse()) {
151
+ try {
129
152
  if (helper._passed) await helper._passed(test)
130
153
  if (helper._after) await helper._after(test)
131
154
  if (helper._finishTest) await helper._finishTest(suite)
132
155
  if (helper._afterSuite) await helper._afterSuite(suite)
156
+ } catch (err) {
157
+ err.message = `${helper.constructor.name} helper: ${err.message}`
158
+ if (checks.teardown instanceof Error) err.message = `${err.message}\n\n${checks.teardown?.message || ''}`.trim()
159
+ checks.teardown = err
133
160
  }
134
- checks.setup = true
135
- } catch (err) {
136
- checks.setup = err
137
161
  }
138
- }
139
162
 
140
- printCheck('Helpers Before/After', checks['setup'], standardActingHelpers.some(h => Object.keys(helpers).includes(h)) ? 'Initializing and closing browser' : '')
163
+ printCheck('Helpers After', checks['teardown'], standardActingHelpers.some(h => Object.keys(helpers).includes(h)) ? 'Closing browser' : '')
164
+ }
141
165
 
142
166
  try {
143
167
  definitions(configFile, { dryRun: true })
@@ -170,7 +194,7 @@ function printCheck(name, value, comment = '') {
170
194
  }
171
195
 
172
196
  if (value instanceof Error) {
173
- comment = `${comment} ${chalk.red.italic(value.message)}`.trim()
197
+ comment = `${comment} ${chalk.red(value.message)}`.trim()
174
198
  }
175
199
 
176
200
  output.print(status, name.toUpperCase(), chalk.dim(comment))
@@ -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
@@ -1,113 +1,113 @@
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('../../mocha/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
56
  const parseSteps = steps => {
57
- const newSteps = [];
58
- let currentKeyword = '';
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
89
  const parseFile = file => {
90
- const ast = parser.parse(fs.readFileSync(file).toString());
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
92
+ if (child.scenario.keyword === 'Scenario Outline') continue // skip scenario outline
93
93
  parseSteps(child.scenario.steps)
94
94
  .map(step => {
95
- return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) });
95
+ return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) })
96
96
  })
97
- .map(step => newSteps.set(`${step.type}(${step})`, step));
97
+ .map(step => newSteps.set(`${step.type}(${step})`, step))
98
98
  }
99
- };
99
+ }
100
100
 
101
- files.forEach(file => parseFile(file));
101
+ files.forEach(file => parseFile(file))
102
102
 
103
- let stepFile = options.path || config.gherkin.steps[0];
103
+ let stepFile = options.path || config.gherkin.steps[0]
104
104
  if (!fs.existsSync(stepFile)) {
105
- output.error(`Please enter a valid step file path ${stepFile}`);
106
- process.exit(1);
105
+ output.error(`Please enter a valid step file path ${stepFile}`)
106
+ process.exit(1)
107
107
  }
108
108
 
109
109
  if (!fsPath.isAbsolute(stepFile)) {
110
- stepFile = fsPath.join(global.codecept_dir, stepFile);
110
+ stepFile = fsPath.join(global.codecept_dir, stepFile)
111
111
  }
112
112
 
113
113
  const snippets = [...newSteps.values()]
@@ -117,18 +117,18 @@ module.exports = function (genPath, options) {
117
117
  ${step.type}(${step.regexp ? '/^' : "'"}${step}${step.regexp ? '$/' : "'"}, () => {
118
118
  // From "${step.file}" ${JSON.stringify(step.location)}
119
119
  throw new Error('Not implemented yet');
120
- });`;
121
- });
120
+ });`
121
+ })
122
122
 
123
123
  if (!snippets.length) {
124
- output.print('No new snippets found');
125
- return;
124
+ output.print('No new snippets found')
125
+ return
126
126
  }
127
- output.success(`Snippets generated: ${snippets.length}`);
128
- output.print(snippets.join('\n'));
127
+ output.success(`Snippets generated: ${snippets.length}`)
128
+ output.print(snippets.join('\n'))
129
129
 
130
130
  if (!options.dryRun) {
131
- output.success(`Snippets added to ${output.colors.bold(stepFile)}`);
132
- 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')
133
133
  }
134
- };
134
+ }
@@ -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
@@ -1,60 +1,60 @@
1
- const glob = require('glob');
2
- const path = require('path');
3
- const fs = require('fs');
1
+ const { globSync } = require('glob')
2
+ const path = require('path')
3
+ const fs = require('fs')
4
4
 
5
5
  /**
6
6
  * Splits a list to (n) parts, defined via the size argument.
7
7
  */
8
8
  const splitFiles = (list, size) => {
9
- const sets = [];
10
- const chunks = list.length / size;
11
- let i = 0;
9
+ const sets = []
10
+ const chunks = list.length / size
11
+ let i = 0
12
12
 
13
13
  while (i < chunks) {
14
- sets[i] = list.splice(0, size);
15
- i++;
14
+ sets[i] = list.splice(0, size)
15
+ i++
16
16
  }
17
17
 
18
- return sets;
19
- };
18
+ return sets
19
+ }
20
20
 
21
21
  /**
22
22
  * Executes a glob pattern and pushes the results to a list.
23
23
  */
24
- const findFiles = (pattern) => {
25
- const files = [];
24
+ const findFiles = pattern => {
25
+ const files = []
26
26
 
27
- glob.sync(pattern).forEach((file) => {
28
- files.push(path.resolve(file));
29
- });
27
+ globSync(pattern).forEach(file => {
28
+ files.push(path.resolve(file))
29
+ })
30
30
 
31
- return files;
32
- };
31
+ return files
32
+ }
33
33
 
34
34
  /**
35
35
  * Joins a list of files to a valid glob pattern
36
36
  */
37
- const flattenFiles = (list) => {
38
- const pattern = list.join(',');
39
- return pattern.indexOf(',') > -1 ? `{${pattern}}` : pattern;
40
- };
37
+ const flattenFiles = list => {
38
+ const pattern = list.join(',')
39
+ return pattern.indexOf(',') > -1 ? `{${pattern}}` : pattern
40
+ }
41
41
 
42
42
  /**
43
43
  * Greps a file by its content, checks if Scenario or Feature text'
44
44
  * matches the grep text.
45
45
  */
46
46
  const grepFile = (file, grep) => {
47
- const contents = fs.readFileSync(file, 'utf8');
48
- const pattern = new RegExp(`((Scenario|Feature)\(.*${grep}.*\))`, 'g'); // <- How future proof/solid is this?
49
- return !!pattern.exec(contents);
50
- };
47
+ const contents = fs.readFileSync(file, 'utf8')
48
+ const pattern = new RegExp(`((Scenario|Feature)\(.*${grep}.*\))`, 'g') // <- How future proof/solid is this?
49
+ return !!pattern.exec(contents)
50
+ }
51
51
 
52
- const mapFileFormats = (files) => {
52
+ const mapFileFormats = files => {
53
53
  return {
54
54
  gherkin: files.filter(file => file.match(/\.feature$/)),
55
55
  js: files.filter(file => file.match(/\.t|js$/)),
56
- };
57
- };
56
+ }
57
+ }
58
58
 
59
59
  /**
60
60
  * Creates a list of chunks incl. configuration by either dividing a list of scenario
@@ -62,30 +62,33 @@ const mapFileFormats = (files) => {
62
62
  * the splitting.
63
63
  */
64
64
  const createChunks = (config, patterns = []) => {
65
- const files = patterns.filter(pattern => !!pattern).map((pattern) => {
66
- return findFiles(pattern).filter((file) => {
67
- return config.grep ? grepFile(file, config.grep) : true;
68
- });
69
- }).reduce((acc, val) => acc.concat(val), []);
65
+ const files = patterns
66
+ .filter(pattern => !!pattern)
67
+ .map(pattern => {
68
+ return findFiles(pattern).filter(file => {
69
+ return config.grep ? grepFile(file, config.grep) : true
70
+ })
71
+ })
72
+ .reduce((acc, val) => acc.concat(val), [])
70
73
 
71
- let chunks = [];
74
+ let chunks = []
72
75
  if (typeof config.chunks === 'function') {
73
- chunks = config.chunks.call(this, files);
76
+ chunks = config.chunks.call(this, files)
74
77
  } else if (typeof config.chunks === 'number' || typeof config.chunks === 'string') {
75
- chunks = splitFiles(files, Math.ceil(files.length / config.chunks));
78
+ chunks = splitFiles(files, Math.ceil(files.length / config.chunks))
76
79
  } else {
77
- throw new Error('chunks is neither a finite number or a valid function');
80
+ throw new Error('chunks is neither a finite number or a valid function')
78
81
  }
79
82
 
80
- const chunkConfig = { ...config };
81
- delete chunkConfig.chunks;
83
+ const chunkConfig = { ...config }
84
+ delete chunkConfig.chunks
82
85
 
83
- return chunks.map((chunkFiles) => {
84
- const { js, gherkin } = mapFileFormats(chunkFiles);
85
- return { ...chunkConfig, tests: flattenFiles(js), gherkin: { features: flattenFiles(gherkin) } };
86
- });
87
- };
86
+ return chunks.map(chunkFiles => {
87
+ const { js, gherkin } = mapFileFormats(chunkFiles)
88
+ return { ...chunkConfig, tests: flattenFiles(js), gherkin: { features: flattenFiles(gherkin) } }
89
+ })
90
+ }
88
91
 
89
92
  module.exports = {
90
93
  createChunks,
91
- };
94
+ }
package/lib/container.js CHANGED
@@ -1,4 +1,4 @@
1
- const glob = require('glob')
1
+ const { globSync } = require('glob')
2
2
  const path = require('path')
3
3
  const debug = require('debug')('codeceptjs:container')
4
4
  const { MetaStep } = require('./step')
@@ -34,6 +34,13 @@ let container = {
34
34
  * Dependency Injection Container
35
35
  */
36
36
  class Container {
37
+ /**
38
+ * Get the standard acting helpers of CodeceptJS Container
39
+ *
40
+ */
41
+ static get STANDARD_ACTING_HELPERS() {
42
+ return ['Playwright', 'WebDriver', 'Puppeteer', 'Appium', 'TestCafe']
43
+ }
37
44
  /**
38
45
  * Create container with all required helpers and support objects
39
46
  *
@@ -162,18 +169,18 @@ class Container {
162
169
  * @param {Object<string, *>} newSupport
163
170
  * @param {Object<string, *>} newPlugins
164
171
  */
165
- static clear(newHelpers, newSupport, newPlugins) {
166
- container.helpers = newHelpers || {}
172
+ static clear(newHelpers = {}, newSupport = {}, newPlugins = {}) {
173
+ container.helpers = newHelpers
167
174
  container.translation = loadTranslation()
168
- container.proxySupport = createSupportObjects(newSupport || {})
169
- container.plugins = newPlugins || {}
175
+ container.proxySupport = createSupportObjects(newSupport)
176
+ container.plugins = newPlugins
170
177
  asyncHelperPromise = Promise.resolve()
171
178
  store.actor = null
172
179
  debug('container cleared')
173
180
  }
174
181
 
175
182
  /**
176
- * @param {Function} fn
183
+ * @param {Function|null} fn
177
184
  * @returns {Promise<void>}
178
185
  */
179
186
  static async started(fn = null) {
@@ -464,7 +471,7 @@ function loadGherkinSteps(paths) {
464
471
  } else {
465
472
  const folderPath = paths.startsWith('.') ? path.join(global.codecept_dir, paths) : ''
466
473
  if (folderPath !== '') {
467
- glob.sync(folderPath).forEach(file => {
474
+ globSync(folderPath).forEach(file => {
468
475
  loadSupportObject(file, `Step Definition from ${file}`)
469
476
  })
470
477
  }
package/lib/effects.js CHANGED
@@ -2,6 +2,7 @@ const recorder = require('./recorder')
2
2
  const { debug } = require('./output')
3
3
  const store = require('./store')
4
4
  const event = require('./event')
5
+ const within = require('./within')
5
6
 
6
7
  /**
7
8
  * A utility function for CodeceptJS tests that acts as a soft assertion.
@@ -178,11 +179,14 @@ async function tryTo(callback) {
178
179
  const sessionName = 'tryTo'
179
180
 
180
181
  let result = false
182
+ let isAutoRetriesEnabled = store.autoRetries
181
183
  return recorder.add(
182
184
  sessionName,
183
185
  () => {
184
186
  recorder.session.start(sessionName)
185
- store.tryTo = true
187
+ isAutoRetriesEnabled = store.autoRetries
188
+ if (isAutoRetriesEnabled) debug('Auto retries disabled inside tryTo effect')
189
+ store.autoRetries = false
186
190
  callback()
187
191
  recorder.add(() => {
188
192
  result = true
@@ -199,7 +203,7 @@ async function tryTo(callback) {
199
203
  return recorder.add(
200
204
  'result',
201
205
  () => {
202
- store.tryTo = undefined
206
+ store.autoRetries = isAutoRetriesEnabled
203
207
  return result
204
208
  },
205
209
  true,
@@ -215,4 +219,5 @@ module.exports = {
215
219
  hopeThat,
216
220
  retryTo,
217
221
  tryTo,
222
+ within,
218
223
  }
package/lib/event.js CHANGED
@@ -54,6 +54,8 @@ module.exports = {
54
54
  * @inner
55
55
  * @property {'hook.start'} started
56
56
  * @property {'hook.passed'} passed
57
+ * @property {'hook.failed'} failed
58
+ * @property {'hook.finished'} finished
57
59
  */
58
60
  hook: {
59
61
  started: 'hook.start',
package/lib/helper/AI.js CHANGED
@@ -3,13 +3,14 @@ const ora = require('ora-classic')
3
3
  const fs = require('fs')
4
4
  const path = require('path')
5
5
  const ai = require('../ai')
6
- const standardActingHelpers = require('../plugin/standardActingHelpers')
7
6
  const Container = require('../container')
8
7
  const { splitByChunks, minifyHtml } = require('../html')
9
8
  const { beautify } = require('../utils')
10
9
  const output = require('../output')
11
10
  const { registerVariable } = require('../pause')
12
11
 
12
+ const standardActingHelpers = Container.STANDARD_ACTING_HELPERS
13
+
13
14
  const gtpRole = {
14
15
  user: 'user',
15
16
  }
@@ -484,7 +484,7 @@ class Playwright extends Helper {
484
484
  this.currentRunningTest = test
485
485
 
486
486
  recorder.retry({
487
- retries: process.env.FAILED_STEP_RETRIES || 3,
487
+ retries: test?.opts?.conditionalRetries || 3,
488
488
  when: err => {
489
489
  if (!err || typeof err.message !== 'string') {
490
490
  return false
@@ -312,7 +312,7 @@ class Puppeteer extends Helper {
312
312
  this.sessionPages = {}
313
313
  this.currentRunningTest = test
314
314
  recorder.retry({
315
- retries: process.env.FAILED_STEP_RETRIES || 3,
315
+ retries: test?.opts?.conditionalRetries || 3,
316
316
  when: err => {
317
317
  if (!err || typeof err.message !== 'string') {
318
318
  return false