codeceptjs 3.6.10 → 3.7.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -119
- package/bin/codecept.js +9 -2
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +66 -102
- package/lib/ai.js +130 -121
- package/lib/assert/empty.js +3 -5
- package/lib/assert/equal.js +4 -7
- package/lib/assert/include.js +4 -6
- package/lib/assert/throws.js +2 -4
- package/lib/assert/truth.js +2 -2
- package/lib/codecept.js +87 -83
- package/lib/command/check.js +186 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +10 -8
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +1 -3
- package/lib/command/init.js +8 -12
- package/lib/command/interactive.js +2 -2
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +5 -57
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +68 -232
- package/lib/container.js +354 -237
- package/lib/data/context.js +10 -13
- package/lib/data/dataScenarioConfig.js +8 -8
- package/lib/data/dataTableArgument.js +6 -6
- package/lib/data/table.js +5 -11
- package/lib/effects.js +218 -0
- package/lib/els.js +158 -0
- package/lib/event.js +19 -17
- package/lib/heal.js +88 -80
- package/lib/helper/AI.js +2 -1
- package/lib/helper/ApiDataFactory.js +3 -6
- package/lib/helper/Appium.js +45 -51
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +3 -3
- package/lib/helper/JSONResponse.js +57 -37
- package/lib/helper/Nightmare.js +35 -53
- package/lib/helper/Playwright.js +211 -252
- package/lib/helper/Protractor.js +54 -77
- package/lib/helper/Puppeteer.js +139 -232
- package/lib/helper/REST.js +5 -17
- package/lib/helper/TestCafe.js +21 -44
- package/lib/helper/WebDriver.js +131 -169
- package/lib/helper/testcafe/testcafe-utils.js +26 -27
- package/lib/listener/emptyRun.js +55 -0
- package/lib/listener/exit.js +7 -10
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/globalTimeout.js +165 -0
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/result.js +12 -0
- package/lib/listener/steps.js +20 -18
- package/lib/listener/store.js +20 -0
- package/lib/mocha/asyncWrapper.js +216 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +308 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +24 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +112 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +29 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +21 -6
- package/lib/mocha/suite.js +81 -0
- package/lib/mocha/test.js +159 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +219 -0
- package/lib/output.js +82 -62
- package/lib/pause.js +155 -138
- package/lib/plugin/analyze.js +349 -0
- package/lib/plugin/autoDelay.js +6 -6
- package/lib/plugin/autoLogin.js +6 -7
- package/lib/plugin/commentStep.js +6 -1
- package/lib/plugin/coverage.js +10 -19
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +52 -0
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +36 -9
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/retryFailedStep.js +4 -4
- package/lib/plugin/retryTo.js +18 -118
- package/lib/plugin/screenshotOnFail.js +17 -49
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/standardActingHelpers.js +4 -1
- package/lib/plugin/stepByStepReport.js +56 -17
- package/lib/plugin/stepTimeout.js +5 -12
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +17 -107
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +146 -125
- package/lib/rerun.js +43 -42
- package/lib/result.js +161 -0
- package/lib/secret.js +1 -1
- package/lib/step/base.js +228 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +21 -332
- package/lib/steps.js +50 -0
- package/lib/store.js +10 -2
- package/lib/template/heal.js +2 -11
- package/lib/timeout.js +66 -0
- package/lib/utils.js +317 -216
- package/lib/within.js +73 -55
- package/lib/workers.js +259 -275
- package/package.json +56 -54
- package/typings/index.d.ts +175 -186
- package/typings/promiseBasedTypes.d.ts +164 -17
- package/typings/types.d.ts +284 -115
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
const debug = require('debug')('codeceptjs:analyze')
|
|
2
|
+
const { isMainThread } = require('node:worker_threads')
|
|
3
|
+
const { arrowRight } = require('figures')
|
|
4
|
+
const container = require('../container')
|
|
5
|
+
const ai = require('../ai')
|
|
6
|
+
const colors = require('chalk')
|
|
7
|
+
const ora = require('ora-classic')
|
|
8
|
+
const event = require('../event')
|
|
9
|
+
const output = require('../output')
|
|
10
|
+
const { ansiRegExp, base64EncodeFile, markdownToAnsi } = require('../utils')
|
|
11
|
+
|
|
12
|
+
const MAX_DATA_LENGTH = 5000
|
|
13
|
+
|
|
14
|
+
const defaultConfig = {
|
|
15
|
+
clusterize: 5,
|
|
16
|
+
analyze: 2,
|
|
17
|
+
vision: false,
|
|
18
|
+
categories: [
|
|
19
|
+
'Browser connection error / browser crash',
|
|
20
|
+
'Network errors (server error, timeout, etc)',
|
|
21
|
+
'HTML / page elements (not found, not visible, etc)',
|
|
22
|
+
'Navigation errors (404, etc)',
|
|
23
|
+
'Code errors (syntax error, JS errors, etc)',
|
|
24
|
+
'Library & framework errors (CodeceptJS internal errors, user-defined libraries, etc)',
|
|
25
|
+
'Data errors (password incorrect, no options in select, invalid format, etc)',
|
|
26
|
+
'Assertion failures',
|
|
27
|
+
'Other errors',
|
|
28
|
+
],
|
|
29
|
+
prompts: {
|
|
30
|
+
clusterize: (tests, config) => {
|
|
31
|
+
const serializedFailedTests = tests
|
|
32
|
+
.map((test, index) => {
|
|
33
|
+
if (!test || !test.err) return
|
|
34
|
+
return `
|
|
35
|
+
#${index + 1}: ${serializeTest(test)}
|
|
36
|
+
${serializeError(test.err).slice(0, MAX_DATA_LENGTH / tests.length)}`.trim()
|
|
37
|
+
})
|
|
38
|
+
.join('\n\n--------\n\n')
|
|
39
|
+
|
|
40
|
+
const messages = [
|
|
41
|
+
{
|
|
42
|
+
role: 'user',
|
|
43
|
+
content: `
|
|
44
|
+
I am test analyst analyzing failed tests in CodeceptJS testing framework.
|
|
45
|
+
|
|
46
|
+
Please analyze the following failed tests and classify them into groups by their cause.
|
|
47
|
+
If there is no groups detected, say: "No common groups found".
|
|
48
|
+
|
|
49
|
+
Provide a short description of the group and a list of failed tests that belong to this group.
|
|
50
|
+
Use percent sign to indicate the percentage of failed tests in the group if this percentage is greater than 30%.
|
|
51
|
+
|
|
52
|
+
Here are failed tests:
|
|
53
|
+
|
|
54
|
+
${serializedFailedTests}
|
|
55
|
+
|
|
56
|
+
Common categories of failures by order of priority:
|
|
57
|
+
|
|
58
|
+
${config.categories.join('\n- ')}
|
|
59
|
+
|
|
60
|
+
If there is no groups of tests, say: "No patterns found"
|
|
61
|
+
Preserve error messages but cut them if they are too long.
|
|
62
|
+
Respond clearly and directly, without introductory words or phrases like ‘Of course,’ ‘Here is the answer,’ etc.
|
|
63
|
+
Do not list more than 3 errors in the group.
|
|
64
|
+
If you identify that all tests in the group have the same tag, add this tag to the group report, otherwise ignore TAG section.
|
|
65
|
+
If you identify that all tests in the group have the same suite, add this suite to the group report, otherwise ignore SUITE section.
|
|
66
|
+
Pick different emojis for each group.
|
|
67
|
+
Order groups by the number of tests in the group.
|
|
68
|
+
If group has one test, skip that group.
|
|
69
|
+
|
|
70
|
+
Provide list of groups in following format:
|
|
71
|
+
|
|
72
|
+
_______________________________
|
|
73
|
+
|
|
74
|
+
## Group <group_number> <emoji>
|
|
75
|
+
|
|
76
|
+
* SUMMARY <summary_of_errors>
|
|
77
|
+
* CATEGORY <category_of_failure>
|
|
78
|
+
* ERROR <error_message_1>, <error_message_2>, ...
|
|
79
|
+
* STEP <step_of_failure> (use CodeceptJS format I.click(), I.see(), etc; if all failures happend on the same step)
|
|
80
|
+
* SUITE <suite_title>, <suite_title> (if SUITE is present, and if all tests in the group have the same suite or suites)
|
|
81
|
+
* TAG <tag> (if TAG is present, and if all tests in the group have the same tag)
|
|
82
|
+
* AFFECTED TESTS (<total number of tests>):
|
|
83
|
+
x <test1 title>
|
|
84
|
+
x <test2 title>
|
|
85
|
+
x <test3 title>
|
|
86
|
+
x ...
|
|
87
|
+
`,
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
role: 'assistant',
|
|
91
|
+
content: `## '
|
|
92
|
+
`,
|
|
93
|
+
},
|
|
94
|
+
]
|
|
95
|
+
return messages
|
|
96
|
+
},
|
|
97
|
+
analyze: (test, config) => {
|
|
98
|
+
const testMessage = serializeTest(test)
|
|
99
|
+
const errorMessage = serializeError(test.err)
|
|
100
|
+
|
|
101
|
+
const messages = [
|
|
102
|
+
{
|
|
103
|
+
role: 'user',
|
|
104
|
+
content: [
|
|
105
|
+
{
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: `
|
|
108
|
+
I am qa engineer analyzing failed tests in CodeceptJS testing framework.
|
|
109
|
+
Please analyze the following failed test and error its error and explain it.
|
|
110
|
+
|
|
111
|
+
Pick one of the categories of failures and explain it.
|
|
112
|
+
|
|
113
|
+
Categories of failures in order of priority:
|
|
114
|
+
|
|
115
|
+
${config.categories.join('\n- ')}
|
|
116
|
+
|
|
117
|
+
Here is the test and error:
|
|
118
|
+
|
|
119
|
+
------- TEST -------
|
|
120
|
+
${testMessage}
|
|
121
|
+
|
|
122
|
+
------- ERROR -------
|
|
123
|
+
${errorMessage}
|
|
124
|
+
|
|
125
|
+
------ INSTRUCTIONS ------
|
|
126
|
+
|
|
127
|
+
Do not get to details, be concise.
|
|
128
|
+
If there is failed step, just write it in STEPS section.
|
|
129
|
+
If you have suggestions for the test, write them in SUMMARY section.
|
|
130
|
+
Do not be too technical in SUMMARY section.
|
|
131
|
+
Inside SUMMARY write exact values, if you have suggestions, explain which information you used to suggest.
|
|
132
|
+
Be concise, each section should not take more than one sentence.
|
|
133
|
+
|
|
134
|
+
Response format:
|
|
135
|
+
|
|
136
|
+
* SUMMARY <explanation_of_failure>
|
|
137
|
+
* ERROR <error_message_1>, <error_message_2>, ...
|
|
138
|
+
* CATEGORY <category_of_failure>
|
|
139
|
+
* STEPS <step_of_failure>
|
|
140
|
+
|
|
141
|
+
Do not add any other sections or explanations. Only CATEGORY, SUMMARY, STEPS.
|
|
142
|
+
${config.vision ? 'Also a screenshot of the page is attached to the prompt.' : ''}
|
|
143
|
+
`,
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
if (config.vision && test.artifacts.screenshot) {
|
|
150
|
+
debug('Adding screenshot to prompt')
|
|
151
|
+
messages[0].content.push({
|
|
152
|
+
type: 'image_url',
|
|
153
|
+
image_url: {
|
|
154
|
+
url: 'data:image/png;base64,' + base64EncodeFile(test.artifacts.screenshot),
|
|
155
|
+
},
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return messages
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
*
|
|
166
|
+
* @param {*} config
|
|
167
|
+
* @returns
|
|
168
|
+
*/
|
|
169
|
+
module.exports = function (config = {}) {
|
|
170
|
+
config = Object.assign(defaultConfig, config)
|
|
171
|
+
|
|
172
|
+
event.dispatcher.on(event.workers.before, () => {
|
|
173
|
+
if (!ai.isEnabled) return
|
|
174
|
+
console.log('Enabled AI analysis')
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
event.dispatcher.on(event.all.result, async result => {
|
|
178
|
+
if (!isMainThread) return // run only on main thread
|
|
179
|
+
if (!ai.isEnabled) {
|
|
180
|
+
console.log('AI is disabled, no analysis will be performed. Run tests with --ai flag to enable it.')
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
printReport(result)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
event.dispatcher.on(event.workers.result, async result => {
|
|
188
|
+
if (!result.hasFailed) {
|
|
189
|
+
console.log('Everything is fine, skipping AI analysis')
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!ai.isEnabled) {
|
|
194
|
+
console.log('AI is disabled, no analysis will be performed. Run tests with --ai flag to enable it.')
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
printReport(result)
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
async function printReport(result) {
|
|
202
|
+
const failedTestsAndErrors = result.tests.filter(t => t.err)
|
|
203
|
+
|
|
204
|
+
if (!failedTestsAndErrors.length) return
|
|
205
|
+
|
|
206
|
+
debug(failedTestsAndErrors.map(t => serializeTest(t) + '\n' + serializeError(t.err)))
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
if (failedTestsAndErrors.length >= config.clusterize) {
|
|
210
|
+
const response = await clusterize(failedTestsAndErrors)
|
|
211
|
+
printHeader()
|
|
212
|
+
console.log(response)
|
|
213
|
+
return
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
output.plugin('analyze', `Analyzing first ${config.analyze} failed tests...`)
|
|
217
|
+
|
|
218
|
+
// we pick only unique errors to not repeat answers
|
|
219
|
+
const uniqueErrors = failedTestsAndErrors.filter((item, index, array) => {
|
|
220
|
+
return array.findIndex(t => t.err?.message === item.err?.message) === index
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
for (let i = 0; i < config.analyze; i++) {
|
|
224
|
+
if (!uniqueErrors[i]) break
|
|
225
|
+
|
|
226
|
+
const response = await analyze(uniqueErrors[i])
|
|
227
|
+
if (!response) {
|
|
228
|
+
break
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
printHeader()
|
|
232
|
+
console.log()
|
|
233
|
+
console.log('--------------------------------')
|
|
234
|
+
console.log(arrowRight, colors.bold.white(uniqueErrors[i].fullTitle()), config.vision ? '👀' : '')
|
|
235
|
+
console.log()
|
|
236
|
+
console.log()
|
|
237
|
+
console.log(response)
|
|
238
|
+
console.log()
|
|
239
|
+
}
|
|
240
|
+
} catch (err) {
|
|
241
|
+
console.error('Error analyzing failed tests', err)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (!Object.keys(container.plugins()).includes('pageInfo')) {
|
|
245
|
+
console.log('To improve analysis, enable pageInfo plugin to get more context for failed tests.')
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
let hasPrintedHeader = false
|
|
250
|
+
|
|
251
|
+
function printHeader() {
|
|
252
|
+
if (!hasPrintedHeader) {
|
|
253
|
+
console.log()
|
|
254
|
+
console.log(colors.bold.white('🪄 AI REPORT:'))
|
|
255
|
+
hasPrintedHeader = true
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function clusterize(failedTestsAndErrors) {
|
|
260
|
+
const spinner = ora('Clusterizing failures...').start()
|
|
261
|
+
const prompt = config.prompts.clusterize(failedTestsAndErrors, config)
|
|
262
|
+
try {
|
|
263
|
+
const response = await ai.createCompletion(prompt)
|
|
264
|
+
spinner.stop()
|
|
265
|
+
return formatResponse(response)
|
|
266
|
+
} catch (err) {
|
|
267
|
+
spinner.stop()
|
|
268
|
+
console.error('Error clusterizing failures', err.message)
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function analyze(failedTestAndError) {
|
|
273
|
+
const spinner = ora('Analyzing failure...').start()
|
|
274
|
+
const prompt = config.prompts.analyze(failedTestAndError, config)
|
|
275
|
+
try {
|
|
276
|
+
const response = await ai.createCompletion(prompt)
|
|
277
|
+
spinner.stop()
|
|
278
|
+
return formatResponse(response)
|
|
279
|
+
} catch (err) {
|
|
280
|
+
spinner.stop()
|
|
281
|
+
console.error('Error analyzing failure:', err.message)
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function serializeError(error) {
|
|
287
|
+
if (typeof error === 'string') {
|
|
288
|
+
return error
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!error) return
|
|
292
|
+
|
|
293
|
+
let errorMessage = 'ERROR: ' + error.message
|
|
294
|
+
|
|
295
|
+
if (error.inspect) {
|
|
296
|
+
errorMessage = 'ERROR: ' + error.inspect()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (error.stack) {
|
|
300
|
+
errorMessage +=
|
|
301
|
+
'\n' +
|
|
302
|
+
error.stack
|
|
303
|
+
.replace(global.codecept_dir || '', '.')
|
|
304
|
+
.split('\n')
|
|
305
|
+
.map(line => line.replace(ansiRegExp(), ''))
|
|
306
|
+
.slice(0, 5)
|
|
307
|
+
.join('\n')
|
|
308
|
+
}
|
|
309
|
+
if (error.steps) {
|
|
310
|
+
errorMessage += '\n STEPS: ' + error.steps.map(s => s.toCode()).join('\n')
|
|
311
|
+
}
|
|
312
|
+
return errorMessage
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function serializeTest(test) {
|
|
316
|
+
if (!test.uid) return
|
|
317
|
+
|
|
318
|
+
let testMessage = 'TEST TITLE: ' + test.title
|
|
319
|
+
|
|
320
|
+
if (test.suite) {
|
|
321
|
+
testMessage += '\n SUITE: ' + test.suite.title
|
|
322
|
+
}
|
|
323
|
+
if (test.parent) {
|
|
324
|
+
testMessage += '\n SUITE: ' + test.parent.title
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (test.steps?.length) {
|
|
328
|
+
const failedSteps = test.steps
|
|
329
|
+
if (failedSteps.length) testMessage += '\n STEP: ' + failedSteps.map(s => s.toCode()).join('; ')
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const pageInfo = test.notes.find(n => n.type === 'pageInfo')
|
|
333
|
+
if (pageInfo) {
|
|
334
|
+
testMessage += '\n PAGE INFO: ' + pageInfo.text
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return testMessage
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function formatResponse(response) {
|
|
341
|
+
if (!response.startsWith('##')) response = '## ' + response
|
|
342
|
+
return response
|
|
343
|
+
.split('\n')
|
|
344
|
+
.map(line => line.trim())
|
|
345
|
+
.filter(line => !/^[A-Z\s]+$/.test(line))
|
|
346
|
+
.map(line => markdownToAnsi(line))
|
|
347
|
+
.map(line => line.replace(/^x /gm, ` ${colors.red.bold('x')} `))
|
|
348
|
+
.join('\n')
|
|
349
|
+
}
|
package/lib/plugin/autoDelay.js
CHANGED
|
@@ -2,8 +2,8 @@ const Container = require('../container')
|
|
|
2
2
|
const store = require('../store')
|
|
3
3
|
const recorder = require('../recorder')
|
|
4
4
|
const event = require('../event')
|
|
5
|
-
const log = require('../output')
|
|
6
|
-
const
|
|
5
|
+
const { log } = require('../output')
|
|
6
|
+
const standardActingHelpers = Container.STANDARD_ACTING_HELPERS
|
|
7
7
|
|
|
8
8
|
const methodsToDelay = ['click', 'fillField', 'checkOption', 'pressKey', 'doubleClick', 'rightClick']
|
|
9
9
|
|
|
@@ -66,25 +66,25 @@ module.exports = function (config) {
|
|
|
66
66
|
|
|
67
67
|
if (!helper) return // no helpers for auto-delay
|
|
68
68
|
|
|
69
|
-
event.dispatcher.on(event.step.before,
|
|
69
|
+
event.dispatcher.on(event.step.before, step => {
|
|
70
70
|
if (config.methods.indexOf(step.helperMethod) < 0) return // skip non-actions
|
|
71
71
|
|
|
72
72
|
recorder.add('auto-delay', async () => {
|
|
73
73
|
if (store.debugMode) return // no need to delay in debug
|
|
74
74
|
log(`Delaying for ${config.delayBefore}ms`)
|
|
75
|
-
return new Promise(
|
|
75
|
+
return new Promise(resolve => {
|
|
76
76
|
setTimeout(resolve, config.delayBefore)
|
|
77
77
|
})
|
|
78
78
|
})
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
-
event.dispatcher.on(event.step.after,
|
|
81
|
+
event.dispatcher.on(event.step.after, step => {
|
|
82
82
|
if (config.methods.indexOf(step.helperMethod) < 0) return // skip non-actions
|
|
83
83
|
|
|
84
84
|
recorder.add('auto-delay', async () => {
|
|
85
85
|
if (store.debugMode) return // no need to delay in debug
|
|
86
86
|
log(`Delaying for ${config.delayAfter}ms`)
|
|
87
|
-
return new Promise(
|
|
87
|
+
return new Promise(resolve => {
|
|
88
88
|
setTimeout(resolve, config.delayAfter)
|
|
89
89
|
})
|
|
90
90
|
})
|
package/lib/plugin/autoLogin.js
CHANGED
|
@@ -8,7 +8,7 @@ const { debug } = require('../output')
|
|
|
8
8
|
const isAsyncFunction = require('../utils').isAsyncFunction
|
|
9
9
|
|
|
10
10
|
const defaultUser = {
|
|
11
|
-
fetch:
|
|
11
|
+
fetch: I => I.grabCookie(),
|
|
12
12
|
check: () => {},
|
|
13
13
|
restore: (I, cookies) => {
|
|
14
14
|
I.amOnPage('/') // open a page
|
|
@@ -253,7 +253,7 @@ const defaultConfig = {
|
|
|
253
253
|
module.exports = function (config) {
|
|
254
254
|
config = Object.assign(defaultConfig, config)
|
|
255
255
|
Object.keys(config.users).map(
|
|
256
|
-
|
|
256
|
+
u =>
|
|
257
257
|
(config.users[u] = {
|
|
258
258
|
...defaultUser,
|
|
259
259
|
...config.users[u],
|
|
@@ -275,12 +275,11 @@ module.exports = function (config) {
|
|
|
275
275
|
}
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
const loginFunction = async
|
|
278
|
+
const loginFunction = async name => {
|
|
279
279
|
const userSession = config.users[name]
|
|
280
280
|
const I = container.support('I')
|
|
281
281
|
const cookies = store[`${name}_session`]
|
|
282
|
-
const shouldAwait =
|
|
283
|
-
isAsyncFunction(userSession.login) || isAsyncFunction(userSession.restore) || isAsyncFunction(userSession.check)
|
|
282
|
+
const shouldAwait = isAsyncFunction(userSession.login) || isAsyncFunction(userSession.restore) || isAsyncFunction(userSession.check)
|
|
284
283
|
|
|
285
284
|
const loginAndSave = async () => {
|
|
286
285
|
if (shouldAwait) {
|
|
@@ -311,7 +310,7 @@ module.exports = function (config) {
|
|
|
311
310
|
userSession.restore(I, cookies)
|
|
312
311
|
userSession.check(I, cookies)
|
|
313
312
|
}
|
|
314
|
-
recorder.session.catch(
|
|
313
|
+
recorder.session.catch(err => {
|
|
315
314
|
debug(`Failed auto login for ${name} due to ${err}`)
|
|
316
315
|
debug('Logging in again')
|
|
317
316
|
recorder.session.start('auto login')
|
|
@@ -320,7 +319,7 @@ module.exports = function (config) {
|
|
|
320
319
|
recorder.add(() => recorder.session.restore('auto login'))
|
|
321
320
|
recorder.catch(() => debug('continue'))
|
|
322
321
|
})
|
|
323
|
-
.catch(
|
|
322
|
+
.catch(err => {
|
|
324
323
|
recorder.session.restore('auto login')
|
|
325
324
|
recorder.session.restore('check login')
|
|
326
325
|
recorder.throw(err)
|
|
@@ -7,6 +7,8 @@ let currentCommentStep
|
|
|
7
7
|
const defaultGlobalName = '__'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
+
* @deprecated
|
|
11
|
+
*
|
|
10
12
|
* Add descriptive nested steps for your tests:
|
|
11
13
|
*
|
|
12
14
|
* ```js
|
|
@@ -100,11 +102,14 @@ const defaultGlobalName = '__'
|
|
|
100
102
|
* ```
|
|
101
103
|
*/
|
|
102
104
|
module.exports = function (config) {
|
|
105
|
+
console.log('commentStep is deprecated, disable it and use Section instead')
|
|
106
|
+
console.log('const { Section: __ } = require("codeceptjs/steps")')
|
|
107
|
+
|
|
103
108
|
event.dispatcher.on(event.test.started, () => {
|
|
104
109
|
currentCommentStep = null
|
|
105
110
|
})
|
|
106
111
|
|
|
107
|
-
event.dispatcher.on(event.step.started,
|
|
112
|
+
event.dispatcher.on(event.step.started, step => {
|
|
108
113
|
if (currentCommentStep) {
|
|
109
114
|
const metaStep = getRootMetaStep(step)
|
|
110
115
|
|
package/lib/plugin/coverage.js
CHANGED
|
@@ -15,7 +15,7 @@ const supportedHelpers = ['Puppeteer', 'Playwright', 'WebDriver']
|
|
|
15
15
|
|
|
16
16
|
const v8CoverageHelpers = {
|
|
17
17
|
Playwright: {
|
|
18
|
-
startCoverage: async
|
|
18
|
+
startCoverage: async page => {
|
|
19
19
|
await Promise.all([
|
|
20
20
|
page.coverage.startJSCoverage({
|
|
21
21
|
resetOnNavigation: false,
|
|
@@ -26,16 +26,13 @@ const v8CoverageHelpers = {
|
|
|
26
26
|
])
|
|
27
27
|
},
|
|
28
28
|
takeCoverage: async (page, coverageReport) => {
|
|
29
|
-
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
30
|
-
page.coverage.stopJSCoverage(),
|
|
31
|
-
page.coverage.stopCSSCoverage(),
|
|
32
|
-
])
|
|
29
|
+
const [jsCoverage, cssCoverage] = await Promise.all([page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage()])
|
|
33
30
|
const coverageList = [...jsCoverage, ...cssCoverage]
|
|
34
31
|
await coverageReport.add(coverageList)
|
|
35
32
|
},
|
|
36
33
|
},
|
|
37
34
|
Puppeteer: {
|
|
38
|
-
startCoverage: async
|
|
35
|
+
startCoverage: async page => {
|
|
39
36
|
await Promise.all([
|
|
40
37
|
page.coverage.startJSCoverage({
|
|
41
38
|
resetOnNavigation: false,
|
|
@@ -47,13 +44,10 @@ const v8CoverageHelpers = {
|
|
|
47
44
|
])
|
|
48
45
|
},
|
|
49
46
|
takeCoverage: async (page, coverageReport) => {
|
|
50
|
-
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
51
|
-
page.coverage.stopJSCoverage(),
|
|
52
|
-
page.coverage.stopCSSCoverage(),
|
|
53
|
-
])
|
|
47
|
+
const [jsCoverage, cssCoverage] = await Promise.all([page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage()])
|
|
54
48
|
// to raw V8 script coverage
|
|
55
49
|
const coverageList = [
|
|
56
|
-
...jsCoverage.map(
|
|
50
|
+
...jsCoverage.map(it => {
|
|
57
51
|
return {
|
|
58
52
|
source: it.text,
|
|
59
53
|
...it.rawScriptCoverage,
|
|
@@ -65,7 +59,7 @@ const v8CoverageHelpers = {
|
|
|
65
59
|
},
|
|
66
60
|
},
|
|
67
61
|
WebDriver: {
|
|
68
|
-
startCoverage: async
|
|
62
|
+
startCoverage: async page => {
|
|
69
63
|
await Promise.all([
|
|
70
64
|
page.coverage.startJSCoverage({
|
|
71
65
|
resetOnNavigation: false,
|
|
@@ -77,13 +71,10 @@ const v8CoverageHelpers = {
|
|
|
77
71
|
])
|
|
78
72
|
},
|
|
79
73
|
takeCoverage: async (page, coverageReport) => {
|
|
80
|
-
const [jsCoverage, cssCoverage] = await Promise.all([
|
|
81
|
-
page.coverage.stopJSCoverage(),
|
|
82
|
-
page.coverage.stopCSSCoverage(),
|
|
83
|
-
])
|
|
74
|
+
const [jsCoverage, cssCoverage] = await Promise.all([page.coverage.stopJSCoverage(), page.coverage.stopCSSCoverage()])
|
|
84
75
|
// to raw V8 script coverage
|
|
85
76
|
const coverageList = [
|
|
86
|
-
...jsCoverage.map(
|
|
77
|
+
...jsCoverage.map(it => {
|
|
87
78
|
return {
|
|
88
79
|
source: it.text,
|
|
89
80
|
...it.rawScriptCoverage,
|
|
@@ -131,7 +122,7 @@ module.exports = function (config) {
|
|
|
131
122
|
let coverageRunning = false
|
|
132
123
|
|
|
133
124
|
const v8Names = Object.keys(v8CoverageHelpers)
|
|
134
|
-
const helperName = Object.keys(helpers).find(
|
|
125
|
+
const helperName = Object.keys(helpers).find(it => v8Names.includes(it))
|
|
135
126
|
if (!helperName) {
|
|
136
127
|
console.error(`Coverage is only supported in ${supportedHelpers.join(' or ')}`)
|
|
137
128
|
// no helpers for screenshot
|
|
@@ -180,7 +171,7 @@ module.exports = function (config) {
|
|
|
180
171
|
})
|
|
181
172
|
|
|
182
173
|
// Save coverage data after every test run
|
|
183
|
-
event.dispatcher.on(event.test.after,
|
|
174
|
+
event.dispatcher.on(event.test.after, test => {
|
|
184
175
|
recorder.add(
|
|
185
176
|
'take coverage',
|
|
186
177
|
async () => {
|
|
@@ -111,7 +111,7 @@ const defaultConfig = {
|
|
|
111
111
|
* I.click('=sign-up'); // matches => [data-qa=sign-up],[data-test=sign-up]
|
|
112
112
|
* ```
|
|
113
113
|
*/
|
|
114
|
-
module.exports =
|
|
114
|
+
module.exports = config => {
|
|
115
115
|
config = { ...defaultConfig, ...config }
|
|
116
116
|
|
|
117
117
|
Locator.addFilter((value, locatorObj) => {
|
|
@@ -125,7 +125,7 @@ module.exports = (config) => {
|
|
|
125
125
|
if (config.strategy.toLowerCase() === 'xpath') {
|
|
126
126
|
locatorObj.value = `.//*[${[]
|
|
127
127
|
.concat(config.attribute)
|
|
128
|
-
.map(
|
|
128
|
+
.map(attr => `@${attr}=${xpathLocator.literal(val)}`)
|
|
129
129
|
.join(' or ')}]`
|
|
130
130
|
locatorObj.type = 'xpath'
|
|
131
131
|
}
|
|
@@ -133,7 +133,7 @@ module.exports = (config) => {
|
|
|
133
133
|
if (config.strategy.toLowerCase() === 'css') {
|
|
134
134
|
locatorObj.value = []
|
|
135
135
|
.concat(config.attribute)
|
|
136
|
-
.map(
|
|
136
|
+
.map(attr => `[${attr}=${val}]`)
|
|
137
137
|
.join(',')
|
|
138
138
|
locatorObj.type = 'css'
|
|
139
139
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const event = require('../event')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Sample custom reporter for CodeceptJS.
|
|
5
|
+
*/
|
|
6
|
+
module.exports = function (config) {
|
|
7
|
+
event.dispatcher.on(event.hook.finished, hook => {
|
|
8
|
+
if (config.onHookFinished) {
|
|
9
|
+
config.onHookFinished(hook)
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
event.dispatcher.on(event.test.before, test => {
|
|
14
|
+
if (config.onTestBefore) {
|
|
15
|
+
config.onTestBefore(test)
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
event.dispatcher.on(event.test.failed, (test, err) => {
|
|
20
|
+
if (config.onTestFailed) {
|
|
21
|
+
config.onTestFailed(test, err)
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
event.dispatcher.on(event.test.passed, test => {
|
|
26
|
+
if (config.onTestPassed) {
|
|
27
|
+
config.onTestPassed(test)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
event.dispatcher.on(event.test.skipped, test => {
|
|
32
|
+
if (config.onTestSkipped) {
|
|
33
|
+
config.onTestSkipped(test)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
event.dispatcher.on(event.test.finished, test => {
|
|
38
|
+
if (config.onTestFinished) {
|
|
39
|
+
config.onTestFinished(test)
|
|
40
|
+
}
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
event.dispatcher.on(event.all.result, result => {
|
|
44
|
+
if (config.onResult) {
|
|
45
|
+
config.onResult(result)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (config.save) {
|
|
49
|
+
result.save()
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
@@ -72,7 +72,7 @@ function eachElement(purpose, locator, fn) {
|
|
|
72
72
|
if (store.dryRun) return
|
|
73
73
|
const helpers = Object.values(container.helpers())
|
|
74
74
|
|
|
75
|
-
const helper = helpers.filter(
|
|
75
|
+
const helper = helpers.filter(h => !!h._locate)[0]
|
|
76
76
|
|
|
77
77
|
if (!helper) {
|
|
78
78
|
throw new Error('No helper enabled with _locate method with returns a list of elements.')
|
|
@@ -40,7 +40,7 @@ const transform = require('../transform')
|
|
|
40
40
|
*
|
|
41
41
|
*/
|
|
42
42
|
module.exports = function (config) {
|
|
43
|
-
transform.addTransformerBeforeAll('gherkin.examples',
|
|
43
|
+
transform.addTransformerBeforeAll('gherkin.examples', value => {
|
|
44
44
|
if (typeof value === 'string' && value.length > 0) {
|
|
45
45
|
return faker.helpers.fake(value)
|
|
46
46
|
}
|