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/recorder.js
CHANGED
|
@@ -1,35 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
let
|
|
11
|
-
let
|
|
12
|
-
let
|
|
13
|
-
let
|
|
14
|
-
let
|
|
15
|
-
let
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
let
|
|
1
|
+
import debugModule from 'debug'
|
|
2
|
+
const debug = debugModule('codeceptjs:recorder')
|
|
3
|
+
import promiseRetry from 'promise-retry'
|
|
4
|
+
import chalk from 'chalk'
|
|
5
|
+
import { printObjectProperties } from './utils.js'
|
|
6
|
+
import output from './output.js'
|
|
7
|
+
import { TimeoutError } from './timeout.js'
|
|
8
|
+
const MAX_TASKS = 100
|
|
9
|
+
|
|
10
|
+
let promise
|
|
11
|
+
let running = false
|
|
12
|
+
let errFn
|
|
13
|
+
let queueId = 0
|
|
14
|
+
let sessionId = null
|
|
15
|
+
let asyncErr = null
|
|
16
|
+
let ignoredErrs = []
|
|
17
|
+
|
|
18
|
+
let tasks = []
|
|
19
|
+
let oldPromises = []
|
|
19
20
|
|
|
20
21
|
const defaultRetryOptions = {
|
|
21
22
|
retries: 0,
|
|
22
23
|
minTimeout: 150,
|
|
23
24
|
maxTimeout: 10000,
|
|
24
|
-
}
|
|
25
|
+
}
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Singleton object to record all test steps as promises and run them in chain.
|
|
28
29
|
* @alias recorder
|
|
29
30
|
* @interface
|
|
30
31
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
export default {
|
|
33
33
|
/**
|
|
34
34
|
* @type {Array<Object<string, *>>}
|
|
35
35
|
* @inner
|
|
@@ -43,11 +43,11 @@ module.exports = {
|
|
|
43
43
|
* @inner
|
|
44
44
|
*/
|
|
45
45
|
start() {
|
|
46
|
-
debug('Starting recording promises')
|
|
47
|
-
running = true
|
|
48
|
-
asyncErr = null
|
|
49
|
-
errFn = null
|
|
50
|
-
this.reset()
|
|
46
|
+
debug('Starting recording promises')
|
|
47
|
+
running = true
|
|
48
|
+
asyncErr = null
|
|
49
|
+
errFn = null
|
|
50
|
+
this.reset()
|
|
51
51
|
},
|
|
52
52
|
|
|
53
53
|
/**
|
|
@@ -55,7 +55,7 @@ module.exports = {
|
|
|
55
55
|
* @inner
|
|
56
56
|
*/
|
|
57
57
|
isRunning() {
|
|
58
|
-
return running
|
|
58
|
+
return running
|
|
59
59
|
},
|
|
60
60
|
|
|
61
61
|
/**
|
|
@@ -64,7 +64,7 @@ module.exports = {
|
|
|
64
64
|
*/
|
|
65
65
|
startUnlessRunning() {
|
|
66
66
|
if (!this.isRunning()) {
|
|
67
|
-
this.start()
|
|
67
|
+
this.start()
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
|
|
@@ -76,7 +76,7 @@ module.exports = {
|
|
|
76
76
|
* @inner
|
|
77
77
|
*/
|
|
78
78
|
errHandler(fn) {
|
|
79
|
-
errFn = fn
|
|
79
|
+
errFn = fn
|
|
80
80
|
},
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -87,16 +87,16 @@ module.exports = {
|
|
|
87
87
|
* @inner
|
|
88
88
|
*/
|
|
89
89
|
reset() {
|
|
90
|
-
if (promise && running) this.catch()
|
|
91
|
-
queueId
|
|
92
|
-
sessionId = null
|
|
93
|
-
asyncErr = null
|
|
94
|
-
log(`${currentQueue()} Starting recording promises`)
|
|
95
|
-
promise = Promise.resolve()
|
|
96
|
-
oldPromises = []
|
|
97
|
-
tasks = []
|
|
98
|
-
ignoredErrs = []
|
|
99
|
-
this.session.running = false
|
|
90
|
+
if (promise && running) this.catch()
|
|
91
|
+
queueId++
|
|
92
|
+
sessionId = null
|
|
93
|
+
asyncErr = null
|
|
94
|
+
output.log(`${currentQueue()} Starting recording promises`)
|
|
95
|
+
promise = Promise.resolve()
|
|
96
|
+
oldPromises = []
|
|
97
|
+
tasks = []
|
|
98
|
+
ignoredErrs = []
|
|
99
|
+
this.session.running = false
|
|
100
100
|
// reset this retries makes the retryFailedStep plugin won't work if there is Before/BeforeSuit block due to retries is undefined on Scenario
|
|
101
101
|
// this.retries = [];
|
|
102
102
|
},
|
|
@@ -123,12 +123,16 @@ module.exports = {
|
|
|
123
123
|
* @inner
|
|
124
124
|
*/
|
|
125
125
|
start(name) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
if (sessionId) {
|
|
127
|
+
debug(`${currentQueue()}Session already started as ${sessionId}`)
|
|
128
|
+
this.restore(sessionId)
|
|
129
|
+
}
|
|
130
|
+
debug(`${currentQueue()}Starting <${name}> session`)
|
|
131
|
+
tasks.push('--->')
|
|
132
|
+
oldPromises.push(promise)
|
|
133
|
+
this.running = true
|
|
134
|
+
sessionId = name
|
|
135
|
+
promise = Promise.resolve()
|
|
132
136
|
},
|
|
133
137
|
|
|
134
138
|
/**
|
|
@@ -136,12 +140,12 @@ module.exports = {
|
|
|
136
140
|
* @inner
|
|
137
141
|
*/
|
|
138
142
|
restore(name) {
|
|
139
|
-
tasks.push('<---')
|
|
140
|
-
debug(`${currentQueue()}Finalize <${name}> session`)
|
|
141
|
-
this.running = false
|
|
142
|
-
sessionId = null
|
|
143
|
-
this.catch(errFn)
|
|
144
|
-
promise = promise.then(() => oldPromises.pop())
|
|
143
|
+
tasks.push('<---')
|
|
144
|
+
debug(`${currentQueue()}Finalize <${name}> session`)
|
|
145
|
+
this.running = false
|
|
146
|
+
sessionId = null
|
|
147
|
+
this.catch(errFn)
|
|
148
|
+
promise = promise.then(() => oldPromises.pop())
|
|
145
149
|
},
|
|
146
150
|
|
|
147
151
|
/**
|
|
@@ -149,9 +153,8 @@ module.exports = {
|
|
|
149
153
|
* @inner
|
|
150
154
|
*/
|
|
151
155
|
catch(fn) {
|
|
152
|
-
promise = promise.catch(fn)
|
|
156
|
+
promise = promise.catch(fn)
|
|
153
157
|
},
|
|
154
|
-
|
|
155
158
|
},
|
|
156
159
|
|
|
157
160
|
/**
|
|
@@ -171,42 +174,48 @@ module.exports = {
|
|
|
171
174
|
*/
|
|
172
175
|
add(taskName, fn = undefined, force = false, retry = undefined, timeout = undefined) {
|
|
173
176
|
if (typeof taskName === 'function') {
|
|
174
|
-
fn = taskName
|
|
175
|
-
taskName = fn.toString()
|
|
176
|
-
if (retry === undefined) retry = false
|
|
177
|
+
fn = taskName
|
|
178
|
+
taskName = fn.toString()
|
|
179
|
+
if (retry === undefined) retry = false
|
|
177
180
|
}
|
|
178
|
-
if (retry === undefined) retry = true
|
|
181
|
+
if (retry === undefined) retry = true
|
|
179
182
|
if (!running && !force) {
|
|
180
|
-
return
|
|
183
|
+
return Promise.resolve()
|
|
181
184
|
}
|
|
182
|
-
tasks.push(taskName)
|
|
183
|
-
debug(chalk.gray(`${currentQueue()} Queued | ${taskName}`))
|
|
185
|
+
tasks.push(taskName)
|
|
186
|
+
debug(chalk.gray(`${currentQueue()} Queued | ${taskName}`))
|
|
184
187
|
|
|
185
|
-
return promise = Promise.resolve(promise).then(
|
|
188
|
+
return (promise = Promise.resolve(promise).then(res => {
|
|
186
189
|
// prefer options for non-conditional retries
|
|
187
|
-
const retryOpts = this.retries
|
|
190
|
+
const retryOpts = this.retries
|
|
191
|
+
.sort((r1, r2) => r1.when && !r2.when)
|
|
192
|
+
.slice(-1)
|
|
193
|
+
.pop()
|
|
188
194
|
// no retries or unnamed tasks
|
|
195
|
+
debug(`${currentQueue()} Running | ${taskName} | Timeout: ${timeout || 'None'}`)
|
|
196
|
+
if (retryOpts) debug(`${currentQueue()} Retry opts`, JSON.stringify(retryOpts))
|
|
197
|
+
|
|
189
198
|
if (!retryOpts || !taskName || !retry) {
|
|
190
|
-
const [promise, timer] = getTimeoutPromise(timeout, taskName)
|
|
191
|
-
return Promise.race([promise, Promise.resolve(res).then(fn)]).finally(() => clearTimeout(timer))
|
|
199
|
+
const [promise, timer] = getTimeoutPromise(timeout, taskName)
|
|
200
|
+
return Promise.race([promise, Promise.resolve(res).then(fn)]).finally(() => clearTimeout(timer))
|
|
192
201
|
}
|
|
193
202
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const retryRules = this.retries.slice().reverse();
|
|
203
|
+
const retryRules = this.retries.slice().reverse()
|
|
197
204
|
return promiseRetry(Object.assign(defaultRetryOptions, retryOpts), (retry, number) => {
|
|
198
|
-
if (number > 1) log(`${currentQueue()}Retrying... Attempt #${number}`)
|
|
199
|
-
const [promise, timer] = getTimeoutPromise(timeout, taskName)
|
|
200
|
-
return Promise.race([promise, Promise.resolve(res).then(fn)])
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
205
|
+
if (number > 1) output.log(`${currentQueue()}Retrying... Attempt #${number}`)
|
|
206
|
+
const [promise, timer] = getTimeoutPromise(timeout, taskName)
|
|
207
|
+
return Promise.race([promise, Promise.resolve(res).then(fn)])
|
|
208
|
+
.finally(() => clearTimeout(timer))
|
|
209
|
+
.catch(err => {
|
|
210
|
+
if (ignoredErrs.includes(err)) return
|
|
211
|
+
for (const retryObj of retryRules) {
|
|
212
|
+
if (!retryObj.when) return retry(err)
|
|
213
|
+
if (retryObj.when && retryObj.when(err)) return retry(err)
|
|
214
|
+
}
|
|
215
|
+
throw err
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
}))
|
|
210
219
|
},
|
|
211
220
|
|
|
212
221
|
/**
|
|
@@ -215,15 +224,17 @@ module.exports = {
|
|
|
215
224
|
* @inner
|
|
216
225
|
*/
|
|
217
226
|
retry(opts) {
|
|
218
|
-
if (!promise) return
|
|
227
|
+
if (!promise) return
|
|
219
228
|
|
|
220
229
|
if (opts === null) {
|
|
221
|
-
opts = {}
|
|
230
|
+
opts = {}
|
|
222
231
|
}
|
|
223
232
|
if (Number.isInteger(opts)) {
|
|
224
|
-
opts = { retries: opts }
|
|
233
|
+
opts = { retries: opts }
|
|
225
234
|
}
|
|
226
|
-
|
|
235
|
+
// Push retry options immediately so first step can see them
|
|
236
|
+
this.retries.push(opts)
|
|
237
|
+
return Promise.resolve()
|
|
227
238
|
},
|
|
228
239
|
|
|
229
240
|
/**
|
|
@@ -232,20 +243,26 @@ module.exports = {
|
|
|
232
243
|
* @inner
|
|
233
244
|
*/
|
|
234
245
|
catch(customErrFn) {
|
|
235
|
-
const fnDescription = customErrFn
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
246
|
+
const fnDescription = customErrFn
|
|
247
|
+
?.toString()
|
|
248
|
+
?.replace(/\s{2,}/g, ' ')
|
|
249
|
+
.replace(/\n/g, ' ')
|
|
250
|
+
?.slice(0, 50)
|
|
251
|
+
debug(chalk.gray(`${currentQueue()} Queued | catch with error handler ${fnDescription || ''}`))
|
|
252
|
+
if (!promise) promise = Promise.resolve()
|
|
253
|
+
return (promise = promise.catch(err => {
|
|
254
|
+
output.log(`${currentQueue()}Error | ${err} ${fnDescription}...`)
|
|
255
|
+
if (!(err instanceof Error)) {
|
|
256
|
+
// strange things may happen
|
|
257
|
+
err = new Error(`[Wrapped Error] ${printObjectProperties(err)}`) // we should be prepared for them
|
|
241
258
|
}
|
|
242
259
|
if (customErrFn) {
|
|
243
|
-
customErrFn(err)
|
|
260
|
+
customErrFn(err)
|
|
244
261
|
} else if (errFn) {
|
|
245
|
-
errFn(err)
|
|
262
|
+
errFn(err)
|
|
246
263
|
}
|
|
247
|
-
this.stop()
|
|
248
|
-
})
|
|
264
|
+
this.stop()
|
|
265
|
+
}))
|
|
249
266
|
},
|
|
250
267
|
|
|
251
268
|
/**
|
|
@@ -254,17 +271,37 @@ module.exports = {
|
|
|
254
271
|
* @inner
|
|
255
272
|
*/
|
|
256
273
|
catchWithoutStop(customErrFn) {
|
|
257
|
-
const fnDescription = customErrFn
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
274
|
+
const fnDescription = customErrFn
|
|
275
|
+
?.toString()
|
|
276
|
+
?.replace(/\s{2,}/g, ' ')
|
|
277
|
+
.replace(/\n/g, ' ')
|
|
278
|
+
?.slice(0, 50)
|
|
279
|
+
if (!promise) promise = Promise.resolve()
|
|
280
|
+
return (promise = promise.catch(err => {
|
|
281
|
+
if (ignoredErrs.includes(err)) return // already caught
|
|
282
|
+
|
|
283
|
+
// Handle terminal errors - don't continue, re-throw immediately
|
|
284
|
+
if (err && (err.isTerminal || (err.message && (err.message.includes('ERR_ABORTED') || err.message.includes('frame was detached') || err.message.includes('Target page, context or browser has been closed'))))) {
|
|
285
|
+
output.log(`${currentQueue()} Terminal Error | ${err} | ${fnDescription || ''}...`)
|
|
286
|
+
throw err // Re-throw terminal errors immediately
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
output.log(`${currentQueue()} Error (Non-Terminated) | ${err} | ${fnDescription || ''}...`)
|
|
290
|
+
if (!(err instanceof Error)) {
|
|
291
|
+
// strange things may happen
|
|
292
|
+
err = new Error(`[Wrapped Error] ${JSON.stringify(err)}`) // we should be prepared for them
|
|
263
293
|
}
|
|
264
294
|
if (customErrFn) {
|
|
265
|
-
|
|
295
|
+
try {
|
|
296
|
+
const result = customErrFn(err)
|
|
297
|
+
// If customErrFn returns a value (not throwing), treat it as handled
|
|
298
|
+
return result
|
|
299
|
+
} catch (thrownErr) {
|
|
300
|
+
// If customErrFn throws an error, propagate it up
|
|
301
|
+
throw thrownErr
|
|
302
|
+
}
|
|
266
303
|
}
|
|
267
|
-
})
|
|
304
|
+
}))
|
|
268
305
|
},
|
|
269
306
|
|
|
270
307
|
/**
|
|
@@ -276,15 +313,15 @@ module.exports = {
|
|
|
276
313
|
*/
|
|
277
314
|
|
|
278
315
|
throw(err) {
|
|
279
|
-
if (ignoredErrs.includes(err)) return promise
|
|
316
|
+
if (ignoredErrs.includes(err)) return promise // already caught
|
|
280
317
|
return this.add(`throw error: ${err.message}`, () => {
|
|
281
|
-
if (ignoredErrs.includes(err)) return
|
|
282
|
-
throw err
|
|
283
|
-
})
|
|
318
|
+
if (ignoredErrs.includes(err)) return // already caught
|
|
319
|
+
throw err
|
|
320
|
+
})
|
|
284
321
|
},
|
|
285
322
|
|
|
286
323
|
ignoreErr(err) {
|
|
287
|
-
ignoredErrs.push(err)
|
|
324
|
+
ignoredErrs.push(err)
|
|
288
325
|
},
|
|
289
326
|
|
|
290
327
|
/**
|
|
@@ -293,7 +330,7 @@ module.exports = {
|
|
|
293
330
|
*/
|
|
294
331
|
saveFirstAsyncError(err) {
|
|
295
332
|
if (asyncErr === null) {
|
|
296
|
-
asyncErr = err
|
|
333
|
+
asyncErr = err
|
|
297
334
|
}
|
|
298
335
|
},
|
|
299
336
|
|
|
@@ -302,7 +339,7 @@ module.exports = {
|
|
|
302
339
|
* @inner
|
|
303
340
|
*/
|
|
304
341
|
getAsyncErr() {
|
|
305
|
-
return asyncErr
|
|
342
|
+
return asyncErr
|
|
306
343
|
},
|
|
307
344
|
|
|
308
345
|
/**
|
|
@@ -310,7 +347,7 @@ module.exports = {
|
|
|
310
347
|
* @inner
|
|
311
348
|
*/
|
|
312
349
|
cleanAsyncErr() {
|
|
313
|
-
asyncErr = null
|
|
350
|
+
asyncErr = null
|
|
314
351
|
},
|
|
315
352
|
|
|
316
353
|
/**
|
|
@@ -319,9 +356,9 @@ module.exports = {
|
|
|
319
356
|
* @inner
|
|
320
357
|
*/
|
|
321
358
|
stop() {
|
|
322
|
-
debug(this.toString())
|
|
323
|
-
log(`${currentQueue()} Stopping recording promises`)
|
|
324
|
-
running = false
|
|
359
|
+
debug(this.toString())
|
|
360
|
+
output.log(`${currentQueue()} Stopping recording promises`)
|
|
361
|
+
running = false
|
|
325
362
|
},
|
|
326
363
|
|
|
327
364
|
/**
|
|
@@ -332,7 +369,7 @@ module.exports = {
|
|
|
332
369
|
* @inner
|
|
333
370
|
*/
|
|
334
371
|
promise() {
|
|
335
|
-
return promise
|
|
372
|
+
return promise
|
|
336
373
|
},
|
|
337
374
|
|
|
338
375
|
/**
|
|
@@ -341,7 +378,7 @@ module.exports = {
|
|
|
341
378
|
* @inner
|
|
342
379
|
*/
|
|
343
380
|
scheduled() {
|
|
344
|
-
return tasks.slice(-MAX_TASKS).join('\n')
|
|
381
|
+
return tasks.slice(-MAX_TASKS).join('\n')
|
|
345
382
|
},
|
|
346
383
|
|
|
347
384
|
/**
|
|
@@ -350,7 +387,7 @@ module.exports = {
|
|
|
350
387
|
* @inner
|
|
351
388
|
*/
|
|
352
389
|
getQueueId() {
|
|
353
|
-
return queueId
|
|
390
|
+
return queueId
|
|
354
391
|
},
|
|
355
392
|
|
|
356
393
|
/**
|
|
@@ -359,21 +396,25 @@ module.exports = {
|
|
|
359
396
|
* @inner
|
|
360
397
|
*/
|
|
361
398
|
toString() {
|
|
362
|
-
return `Queue: ${currentQueue()}\n\nTasks: ${this.scheduled()}
|
|
399
|
+
return `Queue: ${currentQueue()}\n\nTasks: ${this.scheduled()}`
|
|
363
400
|
},
|
|
364
|
-
|
|
365
|
-
};
|
|
401
|
+
}
|
|
366
402
|
|
|
367
403
|
function getTimeoutPromise(timeoutMs, taskName) {
|
|
368
|
-
let timer
|
|
369
|
-
if (timeoutMs) debug(`Timing out in ${timeoutMs}ms`)
|
|
370
|
-
return [
|
|
371
|
-
|
|
372
|
-
|
|
404
|
+
let timer
|
|
405
|
+
if (timeoutMs) debug(`Timing out in ${timeoutMs}ms`)
|
|
406
|
+
return [
|
|
407
|
+
new Promise((done, reject) => {
|
|
408
|
+
timer = setTimeout(() => {
|
|
409
|
+
reject(new TimeoutError(`Action ${taskName} was interrupted on timeout ${timeoutMs}ms`))
|
|
410
|
+
}, timeoutMs || 2e9)
|
|
411
|
+
}),
|
|
412
|
+
timer,
|
|
413
|
+
]
|
|
373
414
|
}
|
|
374
415
|
|
|
375
416
|
function currentQueue() {
|
|
376
|
-
let session = ''
|
|
377
|
-
if (sessionId) session = `<${sessionId}>
|
|
378
|
-
return `[${queueId}] ${session}
|
|
417
|
+
let session = ''
|
|
418
|
+
if (sessionId) session = `<${sessionId}> `
|
|
419
|
+
return `[${queueId}] ${session}`
|
|
379
420
|
}
|
package/lib/rerun.js
CHANGED
|
@@ -1,81 +1,125 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import fsPath from 'path'
|
|
2
|
+
import container from './container.js'
|
|
3
|
+
import event from './event.js'
|
|
4
|
+
import BaseCodecept from './codecept.js'
|
|
5
|
+
import output from './output.js'
|
|
6
|
+
import { createRequire } from 'module'
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url)
|
|
6
9
|
|
|
7
10
|
class CodeceptRerunner extends BaseCodecept {
|
|
8
|
-
runOnce(test) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
mocha.files = mocha.files.filter(t => fsPath.basename(t, '.js') === test || t === test);
|
|
22
|
-
}
|
|
11
|
+
async runOnce(test) {
|
|
12
|
+
await container.started()
|
|
13
|
+
|
|
14
|
+
// Ensure translations are loaded for Gherkin features
|
|
15
|
+
try {
|
|
16
|
+
const { loadTranslations } = await import('./mocha/gherkin.js')
|
|
17
|
+
await loadTranslations()
|
|
18
|
+
} catch (e) {
|
|
19
|
+
// Ignore if gherkin module not available
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return new Promise(async (resolve, reject) => {
|
|
23
23
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
// Create a fresh Mocha instance for each run
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
container.createMocha()
|
|
27
|
+
const mocha = container.mocha()
|
|
28
|
+
|
|
29
|
+
let filesToRun = this.testFiles
|
|
30
|
+
if (test) {
|
|
31
|
+
if (!fsPath.isAbsolute(test)) {
|
|
32
|
+
test = fsPath.join(global.codecept_dir, test)
|
|
33
|
+
}
|
|
34
|
+
filesToRun = this.testFiles.filter(t => fsPath.basename(t, '.js') === test || t === test)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Clear any existing tests/suites
|
|
38
|
+
mocha.suite.suites = []
|
|
39
|
+
mocha.suite.tests = []
|
|
40
|
+
|
|
41
|
+
// Manually load each test file by importing it
|
|
42
|
+
for (const file of filesToRun) {
|
|
43
|
+
try {
|
|
44
|
+
// Clear CommonJS cache if available (for mixed environments)
|
|
45
|
+
try {
|
|
46
|
+
delete require.cache[file]
|
|
47
|
+
} catch (e) {
|
|
48
|
+
// ESM modules don't have require.cache, ignore
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Force reload the module by using a cache-busting query parameter
|
|
52
|
+
const fileUrl = `${fsPath.resolve(file)}?t=${Date.now()}`
|
|
53
|
+
await import(fileUrl)
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(`Error loading test file ${file}:`, e)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const done = () => {
|
|
60
|
+
event.emit(event.all.result, container.result())
|
|
61
|
+
event.emit(event.all.after, this)
|
|
62
|
+
|
|
63
|
+
// Check if there were any failures
|
|
64
|
+
if (container.result().hasFailed) {
|
|
65
|
+
reject(new Error('Test run failed'))
|
|
27
66
|
} else {
|
|
28
|
-
|
|
67
|
+
resolve(undefined)
|
|
29
68
|
}
|
|
30
|
-
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
event.emit(event.all.before, this)
|
|
72
|
+
mocha.run(() => done())
|
|
31
73
|
} catch (e) {
|
|
32
|
-
|
|
74
|
+
output.error(e.stack)
|
|
75
|
+
reject(e)
|
|
33
76
|
}
|
|
34
|
-
})
|
|
77
|
+
})
|
|
35
78
|
}
|
|
36
79
|
|
|
37
80
|
async runTests(test) {
|
|
38
|
-
const configRerun = this.config.rerun || {}
|
|
39
|
-
const minSuccess = configRerun.minSuccess || 1
|
|
40
|
-
const maxReruns = configRerun.maxReruns || 1
|
|
81
|
+
const configRerun = this.config.rerun || {}
|
|
82
|
+
const minSuccess = configRerun.minSuccess || 1
|
|
83
|
+
const maxReruns = configRerun.maxReruns || 1
|
|
41
84
|
if (minSuccess > maxReruns) {
|
|
42
|
-
process.exitCode = 1
|
|
43
|
-
throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`)
|
|
85
|
+
process.exitCode = 1
|
|
86
|
+
throw new Error(`run-rerun Configuration Error: minSuccess must be less than maxReruns. Current values: minSuccess=${minSuccess} maxReruns=${maxReruns}`)
|
|
44
87
|
}
|
|
45
88
|
if (maxReruns === 1) {
|
|
46
|
-
await this.runOnce(test)
|
|
47
|
-
return
|
|
89
|
+
await this.runOnce(test)
|
|
90
|
+
return
|
|
48
91
|
}
|
|
49
|
-
let successCounter = 0
|
|
50
|
-
let rerunsCounter = 0
|
|
92
|
+
let successCounter = 0
|
|
93
|
+
let rerunsCounter = 0
|
|
51
94
|
while (rerunsCounter < maxReruns && successCounter < minSuccess) {
|
|
52
|
-
|
|
95
|
+
container.result().reset() // reset result
|
|
96
|
+
rerunsCounter++
|
|
53
97
|
try {
|
|
54
|
-
await this.runOnce(test)
|
|
55
|
-
successCounter
|
|
56
|
-
output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`)
|
|
98
|
+
await this.runOnce(test)
|
|
99
|
+
successCounter++
|
|
100
|
+
output.success(`\nProcess run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess}\n`)
|
|
57
101
|
} catch (e) {
|
|
58
|
-
output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`)
|
|
59
|
-
console.error(e)
|
|
102
|
+
output.error(`\nFail run ${rerunsCounter} of max ${maxReruns}, success runs ${successCounter}/${minSuccess} \n`)
|
|
103
|
+
console.error(e)
|
|
60
104
|
}
|
|
61
105
|
}
|
|
62
106
|
if (successCounter < minSuccess) {
|
|
63
|
-
throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`)
|
|
107
|
+
throw new Error(`Flaky tests detected! ${successCounter} success runs achieved instead of ${minSuccess} success runs expected`)
|
|
64
108
|
}
|
|
65
109
|
}
|
|
66
110
|
|
|
67
111
|
async run(test) {
|
|
68
|
-
event.emit(event.all.before, this)
|
|
112
|
+
event.emit(event.all.before, this)
|
|
69
113
|
try {
|
|
70
|
-
await this.runTests(test)
|
|
114
|
+
await this.runTests(test)
|
|
71
115
|
} catch (e) {
|
|
72
|
-
output.error(e.stack)
|
|
73
|
-
throw e
|
|
116
|
+
output.error(e.stack)
|
|
117
|
+
throw e
|
|
74
118
|
} finally {
|
|
75
|
-
event.emit(event.all.result, this)
|
|
76
|
-
event.emit(event.all.after, this)
|
|
119
|
+
event.emit(event.all.result, this)
|
|
120
|
+
event.emit(event.all.after, this)
|
|
77
121
|
}
|
|
78
122
|
}
|
|
79
123
|
}
|
|
80
124
|
|
|
81
|
-
|
|
125
|
+
export default CodeceptRerunner
|