codeceptjs 3.6.10-beta.1 → 3.7.0-beta.1
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.
- package/README.md +81 -110
- package/bin/codecept.js +2 -2
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +46 -36
- package/lib/assert/empty.js +3 -5
- package/lib/assert/equal.js +4 -7
- package/lib/assert/include.js +4 -6
- package/lib/assert/throws.js +2 -4
- package/lib/assert/truth.js +2 -2
- package/lib/codecept.js +87 -83
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +5 -25
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +10 -8
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +1 -3
- package/lib/command/init.js +8 -12
- package/lib/command/interactive.js +1 -1
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +10 -10
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +14 -17
- package/lib/container.js +327 -237
- package/lib/data/context.js +10 -13
- package/lib/data/dataScenarioConfig.js +8 -8
- package/lib/data/dataTableArgument.js +6 -6
- package/lib/data/table.js +5 -11
- package/lib/els.js +177 -0
- package/lib/event.js +1 -0
- package/lib/heal.js +78 -80
- package/lib/helper/ApiDataFactory.js +3 -6
- package/lib/helper/Appium.js +15 -30
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +3 -3
- package/lib/helper/JSONResponse.js +57 -37
- package/lib/helper/Nightmare.js +35 -53
- package/lib/helper/Playwright.js +189 -251
- package/lib/helper/Protractor.js +54 -77
- package/lib/helper/Puppeteer.js +134 -232
- package/lib/helper/REST.js +5 -17
- package/lib/helper/TestCafe.js +21 -44
- package/lib/helper/WebDriver.js +103 -162
- package/lib/helper/testcafe/testcafe-utils.js +26 -27
- package/lib/listener/artifacts.js +2 -2
- package/lib/listener/emptyRun.js +58 -0
- package/lib/listener/exit.js +4 -4
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/{timeout.js → globalTimeout.js} +8 -8
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/steps.js +17 -12
- package/lib/listener/store.js +12 -0
- package/lib/mocha/asyncWrapper.js +204 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +257 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +11 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +83 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +24 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +10 -6
- package/lib/mocha/suite.js +55 -0
- package/lib/mocha/test.js +60 -0
- package/lib/mocha/types.d.ts +31 -0
- package/lib/mocha/ui.js +219 -0
- package/lib/output.js +28 -10
- package/lib/pause.js +159 -135
- package/lib/plugin/autoDelay.js +4 -4
- package/lib/plugin/autoLogin.js +6 -7
- package/lib/plugin/commentStep.js +1 -1
- package/lib/plugin/coverage.js +10 -19
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/debugErrors.js +2 -2
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +6 -9
- package/lib/plugin/retryFailedStep.js +4 -4
- package/lib/plugin/retryTo.js +2 -2
- package/lib/plugin/screenshotOnFail.js +9 -36
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/stepByStepReport.js +51 -13
- package/lib/plugin/stepTimeout.js +4 -11
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +1 -1
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +142 -121
- package/lib/secret.js +1 -1
- package/lib/step.js +160 -144
- package/lib/store.js +6 -2
- package/lib/template/heal.js +2 -11
- package/lib/utils.js +224 -216
- package/lib/within.js +73 -55
- package/lib/workers.js +265 -261
- package/package.json +45 -46
- package/typings/index.d.ts +172 -184
- package/typings/promiseBasedTypes.d.ts +53 -516
- package/typings/types.d.ts +127 -587
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/mochaFactory.js +0 -113
- package/lib/scenario.js +0 -224
- 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 =
|
|
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
|
-
|
|
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,
|
|
8
|
+
event.dispatcher.on(event.test.before, test => {
|
|
9
9
|
test.artifacts = {}
|
|
10
10
|
})
|
|
11
11
|
|
|
12
|
-
event.dispatcher.on(event.test.after,
|
|
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
|
+
}
|
package/lib/listener/exit.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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(
|
|
18
|
+
failedTests = failedTests.filter(failed => id !== failed)
|
|
19
19
|
})
|
|
20
20
|
|
|
21
|
-
process.on('beforeExit',
|
|
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,
|
|
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(
|
|
32
|
-
.forEach(
|
|
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,
|
|
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(
|
|
57
|
+
retryConfig = retryConfig.filter(config => !!config.Scenario)
|
|
58
58
|
|
|
59
59
|
for (const config of retryConfig) {
|
|
60
60
|
if (config.grep) {
|
|
@@ -16,7 +16,7 @@ module.exports = function () {
|
|
|
16
16
|
return
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
event.dispatcher.on(event.suite.before,
|
|
19
|
+
event.dispatcher.on(event.suite.before, suite => {
|
|
20
20
|
suiteTimeout = []
|
|
21
21
|
let timeoutConfig = Config.get('timeout')
|
|
22
22
|
|
|
@@ -30,7 +30,7 @@ module.exports = function () {
|
|
|
30
30
|
timeoutConfig = [timeoutConfig]
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
for (const config of timeoutConfig.filter(
|
|
33
|
+
for (const config of timeoutConfig.filter(c => !!c.Feature)) {
|
|
34
34
|
if (config.grep) {
|
|
35
35
|
if (!suite.title.includes(config.grep)) continue
|
|
36
36
|
}
|
|
@@ -42,7 +42,7 @@ module.exports = function () {
|
|
|
42
42
|
output.log(`Timeouts: ${suiteTimeout}`)
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
-
event.dispatcher.on(event.test.before,
|
|
45
|
+
event.dispatcher.on(event.test.before, test => {
|
|
46
46
|
currentTest = test
|
|
47
47
|
let testTimeout = null
|
|
48
48
|
|
|
@@ -53,7 +53,7 @@ module.exports = function () {
|
|
|
53
53
|
timeoutConfig = [timeoutConfig]
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
for (const config of timeoutConfig.filter(
|
|
56
|
+
for (const config of timeoutConfig.filter(c => !!c.Scenario)) {
|
|
57
57
|
console.log('Test Timeout', config, test.title.includes(config.grep))
|
|
58
58
|
if (config.grep) {
|
|
59
59
|
if (!test.title.includes(config.grep)) continue
|
|
@@ -69,15 +69,15 @@ module.exports = function () {
|
|
|
69
69
|
timeout *= 1000
|
|
70
70
|
})
|
|
71
71
|
|
|
72
|
-
event.dispatcher.on(event.test.passed,
|
|
72
|
+
event.dispatcher.on(event.test.passed, test => {
|
|
73
73
|
currentTest = null
|
|
74
74
|
})
|
|
75
75
|
|
|
76
|
-
event.dispatcher.on(event.test.failed,
|
|
76
|
+
event.dispatcher.on(event.test.failed, test => {
|
|
77
77
|
currentTest = null
|
|
78
78
|
})
|
|
79
79
|
|
|
80
|
-
event.dispatcher.on(event.step.before,
|
|
80
|
+
event.dispatcher.on(event.step.before, step => {
|
|
81
81
|
if (typeof timeout !== 'number') return
|
|
82
82
|
|
|
83
83
|
if (timeout < 0) {
|
|
@@ -87,7 +87,7 @@ module.exports = function () {
|
|
|
87
87
|
}
|
|
88
88
|
})
|
|
89
89
|
|
|
90
|
-
event.dispatcher.on(event.step.finished,
|
|
90
|
+
event.dispatcher.on(event.step.finished, step => {
|
|
91
91
|
if (typeof timeout === 'number' && !Number.isNaN(timeout)) timeout -= step.duration
|
|
92
92
|
|
|
93
93
|
if (typeof timeout === 'number' && timeout <= 0 && recorder.isRunning()) {
|
package/lib/listener/helpers.js
CHANGED
|
@@ -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(
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
40
|
+
event.dispatcher.on(event.test.started, test => {
|
|
41
41
|
runHelpersHook('_test', test)
|
|
42
|
-
recorder.catch(
|
|
42
|
+
recorder.catch(e => error(e))
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
-
event.dispatcher.on(event.test.before,
|
|
45
|
+
event.dispatcher.on(event.test.before, test => {
|
|
46
46
|
// schedule config to revert changes
|
|
47
47
|
runAsyncHelpersHook('_before', test, true)
|
|
48
|
-
recorder.catchWithoutStop(
|
|
48
|
+
recorder.catchWithoutStop(e => error(e))
|
|
49
49
|
})
|
|
50
50
|
|
|
51
|
-
event.dispatcher.on(event.test.passed,
|
|
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(
|
|
54
|
+
recorder.catchWithoutStop(e => error(e))
|
|
55
55
|
})
|
|
56
56
|
|
|
57
|
-
event.dispatcher.on(event.test.failed,
|
|
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(
|
|
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(
|
|
65
|
+
recorder.catchWithoutStop(e => error(e))
|
|
66
66
|
})
|
|
67
67
|
|
|
68
|
-
event.dispatcher.on(event.step.before,
|
|
68
|
+
event.dispatcher.on(event.step.before, step => {
|
|
69
69
|
runAsyncHelpersHook('_beforeStep', step)
|
|
70
70
|
})
|
|
71
71
|
|
|
72
|
-
event.dispatcher.on(event.step.after,
|
|
72
|
+
event.dispatcher.on(event.step.after, step => {
|
|
73
73
|
runAsyncHelpersHook('_afterStep', step)
|
|
74
74
|
})
|
|
75
75
|
|
package/lib/listener/mocha.js
CHANGED
package/lib/listener/steps.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
27
|
+
event.dispatcher.on(event.test.after, test => {
|
|
26
28
|
currentTest = null
|
|
27
29
|
})
|
|
28
30
|
|
|
29
|
-
event.dispatcher.on(event.test.finished,
|
|
31
|
+
event.dispatcher.on(event.test.finished, test => {})
|
|
30
32
|
|
|
31
|
-
event.dispatcher.on(event.hook.started,
|
|
32
|
-
currentHook =
|
|
33
|
+
event.dispatcher.on(event.hook.started, hook => {
|
|
34
|
+
currentHook = hook.ctx.test
|
|
33
35
|
currentHook.steps = []
|
|
34
36
|
|
|
35
|
-
|
|
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,
|
|
42
|
+
event.dispatcher.on(event.hook.passed, hook => {
|
|
39
43
|
currentHook = null
|
|
40
|
-
|
|
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(
|
|
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,
|
|
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,
|
|
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
|