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.
- package/bin/codecept.js +1 -1
- package/lib/codecept.js +14 -12
- package/lib/command/check.js +33 -9
- package/lib/command/definitions.js +1 -1
- package/lib/command/gherkin/snippets.js +69 -69
- package/lib/command/interactive.js +1 -1
- package/lib/command/run-multiple/chunk.js +48 -45
- package/lib/container.js +14 -7
- package/lib/effects.js +7 -2
- package/lib/event.js +2 -0
- package/lib/helper/AI.js +2 -1
- package/lib/helper/Playwright.js +1 -1
- package/lib/helper/Puppeteer.js +1 -1
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/mocha/asyncWrapper.js +3 -1
- package/lib/mocha/gherkin.js +1 -1
- package/lib/mocha/inject.js +5 -0
- package/lib/mocha/test.js +5 -2
- package/lib/plugin/analyze.js +50 -3
- package/lib/plugin/auth.js +435 -0
- package/lib/plugin/autoDelay.js +2 -2
- package/lib/plugin/autoLogin.js +3 -337
- package/lib/plugin/pageInfo.js +1 -1
- package/lib/plugin/retryFailedStep.js +13 -14
- package/lib/plugin/retryTo.js +6 -17
- package/lib/plugin/screenshotOnFail.js +4 -5
- package/lib/plugin/standardActingHelpers.js +4 -1
- package/lib/plugin/stepByStepReport.js +1 -1
- package/lib/plugin/tryTo.js +6 -15
- package/lib/recorder.js +1 -0
- package/lib/step/base.js +15 -4
- package/lib/step/comment.js +10 -0
- package/lib/store.js +29 -5
- package/lib/utils.js +1 -1
- package/lib/within.js +2 -0
- package/package.json +18 -18
- package/translations/de-DE.js +4 -3
- package/translations/fr-FR.js +4 -3
- package/translations/index.js +1 -0
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +9 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/promiseBasedTypes.d.ts +0 -652
- 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,
|
|
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
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
this.testFiles.
|
|
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)
|
package/lib/command/check.js
CHANGED
|
@@ -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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
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
|
|
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 = [...
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
25
|
-
const files = []
|
|
24
|
+
const findFiles = pattern => {
|
|
25
|
+
const files = []
|
|
26
26
|
|
|
27
|
-
|
|
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 =
|
|
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')
|
|
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 =
|
|
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
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
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
|
}
|
package/lib/helper/Playwright.js
CHANGED
|
@@ -484,7 +484,7 @@ class Playwright extends Helper {
|
|
|
484
484
|
this.currentRunningTest = test
|
|
485
485
|
|
|
486
486
|
recorder.retry({
|
|
487
|
-
retries:
|
|
487
|
+
retries: test?.opts?.conditionalRetries || 3,
|
|
488
488
|
when: err => {
|
|
489
489
|
if (!err || typeof err.message !== 'string') {
|
|
490
490
|
return false
|
package/lib/helper/Puppeteer.js
CHANGED
|
@@ -312,7 +312,7 @@ class Puppeteer extends Helper {
|
|
|
312
312
|
this.sessionPages = {}
|
|
313
313
|
this.currentRunningTest = test
|
|
314
314
|
recorder.retry({
|
|
315
|
-
retries:
|
|
315
|
+
retries: test?.opts?.conditionalRetries || 3,
|
|
316
316
|
when: err => {
|
|
317
317
|
if (!err || typeof err.message !== 'string') {
|
|
318
318
|
return false
|