codeceptjs 3.6.10 → 3.7.0-beta.2

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 (105) hide show
  1. package/README.md +81 -110
  2. package/bin/codecept.js +2 -2
  3. package/docs/webapi/clearCookie.mustache +1 -1
  4. package/lib/actor.js +46 -36
  5. package/lib/assert/empty.js +3 -5
  6. package/lib/assert/equal.js +4 -7
  7. package/lib/assert/include.js +4 -6
  8. package/lib/assert/throws.js +2 -4
  9. package/lib/assert/truth.js +2 -2
  10. package/lib/codecept.js +87 -83
  11. package/lib/command/configMigrate.js +2 -4
  12. package/lib/command/definitions.js +5 -25
  13. package/lib/command/generate.js +10 -14
  14. package/lib/command/gherkin/snippets.js +10 -8
  15. package/lib/command/gherkin/steps.js +1 -1
  16. package/lib/command/info.js +1 -3
  17. package/lib/command/init.js +8 -12
  18. package/lib/command/interactive.js +1 -1
  19. package/lib/command/list.js +1 -1
  20. package/lib/command/run-multiple.js +12 -35
  21. package/lib/command/run-workers.js +10 -10
  22. package/lib/command/utils.js +5 -6
  23. package/lib/command/workers/runTests.js +14 -17
  24. package/lib/container.js +327 -237
  25. package/lib/data/context.js +10 -13
  26. package/lib/data/dataScenarioConfig.js +8 -8
  27. package/lib/data/dataTableArgument.js +6 -6
  28. package/lib/data/table.js +5 -11
  29. package/lib/els.js +177 -0
  30. package/lib/event.js +1 -0
  31. package/lib/heal.js +78 -80
  32. package/lib/helper/ApiDataFactory.js +3 -6
  33. package/lib/helper/Appium.js +15 -30
  34. package/lib/helper/FileSystem.js +3 -3
  35. package/lib/helper/GraphQLDataFactory.js +3 -3
  36. package/lib/helper/JSONResponse.js +57 -37
  37. package/lib/helper/Nightmare.js +35 -53
  38. package/lib/helper/Playwright.js +189 -251
  39. package/lib/helper/Protractor.js +54 -77
  40. package/lib/helper/Puppeteer.js +134 -232
  41. package/lib/helper/REST.js +5 -17
  42. package/lib/helper/TestCafe.js +21 -44
  43. package/lib/helper/WebDriver.js +103 -162
  44. package/lib/helper/testcafe/testcafe-utils.js +26 -27
  45. package/lib/listener/artifacts.js +2 -2
  46. package/lib/listener/emptyRun.js +58 -0
  47. package/lib/listener/exit.js +4 -4
  48. package/lib/listener/{retry.js → globalRetry.js} +5 -5
  49. package/lib/listener/{timeout.js → globalTimeout.js} +9 -8
  50. package/lib/listener/helpers.js +15 -15
  51. package/lib/listener/mocha.js +1 -1
  52. package/lib/listener/steps.js +17 -12
  53. package/lib/listener/store.js +12 -0
  54. package/lib/mocha/asyncWrapper.js +204 -0
  55. package/lib/{interfaces → mocha}/bdd.js +3 -3
  56. package/lib/mocha/cli.js +257 -0
  57. package/lib/mocha/factory.js +104 -0
  58. package/lib/{interfaces → mocha}/featureConfig.js +11 -12
  59. package/lib/{interfaces → mocha}/gherkin.js +26 -28
  60. package/lib/mocha/hooks.js +83 -0
  61. package/lib/mocha/index.js +12 -0
  62. package/lib/mocha/inject.js +24 -0
  63. package/lib/{interfaces → mocha}/scenarioConfig.js +10 -6
  64. package/lib/mocha/suite.js +55 -0
  65. package/lib/mocha/test.js +60 -0
  66. package/lib/mocha/types.d.ts +31 -0
  67. package/lib/mocha/ui.js +219 -0
  68. package/lib/output.js +28 -10
  69. package/lib/pause.js +159 -135
  70. package/lib/plugin/autoDelay.js +4 -4
  71. package/lib/plugin/autoLogin.js +6 -7
  72. package/lib/plugin/commentStep.js +1 -1
  73. package/lib/plugin/coverage.js +10 -19
  74. package/lib/plugin/customLocator.js +3 -3
  75. package/lib/plugin/debugErrors.js +2 -2
  76. package/lib/plugin/eachElement.js +1 -1
  77. package/lib/plugin/fakerTransform.js +1 -1
  78. package/lib/plugin/heal.js +6 -9
  79. package/lib/plugin/retryFailedStep.js +4 -4
  80. package/lib/plugin/retryTo.js +2 -2
  81. package/lib/plugin/screenshotOnFail.js +9 -36
  82. package/lib/plugin/selenoid.js +15 -35
  83. package/lib/plugin/stepByStepReport.js +51 -13
  84. package/lib/plugin/stepTimeout.js +4 -11
  85. package/lib/plugin/subtitles.js +4 -4
  86. package/lib/plugin/tryTo.js +1 -1
  87. package/lib/plugin/wdio.js +8 -10
  88. package/lib/recorder.js +142 -121
  89. package/lib/secret.js +1 -1
  90. package/lib/step.js +160 -144
  91. package/lib/store.js +6 -2
  92. package/lib/template/heal.js +2 -11
  93. package/lib/utils.js +224 -216
  94. package/lib/within.js +73 -55
  95. package/lib/workers.js +265 -261
  96. package/package.json +46 -47
  97. package/typings/index.d.ts +172 -184
  98. package/typings/promiseBasedTypes.d.ts +95 -516
  99. package/typings/types.d.ts +169 -587
  100. package/lib/cli.js +0 -256
  101. package/lib/helper/ExpectHelper.js +0 -391
  102. package/lib/helper/SoftExpectHelper.js +0 -381
  103. package/lib/mochaFactory.js +0 -113
  104. package/lib/scenario.js +0 -224
  105. package/lib/ui.js +0 -236
@@ -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
+ }
@@ -5,11 +5,11 @@ const recorder = require('../recorder')
5
5
  * Create and clean up empty artifacts
6
6
  */
7
7
  module.exports = function () {
8
- event.dispatcher.on(event.test.before, (test) => {
8
+ event.dispatcher.on(event.test.before, test => {
9
9
  test.artifacts = {}
10
10
  })
11
11
 
12
- event.dispatcher.on(event.test.after, (test) => {
12
+ event.dispatcher.on(event.test.after, test => {
13
13
  recorder.add('clean up empty artifacts', () => {
14
14
  for (const key in test.artifacts || {}) {
15
15
  if (!test.artifacts[key]) delete test.artifacts[key]
@@ -0,0 +1,58 @@
1
+ const figures = require('figures')
2
+ const Container = require('../container')
3
+ const event = require('../event')
4
+ const output = require('../output')
5
+
6
+ module.exports = function () {
7
+ let isEmptyRun = true
8
+
9
+ event.dispatcher.on(event.test.before, test => {
10
+ isEmptyRun = false
11
+ })
12
+
13
+ event.dispatcher.on(event.all.result, () => {
14
+ if (isEmptyRun) {
15
+ const mocha = Container.mocha()
16
+
17
+ if (mocha.options.grep) {
18
+ const Fuse = require('fuse.js')
19
+
20
+ output.print()
21
+ output.print('No tests found by pattern: ' + mocha.options.grep)
22
+
23
+ const allTests = []
24
+ mocha.suite.suites.forEach(suite => {
25
+ suite.tests.forEach(test => {
26
+ allTests.push(test.fullTitle())
27
+ })
28
+ })
29
+
30
+ const fuse = new Fuse(allTests, {
31
+ includeScore: true,
32
+ threshold: 0.6,
33
+ caseSensitive: false,
34
+ })
35
+
36
+ const results = fuse.search(mocha.options.grep.toString())
37
+
38
+ if (results.length > 0) {
39
+ output.print()
40
+ output.print('Maybe you wanted to run one of these tests?')
41
+ results.forEach(result => {
42
+ output.print(figures.checkboxOff, output.styles.log(result.item))
43
+ })
44
+
45
+ output.print()
46
+ output.print(output.styles.debug('To run the first test use the following command:'))
47
+ output.print(output.styles.bold('npx codeceptjs run --debug --grep "' + results[0].item + '"'))
48
+ }
49
+ }
50
+ if (process.env.CI && !process.env.DONT_FAIL_ON_EMPTY_RUN) {
51
+ output.print()
52
+ output.error('No tests were executed. Failing on CI to avoid false positives')
53
+ output.error('To disable this check, set `DONT_FAIL_ON_EMPTY_RUN` environment variable to true in CI config')
54
+ process.exitCode = 1
55
+ }
56
+ }
57
+ })
58
+ }
@@ -3,7 +3,7 @@ const event = require('../event')
3
3
  module.exports = function () {
4
4
  let failedTests = []
5
5
 
6
- event.dispatcher.on(event.test.failed, (testOrSuite) => {
6
+ event.dispatcher.on(event.test.failed, testOrSuite => {
7
7
  // NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
8
8
  // is a suite and not a test
9
9
  const id = testOrSuite.uid || (testOrSuite.ctx && testOrSuite.ctx.test.uid) || 'empty'
@@ -11,14 +11,14 @@ module.exports = function () {
11
11
  })
12
12
 
13
13
  // if test was successful after retries
14
- event.dispatcher.on(event.test.passed, (testOrSuite) => {
14
+ event.dispatcher.on(event.test.passed, testOrSuite => {
15
15
  // NOTE When an error happens in one of the hooks (BeforeAll/BeforeEach...) the event object
16
16
  // is a suite and not a test
17
17
  const id = testOrSuite.uid || (testOrSuite.ctx && testOrSuite.ctx.test.uid) || 'empty'
18
- failedTests = failedTests.filter((failed) => id !== failed)
18
+ failedTests = failedTests.filter(failed => id !== failed)
19
19
  })
20
20
 
21
- process.on('beforeExit', (code) => {
21
+ process.on('beforeExit', code => {
22
22
  if (failedTests.length) {
23
23
  code = 1
24
24
  }
@@ -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) {
@@ -16,7 +16,8 @@ module.exports = function () {
16
16
  return
17
17
  }
18
18
 
19
- event.dispatcher.on(event.suite.before, (suite) => {
19
+ event.dispatcher.on(event.suite.before, suite => {
20
+ timeout = null;
20
21
  suiteTimeout = []
21
22
  let timeoutConfig = Config.get('timeout')
22
23
 
@@ -30,7 +31,7 @@ module.exports = function () {
30
31
  timeoutConfig = [timeoutConfig]
31
32
  }
32
33
 
33
- for (const config of timeoutConfig.filter((c) => !!c.Feature)) {
34
+ for (const config of timeoutConfig.filter(c => !!c.Feature)) {
34
35
  if (config.grep) {
35
36
  if (!suite.title.includes(config.grep)) continue
36
37
  }
@@ -42,7 +43,7 @@ module.exports = function () {
42
43
  output.log(`Timeouts: ${suiteTimeout}`)
43
44
  })
44
45
 
45
- event.dispatcher.on(event.test.before, (test) => {
46
+ event.dispatcher.on(event.test.before, test => {
46
47
  currentTest = test
47
48
  let testTimeout = null
48
49
 
@@ -53,7 +54,7 @@ module.exports = function () {
53
54
  timeoutConfig = [timeoutConfig]
54
55
  }
55
56
 
56
- for (const config of timeoutConfig.filter((c) => !!c.Scenario)) {
57
+ for (const config of timeoutConfig.filter(c => !!c.Scenario)) {
57
58
  console.log('Test Timeout', config, test.title.includes(config.grep))
58
59
  if (config.grep) {
59
60
  if (!test.title.includes(config.grep)) continue
@@ -69,15 +70,15 @@ module.exports = function () {
69
70
  timeout *= 1000
70
71
  })
71
72
 
72
- event.dispatcher.on(event.test.passed, (test) => {
73
+ event.dispatcher.on(event.test.passed, test => {
73
74
  currentTest = null
74
75
  })
75
76
 
76
- event.dispatcher.on(event.test.failed, (test) => {
77
+ event.dispatcher.on(event.test.failed, test => {
77
78
  currentTest = null
78
79
  })
79
80
 
80
- event.dispatcher.on(event.step.before, (step) => {
81
+ event.dispatcher.on(event.step.before, step => {
81
82
  if (typeof timeout !== 'number') return
82
83
 
83
84
  if (timeout < 0) {
@@ -87,7 +88,7 @@ module.exports = function () {
87
88
  }
88
89
  })
89
90
 
90
- event.dispatcher.on(event.step.finished, (step) => {
91
+ event.dispatcher.on(event.step.finished, step => {
91
92
  if (typeof timeout === 'number' && !Number.isNaN(timeout)) timeout -= step.duration
92
93
 
93
94
  if (typeof timeout === 'number' && timeout <= 0 && recorder.isRunning()) {
@@ -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
 
@@ -2,6 +2,7 @@ const debug = require('debug')('codeceptjs:steps')
2
2
  const event = require('../event')
3
3
  const store = require('../store')
4
4
  const output = require('../output')
5
+ const { BeforeHook, AfterHook, BeforeSuiteHook, AfterSuiteHook } = require('../mocha/hooks')
5
6
 
6
7
  let currentTest
7
8
  let currentHook
@@ -10,39 +11,43 @@ let currentHook
10
11
  * Register steps inside tests
11
12
  */
12
13
  module.exports = function () {
13
- event.dispatcher.on(event.test.before, (test) => {
14
+ event.dispatcher.on(event.test.before, test => {
14
15
  test.startedAt = +new Date()
15
16
  test.artifacts = {}
16
17
  })
17
18
 
18
- event.dispatcher.on(event.test.started, (test) => {
19
+ event.dispatcher.on(event.test.started, test => {
19
20
  currentTest = test
20
21
  currentTest.steps = []
21
22
  if (!('retryNum' in currentTest)) currentTest.retryNum = 0
22
23
  else currentTest.retryNum += 1
24
+ output.scenario.started(test)
23
25
  })
24
26
 
25
- event.dispatcher.on(event.test.after, (test) => {
27
+ event.dispatcher.on(event.test.after, test => {
26
28
  currentTest = null
27
29
  })
28
30
 
29
- event.dispatcher.on(event.test.finished, (test) => {})
31
+ event.dispatcher.on(event.test.finished, test => {})
30
32
 
31
- event.dispatcher.on(event.hook.started, (suite) => {
32
- currentHook = suite.ctx.test
33
+ event.dispatcher.on(event.hook.started, hook => {
34
+ currentHook = hook.ctx.test
33
35
  currentHook.steps = []
34
36
 
35
- if (suite.ctx && suite.ctx.test) output.log(`--- STARTED ${suite.ctx.test.title} ---`)
37
+ output.hook.started(hook)
38
+
39
+ if (hook.ctx && hook.ctx.test) debug(`--- STARTED ${hook.ctx.test.title} ---`)
36
40
  })
37
41
 
38
- event.dispatcher.on(event.hook.passed, (suite) => {
42
+ event.dispatcher.on(event.hook.passed, hook => {
39
43
  currentHook = null
40
- if (suite.ctx && suite.ctx.test) output.log(`--- ENDED ${suite.ctx.test.title} ---`)
44
+ output.hook.passed(hook)
45
+ if (hook.ctx && hook.ctx.test) debug(`--- ENDED ${hook.ctx.test.title} ---`)
41
46
  })
42
47
 
43
48
  event.dispatcher.on(event.test.failed, () => {
44
49
  const cutSteps = function (current) {
45
- const failureIndex = current.steps.findIndex((el) => el.status === 'failed')
50
+ const failureIndex = current.steps.findIndex(el => el.status === 'failed')
46
51
  // To be sure that failed test will be failed in report
47
52
  current.state = 'failed'
48
53
  current.steps.length = failureIndex + 1
@@ -65,7 +70,7 @@ module.exports = function () {
65
70
  currentTest.state = 'passed'
66
71
  })
67
72
 
68
- event.dispatcher.on(event.step.started, (step) => {
73
+ event.dispatcher.on(event.step.started, step => {
69
74
  step.startedAt = +new Date()
70
75
  step.test = currentTest
71
76
  if (currentHook && Array.isArray(currentHook.steps)) {
@@ -75,7 +80,7 @@ module.exports = function () {
75
80
  currentTest.steps.push(step)
76
81
  })
77
82
 
78
- event.dispatcher.on(event.step.finished, (step) => {
83
+ event.dispatcher.on(event.step.finished, step => {
79
84
  step.finishedAt = +new Date()
80
85
  if (step.startedAt) step.duration = step.finishedAt - step.startedAt
81
86
  debug(`Step '${step}' finished; Duration: ${step.duration || 0}ms`)
@@ -0,0 +1,12 @@
1
+ const event = require('../event')
2
+ const store = require('../store')
3
+
4
+ module.exports = function () {
5
+ event.dispatcher.on(event.test.before, test => {
6
+ store.currentTest = test
7
+ })
8
+
9
+ event.dispatcher.on(event.test.finished, test => {
10
+ store.currentTest = null
11
+ })
12
+ }
@@ -0,0 +1,204 @@
1
+ const promiseRetry = require('promise-retry')
2
+ const event = require('../event')
3
+ const recorder = require('../recorder')
4
+ const assertThrown = require('../assert/throws')
5
+ const { ucfirst, isAsyncFunction } = require('../utils')
6
+ const { getInjectedArguments } = require('./inject')
7
+ const { fireHook } = require('./hooks')
8
+
9
+ const injectHook = function (inject, suite) {
10
+ try {
11
+ inject()
12
+ } catch (err) {
13
+ recorder.throw(err)
14
+ }
15
+ recorder.catch(err => {
16
+ event.emit(event.test.failed, suite, err)
17
+ throw err
18
+ })
19
+ return recorder.promise()
20
+ }
21
+
22
+ function makeDoneCallableOnce(done) {
23
+ let called = false
24
+ return function (err) {
25
+ if (called) {
26
+ return
27
+ }
28
+ called = true
29
+ return done(err)
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Wraps test function, injects support objects from container,
35
+ * starts promise chain with recorder, performs before/after hooks
36
+ * through event system.
37
+ */
38
+ module.exports.test = test => {
39
+ const testFn = test.fn
40
+ if (!testFn) {
41
+ return test
42
+ }
43
+
44
+ test.timeout(0)
45
+ test.async = true
46
+
47
+ test.fn = function (done) {
48
+ const doneFn = makeDoneCallableOnce(done)
49
+ recorder.errHandler(err => {
50
+ recorder.session.start('teardown')
51
+ recorder.cleanAsyncErr()
52
+ if (test.throws) {
53
+ // check that test should actually fail
54
+ try {
55
+ assertThrown(err, test.throws)
56
+ event.emit(event.test.passed, test)
57
+ event.emit(event.test.finished, test)
58
+ recorder.add(doneFn)
59
+ return
60
+ } catch (newErr) {
61
+ err = newErr
62
+ }
63
+ }
64
+ event.emit(event.test.failed, test, err)
65
+ event.emit(event.test.finished, test)
66
+ recorder.add(() => doneFn(err))
67
+ })
68
+
69
+ if (isAsyncFunction(testFn)) {
70
+ event.emit(event.test.started, test)
71
+ testFn
72
+ .call(test, getInjectedArguments(testFn, test))
73
+ .then(() => {
74
+ recorder.add('fire test.passed', () => {
75
+ event.emit(event.test.passed, test)
76
+ event.emit(event.test.finished, test)
77
+ })
78
+ recorder.add('finish test', doneFn)
79
+ })
80
+ .catch(err => {
81
+ recorder.throw(err)
82
+ })
83
+ .finally(() => {
84
+ recorder.catch()
85
+ })
86
+ return
87
+ }
88
+
89
+ try {
90
+ event.emit(event.test.started, test)
91
+ testFn.call(test, getInjectedArguments(testFn, test))
92
+ } catch (err) {
93
+ recorder.throw(err)
94
+ } finally {
95
+ recorder.add('fire test.passed', () => {
96
+ event.emit(event.test.passed, test)
97
+ event.emit(event.test.finished, test)
98
+ })
99
+ recorder.add('finish test', doneFn)
100
+ recorder.catch()
101
+ }
102
+ }
103
+ return test
104
+ }
105
+
106
+ /**
107
+ * Injects arguments to function from controller
108
+ */
109
+ module.exports.injected = function (fn, suite, hookName) {
110
+ return function (done) {
111
+ const doneFn = makeDoneCallableOnce(done)
112
+ const errHandler = err => {
113
+ recorder.session.start('teardown')
114
+ recorder.cleanAsyncErr()
115
+ event.emit(event.test.failed, suite, err)
116
+ if (hookName === 'after') event.emit(event.test.after, suite)
117
+ if (hookName === 'afterSuite') event.emit(event.suite.after, suite)
118
+ recorder.add(() => doneFn(err))
119
+ }
120
+
121
+ recorder.errHandler(err => {
122
+ errHandler(err)
123
+ })
124
+
125
+ if (!fn) throw new Error('fn is not defined')
126
+
127
+ fireHook(event.hook.started, suite)
128
+
129
+ this.test.body = fn.toString()
130
+
131
+ if (!recorder.isRunning()) {
132
+ recorder.errHandler(err => {
133
+ errHandler(err)
134
+ })
135
+ }
136
+
137
+ const opts = suite.opts || {}
138
+ const retries = opts[`retry${ucfirst(hookName)}`] || 0
139
+
140
+ promiseRetry(
141
+ async (retry, number) => {
142
+ try {
143
+ recorder.startUnlessRunning()
144
+ await fn.call(this, getInjectedArguments(fn))
145
+ await recorder.promise().catch(err => retry(err))
146
+ } catch (err) {
147
+ retry(err)
148
+ } finally {
149
+ if (number < retries) {
150
+ recorder.stop()
151
+ recorder.start()
152
+ }
153
+ }
154
+ },
155
+ { retries },
156
+ )
157
+ .then(() => {
158
+ recorder.add('fire hook.passed', () => fireHook(event.hook.passed, suite))
159
+ recorder.add(`finish ${hookName} hook`, doneFn)
160
+ recorder.catch()
161
+ })
162
+ .catch(e => {
163
+ recorder.throw(e)
164
+ recorder.catch(e => {
165
+ const err = recorder.getAsyncErr() === null ? e : recorder.getAsyncErr()
166
+ errHandler(err)
167
+ })
168
+ recorder.add('fire hook.failed', () => fireHook(event.hook.failed, suite, e))
169
+ })
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Starts promise chain, so helpers could enqueue their hooks
175
+ */
176
+ module.exports.setup = function (suite) {
177
+ return injectHook(() => {
178
+ recorder.startUnlessRunning()
179
+ event.emit(event.test.before, suite && suite.ctx && suite.ctx.currentTest)
180
+ }, suite)
181
+ }
182
+
183
+ module.exports.teardown = function (suite) {
184
+ return injectHook(() => {
185
+ recorder.startUnlessRunning()
186
+ event.emit(event.test.after, suite && suite.ctx && suite.ctx.currentTest)
187
+ }, suite)
188
+ }
189
+
190
+ module.exports.suiteSetup = function (suite) {
191
+ return injectHook(() => {
192
+ recorder.startUnlessRunning()
193
+ event.emit(event.suite.before, suite)
194
+ }, suite)
195
+ }
196
+
197
+ module.exports.suiteTeardown = function (suite) {
198
+ return injectHook(() => {
199
+ recorder.startUnlessRunning()
200
+ event.emit(event.suite.after, suite)
201
+ }, suite)
202
+ }
203
+
204
+ module.exports.getInjectedArguments = getInjectedArguments