codeceptjs 4.0.0-beta.3 → 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.
- package/README.md +134 -119
- package/bin/codecept.js +12 -2
- package/bin/test-server.js +53 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +66 -102
- package/lib/ai.js +130 -121
- 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 +141 -86
- package/lib/command/check.js +201 -0
- package/lib/command/configMigrate.js +2 -4
- package/lib/command/definitions.js +8 -26
- package/lib/command/dryRun.js +30 -35
- package/lib/command/generate.js +10 -14
- package/lib/command/gherkin/snippets.js +75 -73
- package/lib/command/gherkin/steps.js +1 -1
- package/lib/command/info.js +42 -8
- package/lib/command/init.js +13 -12
- package/lib/command/interactive.js +10 -2
- package/lib/command/list.js +1 -1
- package/lib/command/run-multiple/chunk.js +48 -45
- package/lib/command/run-multiple.js +12 -35
- package/lib/command/run-workers.js +21 -58
- package/lib/command/utils.js +5 -6
- package/lib/command/workers/runTests.js +263 -222
- package/lib/container.js +386 -238
- 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/effects.js +223 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +158 -0
- package/lib/event.js +21 -17
- package/lib/heal.js +88 -80
- package/lib/helper/AI.js +2 -1
- package/lib/helper/ApiDataFactory.js +4 -7
- package/lib/helper/Appium.js +50 -57
- package/lib/helper/FileSystem.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +75 -37
- package/lib/helper/Mochawesome.js +31 -9
- package/lib/helper/Nightmare.js +37 -58
- package/lib/helper/Playwright.js +267 -272
- package/lib/helper/Protractor.js +56 -87
- package/lib/helper/Puppeteer.js +247 -264
- package/lib/helper/REST.js +29 -17
- package/lib/helper/TestCafe.js +22 -47
- package/lib/helper/WebDriver.js +157 -368
- package/lib/helper/extras/PlaywrightPropEngine.js +2 -2
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/network/utils.js +1 -1
- package/lib/helper/testcafe/testcafe-utils.js +27 -28
- package/lib/listener/emptyRun.js +55 -0
- package/lib/listener/exit.js +7 -10
- package/lib/listener/{retry.js → globalRetry.js} +5 -5
- package/lib/listener/globalTimeout.js +165 -0
- package/lib/listener/helpers.js +15 -15
- package/lib/listener/mocha.js +1 -1
- package/lib/listener/result.js +12 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +32 -18
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +1 -1
- package/lib/mocha/asyncWrapper.js +231 -0
- package/lib/{interfaces → mocha}/bdd.js +3 -3
- package/lib/mocha/cli.js +308 -0
- package/lib/mocha/factory.js +104 -0
- package/lib/{interfaces → mocha}/featureConfig.js +32 -12
- package/lib/{interfaces → mocha}/gherkin.js +26 -28
- package/lib/mocha/hooks.js +112 -0
- package/lib/mocha/index.js +12 -0
- package/lib/mocha/inject.js +29 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +31 -7
- package/lib/mocha/suite.js +82 -0
- package/lib/mocha/test.js +181 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +232 -0
- package/lib/output.js +93 -65
- package/lib/pause.js +160 -138
- package/lib/plugin/analyze.js +396 -0
- package/lib/plugin/auth.js +435 -0
- package/lib/plugin/autoDelay.js +8 -8
- package/lib/plugin/autoLogin.js +3 -338
- package/lib/plugin/commentStep.js +6 -1
- package/lib/plugin/coverage.js +10 -22
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +52 -0
- package/lib/plugin/eachElement.js +1 -1
- package/lib/plugin/fakerTransform.js +1 -1
- package/lib/plugin/heal.js +36 -9
- package/lib/plugin/htmlReporter.js +1947 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/retryFailedStep.js +17 -18
- package/lib/plugin/retryTo.js +2 -113
- package/lib/plugin/screenshotOnFail.js +17 -58
- package/lib/plugin/selenoid.js +15 -35
- package/lib/plugin/standardActingHelpers.js +4 -1
- package/lib/plugin/stepByStepReport.js +56 -17
- package/lib/plugin/stepTimeout.js +5 -12
- package/lib/plugin/subtitles.js +4 -4
- package/lib/plugin/tryTo.js +3 -102
- package/lib/plugin/wdio.js +8 -10
- package/lib/recorder.js +155 -124
- package/lib/rerun.js +43 -42
- package/lib/result.js +161 -0
- package/lib/secret.js +1 -2
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +21 -332
- package/lib/steps.js +50 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/test-server.js +323 -0
- package/lib/timeout.js +66 -0
- package/lib/utils.js +351 -218
- package/lib/within.js +75 -55
- package/lib/workerStorage.js +2 -1
- package/lib/workers.js +386 -277
- package/package.json +81 -75
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +1 -0
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +9 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +197 -187
- package/typings/promiseBasedTypes.d.ts +53 -903
- package/typings/types.d.ts +372 -1042
- package/lib/cli.js +0 -257
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/MockServer.js +0 -221
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const path = require('path')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const Container = require('../container')
|
|
4
|
+
const recorder = require('../recorder')
|
|
5
|
+
const event = require('../event')
|
|
6
|
+
const supportedHelpers = Container.STANDARD_ACTING_HELPERS
|
|
7
|
+
const { scanForErrorMessages } = require('../html')
|
|
8
|
+
const { output } = require('..')
|
|
9
|
+
const { humanizeString, ucfirst } = require('../utils')
|
|
10
|
+
const { testToFileName } = require('../mocha/test')
|
|
11
|
+
const defaultConfig = {
|
|
12
|
+
errorClasses: ['error', 'warning', 'alert', 'danger'],
|
|
13
|
+
browserLogs: ['error'],
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Collects information from web page after each failed test and adds it to the test as an artifact.
|
|
18
|
+
* It is suggested to enable this plugin if you run tests on CI and you need to debug failed tests.
|
|
19
|
+
* This plugin can be paired with `analyze` plugin to provide more context.
|
|
20
|
+
*
|
|
21
|
+
* It collects URL, HTML errors (by classes), and browser logs.
|
|
22
|
+
*
|
|
23
|
+
* Enable this plugin in config:
|
|
24
|
+
*
|
|
25
|
+
* ```js
|
|
26
|
+
* plugins: {
|
|
27
|
+
* pageInfo: {
|
|
28
|
+
* enabled: true,
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* Additional config options:
|
|
33
|
+
*
|
|
34
|
+
* * `errorClasses` - list of classes to search for errors (default: `['error', 'warning', 'alert', 'danger']`)
|
|
35
|
+
* * `browserLogs` - list of types of errors to search for in browser logs (default: `['error']`)
|
|
36
|
+
*
|
|
37
|
+
*/
|
|
38
|
+
module.exports = function (config = {}) {
|
|
39
|
+
const helpers = Container.helpers()
|
|
40
|
+
let helper
|
|
41
|
+
|
|
42
|
+
config = Object.assign(defaultConfig, config)
|
|
43
|
+
|
|
44
|
+
for (const helperName of supportedHelpers) {
|
|
45
|
+
if (Object.keys(helpers).indexOf(helperName) > -1) {
|
|
46
|
+
helper = helpers[helperName]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!helper) return // no helpers for screenshot
|
|
51
|
+
|
|
52
|
+
event.dispatcher.on(event.test.failed, test => {
|
|
53
|
+
const pageState = {}
|
|
54
|
+
|
|
55
|
+
recorder.add('URL of failed test', async () => {
|
|
56
|
+
try {
|
|
57
|
+
const url = await helper.grabCurrentUrl()
|
|
58
|
+
pageState.url = url
|
|
59
|
+
} catch (err) {
|
|
60
|
+
// not really needed
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
recorder.add('HTML snapshot failed test', async () => {
|
|
64
|
+
try {
|
|
65
|
+
const html = await helper.grabHTMLFrom('body')
|
|
66
|
+
|
|
67
|
+
if (!html) return
|
|
68
|
+
|
|
69
|
+
const errors = scanForErrorMessages(html, config.errorClasses)
|
|
70
|
+
if (errors.length) {
|
|
71
|
+
output.debug('Detected errors in HTML code')
|
|
72
|
+
errors.forEach(error => output.debug(error))
|
|
73
|
+
pageState.htmlErrors = errors
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
// not really needed
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
recorder.add('Browser logs for failed test', async () => {
|
|
81
|
+
try {
|
|
82
|
+
const logs = await helper.grabBrowserLogs()
|
|
83
|
+
|
|
84
|
+
if (!logs) return
|
|
85
|
+
|
|
86
|
+
pageState.browserErrors = getBrowserErrors(logs, config.browserLogs)
|
|
87
|
+
} catch (err) {
|
|
88
|
+
// not really needed
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
recorder.add('Save page info', () => {
|
|
93
|
+
test.addNote('pageInfo', pageStateToMarkdown(pageState))
|
|
94
|
+
|
|
95
|
+
const pageStateFileName = path.join(global.output_dir, `${testToFileName(test)}.pageInfo.md`)
|
|
96
|
+
fs.writeFileSync(pageStateFileName, pageStateToMarkdown(pageState))
|
|
97
|
+
test.artifacts.pageInfo = pageStateFileName
|
|
98
|
+
return pageState
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function pageStateToMarkdown(pageState) {
|
|
104
|
+
let markdown = ''
|
|
105
|
+
|
|
106
|
+
for (const [key, value] of Object.entries(pageState)) {
|
|
107
|
+
if (!value) continue
|
|
108
|
+
let result = ''
|
|
109
|
+
|
|
110
|
+
if (Array.isArray(value)) {
|
|
111
|
+
result = value.map(v => `- ${JSON.stringify(v, null, 2)}`).join('\n')
|
|
112
|
+
} else if (typeof value === 'string') {
|
|
113
|
+
result = `${value}`
|
|
114
|
+
} else {
|
|
115
|
+
result = JSON.stringify(value, null, 2)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!result.trim()) continue
|
|
119
|
+
|
|
120
|
+
markdown += `### ${ucfirst(humanizeString(key))}\n\n`
|
|
121
|
+
markdown += result
|
|
122
|
+
markdown += '\n\n'
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return markdown
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function getBrowserErrors(logs, type = ['error']) {
|
|
129
|
+
// Playwright & WebDriver console messages
|
|
130
|
+
let errors = logs
|
|
131
|
+
.map(log => {
|
|
132
|
+
if (typeof log === 'string') return log
|
|
133
|
+
if (!log.type) return null
|
|
134
|
+
return { type: log.type(), text: log.text() }
|
|
135
|
+
})
|
|
136
|
+
.filter(l => l && (typeof l === 'string' || type.includes(l.type)))
|
|
137
|
+
.map(l => (typeof l === 'string' ? l : l.text))
|
|
138
|
+
|
|
139
|
+
return errors
|
|
140
|
+
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
const event = require('../event')
|
|
2
2
|
const recorder = require('../recorder')
|
|
3
|
-
const
|
|
4
|
-
const { log } = require('../output')
|
|
5
|
-
|
|
3
|
+
const store = require('../store')
|
|
6
4
|
const defaultConfig = {
|
|
7
5
|
retries: 3,
|
|
8
6
|
defaultIgnoredSteps: ['amOnPage', 'wait*', 'send*', 'execute*', 'run*', 'have*'],
|
|
@@ -70,34 +68,29 @@ const defaultConfig = {
|
|
|
70
68
|
* Use scenario configuration to disable plugin for a test
|
|
71
69
|
*
|
|
72
70
|
* ```js
|
|
73
|
-
* Scenario('scenario tite', () => {
|
|
71
|
+
* Scenario('scenario tite', { disableRetryFailedStep: true }, () => {
|
|
74
72
|
* // test goes here
|
|
75
|
-
* })
|
|
73
|
+
* })
|
|
76
74
|
* ```
|
|
77
75
|
*
|
|
78
76
|
*/
|
|
79
|
-
module.exports =
|
|
77
|
+
module.exports = config => {
|
|
80
78
|
config = Object.assign(defaultConfig, config)
|
|
81
79
|
config.ignoredSteps = config.ignoredSteps.concat(config.defaultIgnoredSteps)
|
|
82
80
|
const customWhen = config.when
|
|
83
81
|
|
|
84
82
|
let enableRetry = false
|
|
85
83
|
|
|
86
|
-
const when =
|
|
84
|
+
const when = err => {
|
|
87
85
|
if (!enableRetry) return
|
|
88
|
-
const store = require('../store')
|
|
89
86
|
if (store.debugMode) return false
|
|
87
|
+
if (!store.autoRetries) return false
|
|
90
88
|
if (customWhen) return customWhen(err)
|
|
91
89
|
return true
|
|
92
90
|
}
|
|
93
91
|
config.when = when
|
|
94
92
|
|
|
95
|
-
event.dispatcher.on(event.step.started,
|
|
96
|
-
if (process.env.TRY_TO === 'true') {
|
|
97
|
-
log('Info: RetryFailedStep plugin is disabled inside tryTo block')
|
|
98
|
-
return
|
|
99
|
-
}
|
|
100
|
-
|
|
93
|
+
event.dispatcher.on(event.step.started, step => {
|
|
101
94
|
// if a step is ignored - return
|
|
102
95
|
for (const ignored of config.ignoredSteps) {
|
|
103
96
|
if (step.name === ignored) return
|
|
@@ -112,10 +105,16 @@ module.exports = (config) => {
|
|
|
112
105
|
enableRetry = false
|
|
113
106
|
})
|
|
114
107
|
|
|
115
|
-
event.dispatcher.on(event.test.before,
|
|
116
|
-
|
|
117
|
-
//
|
|
118
|
-
|
|
108
|
+
event.dispatcher.on(event.test.before, test => {
|
|
109
|
+
// pass disableRetryFailedStep is a preferred way to disable retries
|
|
110
|
+
// test.disableRetryFailedStep is used for backward compatibility
|
|
111
|
+
if (test.opts.disableRetryFailedStep || test.disableRetryFailedStep) {
|
|
112
|
+
store.autoRetries = false
|
|
113
|
+
return // disable retry when a test is not active
|
|
114
|
+
}
|
|
115
|
+
// this option is used to set the retries inside _before() block of helpers
|
|
116
|
+
store.autoRetries = true
|
|
117
|
+
test.opts.conditionalRetries = config.retries
|
|
119
118
|
recorder.retry(config)
|
|
120
119
|
})
|
|
121
120
|
}
|
package/lib/plugin/retryTo.js
CHANGED
|
@@ -1,123 +1,12 @@
|
|
|
1
|
-
const
|
|
2
|
-
const { debug } = require('../output')
|
|
1
|
+
const { retryTo } = require('../effects')
|
|
3
2
|
|
|
4
3
|
const defaultConfig = {
|
|
5
4
|
registerGlobal: true,
|
|
6
|
-
pollInterval: 200,
|
|
7
5
|
}
|
|
8
6
|
|
|
9
|
-
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Adds global `retryTo` which retries steps a few times before failing.
|
|
13
|
-
*
|
|
14
|
-
* Enable this plugin in `codecept.conf.js` (enabled by default for new setups):
|
|
15
|
-
*
|
|
16
|
-
* ```js
|
|
17
|
-
* plugins: {
|
|
18
|
-
* retryTo: {
|
|
19
|
-
* enabled: true
|
|
20
|
-
* }
|
|
21
|
-
* }
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
|
-
* Use it in your tests:
|
|
25
|
-
*
|
|
26
|
-
* ```js
|
|
27
|
-
* // retry these steps 5 times before failing
|
|
28
|
-
* await retryTo((tryNum) => {
|
|
29
|
-
* I.switchTo('#editor frame');
|
|
30
|
-
* I.click('Open');
|
|
31
|
-
* I.see('Opened')
|
|
32
|
-
* }, 5);
|
|
33
|
-
* ```
|
|
34
|
-
* Set polling interval as 3rd argument (200ms by default):
|
|
35
|
-
*
|
|
36
|
-
* ```js
|
|
37
|
-
* // retry these steps 5 times before failing
|
|
38
|
-
* await retryTo((tryNum) => {
|
|
39
|
-
* I.switchTo('#editor frame');
|
|
40
|
-
* I.click('Open');
|
|
41
|
-
* I.see('Opened')
|
|
42
|
-
* }, 5, 100);
|
|
43
|
-
* ```
|
|
44
|
-
*
|
|
45
|
-
* Default polling interval can be changed in a config:
|
|
46
|
-
*
|
|
47
|
-
* ```js
|
|
48
|
-
* plugins: {
|
|
49
|
-
* retryTo: {
|
|
50
|
-
* enabled: true,
|
|
51
|
-
* pollInterval: 500,
|
|
52
|
-
* }
|
|
53
|
-
* }
|
|
54
|
-
* ```
|
|
55
|
-
*
|
|
56
|
-
* Disables retryFailedStep plugin for steps inside a block;
|
|
57
|
-
*
|
|
58
|
-
* Use this plugin if:
|
|
59
|
-
*
|
|
60
|
-
* * you need repeat a set of actions in flaky tests
|
|
61
|
-
* * iframe was not rendered and you need to retry switching to it
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* #### Configuration
|
|
65
|
-
*
|
|
66
|
-
* * `pollInterval` - default interval between retries in ms. 200 by default.
|
|
67
|
-
* * `registerGlobal` - to register `retryTo` function globally, true by default
|
|
68
|
-
*
|
|
69
|
-
* If `registerGlobal` is false you can use retryTo from the plugin:
|
|
70
|
-
*
|
|
71
|
-
* ```js
|
|
72
|
-
* const retryTo = codeceptjs.container.plugins('retryTo');
|
|
73
|
-
* ```
|
|
74
|
-
*
|
|
75
|
-
*/
|
|
76
7
|
module.exports = function (config) {
|
|
77
8
|
config = Object.assign(defaultConfig, config)
|
|
78
|
-
|
|
79
|
-
return new Promise((done, reject) => {
|
|
80
|
-
let tries = 1
|
|
81
|
-
|
|
82
|
-
function handleRetryException(err) {
|
|
83
|
-
recorder.throw(err)
|
|
84
|
-
reject(err)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const tryBlock = async () => {
|
|
88
|
-
tries++
|
|
89
|
-
recorder.session.start(`retryTo ${tries}`)
|
|
90
|
-
try {
|
|
91
|
-
await callback(tries)
|
|
92
|
-
} catch (err) {
|
|
93
|
-
handleRetryException(err)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Call done if no errors
|
|
97
|
-
recorder.add(() => {
|
|
98
|
-
recorder.session.restore(`retryTo ${tries}`)
|
|
99
|
-
done(null)
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
// Catch errors and retry
|
|
103
|
-
recorder.session.catch((err) => {
|
|
104
|
-
recorder.session.restore(`retryTo ${tries}`)
|
|
105
|
-
if (tries <= maxTries) {
|
|
106
|
-
debug(`Error ${err}... Retrying`)
|
|
107
|
-
recorder.add(`retryTo ${tries}`, () => setTimeout(tryBlock, pollInterval))
|
|
108
|
-
} else {
|
|
109
|
-
// if maxTries reached
|
|
110
|
-
handleRetryException(err)
|
|
111
|
-
}
|
|
112
|
-
})
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
recorder.add('retryTo', tryBlock).catch((err) => {
|
|
116
|
-
console.error('An error occurred:', err)
|
|
117
|
-
done(null)
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
-
}
|
|
9
|
+
console.log(`Deprecation Warning: 'retryTo' has been moved to the 'codeceptjs/effects' module. Disable retryTo plugin to remove this warning.`)
|
|
121
10
|
|
|
122
11
|
if (config.registerGlobal) {
|
|
123
12
|
global.retryTo = retryTo
|
|
@@ -5,8 +5,9 @@ const Container = require('../container')
|
|
|
5
5
|
const recorder = require('../recorder')
|
|
6
6
|
const event = require('../event')
|
|
7
7
|
const output = require('../output')
|
|
8
|
-
const { fileExists
|
|
8
|
+
const { fileExists } = require('../utils')
|
|
9
9
|
const Codeceptjs = require('../index')
|
|
10
|
+
const { testToFileName } = require('../mocha/test')
|
|
10
11
|
|
|
11
12
|
const defaultConfig = {
|
|
12
13
|
uniqueScreenshotNames: false,
|
|
@@ -14,7 +15,7 @@ const defaultConfig = {
|
|
|
14
15
|
fullPageScreenshots: false,
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
const supportedHelpers =
|
|
18
|
+
const supportedHelpers = Container.STANDARD_ACTING_HELPERS
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* Creates screenshot on failure. Screenshot is saved into `output` directory.
|
|
@@ -63,9 +64,7 @@ module.exports = function (config) {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
if (Codeceptjs.container.mocha()) {
|
|
66
|
-
options.reportDir =
|
|
67
|
-
Codeceptjs.container.mocha().options.reporterOptions &&
|
|
68
|
-
Codeceptjs.container.mocha().options.reporterOptions.reportDir
|
|
67
|
+
options.reportDir = Codeceptjs.container.mocha()?.options?.reporterOptions && Codeceptjs.container.mocha()?.options?.reporterOptions?.reportDir
|
|
69
68
|
}
|
|
70
69
|
|
|
71
70
|
if (options.disableScreenshots) {
|
|
@@ -73,30 +72,23 @@ module.exports = function (config) {
|
|
|
73
72
|
return
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
event.dispatcher.on(event.test.failed, (test) => {
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
'screenshotOnFail',
|
|
80
|
-
'BeforeSuite/AfterSuite do not have any access to the browser, hence it could not take screenshot.',
|
|
81
|
-
)
|
|
75
|
+
event.dispatcher.on(event.test.failed, (test, _err, hookName) => {
|
|
76
|
+
if (hookName === 'BeforeSuite' || hookName === 'AfterSuite') {
|
|
77
|
+
// no browser here
|
|
82
78
|
return
|
|
83
79
|
}
|
|
80
|
+
|
|
84
81
|
recorder.add(
|
|
85
82
|
'screenshot of failed test',
|
|
86
83
|
async () => {
|
|
87
|
-
let fileName = clearString(test.title)
|
|
88
84
|
const dataType = 'image/png'
|
|
89
85
|
// This prevents data driven to be included in the failed screenshot file name
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
if (test.ctx && test.ctx.test && test.ctx.test.type === 'hook')
|
|
94
|
-
fileName = clearString(`${test.title}_${test.ctx.test.title}`)
|
|
86
|
+
let fileName
|
|
87
|
+
|
|
95
88
|
if (options.uniqueScreenshotNames && test) {
|
|
96
|
-
|
|
97
|
-
fileName = `${fileName.substring(0, 10)}_${uuid}.failed.png`
|
|
89
|
+
fileName = `${testToFileName(test, { unique: true })}.failed.png`
|
|
98
90
|
} else {
|
|
99
|
-
fileName
|
|
91
|
+
fileName = `${testToFileName(test)}.failed.png`
|
|
100
92
|
}
|
|
101
93
|
output.plugin('screenshotOnFail', 'Test failed, try to save a screenshot')
|
|
102
94
|
|
|
@@ -112,34 +104,20 @@ module.exports = function (config) {
|
|
|
112
104
|
|
|
113
105
|
if (!test.artifacts) test.artifacts = {}
|
|
114
106
|
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
|
-
) {
|
|
107
|
+
if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments) {
|
|
119
108
|
test.attachments = [path.join(global.output_dir, fileName)]
|
|
120
109
|
}
|
|
121
110
|
|
|
122
111
|
const allureReporter = Container.plugins('allure')
|
|
123
112
|
if (allureReporter) {
|
|
124
|
-
allureReporter.addAttachment(
|
|
125
|
-
'Main session - Last Seen Screenshot',
|
|
126
|
-
fs.readFileSync(path.join(global.output_dir, fileName)),
|
|
127
|
-
dataType,
|
|
128
|
-
)
|
|
113
|
+
allureReporter.addAttachment('Main session - Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)), dataType)
|
|
129
114
|
|
|
130
115
|
if (helper.activeSessionName) {
|
|
131
116
|
const sessions = helper.sessionPages || helper.sessionWindows
|
|
132
117
|
for (const sessionName in sessions) {
|
|
133
118
|
const screenshotFileName = `${sessionName}_${fileName}`
|
|
134
|
-
test.artifacts[`${sessionName.replace(/ /g, '_')}_screenshot`] = path.join(
|
|
135
|
-
|
|
136
|
-
screenshotFileName,
|
|
137
|
-
)
|
|
138
|
-
allureReporter.addAttachment(
|
|
139
|
-
`${sessionName} - Last Seen Screenshot`,
|
|
140
|
-
fs.readFileSync(path.join(global.output_dir, screenshotFileName)),
|
|
141
|
-
dataType,
|
|
142
|
-
)
|
|
119
|
+
test.artifacts[`${sessionName.replace(/ /g, '_')}_screenshot`] = path.join(global.output_dir, screenshotFileName)
|
|
120
|
+
allureReporter.addAttachment(`${sessionName} - Last Seen Screenshot`, fs.readFileSync(path.join(global.output_dir, screenshotFileName)), dataType)
|
|
143
121
|
}
|
|
144
122
|
}
|
|
145
123
|
}
|
|
@@ -150,14 +128,7 @@ module.exports = function (config) {
|
|
|
150
128
|
}
|
|
151
129
|
} catch (err) {
|
|
152
130
|
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
|
-
) {
|
|
131
|
+
if (err && err.type && err.type === 'RuntimeError' && err.message && (err.message.indexOf('was terminated due to') > -1 || err.message.indexOf('no such window: target window already closed') > -1)) {
|
|
161
132
|
output.log(`Can't make screenshot, ${err}`)
|
|
162
133
|
helper.isRunning = false
|
|
163
134
|
}
|
|
@@ -166,16 +137,4 @@ module.exports = function (config) {
|
|
|
166
137
|
true,
|
|
167
138
|
)
|
|
168
139
|
})
|
|
169
|
-
|
|
170
|
-
function _getUUID(test) {
|
|
171
|
-
if (test.uuid) {
|
|
172
|
-
return test.uuid
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (test.ctx && test.ctx.test.uuid) {
|
|
176
|
-
return test.ctx.test.uuid
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
return Math.floor(new Date().getTime() / 1000)
|
|
180
|
-
}
|
|
181
140
|
}
|
package/lib/plugin/selenoid.js
CHANGED
|
@@ -41,12 +41,7 @@ const dockerCreateScriptArr = [
|
|
|
41
41
|
'aerokube/selenoid:latest-release -log-output-dir /opt/selenoid/logs',
|
|
42
42
|
]
|
|
43
43
|
|
|
44
|
-
const dockerImageCheckScript = [
|
|
45
|
-
'docker images',
|
|
46
|
-
"--filter reference='selenoid/video-recorder'",
|
|
47
|
-
"--filter reference='selenoid/chrome:latest'",
|
|
48
|
-
"--filter reference='selenoid/firefox:latest'",
|
|
49
|
-
].join(' ')
|
|
44
|
+
const dockerImageCheckScript = ['docker images', "--filter reference='selenoid/video-recorder'", "--filter reference='selenoid/chrome:latest'", "--filter reference='selenoid/firefox:latest'"].join(' ')
|
|
50
45
|
|
|
51
46
|
let dockerCreateScript = dockerCreateScriptArr.join(' ')
|
|
52
47
|
let dockerStartScript = 'docker start $name$'
|
|
@@ -56,8 +51,8 @@ let seleniumUrl = 'http://localhost:$port$'
|
|
|
56
51
|
const supportedHelpers = ['WebDriver']
|
|
57
52
|
const SELENOID_START_TIMEOUT = 2000
|
|
58
53
|
const SELENOID_STOP_TIMEOUT = 10000
|
|
59
|
-
const wait =
|
|
60
|
-
new Promise(
|
|
54
|
+
const wait = time =>
|
|
55
|
+
new Promise(res => {
|
|
61
56
|
setTimeout(() => {
|
|
62
57
|
// @ts-ignore
|
|
63
58
|
res()
|
|
@@ -179,7 +174,7 @@ const wait = (time) =>
|
|
|
179
174
|
*
|
|
180
175
|
*/
|
|
181
176
|
|
|
182
|
-
const selenoid =
|
|
177
|
+
const selenoid = config => {
|
|
183
178
|
const helpers = container.helpers()
|
|
184
179
|
let helperName
|
|
185
180
|
|
|
@@ -194,14 +189,7 @@ const selenoid = (config) => {
|
|
|
194
189
|
return // no helpers for Selenoid
|
|
195
190
|
}
|
|
196
191
|
|
|
197
|
-
const {
|
|
198
|
-
autoStart,
|
|
199
|
-
name = 'selenoid',
|
|
200
|
-
deletePassed = true,
|
|
201
|
-
additionalParams = '',
|
|
202
|
-
autoCreate = true,
|
|
203
|
-
port = 4444,
|
|
204
|
-
} = config
|
|
192
|
+
const { autoStart, name = 'selenoid', deletePassed = true, additionalParams = '', autoCreate = true, port = 4444 } = config
|
|
205
193
|
const passedTests = []
|
|
206
194
|
|
|
207
195
|
recorder.startUnlessRunning()
|
|
@@ -213,7 +201,7 @@ const selenoid = (config) => {
|
|
|
213
201
|
output.debug('Staring Selenoid... ')
|
|
214
202
|
return createAndStart(autoCreate)
|
|
215
203
|
.then(() => output.debug('Selenoid started'))
|
|
216
|
-
.catch(
|
|
204
|
+
.catch(err => {
|
|
217
205
|
throw new Error(err.stack)
|
|
218
206
|
})
|
|
219
207
|
})
|
|
@@ -226,7 +214,7 @@ const selenoid = (config) => {
|
|
|
226
214
|
.then(() => deletePassedTests(passedTests))
|
|
227
215
|
.then(stopSelenoid)
|
|
228
216
|
.then(() => output.debug('Selenoid stopped'))
|
|
229
|
-
.catch(
|
|
217
|
+
.catch(err => {
|
|
230
218
|
throw new Error(err.stack)
|
|
231
219
|
})
|
|
232
220
|
})
|
|
@@ -241,7 +229,7 @@ const selenoid = (config) => {
|
|
|
241
229
|
}
|
|
242
230
|
})
|
|
243
231
|
|
|
244
|
-
event.dispatcher.on(event.test.before,
|
|
232
|
+
event.dispatcher.on(event.test.before, test => {
|
|
245
233
|
switch (helperName) {
|
|
246
234
|
case 'WebDriver':
|
|
247
235
|
setTestConfigForWebdriver(test)
|
|
@@ -250,7 +238,7 @@ const selenoid = (config) => {
|
|
|
250
238
|
})
|
|
251
239
|
|
|
252
240
|
if (config.enableVideo) {
|
|
253
|
-
event.dispatcher.on(event.test.passed,
|
|
241
|
+
event.dispatcher.on(event.test.passed, test => {
|
|
254
242
|
if (deletePassed) {
|
|
255
243
|
passedTests.push(test.title)
|
|
256
244
|
} else {
|
|
@@ -258,7 +246,7 @@ const selenoid = (config) => {
|
|
|
258
246
|
}
|
|
259
247
|
})
|
|
260
248
|
|
|
261
|
-
event.dispatcher.on(event.test.failed,
|
|
249
|
+
event.dispatcher.on(event.test.failed, test => {
|
|
262
250
|
test.artifacts.video = videoSaved(test)
|
|
263
251
|
})
|
|
264
252
|
}
|
|
@@ -312,16 +300,12 @@ const pullImage = async () => {
|
|
|
312
300
|
console.time('Pulled containers')
|
|
313
301
|
if (!stdout.includes('selenoid/video-recorder')) {
|
|
314
302
|
output.debug('Pulling selenoid/video-recorder...')
|
|
315
|
-
resultPromise = exec('docker pull selenoid/video-recorder:latest-release').then(() =>
|
|
316
|
-
output.debug('Pulled in selenoid/video-recorder'),
|
|
317
|
-
)
|
|
303
|
+
resultPromise = exec('docker pull selenoid/video-recorder:latest-release').then(() => output.debug('Pulled in selenoid/video-recorder'))
|
|
318
304
|
pulls.push(resultPromise)
|
|
319
305
|
}
|
|
320
306
|
if (!stdout.includes('selenoid/chrome')) {
|
|
321
307
|
output.debug('Pulling selenoid/chrome...')
|
|
322
|
-
resultPromise = exec('docker pull selenoid/chrome:latest').then(() =>
|
|
323
|
-
output.debug('Pulled in selenoid/video-recorder'),
|
|
324
|
-
)
|
|
308
|
+
resultPromise = exec('docker pull selenoid/chrome:latest').then(() => output.debug('Pulled in selenoid/video-recorder'))
|
|
325
309
|
pulls.push(resultPromise)
|
|
326
310
|
}
|
|
327
311
|
if (!stdout.includes('selenoid/firefox')) {
|
|
@@ -341,16 +325,12 @@ function createAndStart(autoCreate) {
|
|
|
341
325
|
}
|
|
342
326
|
|
|
343
327
|
function deletePassedTests(passedTests) {
|
|
344
|
-
const deleteVideoPromiseList = passedTests
|
|
345
|
-
|
|
346
|
-
.map((test) => axios.delete(`${seleniumUrl}/video/${test}.mp4`))
|
|
347
|
-
const deleteLogPromiseList = passedTests
|
|
348
|
-
.map(clearString)
|
|
349
|
-
.map((test) => axios.delete(`${seleniumUrl}/logs/${test}.log`))
|
|
328
|
+
const deleteVideoPromiseList = passedTests.map(clearString).map(test => axios.delete(`${seleniumUrl}/video/${test}.mp4`))
|
|
329
|
+
const deleteLogPromiseList = passedTests.map(clearString).map(test => axios.delete(`${seleniumUrl}/logs/${test}.log`))
|
|
350
330
|
|
|
351
331
|
return Promise.all(deleteVideoPromiseList.concat(deleteLogPromiseList))
|
|
352
332
|
.then(() => output.debug('Deleted videos and logs for all passed tests'))
|
|
353
|
-
.catch(
|
|
333
|
+
.catch(err => output.error(`Error while deleting video and log files ${err.stack}`))
|
|
354
334
|
}
|
|
355
335
|
|
|
356
336
|
function setOptionsForWebdriver(config) {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const Container = require('../container')
|
|
2
|
+
// due to using this in internal tooling we won't post deprecation warning
|
|
3
|
+
// but please switch to Container.STANDARD_ACTING_HELPERS
|
|
4
|
+
const standardActingHelpers = Container.STANDARD_ACTING_HELPERS
|
|
2
5
|
|
|
3
6
|
module.exports = standardActingHelpers
|