codeceptjs 4.0.0-beta.4 → 4.0.0-beta.5

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 (150) hide show
  1. package/README.md +134 -119
  2. package/bin/codecept.js +12 -2
  3. package/bin/test-server.js +53 -0
  4. package/docs/webapi/clearCookie.mustache +1 -1
  5. package/lib/actor.js +66 -102
  6. package/lib/ai.js +130 -121
  7. package/lib/assert/empty.js +3 -5
  8. package/lib/assert/equal.js +4 -7
  9. package/lib/assert/include.js +4 -6
  10. package/lib/assert/throws.js +2 -4
  11. package/lib/assert/truth.js +2 -2
  12. package/lib/codecept.js +139 -87
  13. package/lib/command/check.js +201 -0
  14. package/lib/command/configMigrate.js +2 -4
  15. package/lib/command/definitions.js +8 -26
  16. package/lib/command/generate.js +10 -14
  17. package/lib/command/gherkin/snippets.js +75 -73
  18. package/lib/command/gherkin/steps.js +1 -1
  19. package/lib/command/info.js +42 -8
  20. package/lib/command/init.js +13 -12
  21. package/lib/command/interactive.js +10 -2
  22. package/lib/command/list.js +1 -1
  23. package/lib/command/run-multiple/chunk.js +48 -45
  24. package/lib/command/run-multiple.js +12 -35
  25. package/lib/command/run-workers.js +21 -58
  26. package/lib/command/utils.js +5 -6
  27. package/lib/command/workers/runTests.js +262 -220
  28. package/lib/container.js +386 -238
  29. package/lib/data/context.js +10 -13
  30. package/lib/data/dataScenarioConfig.js +8 -8
  31. package/lib/data/dataTableArgument.js +6 -6
  32. package/lib/data/table.js +5 -11
  33. package/lib/effects.js +223 -0
  34. package/lib/element/WebElement.js +327 -0
  35. package/lib/els.js +158 -0
  36. package/lib/event.js +21 -17
  37. package/lib/heal.js +88 -80
  38. package/lib/helper/AI.js +2 -1
  39. package/lib/helper/ApiDataFactory.js +3 -6
  40. package/lib/helper/Appium.js +47 -51
  41. package/lib/helper/FileSystem.js +3 -3
  42. package/lib/helper/GraphQLDataFactory.js +3 -3
  43. package/lib/helper/JSONResponse.js +75 -37
  44. package/lib/helper/Mochawesome.js +31 -9
  45. package/lib/helper/Nightmare.js +35 -53
  46. package/lib/helper/Playwright.js +262 -267
  47. package/lib/helper/Protractor.js +54 -77
  48. package/lib/helper/Puppeteer.js +246 -260
  49. package/lib/helper/REST.js +5 -17
  50. package/lib/helper/TestCafe.js +21 -44
  51. package/lib/helper/WebDriver.js +151 -170
  52. package/lib/helper/extras/Popup.js +22 -22
  53. package/lib/helper/testcafe/testcafe-utils.js +26 -27
  54. package/lib/listener/emptyRun.js +55 -0
  55. package/lib/listener/exit.js +7 -10
  56. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  57. package/lib/listener/globalTimeout.js +165 -0
  58. package/lib/listener/helpers.js +15 -15
  59. package/lib/listener/mocha.js +1 -1
  60. package/lib/listener/result.js +12 -0
  61. package/lib/listener/retryEnhancer.js +85 -0
  62. package/lib/listener/steps.js +32 -18
  63. package/lib/listener/store.js +20 -0
  64. package/lib/mocha/asyncWrapper.js +231 -0
  65. package/lib/{interfaces → mocha}/bdd.js +3 -3
  66. package/lib/mocha/cli.js +308 -0
  67. package/lib/mocha/factory.js +104 -0
  68. package/lib/{interfaces → mocha}/featureConfig.js +32 -12
  69. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  70. package/lib/mocha/hooks.js +112 -0
  71. package/lib/mocha/index.js +12 -0
  72. package/lib/mocha/inject.js +29 -0
  73. package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
  74. package/lib/mocha/suite.js +82 -0
  75. package/lib/mocha/test.js +181 -0
  76. package/lib/mocha/types.d.ts +42 -0
  77. package/lib/mocha/ui.js +232 -0
  78. package/lib/output.js +82 -62
  79. package/lib/pause.js +160 -138
  80. package/lib/plugin/analyze.js +396 -0
  81. package/lib/plugin/auth.js +435 -0
  82. package/lib/plugin/autoDelay.js +8 -8
  83. package/lib/plugin/autoLogin.js +3 -338
  84. package/lib/plugin/commentStep.js +6 -1
  85. package/lib/plugin/coverage.js +10 -19
  86. package/lib/plugin/customLocator.js +3 -3
  87. package/lib/plugin/customReporter.js +52 -0
  88. package/lib/plugin/eachElement.js +1 -1
  89. package/lib/plugin/fakerTransform.js +1 -1
  90. package/lib/plugin/heal.js +36 -9
  91. package/lib/plugin/htmlReporter.js +1947 -0
  92. package/lib/plugin/pageInfo.js +140 -0
  93. package/lib/plugin/retryFailedStep.js +17 -18
  94. package/lib/plugin/retryTo.js +2 -113
  95. package/lib/plugin/screenshotOnFail.js +17 -58
  96. package/lib/plugin/selenoid.js +15 -35
  97. package/lib/plugin/standardActingHelpers.js +4 -1
  98. package/lib/plugin/stepByStepReport.js +56 -17
  99. package/lib/plugin/stepTimeout.js +5 -12
  100. package/lib/plugin/subtitles.js +4 -4
  101. package/lib/plugin/tryTo.js +3 -102
  102. package/lib/plugin/wdio.js +8 -10
  103. package/lib/recorder.js +155 -124
  104. package/lib/rerun.js +43 -42
  105. package/lib/result.js +161 -0
  106. package/lib/secret.js +1 -1
  107. package/lib/step/base.js +239 -0
  108. package/lib/step/comment.js +10 -0
  109. package/lib/step/config.js +50 -0
  110. package/lib/step/func.js +46 -0
  111. package/lib/step/helper.js +50 -0
  112. package/lib/step/meta.js +99 -0
  113. package/lib/step/record.js +74 -0
  114. package/lib/step/retry.js +11 -0
  115. package/lib/step/section.js +55 -0
  116. package/lib/step.js +21 -332
  117. package/lib/steps.js +50 -0
  118. package/lib/store.js +37 -5
  119. package/lib/template/heal.js +2 -11
  120. package/lib/test-server.js +323 -0
  121. package/lib/timeout.js +66 -0
  122. package/lib/utils.js +351 -218
  123. package/lib/within.js +75 -55
  124. package/lib/workerStorage.js +2 -1
  125. package/lib/workers.js +386 -276
  126. package/package.json +76 -70
  127. package/translations/de-DE.js +4 -3
  128. package/translations/fr-FR.js +4 -3
  129. package/translations/index.js +1 -0
  130. package/translations/it-IT.js +4 -3
  131. package/translations/ja-JP.js +4 -3
  132. package/translations/nl-NL.js +76 -0
  133. package/translations/pl-PL.js +4 -3
  134. package/translations/pt-BR.js +4 -3
  135. package/translations/ru-RU.js +4 -3
  136. package/translations/utils.js +9 -0
  137. package/translations/zh-CN.js +4 -3
  138. package/translations/zh-TW.js +4 -3
  139. package/typings/index.d.ts +188 -186
  140. package/typings/promiseBasedTypes.d.ts +18 -705
  141. package/typings/types.d.ts +301 -804
  142. package/lib/cli.js +0 -256
  143. package/lib/helper/ExpectHelper.js +0 -391
  144. package/lib/helper/SoftExpectHelper.js +0 -381
  145. package/lib/listener/artifacts.js +0 -19
  146. package/lib/listener/timeout.js +0 -109
  147. package/lib/mochaFactory.js +0 -113
  148. package/lib/plugin/debugErrors.js +0 -67
  149. package/lib/scenario.js +0 -224
  150. package/lib/ui.js +0 -236
@@ -2,66 +2,66 @@
2
2
  * Class to handle the interaction with the Dialog (Popup) Class from Puppeteer
3
3
  */
4
4
  class Popup {
5
- constructor(popup, defaultAction) {
6
- this._popup = popup || null;
7
- this._actionType = '';
8
- this._defaultAction = defaultAction || '';
5
+ constructor(popup = null, defaultAction = '') {
6
+ this._popup = popup
7
+ this._actionType = ''
8
+ this._defaultAction = defaultAction
9
9
  }
10
10
 
11
11
  _assertValidActionType(action) {
12
12
  if (['accept', 'cancel'].indexOf(action) === -1) {
13
- throw new Error('Invalid Popup action type. Only "accept" or "cancel" actions are accepted');
13
+ throw new Error('Invalid Popup action type. Only "accept" or "cancel" actions are accepted')
14
14
  }
15
15
  }
16
16
 
17
17
  set defaultAction(action) {
18
- this._assertValidActionType(action);
19
- this._defaultAction = action;
18
+ this._assertValidActionType(action)
19
+ this._defaultAction = action
20
20
  }
21
21
 
22
22
  get defaultAction() {
23
- return this._defaultAction;
23
+ return this._defaultAction
24
24
  }
25
25
 
26
26
  get popup() {
27
- return this._popup;
27
+ return this._popup
28
28
  }
29
29
 
30
30
  set popup(popup) {
31
31
  if (this._popup) {
32
- console.error('Popup already exists and was not closed. Popups must always be closed by calling either I.acceptPopup() or I.cancelPopup()');
32
+ return
33
33
  }
34
- this._popup = popup;
34
+ this._popup = popup
35
35
  }
36
36
 
37
37
  get actionType() {
38
- return this._actionType;
38
+ return this._actionType
39
39
  }
40
40
 
41
41
  set actionType(action) {
42
- this._assertValidActionType(action);
43
- this._actionType = action;
42
+ this._assertValidActionType(action)
43
+ this._actionType = action
44
44
  }
45
45
 
46
46
  clear() {
47
- this._popup = null;
48
- this._actionType = '';
47
+ this._popup = null
48
+ this._actionType = ''
49
49
  }
50
50
 
51
51
  assertPopupVisible() {
52
52
  if (!this._popup) {
53
- throw new Error('There is no Popup visible');
53
+ throw new Error('There is no Popup visible')
54
54
  }
55
55
  }
56
56
 
57
57
  assertPopupActionType(type) {
58
- this.assertPopupVisible();
59
- const expectedAction = this._actionType || this._defaultAction;
58
+ this.assertPopupVisible()
59
+ const expectedAction = this._actionType || this._defaultAction
60
60
  if (expectedAction !== type) {
61
- throw new Error(`Popup action does not fit the expected action type. Expected popup action to be '${expectedAction}' not '${type}`);
61
+ throw new Error(`Popup action does not fit the expected action type. Expected popup action to be '${expectedAction}' not '${type}`)
62
62
  }
63
- this.clear();
63
+ this.clear()
64
64
  }
65
65
  }
66
66
 
67
- module.exports = Popup;
67
+ module.exports = Popup
@@ -1,15 +1,15 @@
1
- const { ClientFunction } = require('testcafe');
1
+ const { ClientFunction } = require('testcafe')
2
2
 
3
- const assert = require('assert');
4
- const fs = require('fs');
5
- const path = require('path');
6
- const { getParamNames } = require('../../utils');
3
+ const assert = require('assert')
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+ const { getParamNames } = require('../../utils')
7
7
 
8
8
  const createTestFile = () => {
9
- assert(global.output_dir, 'global.output_dir must be set');
9
+ assert(global.output_dir, 'global.output_dir must be set')
10
10
 
11
- const testFile = path.join(global.output_dir, `${Date.now()}_test.js`);
12
- const testControllerHolderDir = __dirname.replace(/\\/g, '/');
11
+ const testFile = path.join(global.output_dir, `${Date.now()}_test.js`)
12
+ const testControllerHolderDir = __dirname.replace(/\\/g, '/')
13
13
 
14
14
  fs.writeFileSync(
15
15
  testFile,
@@ -17,40 +17,39 @@ const createTestFile = () => {
17
17
  fixture("fixture")\n
18
18
  test\n
19
19
  ("test", testControllerHolder.capture)`,
20
- );
20
+ )
21
21
 
22
- return testFile;
23
- };
22
+ return testFile
23
+ }
24
24
 
25
25
  // TODO Better error mapping (actual, expected)
26
- const mapError = (testcafeError) => {
26
+ const mapError = testcafeError => {
27
27
  // console.log('TODO map error better', JSON.stringify(testcafeError, null, 2));
28
28
  if (testcafeError.errMsg) {
29
- throw new Error(testcafeError.errMsg);
29
+ throw new Error(testcafeError.errMsg)
30
30
  }
31
- const errorInfo = `${testcafeError.callsite ? JSON.stringify(testcafeError.callsite) : ''} ${testcafeError.apiFnChain || JSON.stringify(testcafeError)}`;
32
- throw new Error(`TestCafe Error: ${errorInfo}`);
33
- };
31
+ const errorInfo = `${testcafeError.callsite ? JSON.stringify(testcafeError.callsite) : ''} ${testcafeError.apiFnChain || JSON.stringify(testcafeError)}`
32
+ throw new Error(`TestCafe Error: ${errorInfo}`)
33
+ }
34
34
 
35
35
  function createClientFunction(func, args) {
36
36
  if (!args || !args.length) {
37
- return ClientFunction(func);
37
+ return ClientFunction(func)
38
38
  }
39
- const paramNames = getParamNames(func);
40
- const dependencies = {};
41
- paramNames.forEach((param, i) => dependencies[param] = args[i]);
39
+ const paramNames = getParamNames(func)
40
+ const dependencies = {}
41
+ paramNames.forEach((param, i) => (dependencies[param] = args[i]))
42
42
 
43
- return ClientFunction(getFuncBody(func), { dependencies });
43
+ return ClientFunction(getFuncBody(func), { dependencies })
44
44
  }
45
45
 
46
46
  function getFuncBody(func) {
47
- let fnStr = func.toString();
48
- const arrowIndex = fnStr.indexOf('=>');
47
+ let fnStr = func.toString()
48
+ const arrowIndex = fnStr.indexOf('=>')
49
49
  if (arrowIndex >= 0) {
50
- fnStr = fnStr.slice(arrowIndex + 2);
50
+ fnStr = fnStr.slice(arrowIndex + 2)
51
51
 
52
- // eslint-disable-next-line no-eval
53
- return eval(`() => ${fnStr}`);
52
+ return eval(`() => ${fnStr}`)
54
53
  }
55
54
  // TODO: support general functions
56
55
  }
@@ -59,4 +58,4 @@ module.exports = {
59
58
  createTestFile,
60
59
  mapError,
61
60
  createClientFunction,
62
- };
61
+ }
@@ -0,0 +1,55 @@
1
+ const figures = require('figures')
2
+ const Container = require('../container')
3
+ const event = require('../event')
4
+ const output = require('../output')
5
+ const { searchWithFusejs } = require('../utils')
6
+
7
+ module.exports = function () {
8
+ let isEmptyRun = true
9
+
10
+ event.dispatcher.on(event.test.before, test => {
11
+ isEmptyRun = false
12
+ })
13
+
14
+ event.dispatcher.on(event.all.result, () => {
15
+ if (isEmptyRun) {
16
+ const mocha = Container.mocha()
17
+
18
+ if (mocha.options.grep) {
19
+ output.print()
20
+ output.print('No tests found by pattern: ' + mocha.options.grep)
21
+
22
+ const allTests = []
23
+ mocha.suite.suites.forEach(suite => {
24
+ suite.tests.forEach(test => {
25
+ allTests.push(test.fullTitle())
26
+ })
27
+ })
28
+
29
+ const results = searchWithFusejs(allTests, mocha.options.grep.toString(), {
30
+ includeScore: true,
31
+ threshold: 0.6,
32
+ caseSensitive: false,
33
+ })
34
+
35
+ if (results.length > 0) {
36
+ output.print()
37
+ output.print('Maybe you wanted to run one of these tests?')
38
+ results.forEach(result => {
39
+ output.print(figures.checkboxOff, output.styles.log(result.item))
40
+ })
41
+
42
+ output.print()
43
+ output.print(output.styles.debug('To run the first test use the following command:'))
44
+ output.print(output.styles.bold('npx codeceptjs run --debug --grep "' + results[0].item + '"'))
45
+ }
46
+ }
47
+ if (process.env.CI && !process.env.DONT_FAIL_ON_EMPTY_RUN) {
48
+ output.print()
49
+ output.error('No tests were executed. Failing on CI to avoid false positives')
50
+ output.error('To disable this check, set `DONT_FAIL_ON_EMPTY_RUN` environment variable to true in CI config')
51
+ process.exitCode = 1
52
+ }
53
+ }
54
+ })
55
+ }
@@ -1,24 +1,21 @@
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'
18
- failedTests = failedTests.filter((failed) => id !== failed)
13
+ event.dispatcher.on(event.test.passed, test => {
14
+ const id = test.uid || (test.ctx && test.ctx.test.uid) || 'empty'
15
+ failedTests = failedTests.filter(failed => id !== failed)
19
16
  })
20
17
 
21
- process.on('beforeExit', (code) => {
18
+ process.on('beforeExit', code => {
22
19
  if (failedTests.length) {
23
20
  code = 1
24
21
  }
@@ -6,7 +6,7 @@ const { isNotSet } = require('../utils')
6
6
  const hooks = ['Before', 'After', 'BeforeSuite', 'AfterSuite']
7
7
 
8
8
  module.exports = function () {
9
- event.dispatcher.on(event.suite.before, (suite) => {
9
+ event.dispatcher.on(event.suite.before, suite => {
10
10
  let retryConfig = Config.get('retry')
11
11
  if (!retryConfig) return
12
12
 
@@ -28,8 +28,8 @@ module.exports = function () {
28
28
  }
29
29
 
30
30
  hooks
31
- .filter((hook) => !!config[hook])
32
- .forEach((hook) => {
31
+ .filter(hook => !!config[hook])
32
+ .forEach(hook => {
33
33
  if (isNotSet(suite.opts[`retry${hook}`])) suite.opts[`retry${hook}`] = config[hook]
34
34
  })
35
35
 
@@ -41,7 +41,7 @@ module.exports = function () {
41
41
  }
42
42
  })
43
43
 
44
- event.dispatcher.on(event.test.before, (test) => {
44
+ event.dispatcher.on(event.test.before, test => {
45
45
  let retryConfig = Config.get('retry')
46
46
  if (!retryConfig) return
47
47
 
@@ -54,7 +54,7 @@ module.exports = function () {
54
54
  retryConfig = [retryConfig]
55
55
  }
56
56
 
57
- retryConfig = retryConfig.filter((config) => !!config.Scenario)
57
+ retryConfig = retryConfig.filter(config => !!config.Scenario)
58
58
 
59
59
  for (const config of retryConfig) {
60
60
  if (config.grep) {
@@ -0,0 +1,165 @@
1
+ const event = require('../event')
2
+ const output = require('../output')
3
+ const recorder = require('../recorder')
4
+ const Config = require('../config')
5
+ const store = require('../store')
6
+ const debug = require('debug')('codeceptjs:timeout')
7
+ const { TIMEOUT_ORDER, TimeoutError, TestTimeoutError, StepTimeoutError } = require('../timeout')
8
+ const { BeforeSuiteHook, AfterSuiteHook } = require('../mocha/hooks')
9
+
10
+ module.exports = function () {
11
+ let timeout
12
+ let suiteTimeout = []
13
+ let currentTest
14
+ let currentTimeout
15
+
16
+ if (!store.timeouts) {
17
+ console.log('Timeouts were disabled')
18
+ return
19
+ }
20
+
21
+ // disable timeout for BeforeSuite/AfterSuite hooks
22
+ // add separate configs to them?
23
+ event.dispatcher.on(event.hook.started, hook => {
24
+ if (hook instanceof BeforeSuiteHook) {
25
+ timeout = null
26
+ suiteTimeout = []
27
+ }
28
+ if (hook instanceof AfterSuiteHook) {
29
+ timeout = null
30
+ suiteTimeout = []
31
+ }
32
+ })
33
+
34
+ event.dispatcher.on(event.suite.before, suite => {
35
+ suiteTimeout = []
36
+ let timeoutConfig = Config.get('timeout')
37
+
38
+ if (timeoutConfig) {
39
+ debug('config:', timeoutConfig)
40
+ if (!Number.isNaN(+timeoutConfig)) {
41
+ checkForSeconds(timeoutConfig)
42
+ suiteTimeout.push(timeoutConfig)
43
+ }
44
+
45
+ if (!Array.isArray(timeoutConfig)) {
46
+ timeoutConfig = [timeoutConfig]
47
+ }
48
+
49
+ for (const config of timeoutConfig.filter(c => !!c.Feature)) {
50
+ if (config.grep) {
51
+ if (!suite.title.includes(config.grep)) continue
52
+ }
53
+ suiteTimeout.push(config.Feature)
54
+ }
55
+ }
56
+
57
+ if (suite.totalTimeout) suiteTimeout.push(suite.totalTimeout)
58
+ output.log(`Timeouts: ${suiteTimeout}`)
59
+
60
+ if (suiteTimeout.length > 0) debug(suite.title, 'timeout', suiteTimeout)
61
+ })
62
+
63
+ event.dispatcher.on(event.test.before, test => {
64
+ currentTest = test
65
+ let testTimeout = null
66
+
67
+ let timeoutConfig = Config.get('timeout')
68
+
69
+ if (typeof timeoutConfig === 'object' || Array.isArray(timeoutConfig)) {
70
+ if (!Array.isArray(timeoutConfig)) {
71
+ timeoutConfig = [timeoutConfig]
72
+ }
73
+
74
+ for (const config of timeoutConfig.filter(c => !!c.Scenario)) {
75
+ console.log('Test Timeout', config, test.title.includes(config.grep))
76
+ if (config.grep) {
77
+ if (!test.title.includes(config.grep)) continue
78
+ }
79
+ testTimeout = config.Scenario
80
+ }
81
+ }
82
+
83
+ timeout = test.totalTimeout || testTimeout || suiteTimeout[suiteTimeout.length - 1]
84
+ if (!timeout) return
85
+
86
+ debug(test.title, 'timeout', {
87
+ 'config from file': testTimeout,
88
+ 'suite timeout': suiteTimeout,
89
+ 'dynamic config': test.totalTimeout,
90
+ })
91
+
92
+ currentTimeout = timeout
93
+ output.debug(`Test Timeout: ${timeout}s`)
94
+ timeout *= 1000
95
+ })
96
+
97
+ event.dispatcher.on(event.test.passed, test => {
98
+ currentTest = null
99
+ })
100
+
101
+ event.dispatcher.on(event.test.failed, test => {
102
+ currentTest = null
103
+ })
104
+
105
+ event.dispatcher.on(event.step.before, step => {
106
+ if (typeof timeout !== 'number') return
107
+
108
+ if (!store.timeouts) {
109
+ debug('step', step.toCode().trim(), 'timeout disabled')
110
+ return
111
+ }
112
+
113
+ if (timeout < 0) {
114
+ debug('Previous steps timed out, setting timeout to 0.01s')
115
+ step.setTimeout(0.01, TIMEOUT_ORDER.testOrSuite)
116
+ } else {
117
+ debug(`Setting timeout ${timeout}ms for step ${step.toCode().trim()}`)
118
+ step.setTimeout(timeout, TIMEOUT_ORDER.testOrSuite)
119
+ }
120
+ })
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
+
143
+ event.dispatcher.on(event.step.finished, step => {
144
+ if (!store.timeouts) {
145
+ debug('step', step.toCode().trim(), 'timeout disabled')
146
+ return
147
+ }
148
+
149
+ if (typeof timeout === 'number') debug('Timeout', timeout)
150
+
151
+ debug(`step ${step.toCode().trim()}:${step.status} duration`, step.duration)
152
+ if (typeof timeout === 'number' && !Number.isNaN(timeout)) timeout -= step.duration
153
+
154
+ if (typeof timeout === 'number' && timeout <= 0 && recorder.isRunning()) {
155
+ debug(`step ${step.toCode().trim()} timed out`)
156
+ recorder.throw(new TestTimeoutError(currentTimeout))
157
+ }
158
+ })
159
+ }
160
+
161
+ function checkForSeconds(timeout) {
162
+ if (timeout >= 1000) {
163
+ console.log(`Warning: Timeout was set to ${timeout}secs.\nGlobal timeout should be specified in seconds.`)
164
+ }
165
+ }
@@ -12,7 +12,7 @@ module.exports = function () {
12
12
 
13
13
  const runHelpersHook = (hook, param) => {
14
14
  if (store.dryRun) return
15
- Object.values(helpers).forEach((helper) => {
15
+ Object.values(helpers).forEach(helper => {
16
16
  if (helper[hook]) {
17
17
  helper[hook](param)
18
18
  }
@@ -21,55 +21,55 @@ module.exports = function () {
21
21
 
22
22
  const runAsyncHelpersHook = (hook, param, force) => {
23
23
  if (store.dryRun) return
24
- Object.keys(helpers).forEach((key) => {
24
+ Object.keys(helpers).forEach(key => {
25
25
  if (!helpers[key][hook]) return
26
26
  recorder.add(`hook ${key}.${hook}()`, () => helpers[key][hook](param), force, false)
27
27
  })
28
28
  }
29
29
 
30
- event.dispatcher.on(event.suite.before, (suite) => {
30
+ event.dispatcher.on(event.suite.before, suite => {
31
31
  // if (suite.parent) return; // only for root suite
32
32
  runAsyncHelpersHook('_beforeSuite', suite, true)
33
33
  })
34
34
 
35
- event.dispatcher.on(event.suite.after, (suite) => {
35
+ event.dispatcher.on(event.suite.after, suite => {
36
36
  // if (suite.parent) return; // only for root suite
37
37
  runAsyncHelpersHook('_afterSuite', suite, true)
38
38
  })
39
39
 
40
- event.dispatcher.on(event.test.started, (test) => {
40
+ event.dispatcher.on(event.test.started, test => {
41
41
  runHelpersHook('_test', test)
42
- recorder.catch((e) => error(e))
42
+ recorder.catch(e => error(e))
43
43
  })
44
44
 
45
- event.dispatcher.on(event.test.before, (test) => {
45
+ event.dispatcher.on(event.test.before, test => {
46
46
  // schedule config to revert changes
47
47
  runAsyncHelpersHook('_before', test, true)
48
- recorder.catchWithoutStop((e) => error(e))
48
+ recorder.catchWithoutStop(e => error(e))
49
49
  })
50
50
 
51
- event.dispatcher.on(event.test.passed, (test) => {
51
+ event.dispatcher.on(event.test.passed, test => {
52
52
  runAsyncHelpersHook('_passed', test, true)
53
53
  // should not fail test execution, so errors should be caught
54
- recorder.catchWithoutStop((e) => error(e))
54
+ recorder.catchWithoutStop(e => error(e))
55
55
  })
56
56
 
57
- event.dispatcher.on(event.test.failed, (test) => {
57
+ event.dispatcher.on(event.test.failed, test => {
58
58
  runAsyncHelpersHook('_failed', test, true)
59
59
  // should not fail test execution, so errors should be caught
60
- recorder.catchWithoutStop((e) => error(e))
60
+ recorder.catchWithoutStop(e => error(e))
61
61
  })
62
62
 
63
63
  event.dispatcher.on(event.test.after, () => {
64
64
  runAsyncHelpersHook('_after', {}, true)
65
- recorder.catchWithoutStop((e) => error(e))
65
+ recorder.catchWithoutStop(e => error(e))
66
66
  })
67
67
 
68
- event.dispatcher.on(event.step.before, (step) => {
68
+ event.dispatcher.on(event.step.before, step => {
69
69
  runAsyncHelpersHook('_beforeStep', step)
70
70
  })
71
71
 
72
- event.dispatcher.on(event.step.after, (step) => {
72
+ event.dispatcher.on(event.step.after, step => {
73
73
  runAsyncHelpersHook('_afterStep', step)
74
74
  })
75
75
 
@@ -8,7 +8,7 @@ module.exports = function () {
8
8
  mocha = container.mocha()
9
9
  })
10
10
 
11
- event.dispatcher.on(event.test.passed, (test) => {
11
+ event.dispatcher.on(event.test.passed, test => {
12
12
  mocha.Runner.emit('pass', test)
13
13
  })
14
14
 
@@ -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
+ }
@@ -0,0 +1,85 @@
1
+ const event = require('../event')
2
+ const { enhanceMochaTest } = require('../mocha/test')
3
+
4
+ /**
5
+ * Enhance retried tests by copying CodeceptJS-specific properties from the original test
6
+ * This fixes the issue where Mocha's shallow clone during retries loses CodeceptJS properties
7
+ */
8
+ module.exports = function () {
9
+ event.dispatcher.on(event.test.before, test => {
10
+ // Check if this test is a retry (has a reference to the original test)
11
+ const originalTest = test.retriedTest && test.retriedTest()
12
+
13
+ if (originalTest) {
14
+ // This is a retried test - copy CodeceptJS-specific properties from the original
15
+ copyCodeceptJSProperties(originalTest, test)
16
+
17
+ // Ensure the test is enhanced with CodeceptJS functionality
18
+ enhanceMochaTest(test)
19
+ }
20
+ })
21
+ }
22
+
23
+ /**
24
+ * Copy CodeceptJS-specific properties from the original test to the retried test
25
+ * @param {CodeceptJS.Test} originalTest - The original test object
26
+ * @param {CodeceptJS.Test} retriedTest - The retried test object
27
+ */
28
+ function copyCodeceptJSProperties(originalTest, retriedTest) {
29
+ // Copy CodeceptJS-specific properties
30
+ if (originalTest.opts !== undefined) {
31
+ retriedTest.opts = originalTest.opts ? { ...originalTest.opts } : {}
32
+ }
33
+
34
+ if (originalTest.tags !== undefined) {
35
+ retriedTest.tags = originalTest.tags ? [...originalTest.tags] : []
36
+ }
37
+
38
+ if (originalTest.notes !== undefined) {
39
+ retriedTest.notes = originalTest.notes ? [...originalTest.notes] : []
40
+ }
41
+
42
+ if (originalTest.meta !== undefined) {
43
+ retriedTest.meta = originalTest.meta ? { ...originalTest.meta } : {}
44
+ }
45
+
46
+ if (originalTest.artifacts !== undefined) {
47
+ retriedTest.artifacts = originalTest.artifacts ? [...originalTest.artifacts] : []
48
+ }
49
+
50
+ if (originalTest.steps !== undefined) {
51
+ retriedTest.steps = originalTest.steps ? [...originalTest.steps] : []
52
+ }
53
+
54
+ if (originalTest.config !== undefined) {
55
+ retriedTest.config = originalTest.config ? { ...originalTest.config } : {}
56
+ }
57
+
58
+ if (originalTest.inject !== undefined) {
59
+ retriedTest.inject = originalTest.inject ? { ...originalTest.inject } : {}
60
+ }
61
+
62
+ // Copy methods that might be missing
63
+ if (originalTest.addNote && !retriedTest.addNote) {
64
+ retriedTest.addNote = function (type, note) {
65
+ this.notes = this.notes || []
66
+ this.notes.push({ type, text: note })
67
+ }
68
+ }
69
+
70
+ if (originalTest.applyOptions && !retriedTest.applyOptions) {
71
+ retriedTest.applyOptions = originalTest.applyOptions.bind(retriedTest)
72
+ }
73
+
74
+ if (originalTest.simplify && !retriedTest.simplify) {
75
+ retriedTest.simplify = originalTest.simplify.bind(retriedTest)
76
+ }
77
+
78
+ // Preserve the uid if it exists
79
+ if (originalTest.uid !== undefined) {
80
+ retriedTest.uid = originalTest.uid
81
+ }
82
+
83
+ // Mark as enhanced
84
+ retriedTest.codeceptjs = true
85
+ }