codeceptjs 4.0.0-beta.4 → 4.0.0-beta.6.esm-aria
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 +89 -119
- package/bin/codecept.js +53 -54
- package/docs/webapi/clearCookie.mustache +1 -1
- package/lib/actor.js +70 -102
- package/lib/ai.js +131 -121
- package/lib/assert/empty.js +11 -12
- package/lib/assert/equal.js +16 -21
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +11 -15
- package/lib/assert/throws.js +3 -5
- package/lib/assert/truth.js +10 -7
- package/lib/assert.js +18 -18
- package/lib/codecept.js +112 -101
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +206 -0
- package/lib/command/configMigrate.js +13 -14
- package/lib/command/definitions.js +24 -36
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +38 -39
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +76 -74
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +49 -15
- package/lib/command/init.js +41 -37
- package/lib/command/interactive.js +22 -13
- package/lib/command/list.js +11 -10
- package/lib/command/run-multiple/chunk.js +50 -47
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +27 -47
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +15 -66
- package/lib/command/run.js +8 -8
- package/lib/command/utils.js +22 -21
- package/lib/command/workers/runTests.js +131 -241
- package/lib/config.js +111 -49
- package/lib/container.js +589 -244
- package/lib/data/context.js +16 -18
- package/lib/data/dataScenarioConfig.js +9 -9
- package/lib/data/dataTableArgument.js +7 -7
- package/lib/data/table.js +6 -12
- package/lib/effects.js +307 -0
- package/lib/els.js +160 -0
- package/lib/event.js +24 -19
- package/lib/globals.js +141 -0
- package/lib/heal.js +89 -81
- package/lib/helper/AI.js +3 -2
- package/lib/helper/ApiDataFactory.js +19 -19
- package/lib/helper/Appium.js +47 -51
- package/lib/helper/FileSystem.js +35 -15
- package/lib/helper/GraphQL.js +1 -1
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +72 -45
- package/lib/helper/Mochawesome.js +14 -11
- package/lib/helper/Playwright.js +832 -434
- package/lib/helper/Puppeteer.js +393 -292
- package/lib/helper/REST.js +32 -27
- package/lib/helper/WebDriver.js +320 -219
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/extras/React.js +29 -30
- package/lib/helper/network/actions.js +33 -48
- package/lib/helper/network/utils.js +76 -83
- package/lib/helper/scripts/blurElement.js +6 -6
- package/lib/helper/scripts/focusElement.js +6 -6
- package/lib/helper/scripts/highlightElement.js +9 -9
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -1
- package/lib/history.js +23 -20
- package/lib/hooks.js +10 -10
- package/lib/html.js +90 -100
- package/lib/index.js +48 -21
- package/lib/listener/config.js +8 -9
- package/lib/listener/emptyRun.js +54 -0
- package/lib/listener/exit.js +10 -12
- package/lib/listener/{retry.js → globalRetry.js} +10 -10
- package/lib/listener/globalTimeout.js +166 -0
- package/lib/listener/helpers.js +43 -24
- package/lib/listener/mocha.js +4 -5
- package/lib/listener/result.js +11 -0
- package/lib/listener/steps.js +26 -23
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +264 -0
- package/lib/mocha/bdd.js +167 -0
- package/lib/mocha/cli.js +341 -0
- package/lib/mocha/factory.js +160 -0
- package/lib/{interfaces → mocha}/featureConfig.js +33 -13
- package/lib/{interfaces → mocha}/gherkin.js +75 -45
- package/lib/mocha/hooks.js +121 -0
- package/lib/mocha/index.js +21 -0
- package/lib/mocha/inject.js +46 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +32 -8
- package/lib/mocha/suite.js +89 -0
- package/lib/mocha/test.js +178 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +229 -0
- package/lib/output.js +86 -64
- package/lib/parser.js +44 -44
- package/lib/pause.js +160 -139
- package/lib/plugin/analyze.js +403 -0
- package/lib/plugin/{autoLogin.js → auth.js} +137 -43
- package/lib/plugin/autoDelay.js +19 -15
- package/lib/plugin/coverage.js +22 -27
- package/lib/plugin/customLocator.js +5 -5
- package/lib/plugin/customReporter.js +53 -0
- package/lib/plugin/heal.js +49 -17
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +60 -19
- package/lib/plugin/screenshotOnFail.js +80 -83
- package/lib/plugin/stepByStepReport.js +70 -31
- package/lib/plugin/stepTimeout.js +7 -13
- package/lib/plugin/subtitles.js +10 -9
- package/lib/recorder.js +167 -126
- package/lib/rerun.js +94 -50
- package/lib/result.js +161 -0
- package/lib/secret.js +18 -17
- package/lib/session.js +95 -89
- 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 +18 -332
- package/lib/steps.js +54 -0
- package/lib/store.js +37 -5
- package/lib/template/heal.js +2 -11
- package/lib/timeout.js +60 -0
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils.js +354 -250
- package/lib/workerStorage.js +16 -16
- package/lib/workers.js +366 -282
- package/package.json +107 -95
- package/translations/de-DE.js +5 -4
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +23 -9
- package/translations/it-IT.js +5 -4
- package/translations/ja-JP.js +5 -4
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +5 -4
- package/translations/pt-BR.js +5 -4
- package/translations/ru-RU.js +5 -4
- package/translations/utils.js +18 -0
- package/translations/zh-CN.js +5 -4
- package/translations/zh-TW.js +5 -4
- package/typings/index.d.ts +177 -186
- package/typings/promiseBasedTypes.d.ts +3573 -5941
- package/typings/types.d.ts +4042 -6370
- package/lib/cli.js +0 -256
- package/lib/helper/ExpectHelper.js +0 -391
- package/lib/helper/Nightmare.js +0 -1504
- package/lib/helper/Protractor.js +0 -1863
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/helper/TestCafe.js +0 -1414
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -62
- package/lib/interfaces/bdd.js +0 -81
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -113
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/commentStep.js +0 -136
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -127
- package/lib/plugin/selenoid.js +0 -384
- package/lib/plugin/standardActingHelpers.js +0 -3
- package/lib/plugin/tryTo.js +0 -115
- package/lib/plugin/wdio.js +0 -249
- package/lib/scenario.js +0 -224
- package/lib/ui.js +0 -236
- package/lib/within.js +0 -70
package/lib/data/context.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { isGenerator } from '../utils.js'
|
|
2
|
+
import DataTable from './table.js'
|
|
3
|
+
import DataScenarioConfig from './dataScenarioConfig.js'
|
|
4
|
+
import secretModule from '../secret.js'
|
|
5
|
+
const Secret = secretModule.default || secretModule
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
export default function (context) {
|
|
7
8
|
context.Data = function (dataTable) {
|
|
8
9
|
const data = detectDataType(dataTable)
|
|
9
10
|
return {
|
|
@@ -13,8 +14,8 @@ module.exports = function (context) {
|
|
|
13
14
|
fn = opts
|
|
14
15
|
opts = {}
|
|
15
16
|
}
|
|
16
|
-
opts.data = data.map(
|
|
17
|
-
data.forEach(
|
|
17
|
+
opts.data = data.map(dataRow => dataRow.data)
|
|
18
|
+
data.forEach(dataRow => {
|
|
18
19
|
const dataTitle = replaceTitle(title, dataRow)
|
|
19
20
|
if (dataRow.skip) {
|
|
20
21
|
context.xScenario(dataTitle)
|
|
@@ -32,8 +33,8 @@ module.exports = function (context) {
|
|
|
32
33
|
fn = opts
|
|
33
34
|
opts = {}
|
|
34
35
|
}
|
|
35
|
-
opts.data = data.map(
|
|
36
|
-
data.forEach(
|
|
36
|
+
opts.data = data.map(dataRow => dataRow.data)
|
|
37
|
+
data.forEach(dataRow => {
|
|
37
38
|
const dataTitle = replaceTitle(title, dataRow)
|
|
38
39
|
if (dataRow.skip) {
|
|
39
40
|
context.xScenario(dataTitle)
|
|
@@ -51,8 +52,8 @@ module.exports = function (context) {
|
|
|
51
52
|
context.xData = function (dataTable) {
|
|
52
53
|
const data = detectDataType(dataTable)
|
|
53
54
|
return {
|
|
54
|
-
Scenario:
|
|
55
|
-
data.forEach(
|
|
55
|
+
Scenario: title => {
|
|
56
|
+
data.forEach(dataRow => {
|
|
56
57
|
const dataTitle = replaceTitle(title, dataRow)
|
|
57
58
|
context.xScenario(dataTitle)
|
|
58
59
|
})
|
|
@@ -69,10 +70,7 @@ function replaceTitle(title, dataRow) {
|
|
|
69
70
|
|
|
70
71
|
// if `dataRow` is object and has own `toString()` method,
|
|
71
72
|
// it should be printed
|
|
72
|
-
if (
|
|
73
|
-
Object.prototype.toString.call(dataRow.data) === Object().toString() &&
|
|
74
|
-
dataRow.data.toString() !== Object().toString()
|
|
75
|
-
) {
|
|
73
|
+
if (Object.prototype.toString.call(dataRow.data) === Object().toString() && dataRow.data.toString() !== Object().toString()) {
|
|
76
74
|
return `${title} | ${dataRow.data}`
|
|
77
75
|
}
|
|
78
76
|
|
|
@@ -102,7 +100,7 @@ function detectDataType(dataTable) {
|
|
|
102
100
|
return dataTable()
|
|
103
101
|
}
|
|
104
102
|
if (Array.isArray(dataTable)) {
|
|
105
|
-
return dataTable.map(
|
|
103
|
+
return dataTable.map(item => {
|
|
106
104
|
if (isTableDataRow(item)) {
|
|
107
105
|
return item
|
|
108
106
|
}
|
|
@@ -117,10 +115,10 @@ function detectDataType(dataTable) {
|
|
|
117
115
|
}
|
|
118
116
|
|
|
119
117
|
function maskSecretInTitle(scenarios) {
|
|
120
|
-
scenarios.forEach(
|
|
118
|
+
scenarios.forEach(scenario => {
|
|
121
119
|
const res = []
|
|
122
120
|
|
|
123
|
-
scenario.test.title.split(',').forEach(
|
|
121
|
+
scenario.test.title.split(',').forEach(item => {
|
|
124
122
|
res.push(item.replace(/{"_secret":"(.*)"}/, '"*****"'))
|
|
125
123
|
})
|
|
126
124
|
|
|
@@ -10,7 +10,7 @@ class DataScenarioConfig {
|
|
|
10
10
|
* @param {*} err
|
|
11
11
|
*/
|
|
12
12
|
throws(err) {
|
|
13
|
-
this.scenarios.forEach(
|
|
13
|
+
this.scenarios.forEach(scenario => scenario.throws(err))
|
|
14
14
|
return this
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@ class DataScenarioConfig {
|
|
|
21
21
|
*
|
|
22
22
|
*/
|
|
23
23
|
fails() {
|
|
24
|
-
this.scenarios.forEach(
|
|
24
|
+
this.scenarios.forEach(scenario => scenario.fails())
|
|
25
25
|
return this
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -31,7 +31,7 @@ class DataScenarioConfig {
|
|
|
31
31
|
* @param {*} retries
|
|
32
32
|
*/
|
|
33
33
|
retry(retries) {
|
|
34
|
-
this.scenarios.forEach(
|
|
34
|
+
this.scenarios.forEach(scenario => scenario.retry(retries))
|
|
35
35
|
return this
|
|
36
36
|
}
|
|
37
37
|
|
|
@@ -40,7 +40,7 @@ class DataScenarioConfig {
|
|
|
40
40
|
* @param {*} timeout
|
|
41
41
|
*/
|
|
42
42
|
timeout(timeout) {
|
|
43
|
-
this.scenarios.forEach(
|
|
43
|
+
this.scenarios.forEach(scenario => scenario.timeout(timeout))
|
|
44
44
|
return this
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -49,7 +49,7 @@ class DataScenarioConfig {
|
|
|
49
49
|
* Helper name can be omitted and values will be applied to first helper.
|
|
50
50
|
*/
|
|
51
51
|
config(helper, obj) {
|
|
52
|
-
this.scenarios.forEach(
|
|
52
|
+
this.scenarios.forEach(scenario => scenario.config(helper, obj))
|
|
53
53
|
return this
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -58,7 +58,7 @@ class DataScenarioConfig {
|
|
|
58
58
|
* @param {*} tagName
|
|
59
59
|
*/
|
|
60
60
|
tag(tagName) {
|
|
61
|
-
this.scenarios.forEach(
|
|
61
|
+
this.scenarios.forEach(scenario => scenario.tag(tagName))
|
|
62
62
|
return this
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -67,7 +67,7 @@ class DataScenarioConfig {
|
|
|
67
67
|
* @param {*} obj
|
|
68
68
|
*/
|
|
69
69
|
inject(obj) {
|
|
70
|
-
this.scenarios.forEach(
|
|
70
|
+
this.scenarios.forEach(scenario => scenario.inject(obj))
|
|
71
71
|
return this
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -76,9 +76,9 @@ class DataScenarioConfig {
|
|
|
76
76
|
* @param {*} dependencies
|
|
77
77
|
*/
|
|
78
78
|
injectDependencies(dependencies) {
|
|
79
|
-
this.scenarios.forEach(
|
|
79
|
+
this.scenarios.forEach(scenario => scenario.injectDependencies(dependencies))
|
|
80
80
|
return this
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
84
|
+
export default DataScenarioConfig
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
class DataTableArgument {
|
|
6
6
|
/** @param {*} gherkinDataTable */
|
|
7
7
|
constructor(gherkinDataTable) {
|
|
8
|
-
this.rawData = gherkinDataTable.rows.map(
|
|
9
|
-
return row.cells.map(
|
|
8
|
+
this.rawData = gherkinDataTable.rows.map(row => {
|
|
9
|
+
return row.cells.map(cell => {
|
|
10
10
|
return cell.value
|
|
11
11
|
})
|
|
12
12
|
})
|
|
@@ -34,7 +34,7 @@ class DataTableArgument {
|
|
|
34
34
|
hashes() {
|
|
35
35
|
const copy = this.raw()
|
|
36
36
|
const header = copy.shift()
|
|
37
|
-
return copy.map(
|
|
37
|
+
return copy.map(row => {
|
|
38
38
|
const r = {}
|
|
39
39
|
row.forEach((cell, index) => (r[header[index]] = cell))
|
|
40
40
|
return r
|
|
@@ -47,20 +47,20 @@ class DataTableArgument {
|
|
|
47
47
|
*/
|
|
48
48
|
rowsHash() {
|
|
49
49
|
const rows = this.raw()
|
|
50
|
-
const everyRowHasTwoColumns = rows.every(
|
|
50
|
+
const everyRowHasTwoColumns = rows.every(row => row.length === 2)
|
|
51
51
|
if (!everyRowHasTwoColumns) {
|
|
52
52
|
throw new Error('rowsHash can only be called on a data table where all rows have exactly two columns')
|
|
53
53
|
}
|
|
54
54
|
/** @type {Record<string, string>} */
|
|
55
55
|
const result = {}
|
|
56
|
-
rows.forEach(
|
|
56
|
+
rows.forEach(x => (result[x[0]] = x[1]))
|
|
57
57
|
return result
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/** Transposed the data */
|
|
61
61
|
transpose() {
|
|
62
|
-
this.rawData = this.rawData[0].map((x, i) => this.rawData.map(
|
|
62
|
+
this.rawData = this.rawData[0].map((x, i) => this.rawData.map(y => y[i]))
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
export default DataTableArgument
|
package/lib/data/table.js
CHANGED
|
@@ -10,13 +10,10 @@ class DataTable {
|
|
|
10
10
|
|
|
11
11
|
/** @param {Array<*>} array */
|
|
12
12
|
add(array) {
|
|
13
|
-
if (array.length !== this.array.length)
|
|
14
|
-
throw new Error(
|
|
15
|
-
`There is too many elements in given data array. Please provide data in this format: ${this.array}`,
|
|
16
|
-
)
|
|
13
|
+
if (array.length !== this.array.length) throw new Error(`There is too many elements in given data array. Please provide data in this format: ${this.array}`)
|
|
17
14
|
const tempObj = {}
|
|
18
15
|
let arrayCounter = 0
|
|
19
|
-
this.array.forEach(
|
|
16
|
+
this.array.forEach(elem => {
|
|
20
17
|
tempObj[elem] = array[arrayCounter]
|
|
21
18
|
tempObj.toString = () => JSON.stringify(tempObj)
|
|
22
19
|
arrayCounter++
|
|
@@ -26,13 +23,10 @@ class DataTable {
|
|
|
26
23
|
|
|
27
24
|
/** @param {Array<*>} array */
|
|
28
25
|
xadd(array) {
|
|
29
|
-
if (array.length !== this.array.length)
|
|
30
|
-
throw new Error(
|
|
31
|
-
`There is too many elements in given data array. Please provide data in this format: ${this.array}`,
|
|
32
|
-
)
|
|
26
|
+
if (array.length !== this.array.length) throw new Error(`There is too many elements in given data array. Please provide data in this format: ${this.array}`)
|
|
33
27
|
const tempObj = {}
|
|
34
28
|
let arrayCounter = 0
|
|
35
|
-
this.array.forEach(
|
|
29
|
+
this.array.forEach(elem => {
|
|
36
30
|
tempObj[elem] = array[arrayCounter]
|
|
37
31
|
tempObj.toString = () => JSON.stringify(tempObj)
|
|
38
32
|
arrayCounter++
|
|
@@ -42,8 +36,8 @@ class DataTable {
|
|
|
42
36
|
|
|
43
37
|
/** @param {Function} func */
|
|
44
38
|
filter(func) {
|
|
45
|
-
return this.rows.filter(
|
|
39
|
+
return this.rows.filter(row => func(row.data))
|
|
46
40
|
}
|
|
47
41
|
}
|
|
48
42
|
|
|
49
|
-
|
|
43
|
+
export default DataTable
|
package/lib/effects.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import recorder from './recorder.js'
|
|
2
|
+
import output from './output.js'
|
|
3
|
+
import store from './store.js'
|
|
4
|
+
import event from './event.js'
|
|
5
|
+
import container from './container.js'
|
|
6
|
+
import MetaStep from './step/meta.js'
|
|
7
|
+
import { isAsyncFunction } from './utils.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {CodeceptJS.LocatorOrString} context
|
|
11
|
+
* @param {Function} fn
|
|
12
|
+
* @return {Promise<*> | undefined}
|
|
13
|
+
*/
|
|
14
|
+
function within(context, fn) {
|
|
15
|
+
const helpers = store.dryRun ? {} : container.helpers()
|
|
16
|
+
const locator = typeof context === 'object' ? JSON.stringify(context) : context
|
|
17
|
+
|
|
18
|
+
return recorder.add(
|
|
19
|
+
'register within wrapper',
|
|
20
|
+
() => {
|
|
21
|
+
const metaStep = new WithinStep(locator, fn)
|
|
22
|
+
const defineMetaStep = step => (step.metaStep = metaStep)
|
|
23
|
+
recorder.session.start('within')
|
|
24
|
+
|
|
25
|
+
event.dispatcher.prependListener(event.step.before, defineMetaStep)
|
|
26
|
+
|
|
27
|
+
Object.keys(helpers).forEach(helper => {
|
|
28
|
+
if (helpers[helper]._withinBegin) recorder.add(`[${helper}] start within`, () => helpers[helper]._withinBegin(context))
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
const finalize = () => {
|
|
32
|
+
event.dispatcher.removeListener(event.step.before, defineMetaStep)
|
|
33
|
+
recorder.add('Finalize session within session', () => {
|
|
34
|
+
output.stepShift = 1
|
|
35
|
+
recorder.session.restore('within')
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
const finishHelpers = () => {
|
|
39
|
+
Object.keys(helpers).forEach(helper => {
|
|
40
|
+
if (helpers[helper]._withinEnd) recorder.add(`[${helper}] finish within`, () => helpers[helper]._withinEnd())
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (isAsyncFunction(fn)) {
|
|
45
|
+
return fn()
|
|
46
|
+
.then(res => {
|
|
47
|
+
finishHelpers()
|
|
48
|
+
finalize()
|
|
49
|
+
return recorder.promise().then(() => res)
|
|
50
|
+
})
|
|
51
|
+
.catch(e => {
|
|
52
|
+
finalize()
|
|
53
|
+
recorder.throw(e)
|
|
54
|
+
})
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let res
|
|
58
|
+
try {
|
|
59
|
+
res = fn()
|
|
60
|
+
} catch (err) {
|
|
61
|
+
recorder.throw(err)
|
|
62
|
+
} finally {
|
|
63
|
+
finishHelpers()
|
|
64
|
+
recorder.catch(err => {
|
|
65
|
+
output.stepShift = 1
|
|
66
|
+
throw err
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
finalize()
|
|
70
|
+
return recorder.promise().then(() => res)
|
|
71
|
+
},
|
|
72
|
+
false,
|
|
73
|
+
false,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class WithinStep extends MetaStep {
|
|
78
|
+
constructor(locator, fn) {
|
|
79
|
+
super('Within')
|
|
80
|
+
this.args = [locator]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
toString() {
|
|
84
|
+
return `${this.prefix}Within ${this.humanizeArgs()}${this.suffix}`
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A utility function for CodeceptJS tests that acts as a soft assertion.
|
|
90
|
+
* Executes a callback within a recorded session, ensuring errors are handled gracefully without failing the test immediately.
|
|
91
|
+
*
|
|
92
|
+
* @async
|
|
93
|
+
* @function hopeThat
|
|
94
|
+
* @param {Function} callback - The callback function containing the logic to validate.
|
|
95
|
+
* This function should perform the desired assertion or condition check.
|
|
96
|
+
* @returns {Promise<boolean|any>} A promise resolving to `true` if the assertion or condition was successful,
|
|
97
|
+
* or `false` if an error occurred.
|
|
98
|
+
*
|
|
99
|
+
* @description
|
|
100
|
+
* - Designed for use in CodeceptJS tests as a "soft assertion."
|
|
101
|
+
* Unlike standard assertions, it does not stop the test execution on failure.
|
|
102
|
+
* - Starts a new recorder session named 'hopeThat' and manages state restoration.
|
|
103
|
+
* - Logs errors and attaches them as notes to the test, enabling post-test reporting of soft assertion failures.
|
|
104
|
+
* - Resets the `store.hopeThat` flag after the execution, ensuring clean state for subsequent operations.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* const { hopeThat } = require('codeceptjs/effects')
|
|
108
|
+
* await hopeThat(() => {
|
|
109
|
+
* I.see('Welcome'); // Perform a soft assertion
|
|
110
|
+
* });
|
|
111
|
+
*
|
|
112
|
+
* @throws Will handle errors that occur during the callback execution. Errors are logged and attached as notes to the test.
|
|
113
|
+
*/
|
|
114
|
+
async function hopeThat(callback) {
|
|
115
|
+
if (store.dryRun) return
|
|
116
|
+
const sessionName = 'hopeThat'
|
|
117
|
+
|
|
118
|
+
let result = false
|
|
119
|
+
return recorder.add(
|
|
120
|
+
'hopeThat',
|
|
121
|
+
() => {
|
|
122
|
+
recorder.session.start(sessionName)
|
|
123
|
+
store.hopeThat = true
|
|
124
|
+
callback()
|
|
125
|
+
recorder.add(() => {
|
|
126
|
+
result = true
|
|
127
|
+
recorder.session.restore(sessionName)
|
|
128
|
+
return result
|
|
129
|
+
})
|
|
130
|
+
recorder.session.catch(err => {
|
|
131
|
+
result = false
|
|
132
|
+
const msg = err.inspect ? err.inspect() : err.toString()
|
|
133
|
+
output.debug(`Unsuccessful assertion > ${msg}`)
|
|
134
|
+
event.dispatcher.once(event.test.finished, test => {
|
|
135
|
+
if (!test.notes) test.notes = []
|
|
136
|
+
test.notes.push({ type: 'conditionalError', text: msg })
|
|
137
|
+
})
|
|
138
|
+
recorder.session.restore(sessionName)
|
|
139
|
+
return result
|
|
140
|
+
})
|
|
141
|
+
return recorder.add(
|
|
142
|
+
'result',
|
|
143
|
+
() => {
|
|
144
|
+
store.hopeThat = undefined
|
|
145
|
+
return result
|
|
146
|
+
},
|
|
147
|
+
true,
|
|
148
|
+
false,
|
|
149
|
+
)
|
|
150
|
+
},
|
|
151
|
+
false,
|
|
152
|
+
false,
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* A CodeceptJS utility function to retry a step or callback multiple times with a specified polling interval.
|
|
158
|
+
*
|
|
159
|
+
* @async
|
|
160
|
+
* @function retryTo
|
|
161
|
+
* @param {Function} callback - The function to execute, which will be retried upon failure.
|
|
162
|
+
* Receives the current retry count as an argument.
|
|
163
|
+
* @param {number} maxTries - The maximum number of attempts to retry the callback.
|
|
164
|
+
* @param {number} [pollInterval=200] - The delay (in milliseconds) between retry attempts.
|
|
165
|
+
* @returns {Promise<void|any>} A promise that resolves when the callback executes successfully, or rejects after reaching the maximum retries.
|
|
166
|
+
*
|
|
167
|
+
* @description
|
|
168
|
+
* - This function is designed for use in CodeceptJS tests to handle intermittent or flaky test steps.
|
|
169
|
+
* - Starts a new recorder session for each retry attempt, ensuring proper state management and error handling.
|
|
170
|
+
* - Logs errors and retries the callback until it either succeeds or the maximum number of attempts is reached.
|
|
171
|
+
* - Restores the session state after each attempt, whether successful or not.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* const { hopeThat } = require('codeceptjs/effects')
|
|
175
|
+
* await retryTo((tries) => {
|
|
176
|
+
* if (tries < 3) {
|
|
177
|
+
* I.see('Non-existent element'); // Simulates a failure
|
|
178
|
+
* } else {
|
|
179
|
+
* I.see('Welcome'); // Succeeds on the 3rd attempt
|
|
180
|
+
* }
|
|
181
|
+
* }, 5, 300); // Retry up to 5 times, with a 300ms interval
|
|
182
|
+
*
|
|
183
|
+
* @throws Will reject with the last error encountered if the maximum retries are exceeded.
|
|
184
|
+
*/
|
|
185
|
+
async function retryTo(callback, maxTries, pollInterval = 200) {
|
|
186
|
+
const sessionName = 'retryTo'
|
|
187
|
+
|
|
188
|
+
return new Promise((done, reject) => {
|
|
189
|
+
let tries = 1
|
|
190
|
+
|
|
191
|
+
function handleRetryException(err) {
|
|
192
|
+
recorder.throw(err)
|
|
193
|
+
reject(err)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const tryBlock = async () => {
|
|
197
|
+
tries++
|
|
198
|
+
recorder.session.start(`${sessionName} ${tries}`)
|
|
199
|
+
try {
|
|
200
|
+
await callback(tries)
|
|
201
|
+
} catch (err) {
|
|
202
|
+
handleRetryException(err)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Call done if no errors
|
|
206
|
+
recorder.add(() => {
|
|
207
|
+
recorder.session.restore(`${sessionName} ${tries}`)
|
|
208
|
+
done(null)
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// Catch errors and retry
|
|
212
|
+
recorder.session.catch(err => {
|
|
213
|
+
recorder.session.restore(`${sessionName} ${tries}`)
|
|
214
|
+
if (tries <= maxTries) {
|
|
215
|
+
output.debug(`Error ${err}... Retrying`)
|
|
216
|
+
recorder.add(`${sessionName} ${tries}`, () => setTimeout(tryBlock, pollInterval))
|
|
217
|
+
} else {
|
|
218
|
+
// if maxTries reached
|
|
219
|
+
handleRetryException(err)
|
|
220
|
+
}
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
recorder.add(sessionName, tryBlock).catch(err => {
|
|
225
|
+
console.error('An error occurred:', err)
|
|
226
|
+
done(null)
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* A CodeceptJS utility function to attempt a step or callback without failing the test.
|
|
233
|
+
* If the step fails, the test continues execution without interruption, and the result is logged.
|
|
234
|
+
*
|
|
235
|
+
* @async
|
|
236
|
+
* @function tryTo
|
|
237
|
+
* @param {Function} callback - The function to execute, which may succeed or fail.
|
|
238
|
+
* This function contains the logic to be attempted.
|
|
239
|
+
* @returns {Promise<boolean|any>} A promise resolving to `true` if the step succeeds, or `false` if it fails.
|
|
240
|
+
*
|
|
241
|
+
* @description
|
|
242
|
+
* - Useful for scenarios where certain steps are optional or their failure should not interrupt the test flow.
|
|
243
|
+
* - Starts a new recorder session named 'tryTo' for isolation and error handling.
|
|
244
|
+
* - Captures errors during execution and logs them for debugging purposes.
|
|
245
|
+
* - Ensures the `store.tryTo` flag is reset after execution to maintain a clean state.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* const { tryTo } = require('codeceptjs/effects')
|
|
249
|
+
* const wasSuccessful = await tryTo(() => {
|
|
250
|
+
* I.see('Welcome'); // Attempt to find an element on the page
|
|
251
|
+
* });
|
|
252
|
+
*
|
|
253
|
+
* if (!wasSuccessful) {
|
|
254
|
+
* I.say('Optional step failed, but test continues.');
|
|
255
|
+
* }
|
|
256
|
+
*
|
|
257
|
+
* @throws Will handle errors internally, logging them and returning `false` as the result.
|
|
258
|
+
*/
|
|
259
|
+
async function tryTo(callback) {
|
|
260
|
+
if (store.dryRun) return
|
|
261
|
+
const sessionName = 'tryTo'
|
|
262
|
+
|
|
263
|
+
let result = false
|
|
264
|
+
let isAutoRetriesEnabled = store.autoRetries
|
|
265
|
+
return recorder.add(
|
|
266
|
+
sessionName,
|
|
267
|
+
() => {
|
|
268
|
+
recorder.session.start(sessionName)
|
|
269
|
+
isAutoRetriesEnabled = store.autoRetries
|
|
270
|
+
if (isAutoRetriesEnabled) output.debug('Auto retries disabled inside tryTo effect')
|
|
271
|
+
store.autoRetries = false
|
|
272
|
+
callback()
|
|
273
|
+
recorder.add(() => {
|
|
274
|
+
result = true
|
|
275
|
+
recorder.session.restore(sessionName)
|
|
276
|
+
return result
|
|
277
|
+
})
|
|
278
|
+
recorder.session.catch(err => {
|
|
279
|
+
result = false
|
|
280
|
+
const msg = err.inspect ? err.inspect() : err.toString()
|
|
281
|
+
output.debug(`Unsuccessful try > ${msg}`)
|
|
282
|
+
recorder.session.restore(sessionName)
|
|
283
|
+
return result
|
|
284
|
+
})
|
|
285
|
+
return recorder.add(
|
|
286
|
+
'result',
|
|
287
|
+
() => {
|
|
288
|
+
store.autoRetries = isAutoRetriesEnabled
|
|
289
|
+
return result
|
|
290
|
+
},
|
|
291
|
+
true,
|
|
292
|
+
false,
|
|
293
|
+
)
|
|
294
|
+
},
|
|
295
|
+
false,
|
|
296
|
+
false,
|
|
297
|
+
)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export { hopeThat, retryTo, tryTo, within }
|
|
301
|
+
|
|
302
|
+
export default {
|
|
303
|
+
hopeThat,
|
|
304
|
+
retryTo,
|
|
305
|
+
tryTo,
|
|
306
|
+
within,
|
|
307
|
+
}
|