codeceptjs 3.7.6-beta.4 → 4.0.0-beta.10.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 +1 -3
- package/bin/codecept.js +51 -53
- package/bin/test-server.js +14 -3
- package/docs/webapi/click.mustache +5 -1
- package/lib/actor.js +15 -11
- package/lib/ai.js +72 -107
- package/lib/assert/empty.js +9 -8
- package/lib/assert/equal.js +15 -17
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +9 -11
- package/lib/assert/throws.js +1 -1
- package/lib/assert/truth.js +8 -5
- package/lib/assert.js +18 -18
- package/lib/codecept.js +102 -75
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +32 -27
- package/lib/command/configMigrate.js +11 -10
- package/lib/command/definitions.js +16 -10
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +62 -27
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +14 -14
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +8 -8
- package/lib/command/init.js +36 -29
- package/lib/command/interactive.js +11 -10
- package/lib/command/list.js +10 -9
- package/lib/command/run-multiple/chunk.js +5 -5
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +16 -13
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +24 -9
- package/lib/command/run.js +23 -8
- package/lib/command/utils.js +20 -18
- package/lib/command/workers/runTests.js +197 -114
- package/lib/config.js +124 -51
- package/lib/container.js +438 -87
- package/lib/data/context.js +6 -5
- package/lib/data/dataScenarioConfig.js +1 -1
- package/lib/data/dataTableArgument.js +1 -1
- package/lib/data/table.js +1 -1
- package/lib/effects.js +94 -10
- package/lib/element/WebElement.js +2 -2
- package/lib/els.js +11 -9
- package/lib/event.js +11 -10
- package/lib/globals.js +141 -0
- package/lib/heal.js +12 -12
- package/lib/helper/AI.js +11 -11
- package/lib/helper/ApiDataFactory.js +50 -19
- package/lib/helper/Appium.js +19 -27
- package/lib/helper/FileSystem.js +32 -12
- package/lib/helper/GraphQL.js +3 -3
- package/lib/helper/GraphQLDataFactory.js +4 -4
- package/lib/helper/JSONResponse.js +25 -29
- package/lib/helper/Mochawesome.js +7 -4
- package/lib/helper/Playwright.js +902 -164
- package/lib/helper/Puppeteer.js +383 -76
- package/lib/helper/REST.js +29 -12
- package/lib/helper/WebDriver.js +268 -61
- package/lib/helper/clientscripts/PollyWebDriverExt.js +1 -1
- 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/PlaywrightReactVueLocator.js +18 -9
- package/lib/helper/extras/PlaywrightRestartOpts.js +34 -23
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +29 -30
- package/lib/helper/network/actions.js +29 -44
- 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 +19 -12
- package/lib/listener/emptyRun.js +6 -7
- package/lib/listener/enhancedGlobalRetry.js +6 -6
- package/lib/listener/exit.js +4 -3
- package/lib/listener/globalRetry.js +5 -5
- package/lib/listener/globalTimeout.js +30 -14
- package/lib/listener/helpers.js +39 -14
- package/lib/listener/mocha.js +3 -4
- package/lib/listener/result.js +4 -5
- package/lib/listener/retryEnhancer.js +3 -3
- package/lib/listener/steps.js +8 -7
- package/lib/listener/store.js +3 -3
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +105 -62
- package/lib/mocha/bdd.js +99 -13
- package/lib/mocha/cli.js +59 -26
- package/lib/mocha/factory.js +78 -19
- package/lib/mocha/featureConfig.js +1 -1
- package/lib/mocha/gherkin.js +56 -24
- package/lib/mocha/hooks.js +12 -3
- package/lib/mocha/index.js +13 -4
- package/lib/mocha/inject.js +22 -5
- package/lib/mocha/scenarioConfig.js +2 -2
- package/lib/mocha/suite.js +9 -2
- package/lib/mocha/test.js +10 -7
- package/lib/mocha/ui.js +28 -18
- package/lib/output.js +10 -8
- package/lib/parser.js +44 -44
- package/lib/pause.js +15 -16
- package/lib/plugin/analyze.js +19 -12
- package/lib/plugin/auth.js +20 -21
- package/lib/plugin/autoDelay.js +12 -8
- package/lib/plugin/coverage.js +28 -11
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +3 -2
- package/lib/plugin/enhancedRetryFailedStep.js +6 -6
- package/lib/plugin/heal.js +14 -9
- package/lib/plugin/htmlReporter.js +724 -99
- package/lib/plugin/pageInfo.js +10 -10
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +48 -5
- package/lib/plugin/screenshotOnFail.js +75 -37
- package/lib/plugin/stepByStepReport.js +14 -14
- package/lib/plugin/stepTimeout.js +4 -3
- package/lib/plugin/subtitles.js +6 -5
- package/lib/recorder.js +33 -14
- package/lib/rerun.js +69 -26
- package/lib/result.js +4 -4
- package/lib/retryCoordinator.js +2 -2
- package/lib/secret.js +18 -17
- package/lib/session.js +95 -89
- package/lib/step/base.js +7 -7
- package/lib/step/comment.js +2 -2
- package/lib/step/config.js +1 -1
- package/lib/step/func.js +3 -3
- package/lib/step/helper.js +3 -3
- package/lib/step/meta.js +5 -5
- package/lib/step/record.js +11 -11
- package/lib/step/retry.js +3 -3
- package/lib/step/section.js +3 -3
- package/lib/step.js +7 -10
- package/lib/steps.js +9 -5
- package/lib/store.js +1 -1
- package/lib/template/heal.js +1 -1
- package/lib/template/prompts/generatePageObject.js +31 -0
- package/lib/template/prompts/healStep.js +13 -0
- package/lib/template/prompts/writeStep.js +9 -0
- package/lib/test-server.js +17 -6
- package/lib/timeout.js +1 -7
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils/mask_data.js +4 -10
- package/lib/utils.js +66 -64
- package/lib/workerStorage.js +17 -17
- package/lib/workers.js +214 -84
- package/package.json +41 -37
- package/translations/de-DE.js +2 -2
- package/translations/fr-FR.js +2 -2
- package/translations/index.js +23 -10
- package/translations/it-IT.js +2 -2
- package/translations/ja-JP.js +2 -2
- package/translations/nl-NL.js +2 -2
- package/translations/pl-PL.js +2 -2
- package/translations/pt-BR.js +2 -2
- package/translations/ru-RU.js +2 -2
- package/translations/utils.js +4 -3
- package/translations/zh-CN.js +2 -2
- package/translations/zh-TW.js +2 -2
- package/typings/index.d.ts +5 -3
- package/typings/promiseBasedTypes.d.ts +4 -0
- package/typings/types.d.ts +4 -0
- package/lib/helper/Nightmare.js +0 -1486
- package/lib/helper/Protractor.js +0 -1840
- package/lib/helper/TestCafe.js +0 -1391
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -61
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/autoLogin.js +0 -5
- package/lib/plugin/commentStep.js +0 -141
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -16
- package/lib/plugin/selenoid.js +0 -364
- package/lib/plugin/standardActingHelpers.js +0 -6
- package/lib/plugin/tryTo.js +0 -16
- package/lib/plugin/wdio.js +0 -247
- package/lib/within.js +0 -90
package/lib/plugin/pageInfo.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
const recorder = require('../recorder')
|
|
5
|
-
const event = require('../event')
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import Container from '../container.js'
|
|
6
4
|
const supportedHelpers = Container.STANDARD_ACTING_HELPERS
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
import recorder from '../recorder.js'
|
|
6
|
+
import event from '../event.js'
|
|
7
|
+
import { scanForErrorMessages } from '../html.js'
|
|
8
|
+
import { output } from '../index.js'
|
|
9
|
+
import { humanizeString, ucfirst } from '../utils.js'
|
|
10
|
+
import { testToFileName } from '../mocha/test.js'
|
|
11
11
|
const defaultConfig = {
|
|
12
12
|
errorClasses: ['error', 'warning', 'alert', 'danger'],
|
|
13
13
|
browserLogs: ['error'],
|
|
@@ -35,7 +35,7 @@ const defaultConfig = {
|
|
|
35
35
|
* * `browserLogs` - list of types of errors to search for in browser logs (default: `['error']`)
|
|
36
36
|
*
|
|
37
37
|
*/
|
|
38
|
-
|
|
38
|
+
export default function (config = {}) {
|
|
39
39
|
const helpers = Container.helpers()
|
|
40
40
|
let helper
|
|
41
41
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import event from '../event.js'
|
|
2
|
+
|
|
3
|
+
import pause from '../pause.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Automatically launches [interactive pause](/basics/#pause) when a test fails.
|
|
@@ -21,7 +22,7 @@ const pause = require('../pause')
|
|
|
21
22
|
* ```
|
|
22
23
|
*
|
|
23
24
|
*/
|
|
24
|
-
|
|
25
|
+
export default function() {
|
|
25
26
|
let failed = false
|
|
26
27
|
|
|
27
28
|
event.dispatcher.on(event.test.started, () => {
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import event from '../event.js'
|
|
2
|
+
|
|
3
|
+
import recorder from '../recorder.js'
|
|
4
|
+
|
|
5
|
+
import store from '../store.js'
|
|
6
|
+
|
|
4
7
|
const defaultConfig = {
|
|
5
8
|
retries: 3,
|
|
6
9
|
defaultIgnoredSteps: ['amOnPage', 'wait*', 'send*', 'execute*', 'run*', 'have*'],
|
|
@@ -74,7 +77,7 @@ const defaultConfig = {
|
|
|
74
77
|
* ```
|
|
75
78
|
*
|
|
76
79
|
*/
|
|
77
|
-
|
|
80
|
+
export default function (config) {
|
|
78
81
|
config = Object.assign(defaultConfig, config)
|
|
79
82
|
config.ignoredSteps = config.ignoredSteps.concat(config.defaultIgnoredSteps)
|
|
80
83
|
const customWhen = config.when
|
|
@@ -85,11 +88,20 @@ module.exports = config => {
|
|
|
85
88
|
if (!enableRetry) return
|
|
86
89
|
if (store.debugMode) return false
|
|
87
90
|
if (!store.autoRetries) return false
|
|
91
|
+
// Don't retry terminal errors (e.g., frame detachment errors)
|
|
92
|
+
if (err && err.isTerminal) return false
|
|
93
|
+
// Don't retry navigation errors that are known to be terminal
|
|
94
|
+
if (err && err.message && (err.message.includes('ERR_ABORTED') || err.message.includes('frame was detached') || err.message.includes('Target page, context or browser has been closed'))) return false
|
|
88
95
|
if (customWhen) return customWhen(err)
|
|
89
96
|
return true
|
|
90
97
|
}
|
|
91
98
|
config.when = when
|
|
92
99
|
|
|
100
|
+
// Ensure retry options are available before any steps run
|
|
101
|
+
if (!recorder.retries.find(r => r === config)) {
|
|
102
|
+
recorder.retries.push(config)
|
|
103
|
+
}
|
|
104
|
+
|
|
93
105
|
event.dispatcher.on(event.step.started, step => {
|
|
94
106
|
// if a step is ignored - return
|
|
95
107
|
for (const ignored of config.ignoredSteps) {
|
|
@@ -101,20 +113,51 @@ module.exports = config => {
|
|
|
101
113
|
enableRetry = true // enable retry for a step
|
|
102
114
|
})
|
|
103
115
|
|
|
104
|
-
|
|
116
|
+
// Disable retry only after a successful step; keep it enabled for failure so retry logic can act
|
|
117
|
+
event.dispatcher.on(event.step.passed, () => {
|
|
105
118
|
enableRetry = false
|
|
106
119
|
})
|
|
107
120
|
|
|
108
121
|
event.dispatcher.on(event.test.before, test => {
|
|
109
122
|
// pass disableRetryFailedStep is a preferred way to disable retries
|
|
110
123
|
// test.disableRetryFailedStep is used for backward compatibility
|
|
124
|
+
if (!test.opts) test.opts = {}
|
|
111
125
|
if (test.opts.disableRetryFailedStep || test.disableRetryFailedStep) {
|
|
112
126
|
store.autoRetries = false
|
|
113
127
|
return // disable retry when a test is not active
|
|
114
128
|
}
|
|
129
|
+
|
|
130
|
+
// Don't apply plugin retry logic if there are already manual retries configured
|
|
131
|
+
// Check if any retry configs exist that aren't from this plugin
|
|
132
|
+
const hasManualRetries = recorder.retries.some(retry => retry !== config)
|
|
133
|
+
if (hasManualRetries) {
|
|
134
|
+
store.autoRetries = false
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
115
138
|
// this option is used to set the retries inside _before() block of helpers
|
|
116
139
|
store.autoRetries = true
|
|
117
140
|
test.opts.conditionalRetries = config.retries
|
|
141
|
+
// debug: record applied retries value for tests
|
|
142
|
+
if (process.env.DEBUG_RETRY_PLUGIN) {
|
|
143
|
+
// eslint-disable-next-line no-console
|
|
144
|
+
console.log('[retryFailedStep] applying retries =', config.retries, 'for test', test.title)
|
|
145
|
+
}
|
|
118
146
|
recorder.retry(config)
|
|
119
147
|
})
|
|
148
|
+
|
|
149
|
+
// Fallback for environments where event.test.before wasn't emitted (runner scenarios)
|
|
150
|
+
event.dispatcher.on(event.test.started, test => {
|
|
151
|
+
if (test.opts?.disableRetryFailedStep || test.disableRetryFailedStep) return
|
|
152
|
+
|
|
153
|
+
// Don't apply plugin retry logic if there are already manual retries configured
|
|
154
|
+
// Check if any retry configs exist that aren't from this plugin
|
|
155
|
+
const hasManualRetries = recorder.retries.some(retry => retry !== config)
|
|
156
|
+
if (hasManualRetries) return
|
|
157
|
+
|
|
158
|
+
if (!store.autoRetries) {
|
|
159
|
+
store.autoRetries = true
|
|
160
|
+
test.opts.conditionalRetries = test.opts.conditionalRetries || config.retries
|
|
161
|
+
}
|
|
162
|
+
})
|
|
120
163
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
import Container from '../container.js'
|
|
5
|
+
|
|
6
|
+
import recorder from '../recorder.js'
|
|
7
|
+
|
|
8
|
+
import event from '../event.js'
|
|
9
|
+
|
|
10
|
+
import output from '../output.js'
|
|
11
|
+
|
|
12
|
+
import { fileExists } from '../utils.js'
|
|
13
|
+
import Codeceptjs from '../index.js'
|
|
14
|
+
import { testToFileName } from '../mocha/test.js'
|
|
11
15
|
|
|
12
16
|
const defaultConfig = {
|
|
13
17
|
uniqueScreenshotNames: false,
|
|
@@ -43,7 +47,7 @@ const supportedHelpers = Container.STANDARD_ACTING_HELPERS
|
|
|
43
47
|
*
|
|
44
48
|
*
|
|
45
49
|
*/
|
|
46
|
-
|
|
50
|
+
export default function (config) {
|
|
47
51
|
const helpers = Container.helpers()
|
|
48
52
|
let helper
|
|
49
53
|
|
|
@@ -86,11 +90,28 @@ module.exports = function (config) {
|
|
|
86
90
|
let fileName
|
|
87
91
|
|
|
88
92
|
if (options.uniqueScreenshotNames && test) {
|
|
89
|
-
fileName = `${testToFileName(test, { unique: true })}.failed.png`
|
|
93
|
+
fileName = `${testToFileName(test, { suffix: '', unique: true })}.failed.png`
|
|
90
94
|
} else {
|
|
91
|
-
fileName = `${testToFileName(test)}.failed.png`
|
|
95
|
+
fileName = `${testToFileName(test, { suffix: '', unique: false })}.failed.png`
|
|
96
|
+
}
|
|
97
|
+
const quietMode = !('output_dir' in global) || !global.output_dir
|
|
98
|
+
if (!quietMode) {
|
|
99
|
+
output.plugin('screenshotOnFail', 'Test failed, try to save a screenshot')
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Re-check helpers at runtime in case they weren't ready during plugin init
|
|
103
|
+
const runtimeHelpers = Container.helpers()
|
|
104
|
+
let runtimeHelper = null
|
|
105
|
+
for (const helperName of supportedHelpers) {
|
|
106
|
+
if (Object.keys(runtimeHelpers).indexOf(helperName) > -1) {
|
|
107
|
+
runtimeHelper = runtimeHelpers[helperName]
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (runtimeHelper && typeof runtimeHelper.saveScreenshot === 'function') {
|
|
113
|
+
helper = runtimeHelper
|
|
92
114
|
}
|
|
93
|
-
output.plugin('screenshotOnFail', 'Test failed, try to save a screenshot')
|
|
94
115
|
|
|
95
116
|
try {
|
|
96
117
|
if (options.reportDir) {
|
|
@@ -100,36 +121,53 @@ module.exports = function (config) {
|
|
|
100
121
|
fs.mkdirSync(mochaReportDir)
|
|
101
122
|
}
|
|
102
123
|
}
|
|
103
|
-
await helper.saveScreenshot(fileName, options.fullPageScreenshots)
|
|
104
124
|
|
|
105
|
-
if
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
test.attachments = [path.join(global.output_dir, fileName)]
|
|
125
|
+
// Check if browser/page is still available before attempting screenshot
|
|
126
|
+
if (helper.page && helper.page.isClosed && helper.page.isClosed()) {
|
|
127
|
+
throw new Error('Browser page has been closed')
|
|
109
128
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (allureReporter) {
|
|
113
|
-
allureReporter.addAttachment('Main session - Last Seen Screenshot', fs.readFileSync(path.join(global.output_dir, fileName)), dataType)
|
|
114
|
-
|
|
115
|
-
if (helper.activeSessionName) {
|
|
116
|
-
const sessions = helper.sessionPages || helper.sessionWindows
|
|
117
|
-
for (const sessionName in sessions) {
|
|
118
|
-
const screenshotFileName = `${sessionName}_${fileName}`
|
|
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)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
129
|
+
if (helper.browser && helper.browser.isConnected && !helper.browser.isConnected()) {
|
|
130
|
+
throw new Error('Browser has been disconnected')
|
|
123
131
|
}
|
|
124
132
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
133
|
+
// Add timeout wrapper to prevent hanging with shorter timeout for ESM
|
|
134
|
+
const screenshotPromise = helper.saveScreenshot(fileName, options.fullPageScreenshots)
|
|
135
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
136
|
+
setTimeout(() => reject(new Error('Screenshot timeout after 5 seconds')), 5000)
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
await Promise.race([screenshotPromise, timeoutPromise])
|
|
140
|
+
|
|
141
|
+
if (!test.artifacts) test.artifacts = {}
|
|
142
|
+
// Some unit tests may not define global.output_dir; avoid throwing when it is undefined
|
|
143
|
+
// Detect output directory safely (may not be initialized in narrow unit tests)
|
|
144
|
+
const baseOutputDir = 'output_dir' in global && typeof global.output_dir === 'string' && global.output_dir ? global.output_dir : null
|
|
145
|
+
if (baseOutputDir) {
|
|
146
|
+
test.artifacts.screenshot = path.join(baseOutputDir, fileName)
|
|
147
|
+
if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments) {
|
|
148
|
+
test.attachments = [path.join(baseOutputDir, fileName)]
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
// Fallback: just store the file name to keep tests stable without triggering path errors
|
|
152
|
+
test.artifacts.screenshot = fileName
|
|
128
153
|
}
|
|
129
154
|
} catch (err) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
155
|
+
if (!quietMode) {
|
|
156
|
+
output.plugin('screenshotOnFail', `Failed to save screenshot: ${err.message}`)
|
|
157
|
+
}
|
|
158
|
+
// Enhanced error handling for browser closed scenarios
|
|
159
|
+
if (
|
|
160
|
+
err &&
|
|
161
|
+
((err.message &&
|
|
162
|
+
(err.message.includes('Target page, context or browser has been closed') ||
|
|
163
|
+
err.message.includes('Browser page has been closed') ||
|
|
164
|
+
err.message.includes('Browser has been disconnected') ||
|
|
165
|
+
err.message.includes('was terminated due to') ||
|
|
166
|
+
err.message.includes('no such window: target window already closed') ||
|
|
167
|
+
err.message.includes('Screenshot timeout after'))) ||
|
|
168
|
+
(err.type && err.type === 'RuntimeError'))
|
|
169
|
+
) {
|
|
170
|
+
output.log(`Can't make screenshot, ${err.message}`)
|
|
133
171
|
helper.isRunning = false
|
|
134
172
|
}
|
|
135
173
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
import colors from 'chalk'
|
|
2
|
+
import crypto from 'crypto'
|
|
3
|
+
import figures from 'figures'
|
|
4
|
+
import fs from 'fs'
|
|
5
|
+
import { mkdirp } from 'mkdirp'
|
|
6
|
+
import path from 'path'
|
|
7
|
+
import cheerio from 'cheerio'
|
|
8
|
+
|
|
9
|
+
import Container from '../container.js'
|
|
10
|
+
import recorder from '../recorder.js'
|
|
11
|
+
import event from '../event.js'
|
|
12
|
+
import output from '../output.js'
|
|
13
|
+
import { template, deleteDir } from '../utils.js'
|
|
14
14
|
|
|
15
15
|
const supportedHelpers = Container.STANDARD_ACTING_HELPERS
|
|
16
16
|
|
|
@@ -63,7 +63,7 @@ const templates = {}
|
|
|
63
63
|
* @param {*} config
|
|
64
64
|
*/
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
export default function (config) {
|
|
67
67
|
const helpers = Container.helpers()
|
|
68
68
|
let helper
|
|
69
69
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import event from '../event.js'
|
|
2
|
+
|
|
3
|
+
import { TIMEOUT_ORDER } from '../timeout.js'
|
|
3
4
|
|
|
4
5
|
const defaultConfig = {
|
|
5
6
|
timeout: 150,
|
|
@@ -61,7 +62,7 @@ const defaultConfig = {
|
|
|
61
62
|
* ```
|
|
62
63
|
*
|
|
63
64
|
*/
|
|
64
|
-
|
|
65
|
+
export default function(config) {
|
|
65
66
|
config = Object.assign(defaultConfig, config)
|
|
66
67
|
// below override rule makes sure customTimeoutSteps go first but then they override noTimeoutSteps in case of exact pattern match
|
|
67
68
|
config.customTimeoutSteps = config.customTimeoutSteps.concat(config.noTimeoutSteps).concat(config.customTimeoutSteps)
|
package/lib/plugin/subtitles.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
|
|
1
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
const fsPromise = fs.promises
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import event from '../event.js'
|
|
5
6
|
|
|
6
7
|
// This will convert a given timestamp in milliseconds to
|
|
7
8
|
// an SRT recognized timestamp, ie HH:mm:ss,SSS
|
|
@@ -28,7 +29,7 @@ let testStartedAt
|
|
|
28
29
|
* }
|
|
29
30
|
* ```
|
|
30
31
|
*/
|
|
31
|
-
|
|
32
|
+
export default function () {
|
|
32
33
|
event.dispatcher.on(event.test.before, _ => {
|
|
33
34
|
testStartedAt = Date.now()
|
|
34
35
|
steps = {}
|
package/lib/recorder.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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'
|
|
7
8
|
const MAX_TASKS = 100
|
|
8
9
|
|
|
9
10
|
let promise
|
|
@@ -29,7 +30,7 @@ const defaultRetryOptions = {
|
|
|
29
30
|
* @alias recorder
|
|
30
31
|
* @interface
|
|
31
32
|
*/
|
|
32
|
-
|
|
33
|
+
export default {
|
|
33
34
|
/**
|
|
34
35
|
* @type {Array<Object<string, *>>}
|
|
35
36
|
* @inner
|
|
@@ -92,7 +93,7 @@ module.exports = {
|
|
|
92
93
|
sessionId = null
|
|
93
94
|
sessionStack = [] // Clear the session stack
|
|
94
95
|
asyncErr = null
|
|
95
|
-
log(`${currentQueue()} Starting recording promises`)
|
|
96
|
+
output.log(`${currentQueue()} Starting recording promises`)
|
|
96
97
|
promise = Promise.resolve()
|
|
97
98
|
oldPromises = []
|
|
98
99
|
tasks = []
|
|
@@ -217,7 +218,7 @@ module.exports = {
|
|
|
217
218
|
|
|
218
219
|
const retryRules = this.retries.slice().reverse()
|
|
219
220
|
return promiseRetry(Object.assign(defaultRetryOptions, retryOpts), (retry, number) => {
|
|
220
|
-
if (number > 1) log(`${currentQueue()}Retrying... Attempt #${number}`)
|
|
221
|
+
if (number > 1) output.log(`${currentQueue()}Retrying... Attempt #${number}`)
|
|
221
222
|
const [promise, timer] = getTimeoutPromise(timeout, taskName)
|
|
222
223
|
return Promise.race([promise, Promise.resolve(res).then(fn)])
|
|
223
224
|
.finally(() => clearTimeout(timer))
|
|
@@ -247,7 +248,9 @@ module.exports = {
|
|
|
247
248
|
if (Number.isInteger(opts)) {
|
|
248
249
|
opts = { retries: opts }
|
|
249
250
|
}
|
|
250
|
-
|
|
251
|
+
// Push retry options immediately so first step can see them
|
|
252
|
+
this.retries.push(opts)
|
|
253
|
+
return Promise.resolve()
|
|
251
254
|
},
|
|
252
255
|
|
|
253
256
|
/**
|
|
@@ -262,8 +265,9 @@ module.exports = {
|
|
|
262
265
|
.replace(/\n/g, ' ')
|
|
263
266
|
?.slice(0, 50)
|
|
264
267
|
debug(chalk.gray(`${currentQueue()} Queued | catch with error handler ${fnDescription || ''}`))
|
|
268
|
+
if (!promise) promise = Promise.resolve()
|
|
265
269
|
return (promise = promise.catch(err => {
|
|
266
|
-
log(`${currentQueue()}Error | ${err} ${fnDescription}...`)
|
|
270
|
+
output.log(`${currentQueue()}Error | ${err} ${fnDescription}...`)
|
|
267
271
|
if (!(err instanceof Error)) {
|
|
268
272
|
// strange things may happen
|
|
269
273
|
err = new Error(`[Wrapped Error] ${printObjectProperties(err)}`) // we should be prepared for them
|
|
@@ -288,15 +292,30 @@ module.exports = {
|
|
|
288
292
|
?.replace(/\s{2,}/g, ' ')
|
|
289
293
|
.replace(/\n/g, ' ')
|
|
290
294
|
?.slice(0, 50)
|
|
295
|
+
if (!promise) promise = Promise.resolve()
|
|
291
296
|
return (promise = promise.catch(err => {
|
|
292
297
|
if (ignoredErrs.includes(err)) return // already caught
|
|
293
|
-
|
|
298
|
+
|
|
299
|
+
// Handle terminal errors - don't continue, re-throw immediately
|
|
300
|
+
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'))))) {
|
|
301
|
+
output.log(`${currentQueue()} Terminal Error | ${err} | ${fnDescription || ''}...`)
|
|
302
|
+
throw err // Re-throw terminal errors immediately
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
output.log(`${currentQueue()} Error (Non-Terminated) | ${err} | ${fnDescription || ''}...`)
|
|
294
306
|
if (!(err instanceof Error)) {
|
|
295
307
|
// strange things may happen
|
|
296
308
|
err = new Error(`[Wrapped Error] ${JSON.stringify(err)}`) // we should be prepared for them
|
|
297
309
|
}
|
|
298
310
|
if (customErrFn) {
|
|
299
|
-
|
|
311
|
+
try {
|
|
312
|
+
const result = customErrFn(err)
|
|
313
|
+
// If customErrFn returns a value (not throwing), treat it as handled
|
|
314
|
+
return result
|
|
315
|
+
} catch (thrownErr) {
|
|
316
|
+
// If customErrFn throws an error, propagate it up
|
|
317
|
+
throw thrownErr
|
|
318
|
+
}
|
|
300
319
|
}
|
|
301
320
|
}))
|
|
302
321
|
},
|
|
@@ -354,7 +373,7 @@ module.exports = {
|
|
|
354
373
|
*/
|
|
355
374
|
stop() {
|
|
356
375
|
debug(this.toString())
|
|
357
|
-
log(`${currentQueue()} Stopping recording promises`)
|
|
376
|
+
output.log(`${currentQueue()} Stopping recording promises`)
|
|
358
377
|
running = false
|
|
359
378
|
},
|
|
360
379
|
|
package/lib/rerun.js
CHANGED
|
@@ -1,34 +1,77 @@
|
|
|
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) {
|
|
74
|
+
output.error(e.stack)
|
|
32
75
|
reject(e)
|
|
33
76
|
}
|
|
34
77
|
})
|
|
@@ -79,4 +122,4 @@ class CodeceptRerunner extends BaseCodecept {
|
|
|
79
122
|
}
|
|
80
123
|
}
|
|
81
124
|
|
|
82
|
-
|
|
125
|
+
export default CodeceptRerunner
|
package/lib/result.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { serializeTest } from './mocha/test.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {Object} Stats Statistics for a test result.
|
|
@@ -235,4 +235,4 @@ class Result {
|
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
|
|
238
|
+
export default Result
|
package/lib/retryCoordinator.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import output from './output.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Retry Coordinator - Central coordination for all retry mechanisms
|
|
@@ -196,7 +196,7 @@ function validateConfig(config) {
|
|
|
196
196
|
return warnings
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
export {
|
|
200
200
|
RETRY_PRIORITIES,
|
|
201
201
|
RETRY_TYPES,
|
|
202
202
|
registerRetry,
|