codeceptjs 3.7.0-beta.7 → 3.7.0-beta.8

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 (45) hide show
  1. package/lib/actor.js +1 -2
  2. package/lib/ai.js +130 -121
  3. package/lib/codecept.js +4 -4
  4. package/lib/command/check.js +4 -0
  5. package/lib/command/run-workers.js +1 -53
  6. package/lib/command/workers/runTests.js +25 -189
  7. package/lib/container.js +16 -0
  8. package/lib/event.js +18 -17
  9. package/lib/listener/exit.js +5 -8
  10. package/lib/listener/globalTimeout.js +26 -9
  11. package/lib/listener/result.js +12 -0
  12. package/lib/listener/steps.js +0 -6
  13. package/lib/mocha/asyncWrapper.js +12 -2
  14. package/lib/mocha/cli.js +50 -24
  15. package/lib/mocha/hooks.js +32 -3
  16. package/lib/mocha/suite.js +27 -1
  17. package/lib/mocha/test.js +91 -7
  18. package/lib/mocha/types.d.ts +5 -0
  19. package/lib/output.js +1 -0
  20. package/lib/plugin/analyze.js +351 -0
  21. package/lib/plugin/commentStep.js +5 -0
  22. package/lib/plugin/customReporter.js +52 -0
  23. package/lib/plugin/heal.js +2 -2
  24. package/lib/plugin/pageInfo.js +143 -0
  25. package/lib/plugin/retryTo.js +10 -2
  26. package/lib/plugin/screenshotOnFail.js +4 -6
  27. package/lib/plugin/stepTimeout.js +1 -1
  28. package/lib/plugin/tryTo.js +9 -1
  29. package/lib/recorder.js +4 -4
  30. package/lib/rerun.js +43 -42
  31. package/lib/result.js +161 -0
  32. package/lib/step/base.js +52 -4
  33. package/lib/step/helper.js +3 -0
  34. package/lib/step/meta.js +9 -1
  35. package/lib/step/record.js +5 -5
  36. package/lib/step/section.js +55 -0
  37. package/lib/steps.js +28 -1
  38. package/lib/{step/timeout.js → timeout.js} +24 -0
  39. package/lib/utils.js +35 -0
  40. package/lib/workers.js +28 -38
  41. package/package.json +2 -2
  42. package/typings/promiseBasedTypes.d.ts +12 -518
  43. package/typings/types.d.ts +75 -518
  44. package/lib/listener/artifacts.js +0 -19
  45. package/lib/plugin/debugErrors.js +0 -67
@@ -0,0 +1,351 @@
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: 2,
16
+ analyze: 3,
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
+ Do not include group into report if it has only one test in affected tests section.
68
+
69
+ Provide list of groups in following format:
70
+
71
+ _______________________________
72
+
73
+ ## Group <group_number>
74
+
75
+ * CATEGORY <category_of_failure>
76
+ * ERROR <error_message_1>, <error_message_2>, ...
77
+ * SUMMARY <summary_of_errors>
78
+ * STEP <step_of_failure> (use CodeceptJS format I.click(), I.see(), etc; if all failures happend on the same step)
79
+ * SUITE <suite_title>, <suite_title> (if SUITE is present, and if all tests in the group have the same suite or suites)
80
+ * TAG <tag> (if TAG is present, and if all tests in the group have the same tag)
81
+ * AFFECTED TESTS (<total number of tests>):
82
+ x <test1 title>
83
+ x <test2 title>
84
+ x <test3 title>
85
+ x ...
86
+ `,
87
+ },
88
+ {
89
+ role: 'assistant',
90
+ content: `## '
91
+ `,
92
+ },
93
+ ]
94
+ return messages
95
+ },
96
+ analyze: (test, config) => {
97
+ const testMessage = serializeTest(test)
98
+ const errorMessage = serializeError(test.err)
99
+
100
+ const messages = [
101
+ {
102
+ role: 'user',
103
+ content: [
104
+ {
105
+ type: 'text',
106
+ text: `
107
+ I am qa engineer analyzing failed tests in CodeceptJS testing framework.
108
+ Please analyze the following failed test and error its error and explain it.
109
+
110
+ Pick one of the categories of failures and explain it.
111
+
112
+ Categories of failures in order of priority:
113
+
114
+ ${config.categories.join('\n- ')}
115
+
116
+ Here is the test and error:
117
+
118
+ ------- TEST -------
119
+ ${testMessage}
120
+
121
+ ------- ERROR -------
122
+ ${errorMessage}
123
+
124
+ ------ INSTRUCTIONS ------
125
+
126
+ Do not get to details, be concise.
127
+ If there is failed step, just write it in STEPS section.
128
+ If you have suggestions for the test, write them in SUMMARY section.
129
+ Inside SUMMARY write exact values, if you have suggestions, explain which information you used to suggest.
130
+ Be concise, each section should not take more than one sentence.
131
+
132
+ Response format:
133
+
134
+ * CATEGORY <category_of_failure>
135
+ * STEPS <step_of_failure>
136
+ * SUMMARY <explanation_of_failure>
137
+
138
+ Do not add any other sections or explanations. Only CATEGORY, SUMMARY, STEPS.
139
+ ${config.vision ? 'Also a screenshot of the page is attached to the prompt.' : ''}
140
+ `,
141
+ },
142
+ ],
143
+ },
144
+ ]
145
+
146
+ if (config.vision && test.artifacts.screenshot) {
147
+ debug('Adding screenshot to prompt')
148
+ messages[0].content.push({
149
+ type: 'image_url',
150
+ image_url: {
151
+ url: 'data:image/png;base64,' + base64EncodeFile(test.artifacts.screenshot),
152
+ },
153
+ })
154
+ }
155
+
156
+ messages.push({
157
+ role: 'assistant',
158
+ content: `## `,
159
+ })
160
+
161
+ return messages
162
+ },
163
+ },
164
+ }
165
+
166
+ /**
167
+ *
168
+ * @param {*} config
169
+ * @returns
170
+ */
171
+ module.exports = function (config = {}) {
172
+ config = Object.assign(defaultConfig, config)
173
+
174
+ event.dispatcher.on(event.workers.before, () => {
175
+ if (!ai.isEnabled) return
176
+ console.log('Enabled AI analysis')
177
+ })
178
+
179
+ event.dispatcher.on(event.all.result, async result => {
180
+ if (!isMainThread) return // run only on main thread
181
+ if (!ai.isEnabled) {
182
+ console.log('AI is disabled, no analysis will be performed. Run tests with --ai flag to enable it.')
183
+ return
184
+ }
185
+
186
+ printReport(result)
187
+ })
188
+
189
+ event.dispatcher.on(event.workers.result, async result => {
190
+ if (!result.hasFailed) {
191
+ console.log('Everything is fine, skipping AI analysis')
192
+ return
193
+ }
194
+
195
+ if (!ai.isEnabled) {
196
+ console.log('AI is disabled, no analysis will be performed. Run tests with --ai flag to enable it.')
197
+ return
198
+ }
199
+
200
+ printReport(result)
201
+ })
202
+
203
+ async function printReport(result) {
204
+ const failedTestsAndErrors = result.tests.filter(t => t.err)
205
+
206
+ if (!failedTestsAndErrors.length) return
207
+
208
+ debug(failedTestsAndErrors.map(t => serializeTest(t) + '\n' + serializeError(t.err)))
209
+
210
+ try {
211
+ if (failedTestsAndErrors.length >= config.clusterize) {
212
+ const response = await clusterize(failedTestsAndErrors)
213
+ printHeader()
214
+ console.log(response)
215
+ return
216
+ }
217
+
218
+ output.plugin('analyze', `Analyzing first ${config.analyze} failed tests...`)
219
+
220
+ // we pick only unique errors to not repeat answers
221
+ const uniqueErrors = failedTestsAndErrors.filter((item, index, array) => {
222
+ return array.findIndex(t => t.err?.message === item.err?.message) === index
223
+ })
224
+
225
+ for (let i = 0; i < config.analyze; i++) {
226
+ if (!uniqueErrors[i]) break
227
+
228
+ const response = await analyze(uniqueErrors[i])
229
+ if (!response) {
230
+ break
231
+ }
232
+
233
+ printHeader()
234
+ console.log()
235
+ console.log('--------------------------------')
236
+ console.log(arrowRight, colors.bold.white(uniqueErrors[i].fullTitle()), config.vision ? '👀' : '')
237
+ console.log()
238
+ console.log()
239
+ console.log(response)
240
+ console.log()
241
+ }
242
+ } catch (err) {
243
+ console.error('Error analyzing failed tests', err)
244
+ }
245
+
246
+ if (!Object.keys(container.plugins()).includes('pageInfo')) {
247
+ console.log('To improve analysis, enable pageInfo plugin to get more context for failed tests.')
248
+ }
249
+ }
250
+
251
+ let hasPrintedHeader = false
252
+
253
+ function printHeader() {
254
+ if (!hasPrintedHeader) {
255
+ console.log()
256
+ console.log(colors.bold.white('🪄 AI REPORT:'))
257
+ hasPrintedHeader = true
258
+ }
259
+ }
260
+
261
+ async function clusterize(failedTestsAndErrors) {
262
+ const spinner = ora('Clusterizing failures...').start()
263
+ const prompt = config.prompts.clusterize(failedTestsAndErrors, config)
264
+ try {
265
+ const response = await ai.createCompletion(prompt)
266
+ spinner.stop()
267
+ return formatResponse(response)
268
+ } catch (err) {
269
+ spinner.stop()
270
+ console.error('Error clusterizing failures', err.message)
271
+ }
272
+ }
273
+
274
+ async function analyze(failedTestAndError) {
275
+ const spinner = ora('Analyzing failure...').start()
276
+ const prompt = config.prompts.analyze(failedTestAndError, config)
277
+ try {
278
+ const response = await ai.createCompletion(prompt)
279
+ spinner.stop()
280
+ return formatResponse(response)
281
+ } catch (err) {
282
+ spinner.stop()
283
+ console.error('Error analyzing failure:', err.message)
284
+ }
285
+ }
286
+ }
287
+
288
+ function serializeError(error) {
289
+ if (typeof error === 'string') {
290
+ return error
291
+ }
292
+
293
+ if (!error) return
294
+
295
+ let errorMessage = 'ERROR: ' + error.message
296
+
297
+ if (error.inspect) {
298
+ errorMessage = 'ERROR: ' + error.inspect()
299
+ }
300
+
301
+ if (error.stack) {
302
+ errorMessage +=
303
+ '\n' +
304
+ error.stack
305
+ .replace(global.codecept_dir || '', '.')
306
+ .split('\n')
307
+ .map(line => line.replace(ansiRegExp(), ''))
308
+ .slice(0, 5)
309
+ .join('\n')
310
+ }
311
+ if (error.steps) {
312
+ errorMessage += '\n STEPS: ' + error.steps.map(s => s.toCode()).join('\n')
313
+ }
314
+ return errorMessage
315
+ }
316
+
317
+ function serializeTest(test) {
318
+ if (!test.uid) return
319
+
320
+ let testMessage = 'TEST TITLE: ' + test.title
321
+
322
+ if (test.suite) {
323
+ testMessage += '\n SUITE: ' + test.suite.title
324
+ }
325
+ if (test.parent) {
326
+ testMessage += '\n SUITE: ' + test.parent.title
327
+ }
328
+
329
+ if (test.steps?.length) {
330
+ const failedSteps = test.steps
331
+ if (failedSteps.length) testMessage += '\n STEP: ' + failedSteps.map(s => s.toCode()).join('; ')
332
+ }
333
+
334
+ const pageInfo = test.notes.find(n => n.type === 'pageInfo')
335
+ if (pageInfo) {
336
+ testMessage += '\n PAGE INFO: ' + pageInfo.text
337
+ }
338
+
339
+ return testMessage
340
+ }
341
+
342
+ function formatResponse(response) {
343
+ if (!response.startsWith('##')) response = '## ' + response
344
+ return response
345
+ .split('\n')
346
+ .map(line => line.trim())
347
+ .filter(line => !/^[A-Z\s]+$/.test(line))
348
+ .map(line => markdownToAnsi(line))
349
+ .map(line => line.replace(/^x /gm, ` ${colors.red.bold('x')} `))
350
+ .join('\n')
351
+ }
@@ -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,6 +102,9 @@ 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
  })
@@ -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
+ }
@@ -117,10 +117,10 @@ module.exports = function (config = {}) {
117
117
  }
118
118
  })
119
119
 
120
- event.dispatcher.on(event.workers.result, ({ tests }) => {
120
+ event.dispatcher.on(event.workers.result, result => {
121
121
  const { print } = output
122
122
 
123
- const healedTests = Object.values(tests)
123
+ const healedTests = Object.values(result.tests)
124
124
  .flat()
125
125
  .filter(test => test.notes.some(note => note.type === 'heal'))
126
126
  if (!healedTests.length) return
@@ -0,0 +1,143 @@
1
+ const path = require('path')
2
+ const fs = require('fs')
3
+ const Container = require('../container')
4
+ const recorder = require('../recorder')
5
+ const event = require('../event')
6
+ const supportedHelpers = require('./standardActingHelpers')
7
+ const { scanForErrorMessages } = require('../html')
8
+ const { output } = require('..')
9
+ const { humanizeString, ucfirst } = require('../utils')
10
+ const { testToFileName } = require('../mocha/test')
11
+ const defaultConfig = {
12
+ errorClasses: ['error', 'warning', 'alert', 'danger'],
13
+ browserLogs: ['error'],
14
+ }
15
+
16
+ /**
17
+ * Collects information from web page after each failed test and adds it to the test as an artifact.
18
+ * It is suggested to enable this plugin if you run tests on CI and you need to debug failed tests.
19
+ * This plugin can be paired with `analyze` plugin to provide more context.
20
+ *
21
+ * It collects URL, HTML errors (by classes), and browser logs.
22
+ *
23
+ * Enable this plugin in config:
24
+ *
25
+ * ```js
26
+ * plugins: {
27
+ * pageInfo: {
28
+ * enabled: true,
29
+ * }
30
+ * ```
31
+ *
32
+ * Additional config options:
33
+ *
34
+ * * `errorClasses` - list of classes to search for errors (default: `['error', 'warning', 'alert', 'danger']`)
35
+ * * `browserLogs` - list of types of errors to search for in browser logs (default: `['error']`)
36
+ *
37
+ */
38
+ module.exports = function (config = {}) {
39
+ const helpers = Container.helpers()
40
+ let helper
41
+
42
+ config = Object.assign(defaultConfig, config)
43
+
44
+ for (const helperName of supportedHelpers) {
45
+ if (Object.keys(helpers).indexOf(helperName) > -1) {
46
+ helper = helpers[helperName]
47
+ }
48
+ }
49
+
50
+ if (!helper) return // no helpers for screenshot
51
+
52
+ event.dispatcher.on(event.test.failed, test => {
53
+ const pageState = {}
54
+
55
+ recorder.add('URL of failed test', async () => {
56
+ try {
57
+ const url = await helper.grabCurrentUrl()
58
+ pageState.url = url
59
+ } catch (err) {
60
+ // not really needed
61
+ }
62
+ })
63
+ recorder.add('HTML snapshot failed test', async () => {
64
+ try {
65
+ const currentOutputLevel = output.level()
66
+ output.level(0)
67
+ const html = await helper.grabHTMLFrom('body')
68
+ output.level(currentOutputLevel)
69
+
70
+ if (!html) return
71
+
72
+ const errors = scanForErrorMessages(html, config.errorClasses)
73
+ if (errors.length) {
74
+ output.debug('Detected errors in HTML code')
75
+ errors.forEach(error => output.debug(error))
76
+ pageState.htmlErrors = errors
77
+ }
78
+ } catch (err) {
79
+ // not really needed
80
+ }
81
+ })
82
+
83
+ recorder.add('Browser logs for failed test', async () => {
84
+ try {
85
+ const logs = await helper.grabBrowserLogs()
86
+
87
+ if (!logs) return
88
+
89
+ pageState.browserErrors = getBrowserErrors(logs, config.browserLogs)
90
+ } catch (err) {
91
+ // not really needed
92
+ }
93
+ })
94
+
95
+ recorder.add('Save page info', () => {
96
+ test.addNote('pageInfo', pageStateToMarkdown(pageState))
97
+
98
+ const pageStateFileName = path.join(global.output_dir, `${testToFileName(test)}.pageInfo.md`)
99
+ fs.writeFileSync(pageStateFileName, pageStateToMarkdown(pageState))
100
+ test.artifacts.pageInfo = pageStateFileName
101
+ return pageState
102
+ })
103
+ })
104
+ }
105
+
106
+ function pageStateToMarkdown(pageState) {
107
+ let markdown = ''
108
+
109
+ for (const [key, value] of Object.entries(pageState)) {
110
+ if (!value) continue
111
+ let result = ''
112
+
113
+ if (Array.isArray(value)) {
114
+ result = value.map(v => `- ${JSON.stringify(v, null, 2)}`).join('\n')
115
+ } else if (typeof value === 'string') {
116
+ result = `${value}`
117
+ } else {
118
+ result = JSON.stringify(value, null, 2)
119
+ }
120
+
121
+ if (!result.trim()) continue
122
+
123
+ markdown += `### ${ucfirst(humanizeString(key))}\n\n`
124
+ markdown += result
125
+ markdown += '\n\n'
126
+ }
127
+
128
+ return markdown
129
+ }
130
+
131
+ function getBrowserErrors(logs, type = ['error']) {
132
+ // Playwright & WebDriver console messages
133
+ let errors = logs
134
+ .map(log => {
135
+ if (typeof log === 'string') return log
136
+ if (!log.type) return null
137
+ return { type: log.type(), text: log.text() }
138
+ })
139
+ .filter(l => l && (typeof l === 'string' || type.includes(l.type)))
140
+ .map(l => (typeof l === 'string' ? l : l.text))
141
+
142
+ return errors
143
+ }
@@ -1,6 +1,8 @@
1
- module.exports = function () {
1
+ const { retryTo } = require('../effects')
2
+
3
+ module.exports = function (config) {
2
4
  console.log(`
3
- Deprecated Warning: 'retryTo' has been moved to the effects module.
5
+ Deprecation Warning: 'retryTo' has been moved to the effects module.
4
6
  You should update your tests to use it as follows:
5
7
 
6
8
  \`\`\`javascript
@@ -16,4 +18,10 @@ await retryTo((tryNum) => {
16
18
 
17
19
  For more details, refer to the documentation.
18
20
  `)
21
+
22
+ if (config.registerGlobal) {
23
+ global.retryTo = retryTo
24
+ }
25
+
26
+ return retryTo
19
27
  }
@@ -5,8 +5,9 @@ const Container = require('../container')
5
5
  const recorder = require('../recorder')
6
6
  const event = require('../event')
7
7
  const output = require('../output')
8
- const { fileExists, clearString } = require('../utils')
8
+ const { fileExists } = require('../utils')
9
9
  const Codeceptjs = require('../index')
10
+ const { testToFileName } = require('../mocha/test')
10
11
 
11
12
  const defaultConfig = {
12
13
  uniqueScreenshotNames: false,
@@ -79,13 +80,10 @@ module.exports = function (config) {
79
80
  recorder.add(
80
81
  'screenshot of failed test',
81
82
  async () => {
82
- let fileName = clearString(test.title)
83
83
  const dataType = 'image/png'
84
84
  // This prevents data driven to be included in the failed screenshot file name
85
- if (fileName.indexOf('{') !== -1) {
86
- fileName = fileName.substr(0, fileName.indexOf('{') - 3).trim()
87
- }
88
- if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`)
85
+ let fileName = testToFileName(test)
86
+
89
87
  if (options.uniqueScreenshotNames && test) {
90
88
  const uuid = _getUUID(test)
91
89
  fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`
@@ -1,5 +1,5 @@
1
1
  const event = require('../event')
2
- const { TIMEOUT_ORDER } = require('../step/timeout')
2
+ const { TIMEOUT_ORDER } = require('../timeout')
3
3
 
4
4
  const defaultConfig = {
5
5
  timeout: 150,
@@ -1,4 +1,6 @@
1
- module.exports = function () {
1
+ const { tryTo } = require('../effects')
2
+
3
+ module.exports = function (config) {
2
4
  console.log(`
3
5
  Deprecated Warning: 'tryTo' has been moved to the effects module.
4
6
  You should update your tests to use it as follows:
@@ -14,4 +16,10 @@ await tryTo(() => {
14
16
 
15
17
  For more details, refer to the documentation.
16
18
  `)
19
+
20
+ if (config.registerGlobal) {
21
+ global.tryTo = tryTo
22
+ }
23
+
24
+ return tryTo
17
25
  }
package/lib/recorder.js CHANGED
@@ -3,7 +3,7 @@ const promiseRetry = require('promise-retry')
3
3
  const chalk = require('chalk')
4
4
  const { printObjectProperties } = require('./utils')
5
5
  const { log } = require('./output')
6
-
6
+ const { TimeoutError } = require('./timeout')
7
7
  const MAX_TASKS = 100
8
8
 
9
9
  let promise
@@ -191,13 +191,13 @@ module.exports = {
191
191
  .slice(-1)
192
192
  .pop()
193
193
  // no retries or unnamed tasks
194
+ debug(`${currentQueue()} Running | ${taskName} | Timeout: ${timeout || 'None'}`)
195
+
194
196
  if (!retryOpts || !taskName || !retry) {
195
197
  const [promise, timer] = getTimeoutPromise(timeout, taskName)
196
198
  return Promise.race([promise, Promise.resolve(res).then(fn)]).finally(() => clearTimeout(timer))
197
199
  }
198
200
 
199
- debug(`${currentQueue()} Running | ${taskName}`)
200
-
201
201
  const retryRules = this.retries.slice().reverse()
202
202
  return promiseRetry(Object.assign(defaultRetryOptions, retryOpts), (retry, number) => {
203
203
  if (number > 1) log(`${currentQueue()}Retrying... Attempt #${number}`)
@@ -386,7 +386,7 @@ function getTimeoutPromise(timeoutMs, taskName) {
386
386
  return [
387
387
  new Promise((done, reject) => {
388
388
  timer = setTimeout(() => {
389
- reject(new Error(`Action ${taskName} was interrupted on step timeout ${timeoutMs}ms`))
389
+ reject(new TimeoutError(`Action ${taskName} was interrupted on timeout ${timeoutMs}ms`))
390
390
  }, timeoutMs || 2e9)
391
391
  }),
392
392
  timer,