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
@@ -75,217 +75,53 @@ function filterTests() {
75
75
  }
76
76
 
77
77
  function initializeListeners() {
78
- function simplifyError(error) {
79
- if (error) {
80
- const { stack, uncaught, message, actual, expected } = error
81
-
82
- return {
83
- stack,
84
- uncaught,
85
- message,
86
- actual,
87
- expected,
88
- }
89
- }
90
-
91
- return null
92
- }
93
- function simplifyTest(test, err = null) {
94
- test = { ...test }
95
-
96
- if (test.start && !test.duration) {
97
- const end = new Date()
98
- test.duration = end - test.start
99
- }
100
-
101
- if (test.err) {
102
- err = simplifyError(test.err)
103
- test.status = 'failed'
104
- } else if (err) {
105
- err = simplifyError(err)
106
- test.status = 'failed'
107
- }
108
- const parent = {}
109
- if (test.parent) {
110
- parent.title = test.parent.title
111
- }
112
-
113
- if (test.opts) {
114
- Object.keys(test.opts).forEach(k => {
115
- if (typeof test.opts[k] === 'object') delete test.opts[k]
116
- if (typeof test.opts[k] === 'function') delete test.opts[k]
117
- })
118
- }
119
-
120
- return {
121
- opts: test.opts || {},
122
- tags: test.tags || [],
123
- uid: test.uid,
124
- workerIndex,
125
- retries: test._retries,
126
- title: test.title,
127
- status: test.status,
128
- notes: test.notes || [],
129
- meta: test.meta || {},
130
- artifacts: test.artifacts || [],
131
- duration: test.duration || 0,
132
- err,
133
- parent,
134
- steps: test.steps && test.steps.length > 0 ? simplifyStepsInTestObject(test.steps, err) : [],
135
- }
136
- }
137
-
138
- function simplifyStepsInTestObject(steps, err) {
139
- steps = [...steps]
140
- const _steps = []
141
-
142
- for (step of steps) {
143
- const _args = []
144
-
145
- if (step.args) {
146
- for (const arg of step.args) {
147
- // check if arg is a JOI object
148
- if (arg && arg.$_root) {
149
- _args.push(JSON.stringify(arg).slice(0, 300))
150
- // check if arg is a function
151
- } else if (arg && typeof arg === 'function') {
152
- _args.push(arg.name)
153
- } else {
154
- _args.push(arg)
155
- }
156
- }
157
- }
158
-
159
- _steps.push({
160
- actor: step.actor,
161
- name: step.name,
162
- status: step.status,
163
- args: JSON.stringify(_args),
164
- startedAt: step.startedAt,
165
- startTime: step.startTime,
166
- endTime: step.endTime,
167
- finishedAt: step.finishedAt,
168
- duration: step.duration,
169
- err,
170
- })
171
- }
172
-
173
- return _steps
174
- }
175
-
176
- function simplifyStep(step, err = null) {
177
- step = { ...step }
178
-
179
- if (step.startTime && !step.duration) {
180
- const end = new Date()
181
- step.duration = end - step.startTime
182
- }
183
-
184
- if (step.err) {
185
- err = simplifyError(step.err)
186
- step.status = 'failed'
187
- } else if (err) {
188
- err = simplifyError(err)
189
- step.status = 'failed'
190
- }
191
-
192
- const parent = {}
193
- if (step.metaStep) {
194
- parent.title = step.metaStep.actor
195
- }
196
-
197
- if (step.opts) {
198
- Object.keys(step.opts).forEach(k => {
199
- if (typeof step.opts[k] === 'object') delete step.opts[k]
200
- if (typeof step.opts[k] === 'function') delete step.opts[k]
201
- })
202
- }
203
-
204
- return {
205
- opts: step.opts || {},
206
- workerIndex,
207
- title: step.name,
208
- status: step.status,
209
- duration: step.duration || 0,
210
- err,
211
- parent,
212
- test: simplifyTest(step.test),
213
- }
214
- }
215
-
216
- collectStats()
217
78
  // suite
218
- event.dispatcher.on(event.suite.before, suite => sendToParentThread({ event: event.suite.before, workerIndex, data: simplifyTest(suite) }))
219
- event.dispatcher.on(event.suite.after, suite => sendToParentThread({ event: event.suite.after, workerIndex, data: simplifyTest(suite) }))
79
+ event.dispatcher.on(event.suite.before, suite => sendToParentThread({ event: event.suite.before, workerIndex, data: suite.simplify() }))
80
+ event.dispatcher.on(event.suite.after, suite => sendToParentThread({ event: event.suite.after, workerIndex, data: suite.simplify() }))
220
81
 
221
82
  // calculate duration
222
83
  event.dispatcher.on(event.test.started, test => (test.start = new Date()))
223
84
 
224
85
  // tests
225
- event.dispatcher.on(event.test.before, test => sendToParentThread({ event: event.test.before, workerIndex, data: simplifyTest(test) }))
226
- event.dispatcher.on(event.test.after, test => sendToParentThread({ event: event.test.after, workerIndex, data: simplifyTest(test) }))
86
+ event.dispatcher.on(event.test.before, test => sendToParentThread({ event: event.test.before, workerIndex, data: test.simplify() }))
87
+ event.dispatcher.on(event.test.after, test => sendToParentThread({ event: event.test.after, workerIndex, data: test.simplify() }))
227
88
  // we should force-send correct errors to prevent race condition
228
- event.dispatcher.on(event.test.finished, (test, err) => sendToParentThread({ event: event.test.finished, workerIndex, data: simplifyTest(test, err) }))
229
- event.dispatcher.on(event.test.failed, (test, err) => sendToParentThread({ event: event.test.failed, workerIndex, data: simplifyTest(test, err) }))
230
- event.dispatcher.on(event.test.passed, (test, err) => sendToParentThread({ event: event.test.passed, workerIndex, data: simplifyTest(test, err) }))
231
- event.dispatcher.on(event.test.started, test => sendToParentThread({ event: event.test.started, workerIndex, data: simplifyTest(test) }))
232
- event.dispatcher.on(event.test.skipped, test => sendToParentThread({ event: event.test.skipped, workerIndex, data: simplifyTest(test) }))
89
+ event.dispatcher.on(event.test.finished, (test, err) => sendToParentThread({ event: event.test.finished, workerIndex, data: { ...test.simplify(), err } }))
90
+ event.dispatcher.on(event.test.failed, (test, err) => sendToParentThread({ event: event.test.failed, workerIndex, data: { ...test.simplify(), err } }))
91
+ event.dispatcher.on(event.test.passed, (test, err) => sendToParentThread({ event: event.test.passed, workerIndex, data: { ...test.simplify(), err } }))
92
+ event.dispatcher.on(event.test.started, test => sendToParentThread({ event: event.test.started, workerIndex, data: test.simplify() }))
93
+ event.dispatcher.on(event.test.skipped, test => sendToParentThread({ event: event.test.skipped, workerIndex, data: test.simplify() }))
233
94
 
234
95
  // steps
235
- event.dispatcher.on(event.step.finished, step => sendToParentThread({ event: event.step.finished, workerIndex, data: simplifyStep(step) }))
236
- event.dispatcher.on(event.step.started, step => sendToParentThread({ event: event.step.started, workerIndex, data: simplifyStep(step) }))
237
- event.dispatcher.on(event.step.passed, step => sendToParentThread({ event: event.step.passed, workerIndex, data: simplifyStep(step) }))
238
- event.dispatcher.on(event.step.failed, step => sendToParentThread({ event: event.step.failed, workerIndex, data: simplifyStep(step) }))
96
+ event.dispatcher.on(event.step.finished, step => sendToParentThread({ event: event.step.finished, workerIndex, data: step.simplify() }))
97
+ event.dispatcher.on(event.step.started, step => sendToParentThread({ event: event.step.started, workerIndex, data: step.simplify() }))
98
+ event.dispatcher.on(event.step.passed, step => sendToParentThread({ event: event.step.passed, workerIndex, data: step.simplify() }))
99
+ event.dispatcher.on(event.step.failed, step => sendToParentThread({ event: event.step.failed, workerIndex, data: step.simplify() }))
239
100
 
240
- event.dispatcher.on(event.hook.failed, (test, err) => sendToParentThread({ event: event.hook.failed, workerIndex, data: simplifyTest(test, err) }))
241
- event.dispatcher.on(event.hook.passed, (test, err) => sendToParentThread({ event: event.hook.passed, workerIndex, data: simplifyTest(test, err) }))
242
- event.dispatcher.on(event.all.failures, data => sendToParentThread({ event: event.all.failures, workerIndex, data }))
101
+ event.dispatcher.on(event.hook.failed, (hook, err) => sendToParentThread({ event: event.hook.failed, workerIndex, data: { ...hook.simplify(), err } }))
102
+ event.dispatcher.on(event.hook.passed, hook => sendToParentThread({ event: event.hook.passed, workerIndex, data: hook.simplify() }))
103
+ event.dispatcher.on(event.hook.finished, hook => sendToParentThread({ event: event.hook.finished, workerIndex, data: hook.simplify() }))
243
104
 
105
+ event.dispatcher.once(event.all.after, () => {
106
+ sendToParentThread({ event: event.all.after, workerIndex, data: container.result().simplify() })
107
+ })
244
108
  // all
245
- event.dispatcher.once(event.all.result, () => parentPort.close())
109
+ event.dispatcher.once(event.all.result, () => {
110
+ sendToParentThread({ event: event.all.result, workerIndex, data: container.result().simplify() })
111
+ parentPort?.close()
112
+ })
246
113
  }
247
114
 
248
115
  function disablePause() {
249
116
  global.pause = () => {}
250
117
  }
251
118
 
252
- function collectStats() {
253
- const stats = {
254
- passes: 0,
255
- failures: 0,
256
- skipped: 0,
257
- tests: 0,
258
- pending: 0,
259
- }
260
- event.dispatcher.on(event.test.skipped, () => {
261
- stats.skipped++
262
- })
263
- event.dispatcher.on(event.test.passed, () => {
264
- stats.passes++
265
- })
266
- event.dispatcher.on(event.test.failed, test => {
267
- if (test.ctx._runnable.title.includes('hook: AfterSuite')) {
268
- stats.failedHooks += 1
269
- }
270
- stats.failures++
271
- })
272
- event.dispatcher.on(event.test.skipped, () => {
273
- stats.pending++
274
- })
275
- event.dispatcher.on(event.test.finished, () => {
276
- stats.tests++
277
- })
278
- event.dispatcher.once(event.all.after, () => {
279
- sendToParentThread({ event: event.all.after, data: stats })
280
- })
281
- }
282
-
283
119
  function sendToParentThread(data) {
284
- parentPort.postMessage(data)
120
+ parentPort?.postMessage(data)
285
121
  }
286
122
 
287
123
  function listenToParentThread() {
288
- parentPort.on('message', eventData => {
124
+ parentPort?.on('message', eventData => {
289
125
  container.append({ support: eventData.data })
290
126
  })
291
127
  }
package/lib/container.js CHANGED
@@ -9,6 +9,7 @@ const recorder = require('./recorder')
9
9
  const event = require('./event')
10
10
  const WorkerStorage = require('./workerStorage')
11
11
  const store = require('./store')
12
+ const Result = require('./result')
12
13
  const ai = require('./ai')
13
14
 
14
15
  let asyncHelperPromise
@@ -25,6 +26,8 @@ let container = {
25
26
  */
26
27
  mocha: {},
27
28
  translation: {},
29
+ /** @type {Result | null} */
30
+ result: null,
28
31
  }
29
32
 
30
33
  /**
@@ -54,6 +57,7 @@ class Container {
54
57
  container.translation = loadTranslation(config.translation || null, config.vocabularies || [])
55
58
  container.proxySupport = createSupportObjects(config.include || {})
56
59
  container.plugins = createPlugins(config.plugins || {}, opts)
60
+ container.result = new Result()
57
61
 
58
62
  createActor(config.include?.I)
59
63
 
@@ -127,6 +131,18 @@ class Container {
127
131
  return container.mocha
128
132
  }
129
133
 
134
+ /**
135
+ * Get result
136
+ *
137
+ * @returns {Result}
138
+ */
139
+ static result() {
140
+ if (!container.result) {
141
+ container.result = new Result()
142
+ }
143
+ return container.result
144
+ }
145
+
130
146
  /**
131
147
  * Append new services to container
132
148
  *
package/lib/event.js CHANGED
@@ -1,10 +1,10 @@
1
- const debug = require('debug')('codeceptjs:event');
2
- const events = require('events');
3
- const { error } = require('./output');
1
+ const debug = require('debug')('codeceptjs:event')
2
+ const events = require('events')
3
+ const { error } = require('./output')
4
4
 
5
- const dispatcher = new events.EventEmitter();
5
+ const dispatcher = new events.EventEmitter()
6
6
 
7
- dispatcher.setMaxListeners(50);
7
+ dispatcher.setMaxListeners(50)
8
8
  /**
9
9
  * @namespace
10
10
  * @alias event
@@ -59,6 +59,7 @@ module.exports = {
59
59
  started: 'hook.start',
60
60
  passed: 'hook.passed',
61
61
  failed: 'hook.failed',
62
+ finished: 'hook.finished',
62
63
  },
63
64
 
64
65
  /**
@@ -141,33 +142,33 @@ module.exports = {
141
142
  * @param {*} [param]
142
143
  */
143
144
  emit(event, param) {
144
- let msg = `Emitted | ${event}`;
145
+ let msg = `Emitted | ${event}`
145
146
  if (param && param.toString()) {
146
- msg += ` (${param.toString()})`;
147
+ msg += ` (${param.toString()})`
147
148
  }
148
- debug(msg);
149
+ debug(msg)
149
150
  try {
150
- this.dispatcher.emit.apply(this.dispatcher, arguments);
151
+ this.dispatcher.emit.apply(this.dispatcher, arguments)
151
152
  } catch (err) {
152
- error(`Error processing ${event} event:`);
153
- error(err.stack);
153
+ error(`Error processing ${event} event:`)
154
+ error(err.stack)
154
155
  }
155
156
  },
156
157
 
157
158
  /** for testing only! */
158
159
  cleanDispatcher: () => {
159
- let event;
160
+ let event
160
161
  for (event in this.test) {
161
- this.dispatcher.removeAllListeners(this.test[event]);
162
+ this.dispatcher.removeAllListeners(this.test[event])
162
163
  }
163
164
  for (event in this.suite) {
164
- this.dispatcher.removeAllListeners(this.test[event]);
165
+ this.dispatcher.removeAllListeners(this.test[event])
165
166
  }
166
167
  for (event in this.step) {
167
- this.dispatcher.removeAllListeners(this.test[event]);
168
+ this.dispatcher.removeAllListeners(this.test[event])
168
169
  }
169
170
  for (event in this.all) {
170
- this.dispatcher.removeAllListeners(this.test[event]);
171
+ this.dispatcher.removeAllListeners(this.test[event])
171
172
  }
172
173
  },
173
- };
174
+ }
@@ -44,7 +44,7 @@ const vendorPrefix = {
44
44
  *
45
45
  * This helper should be configured in codecept.conf.ts or codecept.conf.js
46
46
  *
47
- * * `appiumV2`: set this to true if you want to run tests with AppiumV2. See more how to setup [here](https://codecept.io/mobile/#setting-up)
47
+ * * `appiumV2`: by default is true, set this to false if you want to run tests with AppiumV1. See more how to setup [here](https://codecept.io/mobile/#setting-up)
48
48
  * * `app`: Application path. Local path or remote URL to an .ipa or .apk file, or a .zip containing one of these. Alias to desiredCapabilities.appPackage
49
49
  * * `host`: (default: 'localhost') Appium host
50
50
  * * `port`: (default: '4723') Appium port
@@ -124,7 +124,7 @@ const vendorPrefix = {
124
124
  * {
125
125
  * helpers: {
126
126
  * Appium: {
127
- * appiumV2: true,
127
+ * appiumV2: true, // By default is true, set to false if you want to run against Appium v1
128
128
  * host: "hub-cloud.browserstack.com",
129
129
  * port: 4444,
130
130
  * user: process.env.BROWSERSTACK_USER,
@@ -178,14 +178,12 @@ class Appium extends Webdriver {
178
178
  super(config)
179
179
 
180
180
  this.isRunning = false
181
- if (config.appiumV2 === true) {
182
- this.appiumV2 = true
183
- }
181
+ this.appiumV2 = config.appiumV2 || true
184
182
  this.axios = axios.create()
185
183
 
186
184
  webdriverio = require('webdriverio')
187
185
  if (!config.appiumV2) {
188
- console.log('The Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. Please migrating to Appium 2.x by adding appiumV2: true to your config.')
186
+ console.log('The Appium core team does not maintain Appium 1.x anymore since the 1st of January 2022. Appium 2.x is used by default.')
189
187
  console.log('More info: https://bit.ly/appium-v2-migration')
190
188
  console.log('This Appium 1.x support will be removed in next major release.')
191
189
  }
@@ -24,6 +24,7 @@ const { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTr
24
24
 
25
25
  const SHADOW = 'shadow'
26
26
  const webRoot = 'body'
27
+ let browserLogs = []
27
28
 
28
29
  /**
29
30
  * ## Configuration
@@ -621,6 +622,10 @@ class WebDriver extends Helper {
621
622
  }
622
623
 
623
624
  this.browser.on('dialog', () => {})
625
+
626
+ await this.browser.sessionSubscribe({ events: ['log.entryAdded'] })
627
+ this.browser.on('log.entryAdded', logEvents)
628
+
624
629
  return this.browser
625
630
  }
626
631
 
@@ -658,6 +663,7 @@ class WebDriver extends Helper {
658
663
  if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
659
664
  })
660
665
  await this.closeOtherTabs()
666
+ browserLogs = []
661
667
  return this.browser
662
668
  }
663
669
 
@@ -1522,11 +1528,7 @@ class WebDriver extends Helper {
1522
1528
  * {{> grabBrowserLogs }}
1523
1529
  */
1524
1530
  async grabBrowserLogs() {
1525
- if (this.browser.isW3C) {
1526
- this.debug('Logs not available in W3C specification')
1527
- return
1528
- }
1529
- return this.browser.getLogs('browser')
1531
+ return browserLogs
1530
1532
  }
1531
1533
 
1532
1534
  /**
@@ -1788,18 +1790,25 @@ class WebDriver extends Helper {
1788
1790
 
1789
1791
  if (browser) {
1790
1792
  this.debug(`Screenshot of ${sessionName} session has been saved to ${outputFile}`)
1791
- return browser.saveScreenshot(outputFile)
1793
+ await browser.saveScreenshot(outputFile)
1792
1794
  }
1793
1795
  }
1794
1796
  }
1795
1797
 
1796
1798
  if (!fullPage) {
1797
1799
  this.debug(`Screenshot has been saved to ${outputFile}`)
1798
- return this.browser.saveScreenshot(outputFile)
1800
+ await this.browser.saveScreenshot(outputFile)
1799
1801
  }
1800
1802
 
1801
1803
  const originalWindowSize = await this.browser.getWindowSize()
1802
1804
 
1805
+ // this case running on device, so we could not set the windowSize
1806
+ if (this.browser.isMobile) {
1807
+ this.debug(`Screenshot has been saved to ${outputFile}, size: ${originalWindowSize.width}x${originalWindowSize.height}`)
1808
+ const buffer = await this.browser.saveScreenshot(outputFile)
1809
+ return buffer
1810
+ }
1811
+
1803
1812
  let { width, height } = await this.browser
1804
1813
  .execute(function () {
1805
1814
  return {
@@ -3134,4 +3143,8 @@ function prepareLocateFn(context) {
3134
3143
  }
3135
3144
  }
3136
3145
 
3146
+ function logEvents(event) {
3147
+ browserLogs.push(event.text) // add log message to the array
3148
+ }
3149
+
3137
3150
  module.exports = WebDriver
@@ -1,20 +1,17 @@
1
1
  const event = require('../event')
2
+ const debug = require('debug')('codeceptjs:exit')
2
3
 
3
4
  module.exports = function () {
4
5
  let failedTests = []
5
6
 
6
- event.dispatcher.on(event.test.failed, testOrSuite => {
7
- // NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
8
- // is a suite and not a test
9
- const id = testOrSuite.uid || (testOrSuite.ctx && testOrSuite.ctx.test.uid) || 'empty'
7
+ event.dispatcher.on(event.test.failed, test => {
8
+ const id = test.uid || (test.ctx && test.ctx.test.uid) || 'empty'
10
9
  failedTests.push(id)
11
10
  })
12
11
 
13
12
  // if test was successful after retries
14
- event.dispatcher.on(event.test.passed, testOrSuite => {
15
- // NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
16
- // is a suite and not a test
17
- const id = testOrSuite.uid || (testOrSuite.ctx && testOrSuite.ctx.test.uid) || 'empty'
13
+ event.dispatcher.on(event.test.passed, test => {
14
+ const id = test.uid || (test.ctx && test.ctx.test.uid) || 'empty'
18
15
  failedTests = failedTests.filter(failed => id !== failed)
19
16
  })
20
17
 
@@ -4,7 +4,7 @@ const recorder = require('../recorder')
4
4
  const Config = require('../config')
5
5
  const store = require('../store')
6
6
  const debug = require('debug')('codeceptjs:timeout')
7
- const { TIMEOUT_ORDER } = require('../step/timeout')
7
+ const { TIMEOUT_ORDER, TimeoutError, TestTimeoutError, StepTimeoutError } = require('../timeout')
8
8
  const { BeforeSuiteHook, AfterSuiteHook } = require('../mocha/hooks')
9
9
 
10
10
  module.exports = function () {
@@ -119,24 +119,41 @@ module.exports = function () {
119
119
  }
120
120
  })
121
121
 
122
+ event.dispatcher.on(event.step.after, step => {
123
+ if (typeof timeout !== 'number') return
124
+ if (!store.timeouts) return
125
+
126
+ recorder.catchWithoutStop(err => {
127
+ // we wrap timeout errors in a StepTimeoutError
128
+ // but only if global timeout is set
129
+ // should we wrap all timeout errors?
130
+ if (err instanceof TimeoutError) {
131
+ const testTimeoutExceeded = timeout && +Date.now() - step.startTime >= timeout
132
+ debug('Step failed due to global test or suite timeout')
133
+ if (testTimeoutExceeded) {
134
+ debug('Test failed due to global test or suite timeout')
135
+ throw new TestTimeoutError(currentTimeout)
136
+ }
137
+ throw new StepTimeoutError(currentTimeout, step)
138
+ }
139
+ throw err
140
+ })
141
+ })
142
+
122
143
  event.dispatcher.on(event.step.finished, step => {
123
144
  if (!store.timeouts) {
124
145
  debug('step', step.toCode().trim(), 'timeout disabled')
125
146
  return
126
147
  }
127
148
 
149
+ if (typeof timeout === 'number') debug('Timeout', timeout)
150
+
151
+ debug(`step ${step.toCode().trim()}:${step.status} duration`, step.duration)
128
152
  if (typeof timeout === 'number' && !Number.isNaN(timeout)) timeout -= step.duration
129
153
 
130
154
  if (typeof timeout === 'number' && timeout <= 0 && recorder.isRunning()) {
131
155
  debug(`step ${step.toCode().trim()} timed out`)
132
- if (currentTest && currentTest.callback) {
133
- debug(`Failing test ${currentTest.title} with timeout ${currentTimeout}s`)
134
- recorder.reset()
135
- // replace mocha timeout with custom timeout
136
- currentTest.timeout(0)
137
- currentTest.callback(new Error(`Timeout ${currentTimeout}s exceeded (with Before hook)`))
138
- currentTest.timedOut = true
139
- }
156
+ recorder.throw(new TestTimeoutError(currentTimeout))
140
157
  }
141
158
  })
142
159
  }
@@ -0,0 +1,12 @@
1
+ const event = require('../event')
2
+ const container = require('../container')
3
+
4
+ module.exports = function () {
5
+ event.dispatcher.on(event.hook.failed, err => {
6
+ container.result().addStats({ failedHooks: 1 })
7
+ })
8
+
9
+ event.dispatcher.on(event.test.before, test => {
10
+ container.result().addTest(test)
11
+ })
12
+ }
@@ -13,7 +13,6 @@ let currentHook
13
13
  module.exports = function () {
14
14
  event.dispatcher.on(event.test.before, test => {
15
15
  test.startedAt = +new Date()
16
- test.artifacts = {}
17
16
  })
18
17
 
19
18
  event.dispatcher.on(event.test.started, test => {
@@ -71,8 +70,6 @@ module.exports = function () {
71
70
  })
72
71
 
73
72
  event.dispatcher.on(event.step.started, step => {
74
- step.startedAt = +new Date()
75
- step.test = currentTest
76
73
  store.currentStep = step
77
74
  if (currentHook && Array.isArray(currentHook.steps)) {
78
75
  return currentHook.steps.push(step)
@@ -82,9 +79,6 @@ module.exports = function () {
82
79
  })
83
80
 
84
81
  event.dispatcher.on(event.step.finished, step => {
85
- step.finishedAt = +new Date()
86
- if (step.startedAt) step.duration = step.finishedAt - step.startedAt
87
- debug(`Step '${step}' finished; Duration: ${step.duration || 0}ms`)
88
82
  store.currentStep = null
89
83
  store.stepOptions = null
90
84
  })
@@ -13,12 +13,19 @@ const injectHook = function (inject, suite) {
13
13
  recorder.throw(err)
14
14
  }
15
15
  recorder.catch(err => {
16
- event.emit(event.test.failed, suite, err)
16
+ suiteTestFailedHookError(suite, err)
17
17
  throw err
18
18
  })
19
19
  return recorder.promise()
20
20
  }
21
21
 
22
+ function suiteTestFailedHookError(suite, err) {
23
+ suite.eachTest(test => {
24
+ test.err = err
25
+ event.emit(event.test.failed, test, err)
26
+ })
27
+ }
28
+
22
29
  function makeDoneCallableOnce(done) {
23
30
  let called = false
24
31
  return function (err) {
@@ -61,6 +68,7 @@ module.exports.test = test => {
61
68
  err = newErr
62
69
  }
63
70
  }
71
+ test.err = err
64
72
  event.emit(event.test.failed, test, err)
65
73
  event.emit(event.test.finished, test)
66
74
  recorder.add(() => doneFn(err))
@@ -112,7 +120,7 @@ module.exports.injected = function (fn, suite, hookName) {
112
120
  const errHandler = err => {
113
121
  recorder.session.start('teardown')
114
122
  recorder.cleanAsyncErr()
115
- event.emit(event.test.failed, suite, err)
123
+ if (hookName == 'before' || hookName == 'beforeSuite') suiteTestFailedHookError(suite, err)
116
124
  if (hookName === 'after') event.emit(event.test.after, suite)
117
125
  if (hookName === 'afterSuite') event.emit(event.suite.after, suite)
118
126
  recorder.add(() => doneFn(err))
@@ -156,6 +164,7 @@ module.exports.injected = function (fn, suite, hookName) {
156
164
  )
157
165
  .then(() => {
158
166
  recorder.add('fire hook.passed', () => fireHook(event.hook.passed, suite))
167
+ recorder.add('fire hook.finished', () => fireHook(event.hook.finished, suite))
159
168
  recorder.add(`finish ${hookName} hook`, doneFn)
160
169
  recorder.catch()
161
170
  })
@@ -166,6 +175,7 @@ module.exports.injected = function (fn, suite, hookName) {
166
175
  errHandler(err)
167
176
  })
168
177
  recorder.add('fire hook.failed', () => fireHook(event.hook.failed, suite, e))
178
+ recorder.add('fire hook.finished', () => fireHook(event.hook.finished, suite))
169
179
  })
170
180
  }
171
181
  }