codeceptjs 3.6.4-beta.2 → 3.6.5-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/bin/codecept.js +84 -63
- package/lib/ai.js +47 -1
- package/lib/assert/empty.js +19 -19
- package/lib/assert/equal.js +32 -30
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +42 -42
- package/lib/assert/throws.js +13 -11
- package/lib/assert/truth.js +17 -18
- package/lib/command/configMigrate.js +57 -52
- package/lib/command/definitions.js +88 -88
- package/lib/command/dryRun.js +65 -63
- package/lib/command/generate.js +191 -181
- package/lib/command/info.js +39 -37
- package/lib/command/init.js +289 -286
- package/lib/command/interactive.js +32 -32
- package/lib/command/list.js +26 -26
- package/lib/command/run-multiple.js +113 -93
- package/lib/command/run-rerun.js +22 -22
- package/lib/command/run-workers.js +63 -63
- package/lib/command/run.js +24 -26
- package/lib/command/utils.js +64 -63
- package/lib/data/context.js +60 -60
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +29 -29
- package/lib/data/table.js +26 -20
- package/lib/helper/AI.js +114 -35
- package/lib/helper/ApiDataFactory.js +72 -69
- package/lib/helper/Appium.js +409 -379
- package/lib/helper/ExpectHelper.js +214 -248
- package/lib/helper/FileSystem.js +77 -78
- package/lib/helper/GraphQL.js +44 -43
- package/lib/helper/GraphQLDataFactory.js +49 -50
- package/lib/helper/JSONResponse.js +64 -62
- package/lib/helper/Mochawesome.js +28 -28
- package/lib/helper/MockServer.js +12 -12
- package/lib/helper/Nightmare.js +664 -572
- package/lib/helper/Playwright.js +1320 -1211
- package/lib/helper/Protractor.js +663 -629
- package/lib/helper/Puppeteer.js +1232 -1124
- package/lib/helper/REST.js +115 -69
- package/lib/helper/TestCafe.js +490 -491
- package/lib/helper/WebDriver.js +1294 -1156
- package/lib/history.js +16 -3
- package/lib/interfaces/bdd.js +38 -51
- package/lib/interfaces/featureConfig.js +19 -19
- package/lib/interfaces/gherkin.js +122 -111
- package/lib/interfaces/scenarioConfig.js +29 -29
- package/lib/listener/artifacts.js +9 -9
- package/lib/listener/config.js +24 -23
- package/lib/listener/exit.js +12 -12
- package/lib/listener/helpers.js +42 -42
- package/lib/listener/mocha.js +11 -11
- package/lib/listener/retry.js +32 -30
- package/lib/listener/steps.js +50 -51
- package/lib/listener/timeout.js +53 -53
- package/lib/pause.js +17 -3
- package/lib/plugin/allure.js +14 -14
- package/lib/plugin/autoDelay.js +29 -36
- package/lib/plugin/autoLogin.js +70 -66
- package/lib/plugin/commentStep.js +18 -18
- package/lib/plugin/coverage.js +92 -77
- package/lib/plugin/customLocator.js +20 -19
- package/lib/plugin/debugErrors.js +24 -24
- package/lib/plugin/eachElement.js +37 -37
- package/lib/plugin/fakerTransform.js +6 -6
- package/lib/plugin/heal.js +66 -63
- package/lib/plugin/pauseOnFail.js +10 -10
- package/lib/plugin/retryFailedStep.js +31 -38
- package/lib/plugin/retryTo.js +28 -28
- package/lib/plugin/screenshotOnFail.js +107 -86
- package/lib/plugin/selenoid.js +131 -117
- package/lib/plugin/standardActingHelpers.js +2 -8
- package/lib/plugin/stepByStepReport.js +102 -92
- package/lib/plugin/stepTimeout.js +23 -22
- package/lib/plugin/subtitles.js +34 -34
- package/lib/plugin/tryTo.js +39 -29
- package/lib/plugin/wdio.js +77 -72
- package/lib/template/heal.js +11 -14
- package/package.json +5 -3
- package/translations/de-DE.js +1 -1
- package/translations/fr-FR.js +1 -1
- package/translations/index.js +9 -9
- package/translations/it-IT.js +1 -1
- package/translations/ja-JP.js +1 -1
- package/translations/pl-PL.js +1 -1
- package/translations/pt-BR.js +1 -1
- package/translations/ru-RU.js +1 -1
- package/translations/zh-CN.js +1 -1
- package/translations/zh-TW.js +1 -1
- package/typings/index.d.ts +42 -19
- package/typings/promiseBasedTypes.d.ts +280 -1
- package/typings/types.d.ts +76 -1
package/lib/plugin/heal.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
const debug = require('debug')('codeceptjs:heal')
|
|
2
|
-
const colors = require('chalk')
|
|
3
|
-
const recorder = require('../recorder')
|
|
4
|
-
const event = require('../event')
|
|
5
|
-
const output = require('../output')
|
|
6
|
-
const heal = require('../heal')
|
|
7
|
-
const store = require('../store')
|
|
1
|
+
const debug = require('debug')('codeceptjs:heal')
|
|
2
|
+
const colors = require('chalk')
|
|
3
|
+
const recorder = require('../recorder')
|
|
4
|
+
const event = require('../event')
|
|
5
|
+
const output = require('../output')
|
|
6
|
+
const heal = require('../heal')
|
|
7
|
+
const store = require('../store')
|
|
8
8
|
|
|
9
9
|
const defaultConfig = {
|
|
10
10
|
healLimit: 2,
|
|
11
|
-
}
|
|
11
|
+
}
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Self-healing tests with AI.
|
|
@@ -31,88 +31,91 @@ const defaultConfig = {
|
|
|
31
31
|
module.exports = function (config = {}) {
|
|
32
32
|
if (store.debugMode && !process.env.DEBUG) {
|
|
33
33
|
event.dispatcher.on(event.test.failed, () => {
|
|
34
|
-
output.plugin(
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
output.plugin(
|
|
35
|
+
'heal',
|
|
36
|
+
'Healing is disabled in --debug mode, use DEBUG="codeceptjs:heal" to enable it in debug mode',
|
|
37
|
+
)
|
|
38
|
+
})
|
|
39
|
+
return
|
|
37
40
|
}
|
|
38
41
|
|
|
39
|
-
let currentTest = null
|
|
40
|
-
let currentStep = null
|
|
41
|
-
let healedSteps = 0
|
|
42
|
-
let caughtError
|
|
43
|
-
let healTries = 0
|
|
44
|
-
let isHealing = false
|
|
42
|
+
let currentTest = null
|
|
43
|
+
let currentStep = null
|
|
44
|
+
let healedSteps = 0
|
|
45
|
+
let caughtError
|
|
46
|
+
let healTries = 0
|
|
47
|
+
let isHealing = false
|
|
45
48
|
|
|
46
|
-
config = Object.assign(defaultConfig, config)
|
|
49
|
+
config = Object.assign(defaultConfig, config)
|
|
47
50
|
|
|
48
51
|
event.dispatcher.on(event.test.before, (test) => {
|
|
49
|
-
currentTest = test
|
|
50
|
-
healedSteps = 0
|
|
51
|
-
caughtError = null
|
|
52
|
-
})
|
|
52
|
+
currentTest = test
|
|
53
|
+
healedSteps = 0
|
|
54
|
+
caughtError = null
|
|
55
|
+
})
|
|
53
56
|
|
|
54
|
-
event.dispatcher.on(event.step.started, step => currentStep = step)
|
|
57
|
+
event.dispatcher.on(event.step.started, (step) => (currentStep = step))
|
|
55
58
|
|
|
56
59
|
event.dispatcher.on(event.step.after, (step) => {
|
|
57
|
-
if (isHealing) return
|
|
58
|
-
if (healTries >= config.healLimit) return
|
|
60
|
+
if (isHealing) return
|
|
61
|
+
if (healTries >= config.healLimit) return // out of limit
|
|
59
62
|
|
|
60
|
-
if (!heal.hasCorrespondingRecipes(step)) return
|
|
63
|
+
if (!heal.hasCorrespondingRecipes(step)) return
|
|
61
64
|
|
|
62
65
|
recorder.catchWithoutStop(async (err) => {
|
|
63
|
-
isHealing = true
|
|
64
|
-
if (caughtError === err) throw err
|
|
65
|
-
caughtError = err
|
|
66
|
+
isHealing = true
|
|
67
|
+
if (caughtError === err) throw err // avoid double handling
|
|
68
|
+
caughtError = err
|
|
66
69
|
|
|
67
|
-
const test = currentTest
|
|
70
|
+
const test = currentTest
|
|
68
71
|
|
|
69
|
-
recorder.session.start('heal')
|
|
72
|
+
recorder.session.start('heal')
|
|
70
73
|
|
|
71
|
-
debug('Self-healing started', step.toCode())
|
|
74
|
+
debug('Self-healing started', step.toCode())
|
|
72
75
|
|
|
73
|
-
await heal.healStep(step, err, { test })
|
|
76
|
+
await heal.healStep(step, err, { test })
|
|
74
77
|
|
|
75
|
-
healTries
|
|
78
|
+
healTries++
|
|
76
79
|
|
|
77
80
|
recorder.add('close healing session', () => {
|
|
78
|
-
recorder.reset()
|
|
79
|
-
recorder.session.restore('heal')
|
|
80
|
-
recorder.ignoreErr(err)
|
|
81
|
-
})
|
|
82
|
-
await recorder.promise()
|
|
81
|
+
recorder.reset()
|
|
82
|
+
recorder.session.restore('heal')
|
|
83
|
+
recorder.ignoreErr(err)
|
|
84
|
+
})
|
|
85
|
+
await recorder.promise()
|
|
83
86
|
|
|
84
|
-
isHealing = false
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
+
isHealing = false
|
|
88
|
+
})
|
|
89
|
+
})
|
|
87
90
|
|
|
88
91
|
event.dispatcher.on(event.all.result, () => {
|
|
89
|
-
if (!heal.fixes?.length) return
|
|
92
|
+
if (!heal.fixes?.length) return
|
|
90
93
|
|
|
91
|
-
const { print } = output
|
|
94
|
+
const { print } = output
|
|
92
95
|
|
|
93
|
-
print('')
|
|
94
|
-
print('===================')
|
|
95
|
-
print(colors.bold.green('Self-Healing Report:'))
|
|
96
|
+
print('')
|
|
97
|
+
print('===================')
|
|
98
|
+
print(colors.bold.green('Self-Healing Report:'))
|
|
96
99
|
|
|
97
|
-
print(`${colors.bold(heal.fixes.length)} ${heal.fixes.length === 1 ? 'step was' : 'steps were'} healed`)
|
|
100
|
+
print(`${colors.bold(heal.fixes.length)} ${heal.fixes.length === 1 ? 'step was' : 'steps were'} healed`)
|
|
98
101
|
|
|
99
|
-
const suggestions = heal.fixes.filter(fix => fix.recipe && heal.recipes[fix.recipe].suggest)
|
|
102
|
+
const suggestions = heal.fixes.filter((fix) => fix.recipe && heal.recipes[fix.recipe].suggest)
|
|
100
103
|
|
|
101
|
-
if (!suggestions.length) return
|
|
104
|
+
if (!suggestions.length) return
|
|
102
105
|
|
|
103
|
-
let i = 1
|
|
104
|
-
print('')
|
|
105
|
-
print('Suggested changes:')
|
|
106
|
-
print('')
|
|
106
|
+
let i = 1
|
|
107
|
+
print('')
|
|
108
|
+
print('Suggested changes:')
|
|
109
|
+
print('')
|
|
107
110
|
|
|
108
111
|
for (const suggestion of suggestions) {
|
|
109
|
-
print(`${i}. To fix ${colors.bold.magenta(suggestion.test?.title)}`)
|
|
110
|
-
print(' Replace the failed code:', colors.gray(`(suggested by ${colors.bold(suggestion.recipe)})`))
|
|
111
|
-
print(colors.red(`- ${suggestion.step.toCode()}`))
|
|
112
|
-
print(colors.green(`+ ${suggestion.snippet}`))
|
|
113
|
-
print(suggestion.step.line())
|
|
114
|
-
print('')
|
|
115
|
-
i
|
|
112
|
+
print(`${i}. To fix ${colors.bold.magenta(suggestion.test?.title)}`)
|
|
113
|
+
print(' Replace the failed code:', colors.gray(`(suggested by ${colors.bold(suggestion.recipe)})`))
|
|
114
|
+
print(colors.red(`- ${suggestion.step.toCode()}`))
|
|
115
|
+
print(colors.green(`+ ${suggestion.snippet}`))
|
|
116
|
+
print(suggestion.step.line())
|
|
117
|
+
print('')
|
|
118
|
+
i++
|
|
116
119
|
}
|
|
117
|
-
})
|
|
118
|
-
}
|
|
120
|
+
})
|
|
121
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const event = require('../event')
|
|
2
|
-
const pause = require('../pause')
|
|
1
|
+
const event = require('../event')
|
|
2
|
+
const pause = require('../pause')
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Automatically launches [interactive pause](/basics/#pause) when a test fails.
|
|
@@ -22,17 +22,17 @@ const pause = require('../pause');
|
|
|
22
22
|
*
|
|
23
23
|
*/
|
|
24
24
|
module.exports = () => {
|
|
25
|
-
let failed = false
|
|
25
|
+
let failed = false
|
|
26
26
|
|
|
27
27
|
event.dispatcher.on(event.test.started, () => {
|
|
28
|
-
failed = false
|
|
29
|
-
})
|
|
28
|
+
failed = false
|
|
29
|
+
})
|
|
30
30
|
|
|
31
31
|
event.dispatcher.on(event.step.failed, () => {
|
|
32
|
-
failed = true
|
|
33
|
-
})
|
|
32
|
+
failed = true
|
|
33
|
+
})
|
|
34
34
|
|
|
35
35
|
event.dispatcher.on(event.test.after, () => {
|
|
36
|
-
if (failed) pause()
|
|
37
|
-
})
|
|
38
|
-
}
|
|
36
|
+
if (failed) pause()
|
|
37
|
+
})
|
|
38
|
+
}
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
const event = require('../event')
|
|
2
|
-
const recorder = require('../recorder')
|
|
3
|
-
const container = require('../container')
|
|
4
|
-
const { log } = require('../output')
|
|
1
|
+
const event = require('../event')
|
|
2
|
+
const recorder = require('../recorder')
|
|
3
|
+
const container = require('../container')
|
|
4
|
+
const { log } = require('../output')
|
|
5
5
|
|
|
6
6
|
const defaultConfig = {
|
|
7
7
|
retries: 3,
|
|
8
|
-
defaultIgnoredSteps: [
|
|
9
|
-
'amOnPage',
|
|
10
|
-
'wait*',
|
|
11
|
-
'send*',
|
|
12
|
-
'execute*',
|
|
13
|
-
'run*',
|
|
14
|
-
'have*',
|
|
15
|
-
],
|
|
8
|
+
defaultIgnoredSteps: ['amOnPage', 'wait*', 'send*', 'execute*', 'run*', 'have*'],
|
|
16
9
|
factor: 1.5,
|
|
17
10
|
ignoredSteps: [],
|
|
18
|
-
}
|
|
11
|
+
}
|
|
19
12
|
|
|
20
13
|
/**
|
|
21
14
|
* Retries each failed step in a test.
|
|
@@ -84,45 +77,45 @@ const defaultConfig = {
|
|
|
84
77
|
*
|
|
85
78
|
*/
|
|
86
79
|
module.exports = (config) => {
|
|
87
|
-
config = Object.assign(defaultConfig, config)
|
|
88
|
-
config.ignoredSteps = config.ignoredSteps.concat(config.defaultIgnoredSteps)
|
|
89
|
-
const customWhen = config.when
|
|
80
|
+
config = Object.assign(defaultConfig, config)
|
|
81
|
+
config.ignoredSteps = config.ignoredSteps.concat(config.defaultIgnoredSteps)
|
|
82
|
+
const customWhen = config.when
|
|
90
83
|
|
|
91
|
-
let enableRetry = false
|
|
84
|
+
let enableRetry = false
|
|
92
85
|
|
|
93
86
|
const when = (err) => {
|
|
94
|
-
if (!enableRetry) return
|
|
95
|
-
const store = require('../store')
|
|
96
|
-
if (store.debugMode) return false
|
|
97
|
-
if (customWhen) return customWhen(err)
|
|
98
|
-
return true
|
|
99
|
-
}
|
|
100
|
-
config.when = when
|
|
87
|
+
if (!enableRetry) return
|
|
88
|
+
const store = require('../store')
|
|
89
|
+
if (store.debugMode) return false
|
|
90
|
+
if (customWhen) return customWhen(err)
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
93
|
+
config.when = when
|
|
101
94
|
|
|
102
95
|
event.dispatcher.on(event.step.started, (step) => {
|
|
103
96
|
if (process.env.TRY_TO === 'true') {
|
|
104
|
-
log('Info: RetryFailedStep plugin is disabled inside tryTo block')
|
|
105
|
-
return
|
|
97
|
+
log('Info: RetryFailedStep plugin is disabled inside tryTo block')
|
|
98
|
+
return
|
|
106
99
|
}
|
|
107
100
|
|
|
108
101
|
// if a step is ignored - return
|
|
109
102
|
for (const ignored of config.ignoredSteps) {
|
|
110
|
-
if (step.name === ignored) return
|
|
103
|
+
if (step.name === ignored) return
|
|
111
104
|
if (ignored instanceof RegExp) {
|
|
112
|
-
if (step.name.match(ignored)) return
|
|
113
|
-
} else if (ignored.indexOf('*') && step.name.startsWith(ignored.slice(0, -1))) return
|
|
105
|
+
if (step.name.match(ignored)) return
|
|
106
|
+
} else if (ignored.indexOf('*') && step.name.startsWith(ignored.slice(0, -1))) return
|
|
114
107
|
}
|
|
115
|
-
enableRetry = true
|
|
116
|
-
})
|
|
108
|
+
enableRetry = true // enable retry for a step
|
|
109
|
+
})
|
|
117
110
|
|
|
118
111
|
event.dispatcher.on(event.step.finished, () => {
|
|
119
|
-
enableRetry = false
|
|
120
|
-
})
|
|
112
|
+
enableRetry = false
|
|
113
|
+
})
|
|
121
114
|
|
|
122
115
|
event.dispatcher.on(event.test.before, (test) => {
|
|
123
|
-
if (test && test.disableRetryFailedStep) return
|
|
116
|
+
if (test && test.disableRetryFailedStep) return // disable retry when a test is not active
|
|
124
117
|
// this env var is used to set the retries inside _before() block of helpers
|
|
125
|
-
process.env.FAILED_STEP_RETRIES = config.retries
|
|
126
|
-
recorder.retry(config)
|
|
127
|
-
})
|
|
128
|
-
}
|
|
118
|
+
process.env.FAILED_STEP_RETRIES = config.retries
|
|
119
|
+
recorder.retry(config)
|
|
120
|
+
})
|
|
121
|
+
}
|
package/lib/plugin/retryTo.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
const recorder = require('../recorder')
|
|
2
|
-
const { debug } = require('../output')
|
|
1
|
+
const recorder = require('../recorder')
|
|
2
|
+
const { debug } = require('../output')
|
|
3
3
|
|
|
4
4
|
const defaultConfig = {
|
|
5
5
|
registerGlobal: true,
|
|
6
6
|
pollInterval: 200,
|
|
7
|
-
}
|
|
7
|
+
}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
*
|
|
@@ -74,54 +74,54 @@ const defaultConfig = {
|
|
|
74
74
|
*
|
|
75
75
|
*/
|
|
76
76
|
module.exports = function (config) {
|
|
77
|
-
config = Object.assign(defaultConfig, config)
|
|
77
|
+
config = Object.assign(defaultConfig, config)
|
|
78
78
|
function retryTo(callback, maxTries, pollInterval = config.pollInterval) {
|
|
79
79
|
return new Promise((done, reject) => {
|
|
80
|
-
let tries = 1
|
|
80
|
+
let tries = 1
|
|
81
81
|
|
|
82
82
|
function handleRetryException(err) {
|
|
83
|
-
recorder.throw(err)
|
|
84
|
-
reject(err)
|
|
83
|
+
recorder.throw(err)
|
|
84
|
+
reject(err)
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
const tryBlock = async () => {
|
|
88
|
-
tries
|
|
89
|
-
recorder.session.start(`retryTo ${tries}`)
|
|
88
|
+
tries++
|
|
89
|
+
recorder.session.start(`retryTo ${tries}`)
|
|
90
90
|
try {
|
|
91
|
-
await callback(tries)
|
|
91
|
+
await callback(tries)
|
|
92
92
|
} catch (err) {
|
|
93
|
-
handleRetryException(err)
|
|
93
|
+
handleRetryException(err)
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
// Call done if no errors
|
|
97
97
|
recorder.add(() => {
|
|
98
|
-
recorder.session.restore(`retryTo ${tries}`)
|
|
99
|
-
done(null)
|
|
100
|
-
})
|
|
98
|
+
recorder.session.restore(`retryTo ${tries}`)
|
|
99
|
+
done(null)
|
|
100
|
+
})
|
|
101
101
|
|
|
102
102
|
// Catch errors and retry
|
|
103
103
|
recorder.session.catch((err) => {
|
|
104
|
-
recorder.session.restore(`retryTo ${tries}`)
|
|
104
|
+
recorder.session.restore(`retryTo ${tries}`)
|
|
105
105
|
if (tries <= maxTries) {
|
|
106
|
-
debug(`Error ${err}... Retrying`)
|
|
107
|
-
recorder.add(`retryTo ${tries}`, () => setTimeout(tryBlock, pollInterval))
|
|
106
|
+
debug(`Error ${err}... Retrying`)
|
|
107
|
+
recorder.add(`retryTo ${tries}`, () => setTimeout(tryBlock, pollInterval))
|
|
108
108
|
} else {
|
|
109
109
|
// if maxTries reached
|
|
110
|
-
handleRetryException(err)
|
|
110
|
+
handleRetryException(err)
|
|
111
111
|
}
|
|
112
|
-
})
|
|
113
|
-
}
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
114
|
|
|
115
|
-
recorder.add('retryTo', tryBlock).catch(err => {
|
|
116
|
-
console.error('An error occurred:', err)
|
|
117
|
-
done(null)
|
|
118
|
-
})
|
|
119
|
-
})
|
|
115
|
+
recorder.add('retryTo', tryBlock).catch((err) => {
|
|
116
|
+
console.error('An error occurred:', err)
|
|
117
|
+
done(null)
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
if (config.registerGlobal) {
|
|
123
|
-
global.retryTo = retryTo
|
|
123
|
+
global.retryTo = retryTo
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
return retryTo
|
|
127
|
-
}
|
|
126
|
+
return retryTo
|
|
127
|
+
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
const fs = require('fs')
|
|
2
|
-
const path = require('path')
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
3
|
|
|
4
|
-
const Container = require('../container')
|
|
5
|
-
const recorder = require('../recorder')
|
|
6
|
-
const event = require('../event')
|
|
7
|
-
const output = require('../output')
|
|
8
|
-
const { fileExists, clearString } = require('../utils')
|
|
9
|
-
const Codeceptjs = require('../index')
|
|
4
|
+
const Container = require('../container')
|
|
5
|
+
const recorder = require('../recorder')
|
|
6
|
+
const event = require('../event')
|
|
7
|
+
const output = require('../output')
|
|
8
|
+
const { fileExists, clearString } = require('../utils')
|
|
9
|
+
const Codeceptjs = require('../index')
|
|
10
10
|
|
|
11
11
|
const defaultConfig = {
|
|
12
12
|
uniqueScreenshotNames: false,
|
|
13
13
|
disableScreenshots: false,
|
|
14
14
|
fullPageScreenshots: false,
|
|
15
|
-
}
|
|
15
|
+
}
|
|
16
16
|
|
|
17
|
-
const supportedHelpers = require('./standardActingHelpers')
|
|
17
|
+
const supportedHelpers = require('./standardActingHelpers')
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Creates screenshot on failure. Screenshot is saved into `output` directory.
|
|
@@ -43,118 +43,139 @@ const supportedHelpers = require('./standardActingHelpers');
|
|
|
43
43
|
*
|
|
44
44
|
*/
|
|
45
45
|
module.exports = function (config) {
|
|
46
|
-
const helpers = Container.helpers()
|
|
47
|
-
let helper
|
|
46
|
+
const helpers = Container.helpers()
|
|
47
|
+
let helper
|
|
48
48
|
|
|
49
49
|
for (const helperName of supportedHelpers) {
|
|
50
50
|
if (Object.keys(helpers).indexOf(helperName) > -1) {
|
|
51
|
-
helper = helpers[helperName]
|
|
51
|
+
helper = helpers[helperName]
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
if (!helper) return
|
|
55
|
+
if (!helper) return // no helpers for screenshot
|
|
56
56
|
|
|
57
|
-
const options = Object.assign(defaultConfig, helper.options, config)
|
|
57
|
+
const options = Object.assign(defaultConfig, helper.options, config)
|
|
58
58
|
|
|
59
59
|
if (helpers.Mochawesome) {
|
|
60
60
|
if (helpers.Mochawesome.config) {
|
|
61
|
-
options.uniqueScreenshotNames = helpers.Mochawesome.config.uniqueScreenshotNames
|
|
61
|
+
options.uniqueScreenshotNames = helpers.Mochawesome.config.uniqueScreenshotNames
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
if (Codeceptjs.container.mocha()) {
|
|
66
|
-
options.reportDir =
|
|
67
|
-
|
|
66
|
+
options.reportDir =
|
|
67
|
+
Codeceptjs.container.mocha().options.reporterOptions &&
|
|
68
|
+
Codeceptjs.container.mocha().options.reporterOptions.reportDir
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
if (options.disableScreenshots) {
|
|
71
72
|
// old version of disabling screenshots
|
|
72
|
-
return
|
|
73
|
+
return
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
event.dispatcher.on(event.test.failed, (test) => {
|
|
76
77
|
if (test.ctx?._runnable.title.includes('hook: ')) {
|
|
77
|
-
output.plugin(
|
|
78
|
-
|
|
78
|
+
output.plugin(
|
|
79
|
+
'screenshotOnFail',
|
|
80
|
+
'BeforeSuite/AfterSuite do not have any access to the browser, hence it could not take screenshot.',
|
|
81
|
+
)
|
|
82
|
+
return
|
|
79
83
|
}
|
|
80
|
-
recorder.add(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
if (options.uniqueScreenshotNames && test) {
|
|
89
|
-
const uuid = _getUUID(test);
|
|
90
|
-
fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`;
|
|
91
|
-
} else {
|
|
92
|
-
fileName += '.failed.png';
|
|
93
|
-
}
|
|
94
|
-
output.plugin('screenshotOnFail', 'Test failed, try to save a screenshot');
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
if (options.reportDir) {
|
|
98
|
-
fileName = path.join(options.reportDir, fileName);
|
|
99
|
-
const mochaReportDir = path.resolve(process.cwd(), options.reportDir);
|
|
100
|
-
if (!fileExists(mochaReportDir)) {
|
|
101
|
-
fs.mkdirSync(mochaReportDir);
|
|
102
|
-
}
|
|
84
|
+
recorder.add(
|
|
85
|
+
'screenshot of failed test',
|
|
86
|
+
async () => {
|
|
87
|
+
let fileName = clearString(test.title)
|
|
88
|
+
const dataType = 'image/png'
|
|
89
|
+
// This prevents data driven to be included in the failed screenshot file name
|
|
90
|
+
if (fileName.indexOf('{') !== -1) {
|
|
91
|
+
fileName = fileName.substr(0, fileName.indexOf('{') - 3).trim()
|
|
103
92
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
93
|
+
if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook')
|
|
94
|
+
fileName = clearString(`${test.title}_${test.ctx.test.title}`)
|
|
95
|
+
if (options.uniqueScreenshotNames && test) {
|
|
96
|
+
const uuid = _getUUID(test)
|
|
97
|
+
fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`
|
|
98
|
+
} else {
|
|
99
|
+
fileName += '.failed.png'
|
|
110
100
|
}
|
|
101
|
+
output.plugin('screenshotOnFail', 'Test failed, try to save a screenshot')
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
if (options.reportDir) {
|
|
105
|
+
fileName = path.join(options.reportDir, fileName)
|
|
106
|
+
const mochaReportDir = path.resolve(process.cwd(), options.reportDir)
|
|
107
|
+
if (!fileExists(mochaReportDir)) {
|
|
108
|
+
fs.mkdirSync(mochaReportDir)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
await helper.saveScreenshot(fileName, options.fullPageScreenshots)
|
|
112
|
+
|
|
113
|
+
if (!test.artifacts) test.artifacts = {}
|
|
114
|
+
test.artifacts.screenshot = path.join(global.output_dir, fileName)
|
|
115
|
+
if (
|
|
116
|
+
Container.mocha().options.reporterOptions['mocha-junit-reporter'] &&
|
|
117
|
+
Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments
|
|
118
|
+
) {
|
|
119
|
+
test.attachments = [path.join(global.output_dir, fileName)]
|
|
120
|
+
}
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
+
const allureReporter = Container.plugins('allure')
|
|
123
|
+
if (allureReporter) {
|
|
124
|
+
allureReporter.addAttachment(
|
|
125
|
+
'Main session - Last Seen Screenshot',
|
|
126
|
+
fs.readFileSync(path.join(global.output_dir, fileName)),
|
|
127
|
+
dataType,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if (helper.activeSessionName) {
|
|
131
|
+
const sessions = helper.sessionPages || helper.sessionWindows
|
|
132
|
+
for (const sessionName in sessions) {
|
|
133
|
+
const screenshotFileName = `${sessionName}_${fileName}`
|
|
134
|
+
test.artifacts[`${sessionName.replace(/ /g, '_')}_screenshot`] = path.join(
|
|
135
|
+
global.output_dir,
|
|
136
|
+
screenshotFileName,
|
|
137
|
+
)
|
|
138
|
+
allureReporter.addAttachment(
|
|
139
|
+
`${sessionName} - Last Seen Screenshot`,
|
|
140
|
+
fs.readFileSync(path.join(global.output_dir, screenshotFileName)),
|
|
141
|
+
dataType,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
122
144
|
}
|
|
123
145
|
}
|
|
124
|
-
}
|
|
125
146
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
helper.isRunning = false;
|
|
147
|
+
const cucumberReporter = Container.plugins('cucumberJsonReporter')
|
|
148
|
+
if (cucumberReporter) {
|
|
149
|
+
cucumberReporter.addScreenshot(test.artifacts.screenshot)
|
|
150
|
+
}
|
|
151
|
+
} catch (err) {
|
|
152
|
+
output.plugin(err)
|
|
153
|
+
if (
|
|
154
|
+
err &&
|
|
155
|
+
err.type &&
|
|
156
|
+
err.type === 'RuntimeError' &&
|
|
157
|
+
err.message &&
|
|
158
|
+
(err.message.indexOf('was terminated due to') > -1 ||
|
|
159
|
+
err.message.indexOf('no such window: target window already closed') > -1)
|
|
160
|
+
) {
|
|
161
|
+
output.log(`Can't make screenshot, ${err}`)
|
|
162
|
+
helper.isRunning = false
|
|
163
|
+
}
|
|
144
164
|
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
},
|
|
166
|
+
true,
|
|
167
|
+
)
|
|
168
|
+
})
|
|
148
169
|
|
|
149
170
|
function _getUUID(test) {
|
|
150
171
|
if (test.uuid) {
|
|
151
|
-
return test.uuid
|
|
172
|
+
return test.uuid
|
|
152
173
|
}
|
|
153
174
|
|
|
154
175
|
if (test.ctx && test.ctx.test.uuid) {
|
|
155
|
-
return test.ctx.test.uuid
|
|
176
|
+
return test.ctx.test.uuid
|
|
156
177
|
}
|
|
157
178
|
|
|
158
|
-
return Math.floor(new Date().getTime() / 1000)
|
|
179
|
+
return Math.floor(new Date().getTime() / 1000)
|
|
159
180
|
}
|
|
160
|
-
}
|
|
181
|
+
}
|