codeceptjs 3.7.0-beta.6 → 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 (49) hide show
  1. package/README.md +7 -7
  2. package/lib/actor.js +1 -2
  3. package/lib/ai.js +130 -121
  4. package/lib/codecept.js +4 -4
  5. package/lib/command/check.js +4 -0
  6. package/lib/command/run-workers.js +1 -53
  7. package/lib/command/workers/runTests.js +25 -189
  8. package/lib/container.js +16 -0
  9. package/lib/event.js +18 -17
  10. package/lib/helper/Appium.js +4 -6
  11. package/lib/helper/WebDriver.js +20 -7
  12. package/lib/listener/exit.js +5 -8
  13. package/lib/listener/globalTimeout.js +26 -9
  14. package/lib/listener/result.js +12 -0
  15. package/lib/listener/steps.js +0 -6
  16. package/lib/mocha/asyncWrapper.js +12 -2
  17. package/lib/mocha/cli.js +50 -24
  18. package/lib/mocha/hooks.js +32 -3
  19. package/lib/mocha/suite.js +27 -1
  20. package/lib/mocha/test.js +91 -7
  21. package/lib/mocha/types.d.ts +5 -0
  22. package/lib/output.js +1 -0
  23. package/lib/pause.js +0 -3
  24. package/lib/plugin/analyze.js +351 -0
  25. package/lib/plugin/commentStep.js +5 -0
  26. package/lib/plugin/customReporter.js +52 -0
  27. package/lib/plugin/heal.js +2 -2
  28. package/lib/plugin/pageInfo.js +143 -0
  29. package/lib/plugin/retryTo.js +10 -2
  30. package/lib/plugin/screenshotOnFail.js +4 -6
  31. package/lib/plugin/stepTimeout.js +1 -1
  32. package/lib/plugin/tryTo.js +9 -1
  33. package/lib/recorder.js +4 -4
  34. package/lib/rerun.js +43 -42
  35. package/lib/result.js +161 -0
  36. package/lib/step/base.js +52 -4
  37. package/lib/step/helper.js +3 -0
  38. package/lib/step/meta.js +9 -1
  39. package/lib/step/record.js +5 -5
  40. package/lib/step/section.js +55 -0
  41. package/lib/steps.js +28 -1
  42. package/lib/{step/timeout.js → timeout.js} +24 -0
  43. package/lib/utils.js +35 -0
  44. package/lib/workers.js +28 -38
  45. package/package.json +12 -12
  46. package/typings/promiseBasedTypes.d.ts +6 -520
  47. package/typings/types.d.ts +115 -562
  48. package/lib/listener/artifacts.js +0 -19
  49. package/lib/plugin/debugErrors.js +0 -67
package/lib/mocha/cli.js CHANGED
@@ -1,12 +1,12 @@
1
1
  const {
2
2
  reporters: { Base },
3
3
  } = require('mocha')
4
- const figures = require('figures')
5
4
  const ms = require('ms')
5
+ const figures = require('figures')
6
6
  const event = require('../event')
7
7
  const AssertionFailedError = require('../assert/error')
8
8
  const output = require('../output')
9
-
9
+ const { cloneTest } = require('./test')
10
10
  const cursor = Base.cursor
11
11
  let currentMetaStep = []
12
12
  let codeceptjsEventDispatchersRegistered = false
@@ -32,6 +32,16 @@ class Cli extends Base {
32
32
  output.print(output.styles.debug(`Plugins: ${Object.keys(Containter.plugins()).join(', ')}`))
33
33
  }
34
34
 
35
+ if (level >= 3) {
36
+ process.on('warning', warning => {
37
+ console.log('\nWarning Details:')
38
+ console.log('Name:', warning.name)
39
+ console.log('Message:', warning.message)
40
+ console.log('Stack:', warning.stack)
41
+ console.log('-------------------')
42
+ })
43
+ }
44
+
35
45
  runner.on('start', () => {
36
46
  console.log()
37
47
  })
@@ -91,9 +101,11 @@ class Cli extends Base {
91
101
  event.dispatcher.on(event.step.started, step => {
92
102
  let processingStep = step
93
103
  const metaSteps = []
104
+ let isHidden = false
94
105
  while (processingStep.metaStep) {
95
106
  metaSteps.unshift(processingStep.metaStep)
96
107
  processingStep = processingStep.metaStep
108
+ if (processingStep.collapsed) isHidden = true
97
109
  }
98
110
  const shift = metaSteps.length
99
111
 
@@ -107,10 +119,9 @@ class Cli extends Base {
107
119
  }
108
120
  }
109
121
  currentMetaStep = metaSteps
122
+ if (isHidden) return
110
123
  output.stepShift = 3 + 2 * shift
111
- if (step.helper.constructor.name !== 'ExpectHelper') {
112
- output.step(step)
113
- }
124
+ output.step(step)
114
125
  })
115
126
 
116
127
  event.dispatcher.on(event.step.finished, () => {
@@ -138,16 +149,19 @@ class Cli extends Base {
138
149
  }
139
150
  }
140
151
 
141
- this.stats.pending += skippedCount
142
- this.stats.tests += skippedCount
152
+ const container = require('../container')
153
+ container.result().addStats({ pending: skippedCount, tests: skippedCount })
143
154
  })
144
155
 
145
156
  runner.on('end', this.result.bind(this))
146
157
  }
147
158
 
148
159
  result() {
149
- const stats = this.stats
150
- stats.failedHooks = 0
160
+ const container = require('../container')
161
+ container.result().addStats(this.stats)
162
+ container.result().finish()
163
+
164
+ const stats = container.result().stats
151
165
  console.log()
152
166
 
153
167
  // passes
@@ -160,7 +174,8 @@ class Cli extends Base {
160
174
  // failures
161
175
  if (stats.failures) {
162
176
  // append step traces
163
- this.failures.map(test => {
177
+ this.failures = this.failures.map(test => {
178
+ // we will change the stack trace, so we need to clone the test
164
179
  const err = test.err
165
180
 
166
181
  let log = ''
@@ -170,8 +185,18 @@ class Cli extends Base {
170
185
  err.message = err.inspect()
171
186
  }
172
187
 
173
- // multi-line error messages
174
- err.message = '\n ' + (err.message || '').replace(/^/gm, ' ').trim()
188
+ // multi-line error messages (for Playwright)
189
+ if (err.message && err.message.includes('\n')) {
190
+ const lines = err.message.split('\n')
191
+ const truncatedLines = lines.slice(0, 5)
192
+ if (lines.length > 5) {
193
+ truncatedLines.push('...')
194
+ }
195
+ err.message = truncatedLines.join('\n').replace(/^/gm, ' ').trim()
196
+ }
197
+
198
+ // add new line before the message
199
+ err.message = '\n ' + err.message
175
200
 
176
201
  const steps = test.steps || (test.ctx && test.ctx.test.steps)
177
202
 
@@ -204,25 +229,30 @@ class Cli extends Base {
204
229
 
205
230
  try {
206
231
  let stack = err.stack
207
- stack = stack.replace(originalMessage, '')
232
+ stack = (stack || '').replace(originalMessage, '')
208
233
  stack = stack ? stack.split('\n') : []
209
234
 
210
235
  if (stack[0] && stack[0].includes(err.message)) {
211
236
  stack.shift()
212
237
  }
213
238
 
239
+ if (stack[0] && stack[0].trim() == 'Error:') {
240
+ stack.shift()
241
+ }
242
+
214
243
  if (output.level() < 3) {
215
244
  stack = stack.slice(0, 3)
216
245
  }
217
246
 
218
247
  err.stack = `${stack.join('\n')}\n\n${output.colors.blue(log)}`
219
-
220
- // clone err object so stack trace adjustments won't affect test other reports
221
- test.err = err
222
- return test
223
248
  } catch (e) {
224
- throw Error(e)
249
+ console.error(e)
225
250
  }
251
+
252
+ // we will change the stack trace, so we need to clone the test
253
+ test = cloneTest(test)
254
+ test.err = err
255
+ return test
226
256
  })
227
257
 
228
258
  const originalLog = Base.consoleLog
@@ -235,12 +265,8 @@ class Cli extends Base {
235
265
  console.log()
236
266
  }
237
267
 
238
- this.failures.forEach(failure => {
239
- if (failure.constructor.name === 'Hook') {
240
- stats.failedHooks += 1
241
- }
242
- })
243
- event.emit(event.all.failures, { failuresLog, stats })
268
+ container.result().addFailures(failuresLog)
269
+
244
270
  output.result(stats.passes, stats.failures, stats.pending, ms(stats.duration), stats.failedHooks)
245
271
 
246
272
  if (stats.failures && output.level() < 3) {
@@ -1,18 +1,47 @@
1
1
  const event = require('../event')
2
+ const { serializeError } = require('../utils')
3
+ // const { serializeTest } = require('./test')
2
4
 
5
+ /**
6
+ * Represents a test hook in the testing framework
7
+ * @class
8
+ * @property {Object} suite - The test suite this hook belongs to
9
+ * @property {Object} test - The test object associated with this hook
10
+ * @property {Object} runnable - The current test being executed
11
+ * @property {Object} ctx - The context object
12
+ * @property {Error|null} err - The error that occurred during hook execution, if any
13
+ */
3
14
  class Hook {
15
+ /**
16
+ * Creates a new Hook instance
17
+ * @param {Object} context - The context object containing suite and test information
18
+ * @param {Object} context.suite - The test suite
19
+ * @param {Object} context.test - The test object
20
+ * @param {Object} context.ctx - The context object
21
+ * @param {Error} error - The error object if hook execution failed
22
+ */
4
23
  constructor(context, error) {
5
24
  this.suite = context.suite
6
25
  this.test = context.test
7
26
  this.runnable = context?.ctx?.test
8
27
  this.ctx = context.ctx
9
- this.error = error
28
+ this.err = error
10
29
  }
11
30
 
12
31
  get hookName() {
13
32
  return this.constructor.name.replace('Hook', '')
14
33
  }
15
34
 
35
+ simplify() {
36
+ return {
37
+ hookName: this.hookName,
38
+ title: this.title,
39
+ // test: this.test ? serializeTest(this.test) : null,
40
+ // suite: this.suite ? serializeSuite(this.suite) : null,
41
+ error: this.err ? serializeError(this.err) : null,
42
+ }
43
+ }
44
+
16
45
  toString() {
17
46
  return this.hookName
18
47
  }
@@ -46,13 +75,13 @@ function fireHook(eventType, suite, error) {
46
75
  const hook = suite.ctx?.test?.title?.match(/"([^"]*)"/)[1]
47
76
  switch (hook) {
48
77
  case 'before each':
49
- event.emit(eventType, new BeforeHook(suite))
78
+ event.emit(eventType, new BeforeHook(suite, error))
50
79
  break
51
80
  case 'after each':
52
81
  event.emit(eventType, new AfterHook(suite, error))
53
82
  break
54
83
  case 'before all':
55
- event.emit(eventType, new BeforeSuiteHook(suite))
84
+ event.emit(eventType, new BeforeSuiteHook(suite, error))
56
85
  break
57
86
  case 'after all':
58
87
  event.emit(eventType, new AfterSuiteHook(suite, error))
@@ -1,5 +1,4 @@
1
1
  const MochaSuite = require('mocha/lib/suite')
2
-
3
2
  /**
4
3
  * @typedef {import('mocha')} Mocha
5
4
  */
@@ -34,6 +33,10 @@ function enhanceMochaSuite(suite) {
34
33
  }
35
34
  }
36
35
 
36
+ suite.simplify = function () {
37
+ return serializeSuite(this)
38
+ }
39
+
37
40
  return suite
38
41
  }
39
42
 
@@ -49,7 +52,30 @@ function createSuite(parent, title) {
49
52
  return enhanceMochaSuite(suite)
50
53
  }
51
54
 
55
+ function serializeSuite(suite) {
56
+ suite = { ...suite }
57
+
58
+ return {
59
+ opts: suite.opts || {},
60
+ tags: suite.tags || [],
61
+ retries: suite._retries,
62
+ title: suite.title,
63
+ status: suite.status,
64
+ notes: suite.notes || [],
65
+ meta: suite.meta || {},
66
+ duration: suite.duration || 0,
67
+ }
68
+ }
69
+
70
+ function deserializeSuite(suite) {
71
+ suite = Object.assign(new MochaSuite(suite.title), suite)
72
+ enhanceMochaSuite(suite)
73
+ return suite
74
+ }
75
+
52
76
  module.exports = {
53
77
  createSuite,
54
78
  enhanceMochaSuite,
79
+ serializeSuite,
80
+ deserializeSuite,
55
81
  }
package/lib/mocha/test.js CHANGED
@@ -1,9 +1,9 @@
1
1
  const Test = require('mocha/lib/test')
2
2
  const Suite = require('mocha/lib/suite')
3
3
  const { test: testWrapper } = require('./asyncWrapper')
4
- const { enhanceMochaSuite } = require('./suite')
5
- const { genTestId } = require('../utils')
6
-
4
+ const { enhanceMochaSuite, createSuite } = require('./suite')
5
+ const { genTestId, serializeError, clearString, relativeDir } = require('../utils')
6
+ const Step = require('../step/base')
7
7
  /**
8
8
  * Factory function to create enhanced tests
9
9
  * @param {string} title - Test title
@@ -46,6 +46,7 @@ function enhanceMochaTest(test) {
46
46
  test.addToSuite = function (suite) {
47
47
  enhanceMochaSuite(suite)
48
48
  suite.addTest(testWrapper(this))
49
+ if (test.file && !suite.file) suite.file = test.file
49
50
  test.tags = [...(test.tags || []), ...(suite.tags || [])]
50
51
  test.fullTitle = () => `${suite.title}: ${test.title}`
51
52
  test.uid = genTestId(test)
@@ -59,17 +60,100 @@ function enhanceMochaTest(test) {
59
60
  if (opts.retries) this.retries(opts.retries)
60
61
  }
61
62
 
63
+ test.simplify = function () {
64
+ return serializeTest(this)
65
+ }
66
+
62
67
  return test
63
68
  }
64
69
 
65
- function repackTestForWorkersTransport(test) {
66
- test = Object.assign(new Test(test.title || '', () => {}), test)
67
- test.parent = Object.assign(new Suite(test.parent.title), test.parent)
70
+ function deserializeTest(test) {
71
+ test = Object.assign(
72
+ createTest(test.title || '', () => {}),
73
+ test,
74
+ )
75
+ test.parent = Object.assign(new Suite(test.parent?.title || 'Suite'), test.parent)
76
+ enhanceMochaSuite(test.parent)
77
+ if (test.steps) test.steps = test.steps.map(step => Object.assign(new Step(step.title), step))
68
78
  return test
69
79
  }
70
80
 
81
+ function serializeTest(test, error = null) {
82
+ // test = { ...test }
83
+
84
+ if (test.start && !test.duration) {
85
+ const end = +new Date()
86
+ test.duration = end - test.start
87
+ }
88
+
89
+ let err
90
+
91
+ if (test.err) {
92
+ err = serializeError(test.err)
93
+ test.state = 'failed'
94
+ } else if (error) {
95
+ err = serializeError(error)
96
+ test.state = 'failed'
97
+ }
98
+ const parent = {}
99
+ if (test.parent) {
100
+ parent.title = test.parent.title
101
+ }
102
+
103
+ if (test.opts) {
104
+ Object.keys(test.opts).forEach(k => {
105
+ if (typeof test.opts[k] === 'object') delete test.opts[k]
106
+ if (typeof test.opts[k] === 'function') delete test.opts[k]
107
+ })
108
+ }
109
+
110
+ let steps = undefined
111
+ if (Array.isArray(test.steps)) {
112
+ steps = test.steps.map(step => (step.simplify ? step.simplify() : step))
113
+ }
114
+
115
+ return {
116
+ opts: test.opts || {},
117
+ tags: test.tags || [],
118
+ uid: test.uid,
119
+ retries: test._retries,
120
+ title: test.title,
121
+ state: test.state,
122
+ notes: test.notes || [],
123
+ meta: test.meta || {},
124
+ artifacts: test.artifacts || {},
125
+ duration: test.duration || 0,
126
+ err,
127
+ parent,
128
+ steps,
129
+ }
130
+ }
131
+
132
+ function cloneTest(test) {
133
+ return deserializeTest(serializeTest(test))
134
+ }
135
+
136
+ function testToFileName(test) {
137
+ let fileName = clearString(test.title)
138
+ // remove tags with empty string (disable for now)
139
+ // fileName = fileName.replace(/\@\w+/g, '')
140
+ fileName = fileName.slice(0, 100)
141
+ if (fileName.indexOf('{') !== -1) {
142
+ fileName = fileName.substr(0, fileName.indexOf('{') - 3).trim()
143
+ }
144
+ if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook') fileName = clearString(`${test.title}_${test.ctx.test.title}`)
145
+ // TODO: add suite title to file name
146
+ // if (test.parent && test.parent.title) {
147
+ // fileName = `${clearString(test.parent.title)}_${fileName}`
148
+ // }
149
+ return fileName
150
+ }
151
+
71
152
  module.exports = {
72
153
  createTest,
154
+ testToFileName,
73
155
  enhanceMochaTest,
74
- repackTestForWorkersTransport,
156
+ serializeTest,
157
+ deserializeTest,
158
+ cloneTest,
75
159
  }
@@ -12,14 +12,19 @@ declare global {
12
12
  type: string
13
13
  text: string
14
14
  }>
15
+ state: string
16
+ err?: Error
15
17
  config: Record<string, any>
16
18
  artifacts: string[]
17
19
  inject: Record<string, any>
18
20
  opts: Record<string, any>
19
21
  throws?: Error | string | RegExp | Function
20
22
  totalTimeout?: number
23
+ relativeFile?: string
21
24
  addToSuite(suite: Mocha.Suite): void
22
25
  applyOptions(opts: Record<string, any>): void
26
+ simplify(): Record<string, any>
27
+ toFileName(): string
23
28
  addNote(type: string, note: string): void
24
29
  codeceptjs: boolean
25
30
  }
package/lib/output.js CHANGED
@@ -136,6 +136,7 @@ module.exports = {
136
136
  started: suite => {
137
137
  if (!suite.title) return
138
138
  print(`${colors.bold(suite.title)} --`)
139
+ if (suite.file && outputLevel >= 1) print(colors.underline.grey(suite.file))
139
140
  if (suite.comment) print(suite.comment)
140
141
  },
141
142
  },
package/lib/pause.js CHANGED
@@ -84,12 +84,10 @@ function pauseSession(passedObject = {}) {
84
84
  })
85
85
  return new Promise(resolve => {
86
86
  finish = resolve
87
- // eslint-disable-next-line
88
87
  return askForStep()
89
88
  })
90
89
  }
91
90
 
92
- /* eslint-disable */
93
91
  async function parseInput(cmd) {
94
92
  rl.pause()
95
93
  next = false
@@ -198,7 +196,6 @@ async function parseInput(cmd) {
198
196
  recorder.add('ask for next step', askForStep)
199
197
  nextStep()
200
198
  }
201
- /* eslint-enable */
202
199
 
203
200
  function askForStep() {
204
201
  return new Promise(resolve => {