codeceptjs 4.0.1-beta.9 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -28
- package/bin/codecept.js +17 -4
- package/bin/codeceptq.js +49 -0
- package/bin/mcp-server.js +1189 -0
- package/docs/advanced.md +201 -0
- package/docs/agents.md +181 -0
- package/docs/ai.md +489 -0
- package/docs/aitrace.md +266 -0
- package/docs/api.md +332 -0
- package/docs/architecture.md +235 -0
- package/docs/assertions.md +415 -0
- package/docs/auth.md +318 -0
- package/docs/basics.md +424 -0
- package/docs/bdd.md +539 -0
- package/docs/best.md +240 -0
- package/docs/bootstrap.md +132 -0
- package/docs/commands.md +352 -0
- package/docs/community-helpers.md +63 -0
- package/docs/configuration.md +185 -0
- package/docs/continuous-integration.md +431 -0
- package/docs/custom-helpers.md +297 -0
- package/docs/data.md +448 -0
- package/docs/debugging.md +332 -0
- package/docs/detox.md +235 -0
- package/docs/docker.md +107 -0
- package/docs/effects.md +179 -0
- package/docs/element-based-testing.md +295 -0
- package/docs/element-selection.md +125 -0
- package/docs/els.md +328 -0
- package/docs/environment-variables.md +131 -0
- package/docs/examples.md +160 -0
- package/docs/heal.md +213 -0
- package/docs/helpers/ApiDataFactory.md +267 -0
- package/docs/helpers/Appium.md +1419 -0
- package/docs/helpers/Detox.md +665 -0
- package/docs/helpers/ExpectHelper.md +275 -0
- package/docs/helpers/FileSystem.md +152 -0
- package/docs/helpers/GraphQL.md +152 -0
- package/docs/helpers/GraphQLDataFactory.md +226 -0
- package/docs/helpers/JSONResponse.md +255 -0
- package/docs/helpers/MockRequest.md +377 -0
- package/docs/helpers/Playwright.md +2970 -0
- package/docs/helpers/Puppeteer-firefox.md +86 -0
- package/docs/helpers/Puppeteer.md +2583 -0
- package/docs/helpers/REST.md +289 -0
- package/docs/helpers/WebDriver.md +2639 -0
- package/docs/hooks.md +148 -0
- package/docs/index.md +111 -0
- package/docs/installation.md +121 -0
- package/docs/internal-test-server.md +89 -0
- package/docs/locators.md +355 -0
- package/docs/mcp.md +485 -0
- package/docs/migrate-from-cypress.md +98 -0
- package/docs/migrate-from-java.md +108 -0
- package/docs/migrate-from-protractor.md +101 -0
- package/docs/migrate-from-testcafe.md +99 -0
- package/docs/migration-4.md +745 -0
- package/docs/mobile.md +338 -0
- package/docs/pageobjects.md +399 -0
- package/docs/parallel.md +187 -0
- package/docs/playwright.md +714 -0
- package/docs/plugins/aiTrace.md +49 -0
- package/docs/plugins/analyze.md +66 -0
- package/docs/plugins/auth.md +241 -0
- package/docs/plugins/autoDelay.md +48 -0
- package/docs/plugins/browser.md +41 -0
- package/docs/plugins/coverage.md +39 -0
- package/docs/plugins/customLocator.md +119 -0
- package/docs/plugins/customReporter.md +16 -0
- package/docs/plugins/expose.md +75 -0
- package/docs/plugins/heal.md +44 -0
- package/docs/plugins/junitReporter.md +51 -0
- package/docs/plugins/pageInfo.md +34 -0
- package/docs/plugins/pause.md +43 -0
- package/docs/plugins/pauseOnFail.md +18 -0
- package/docs/plugins/retryFailedStep.md +75 -0
- package/docs/plugins/screencast.md +55 -0
- package/docs/plugins/screenshot.md +58 -0
- package/docs/plugins/screenshotOnFail.md +18 -0
- package/docs/plugins/stepTimeout.md +65 -0
- package/docs/plugins.md +87 -0
- package/docs/puppeteer.md +314 -0
- package/docs/quickstart.md +120 -0
- package/docs/reports.md +195 -0
- package/docs/retry.md +311 -0
- package/docs/secrets.md +150 -0
- package/docs/sessions.md +80 -0
- package/docs/shadow.md +68 -0
- package/docs/store.md +94 -0
- package/docs/test-structure.md +275 -0
- package/docs/timeouts.md +183 -0
- package/docs/translation.md +247 -0
- package/docs/tutorial.md +323 -0
- package/docs/typescript.md +159 -0
- package/docs/web-element.md +251 -0
- package/docs/webdriver.md +641 -0
- package/docs/within.md +55 -0
- package/lib/actor.js +1 -36
- package/lib/ai.js +3 -2
- package/lib/aria.js +260 -0
- package/lib/assertions.js +18 -0
- package/lib/codecept.js +34 -25
- package/lib/command/check.js +2 -1
- package/lib/command/definitions.js +14 -10
- package/lib/command/dryRun.js +24 -5
- package/lib/command/generate.js +3 -1
- package/lib/command/gherkin/snippets.js +5 -4
- package/lib/command/init.js +249 -270
- package/lib/command/list.js +150 -10
- package/lib/command/query.js +218 -0
- package/lib/command/run-multiple.js +3 -2
- package/lib/command/run-workers.js +14 -16
- package/lib/command/run.js +3 -17
- package/lib/command/utils.js +14 -0
- package/lib/command/workers/runTests.js +117 -9
- package/lib/config.js +98 -19
- package/lib/container.js +188 -19
- package/lib/effects.js +17 -0
- package/lib/element/WebElement.js +246 -2
- package/lib/els.js +12 -6
- package/lib/globals.js +32 -19
- package/lib/heal.js +7 -4
- package/lib/helper/ApiDataFactory.js +2 -1
- package/lib/helper/Appium.js +8 -8
- package/lib/helper/FileSystem.js +3 -2
- package/lib/helper/GraphQLDataFactory.js +2 -1
- package/lib/helper/Playwright.js +367 -516
- package/lib/helper/Puppeteer.js +343 -197
- package/lib/helper/WebDriver.js +324 -111
- package/lib/helper/errors/ElementNotFound.js +5 -2
- package/lib/helper/errors/MultipleElementsFound.js +52 -0
- package/lib/helper/errors/NonFocusedType.js +8 -0
- package/lib/helper/extras/Download.js +45 -0
- package/lib/helper/extras/PlaywrightLocator.js +7 -107
- package/lib/helper/extras/elementSelection.js +58 -0
- package/lib/helper/extras/focusCheck.js +43 -0
- package/lib/helper/extras/richTextEditor.js +178 -0
- package/lib/helper/scripts/dropFile.js +11 -0
- package/lib/history.js +3 -2
- package/lib/html.js +103 -16
- package/lib/index.js +9 -1
- package/lib/listener/config.js +6 -4
- package/lib/listener/emptyRun.js +2 -1
- package/lib/listener/globalRetry.js +32 -6
- package/lib/listener/helpers.js +6 -15
- package/lib/listener/mocha.js +2 -1
- package/lib/listener/pageobjects.js +43 -0
- package/lib/listener/result.js +3 -2
- package/lib/locator.js +158 -16
- package/lib/mocha/cli.js +19 -1
- package/lib/mocha/factory.js +13 -28
- package/lib/mocha/inject.js +1 -1
- package/lib/mocha/scenarioConfig.js +2 -1
- package/lib/mocha/test.js +4 -2
- package/lib/mocha/ui.js +5 -6
- package/lib/output.js +2 -2
- package/lib/parser.js +2 -2
- package/lib/pause.js +38 -4
- package/lib/plugin/aiTrace.js +457 -0
- package/lib/plugin/analyze.js +9 -9
- package/lib/plugin/auth.js +5 -4
- package/lib/plugin/browser.js +77 -0
- package/lib/plugin/expose.js +159 -0
- package/lib/plugin/heal.js +47 -3
- package/lib/plugin/junitReporter.js +303 -0
- package/lib/plugin/pageInfo.js +54 -52
- package/lib/plugin/pause.js +131 -0
- package/lib/plugin/pauseOnFail.js +11 -33
- package/lib/plugin/retryFailedStep.js +43 -32
- package/lib/plugin/screencast.js +289 -0
- package/lib/plugin/screenshot.js +558 -0
- package/lib/plugin/screenshotOnFail.js +9 -170
- package/lib/plugin/stepTimeout.js +3 -2
- package/lib/recorder.js +1 -1
- package/lib/rerun.js +2 -1
- package/lib/result.js +2 -1
- package/lib/step/base.js +23 -9
- package/lib/step/comment.js +2 -2
- package/lib/step/config.js +15 -2
- package/lib/step/helper.js +4 -4
- package/lib/step/meta.js +3 -3
- package/lib/step/record.js +12 -4
- package/lib/store.js +72 -3
- package/lib/translation.js +2 -1
- package/lib/utils/loaderCheck.js +41 -3
- package/lib/utils/mask_data.js +2 -1
- package/lib/utils/pluginParser.js +151 -0
- package/lib/utils/trace.js +297 -0
- package/lib/utils/typescript.js +261 -49
- package/lib/utils.js +77 -3
- package/lib/workers.js +123 -17
- package/package.json +48 -43
- package/typings/index.d.ts +120 -9
- package/typings/promiseBasedTypes.d.ts +3243 -6057
- package/typings/types.d.ts +3541 -6506
- package/docs/webapi/amOnPage.mustache +0 -11
- package/docs/webapi/appendField.mustache +0 -11
- package/docs/webapi/attachFile.mustache +0 -12
- package/docs/webapi/blur.mustache +0 -18
- package/docs/webapi/checkOption.mustache +0 -13
- package/docs/webapi/clearCookie.mustache +0 -9
- package/docs/webapi/clearField.mustache +0 -9
- package/docs/webapi/click.mustache +0 -29
- package/docs/webapi/clickLink.mustache +0 -8
- package/docs/webapi/closeCurrentTab.mustache +0 -7
- package/docs/webapi/closeOtherTabs.mustache +0 -8
- package/docs/webapi/dontSee.mustache +0 -11
- package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/dontSeeCookie.mustache +0 -8
- package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
- package/docs/webapi/dontSeeElement.mustache +0 -8
- package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
- package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
- package/docs/webapi/dontSeeInField.mustache +0 -11
- package/docs/webapi/dontSeeInSource.mustache +0 -8
- package/docs/webapi/dontSeeInTitle.mustache +0 -8
- package/docs/webapi/dontSeeTraffic.mustache +0 -13
- package/docs/webapi/doubleClick.mustache +0 -13
- package/docs/webapi/downloadFile.mustache +0 -12
- package/docs/webapi/dragAndDrop.mustache +0 -9
- package/docs/webapi/dragSlider.mustache +0 -11
- package/docs/webapi/executeAsyncScript.mustache +0 -24
- package/docs/webapi/executeScript.mustache +0 -26
- package/docs/webapi/fillField.mustache +0 -16
- package/docs/webapi/flushNetworkTraffics.mustache +0 -5
- package/docs/webapi/focus.mustache +0 -13
- package/docs/webapi/forceClick.mustache +0 -28
- package/docs/webapi/forceRightClick.mustache +0 -18
- package/docs/webapi/grabAllWindowHandles.mustache +0 -7
- package/docs/webapi/grabAttributeFrom.mustache +0 -10
- package/docs/webapi/grabAttributeFromAll.mustache +0 -9
- package/docs/webapi/grabBrowserLogs.mustache +0 -9
- package/docs/webapi/grabCookie.mustache +0 -11
- package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
- package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
- package/docs/webapi/grabCurrentUrl.mustache +0 -9
- package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
- package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
- package/docs/webapi/grabElementBoundingRect.mustache +0 -20
- package/docs/webapi/grabGeoLocation.mustache +0 -8
- package/docs/webapi/grabHTMLFrom.mustache +0 -10
- package/docs/webapi/grabHTMLFromAll.mustache +0 -9
- package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
- package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
- package/docs/webapi/grabPageScrollPosition.mustache +0 -8
- package/docs/webapi/grabPopupText.mustache +0 -5
- package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
- package/docs/webapi/grabSource.mustache +0 -8
- package/docs/webapi/grabTextFrom.mustache +0 -10
- package/docs/webapi/grabTextFromAll.mustache +0 -9
- package/docs/webapi/grabTitle.mustache +0 -8
- package/docs/webapi/grabValueFrom.mustache +0 -9
- package/docs/webapi/grabValueFromAll.mustache +0 -8
- package/docs/webapi/grabWebElement.mustache +0 -9
- package/docs/webapi/grabWebElements.mustache +0 -9
- package/docs/webapi/moveCursorTo.mustache +0 -12
- package/docs/webapi/openNewTab.mustache +0 -7
- package/docs/webapi/pressKey.mustache +0 -12
- package/docs/webapi/pressKeyDown.mustache +0 -12
- package/docs/webapi/pressKeyUp.mustache +0 -12
- package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
- package/docs/webapi/refreshPage.mustache +0 -6
- package/docs/webapi/resizeWindow.mustache +0 -6
- package/docs/webapi/rightClick.mustache +0 -14
- package/docs/webapi/saveElementScreenshot.mustache +0 -10
- package/docs/webapi/saveScreenshot.mustache +0 -12
- package/docs/webapi/say.mustache +0 -10
- package/docs/webapi/scrollIntoView.mustache +0 -11
- package/docs/webapi/scrollPageToBottom.mustache +0 -6
- package/docs/webapi/scrollPageToTop.mustache +0 -6
- package/docs/webapi/scrollTo.mustache +0 -12
- package/docs/webapi/see.mustache +0 -11
- package/docs/webapi/seeAttributesOnElements.mustache +0 -9
- package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
- package/docs/webapi/seeCookie.mustache +0 -8
- package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
- package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
- package/docs/webapi/seeElement.mustache +0 -8
- package/docs/webapi/seeElementInDOM.mustache +0 -8
- package/docs/webapi/seeInCurrentUrl.mustache +0 -8
- package/docs/webapi/seeInField.mustache +0 -12
- package/docs/webapi/seeInPopup.mustache +0 -8
- package/docs/webapi/seeInSource.mustache +0 -7
- package/docs/webapi/seeInTitle.mustache +0 -8
- package/docs/webapi/seeNumberOfElements.mustache +0 -11
- package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/seeTextEquals.mustache +0 -9
- package/docs/webapi/seeTitleEquals.mustache +0 -8
- package/docs/webapi/seeTraffic.mustache +0 -36
- package/docs/webapi/selectOption.mustache +0 -21
- package/docs/webapi/setCookie.mustache +0 -16
- package/docs/webapi/setGeoLocation.mustache +0 -12
- package/docs/webapi/startRecordingTraffic.mustache +0 -8
- package/docs/webapi/startRecordingWebSocketMessages.mustache +0 -8
- package/docs/webapi/stopRecordingTraffic.mustache +0 -5
- package/docs/webapi/stopRecordingWebSocketMessages.mustache +0 -7
- package/docs/webapi/switchTo.mustache +0 -9
- package/docs/webapi/switchToNextTab.mustache +0 -10
- package/docs/webapi/switchToPreviousTab.mustache +0 -10
- package/docs/webapi/type.mustache +0 -21
- package/docs/webapi/uncheckOption.mustache +0 -13
- package/docs/webapi/wait.mustache +0 -8
- package/docs/webapi/waitForClickable.mustache +0 -11
- package/docs/webapi/waitForCookie.mustache +0 -9
- package/docs/webapi/waitForDetached.mustache +0 -10
- package/docs/webapi/waitForDisabled.mustache +0 -6
- package/docs/webapi/waitForElement.mustache +0 -11
- package/docs/webapi/waitForEnabled.mustache +0 -6
- package/docs/webapi/waitForFunction.mustache +0 -17
- package/docs/webapi/waitForInvisible.mustache +0 -10
- package/docs/webapi/waitForNumberOfTabs.mustache +0 -9
- package/docs/webapi/waitForText.mustache +0 -13
- package/docs/webapi/waitForValue.mustache +0 -10
- package/docs/webapi/waitForVisible.mustache +0 -10
- package/docs/webapi/waitInUrl.mustache +0 -9
- package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
- package/docs/webapi/waitToHide.mustache +0 -10
- package/docs/webapi/waitUrlEquals.mustache +0 -10
- package/lib/helper/AI.js +0 -214
- package/lib/helper/Mochawesome.js +0 -96
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -52
- package/lib/helper/extras/React.js +0 -65
- package/lib/listener/enhancedGlobalRetry.js +0 -110
- package/lib/plugin/enhancedRetryFailedStep.js +0 -99
- package/lib/plugin/htmlReporter.js +0 -3648
- package/lib/plugin/stepByStepReport.js +0 -427
- package/lib/plugin/subtitles.js +0 -89
- package/lib/retryCoordinator.js +0 -207
|
@@ -11,7 +11,7 @@ import { parentPort, workerData } from 'worker_threads'
|
|
|
11
11
|
|
|
12
12
|
// Delay imports to avoid ES Module loader race conditions in Node 22.x worker threads
|
|
13
13
|
// These will be imported dynamically when needed
|
|
14
|
-
let event, container, Codecept, getConfig, tryOrDefault, deepMerge
|
|
14
|
+
let event, container, Codecept, getConfig, tryOrDefault, deepMerge, fixErrorStack
|
|
15
15
|
|
|
16
16
|
let stdout = ''
|
|
17
17
|
|
|
@@ -19,6 +19,44 @@ const stderr = ''
|
|
|
19
19
|
|
|
20
20
|
const { options, tests, testRoot, workerIndex, poolMode } = workerData
|
|
21
21
|
|
|
22
|
+
// Global error handlers to catch critical errors but not test failures
|
|
23
|
+
process.on('uncaughtException', (err) => {
|
|
24
|
+
if (container?.tsFileMapping && fixErrorStack) {
|
|
25
|
+
const fileMapping = container.tsFileMapping()
|
|
26
|
+
if (fileMapping) {
|
|
27
|
+
fixErrorStack(err, fileMapping)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Log to stderr to bypass stdout suppression
|
|
32
|
+
process.stderr.write(`[Worker ${workerIndex}] UNCAUGHT EXCEPTION: ${err.message}\n`)
|
|
33
|
+
process.stderr.write(`${err.stack}\n`)
|
|
34
|
+
|
|
35
|
+
// Don't exit on test assertion errors - those are handled by mocha
|
|
36
|
+
if (err.name === 'AssertionError' || err.message?.includes('expected')) {
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
process.exit(1)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
43
|
+
if (reason && typeof reason === 'object' && reason.stack && container?.tsFileMapping && fixErrorStack) {
|
|
44
|
+
const fileMapping = container.tsFileMapping()
|
|
45
|
+
if (fileMapping) {
|
|
46
|
+
fixErrorStack(reason, fileMapping)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Log to stderr to bypass stdout suppression
|
|
51
|
+
const msg = reason?.message || String(reason)
|
|
52
|
+
process.stderr.write(`[Worker ${workerIndex}] UNHANDLED REJECTION: ${msg}\n`)
|
|
53
|
+
if (reason?.stack) {
|
|
54
|
+
process.stderr.write(`${reason.stack}\n`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Do not exit — killing the worker silently drops every remaining test from the report.
|
|
58
|
+
})
|
|
59
|
+
|
|
22
60
|
// hide worker output
|
|
23
61
|
// In pool mode, only suppress output if debug is NOT enabled
|
|
24
62
|
// In regular mode, hide result output but allow step output in verbose/debug
|
|
@@ -26,6 +64,10 @@ if (poolMode && !options.debug) {
|
|
|
26
64
|
// In pool mode without debug, allow test names and important output but suppress verbose details
|
|
27
65
|
const originalWrite = process.stdout.write
|
|
28
66
|
process.stdout.write = string => {
|
|
67
|
+
// Always allow Worker logs
|
|
68
|
+
if (string.includes('[Worker')) {
|
|
69
|
+
return originalWrite.call(process.stdout, string)
|
|
70
|
+
}
|
|
29
71
|
// Allow test names (✔ or ✖), Scenario Steps, failures, and important markers
|
|
30
72
|
if (
|
|
31
73
|
string.includes('✔') ||
|
|
@@ -45,7 +87,12 @@ if (poolMode && !options.debug) {
|
|
|
45
87
|
return originalWrite.call(process.stdout, string)
|
|
46
88
|
}
|
|
47
89
|
} else if (!poolMode && !options.debug && !options.verbose) {
|
|
90
|
+
const originalWrite = process.stdout.write
|
|
48
91
|
process.stdout.write = string => {
|
|
92
|
+
// Always allow Worker logs
|
|
93
|
+
if (string.includes('[Worker')) {
|
|
94
|
+
return originalWrite.call(process.stdout, string)
|
|
95
|
+
}
|
|
49
96
|
stdout += string
|
|
50
97
|
return true
|
|
51
98
|
}
|
|
@@ -82,30 +129,69 @@ let config
|
|
|
82
129
|
// Load test and run
|
|
83
130
|
initPromise = (async function () {
|
|
84
131
|
try {
|
|
132
|
+
// Add staggered delay at the very start to prevent resource conflicts
|
|
133
|
+
// Longer delay for browser initialization conflicts
|
|
134
|
+
const delay = (workerIndex - 1) * 2000 // 0ms, 2s, 4s, etc.
|
|
135
|
+
if (delay > 0) {
|
|
136
|
+
await new Promise(resolve => setTimeout(resolve, delay))
|
|
137
|
+
}
|
|
138
|
+
|
|
85
139
|
// Import modules dynamically to avoid ES Module loader race conditions in Node 22.x
|
|
86
140
|
const eventModule = await import('../../event.js')
|
|
87
141
|
const containerModule = await import('../../container.js')
|
|
88
142
|
const utilsModule = await import('../utils.js')
|
|
89
143
|
const coreUtilsModule = await import('../../utils.js')
|
|
90
144
|
const CodeceptModule = await import('../../codecept.js')
|
|
91
|
-
|
|
145
|
+
const typescriptModule = await import('../../utils/typescript.js')
|
|
146
|
+
|
|
92
147
|
event = eventModule.default
|
|
93
148
|
container = containerModule.default
|
|
94
149
|
getConfig = utilsModule.getConfig
|
|
95
150
|
tryOrDefault = coreUtilsModule.tryOrDefault
|
|
96
151
|
deepMerge = coreUtilsModule.deepMerge
|
|
97
152
|
Codecept = CodeceptModule.default
|
|
153
|
+
fixErrorStack = typescriptModule.fixErrorStack
|
|
98
154
|
|
|
99
155
|
const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {})
|
|
100
156
|
|
|
101
|
-
|
|
102
|
-
|
|
157
|
+
let baseConfig
|
|
158
|
+
try {
|
|
159
|
+
// IMPORTANT: await is required here since getConfig is async
|
|
160
|
+
baseConfig = await getConfig(options.config || testRoot)
|
|
161
|
+
} catch (configErr) {
|
|
162
|
+
if (container?.tsFileMapping && fixErrorStack) {
|
|
163
|
+
const fileMapping = container.tsFileMapping()
|
|
164
|
+
if (fileMapping) {
|
|
165
|
+
fixErrorStack(configErr, fileMapping)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
process.stderr.write(`[Worker ${workerIndex}] FAILED loading config: ${configErr.message}\n`)
|
|
169
|
+
process.stderr.write(`${configErr.stack}\n`)
|
|
170
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
171
|
+
process.exit(1)
|
|
172
|
+
}
|
|
103
173
|
|
|
104
174
|
// important deep merge so dynamic things e.g. functions on config are not overridden
|
|
105
175
|
config = deepMerge(baseConfig, overrideConfigs)
|
|
106
176
|
|
|
107
|
-
|
|
108
|
-
|
|
177
|
+
// Pass workerIndex as child option for output.process() to display worker prefix
|
|
178
|
+
const optsWithChild = { ...options, child: workerIndex }
|
|
179
|
+
codecept = new Codecept(config, optsWithChild)
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
await codecept.init(testRoot)
|
|
183
|
+
} catch (initErr) {
|
|
184
|
+
if (container?.tsFileMapping && fixErrorStack) {
|
|
185
|
+
const fileMapping = container.tsFileMapping()
|
|
186
|
+
if (fileMapping) {
|
|
187
|
+
fixErrorStack(initErr, fileMapping)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
process.stderr.write(`[Worker ${workerIndex}] FAILED during codecept.init(): ${initErr.message}\n`)
|
|
191
|
+
process.stderr.write(`${initErr.stack}\n`)
|
|
192
|
+
process.exit(1)
|
|
193
|
+
}
|
|
194
|
+
|
|
109
195
|
codecept.loadTests()
|
|
110
196
|
mocha = container.mocha()
|
|
111
197
|
|
|
@@ -124,10 +210,18 @@ initPromise = (async function () {
|
|
|
124
210
|
await runTests()
|
|
125
211
|
} else {
|
|
126
212
|
// No tests to run, close the worker
|
|
213
|
+
console.error(`[Worker ${workerIndex}] ERROR: No tests found after filtering! Assigned ${tests.length} UIDs but none matched.`)
|
|
127
214
|
parentPort?.close()
|
|
128
215
|
}
|
|
129
216
|
} catch (err) {
|
|
130
|
-
|
|
217
|
+
if (container?.tsFileMapping && fixErrorStack) {
|
|
218
|
+
const fileMapping = container.tsFileMapping()
|
|
219
|
+
if (fileMapping) {
|
|
220
|
+
fixErrorStack(err, fileMapping)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
process.stderr.write(`[Worker ${workerIndex}] FATAL ERROR: ${err.message}\n`)
|
|
224
|
+
process.stderr.write(`${err.stack}\n`)
|
|
131
225
|
process.exit(1)
|
|
132
226
|
}
|
|
133
227
|
})()
|
|
@@ -145,8 +239,14 @@ async function runTests() {
|
|
|
145
239
|
disablePause()
|
|
146
240
|
try {
|
|
147
241
|
await codecept.run()
|
|
242
|
+
} catch (err) {
|
|
243
|
+
throw err
|
|
148
244
|
} finally {
|
|
149
|
-
|
|
245
|
+
try {
|
|
246
|
+
await codecept.teardown()
|
|
247
|
+
} catch (err) {
|
|
248
|
+
// Ignore teardown errors
|
|
249
|
+
}
|
|
150
250
|
}
|
|
151
251
|
}
|
|
152
252
|
|
|
@@ -334,8 +434,16 @@ function filterTests() {
|
|
|
334
434
|
mocha.files = files
|
|
335
435
|
mocha.loadFiles()
|
|
336
436
|
|
|
337
|
-
|
|
437
|
+
// Recursively filter tests in all suites (including nested ones)
|
|
438
|
+
const filterSuiteTests = (suite) => {
|
|
338
439
|
suite.tests = suite.tests.filter(test => tests.indexOf(test.uid) >= 0)
|
|
440
|
+
for (const childSuite of suite.suites) {
|
|
441
|
+
filterSuiteTests(childSuite)
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
for (const suite of mocha.suite.suites) {
|
|
446
|
+
filterSuiteTests(suite)
|
|
339
447
|
}
|
|
340
448
|
}
|
|
341
449
|
|
package/lib/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'fs'
|
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import { createRequire } from 'module'
|
|
4
4
|
import { fileExists, isFile, deepMerge, deepClone } from './utils.js'
|
|
5
|
-
import { transpileTypeScript, cleanupTempFiles } from './utils/typescript.js'
|
|
5
|
+
import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
|
|
6
6
|
|
|
7
7
|
const defaultConfig = {
|
|
8
8
|
output: './_output',
|
|
@@ -15,8 +15,9 @@ const defaultConfig = {
|
|
|
15
15
|
hooks: [],
|
|
16
16
|
gherkin: {},
|
|
17
17
|
plugins: {
|
|
18
|
-
|
|
19
|
-
enabled: true,
|
|
18
|
+
screenshot: {
|
|
19
|
+
enabled: true,
|
|
20
|
+
on: 'fail',
|
|
20
21
|
},
|
|
21
22
|
},
|
|
22
23
|
stepTimeout: 0,
|
|
@@ -32,9 +33,27 @@ const defaultConfig = {
|
|
|
32
33
|
],
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
// Array<{ fn: (cfg) => void, ran: boolean, error?: Error }>
|
|
35
37
|
let hooks = []
|
|
36
38
|
let config = {}
|
|
37
39
|
|
|
40
|
+
// Apply a single hook against `cfg`, swallowing errors so one broken hook
|
|
41
|
+
// can't take down the whole run. The failure is logged through the
|
|
42
|
+
// framework's own output module (when available) so it shows up in test
|
|
43
|
+
// reports; the hook is still marked ran so it doesn't get retried.
|
|
44
|
+
function applyHook(hook, cfg) {
|
|
45
|
+
try {
|
|
46
|
+
hook.fn(cfg)
|
|
47
|
+
} catch (err) {
|
|
48
|
+
hook.error = err
|
|
49
|
+
const out = globalThis.codeceptjs?.output
|
|
50
|
+
if (out && typeof out.error === 'function') out.error(`config hook failed: ${err.message}`)
|
|
51
|
+
else console.error('config hook failed:', err)
|
|
52
|
+
} finally {
|
|
53
|
+
hook.ran = true
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
38
57
|
const configFileNames = ['codecept.config.js', 'codecept.conf.js', 'codecept.js', 'codecept.config.cjs', 'codecept.conf.cjs', 'codecept.config.ts', 'codecept.conf.ts']
|
|
39
58
|
|
|
40
59
|
/**
|
|
@@ -49,7 +68,11 @@ class Config {
|
|
|
49
68
|
*/
|
|
50
69
|
static create(newConfig) {
|
|
51
70
|
config = deepMerge(deepClone(defaultConfig), newConfig)
|
|
52
|
-
hooks
|
|
71
|
+
// Re-apply every hook against the freshly built config; hooks added later
|
|
72
|
+
// (e.g. from plugin boot) stay pending until runPendingHooks. Array
|
|
73
|
+
// iterators re-check length on each step, so hooks pushed during a hook
|
|
74
|
+
// execution are visited in this same pass.
|
|
75
|
+
for (const hook of hooks) applyHook(hook, config)
|
|
53
76
|
return config
|
|
54
77
|
}
|
|
55
78
|
|
|
@@ -121,7 +144,48 @@ class Config {
|
|
|
121
144
|
}
|
|
122
145
|
|
|
123
146
|
static addHook(fn) {
|
|
124
|
-
hooks.push(fn)
|
|
147
|
+
hooks.push({ fn, ran: false })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Run every hook that hasn't been applied to the current config yet.
|
|
152
|
+
* Hooks added after `Config.create()` (e.g. from plugin boot code) stay
|
|
153
|
+
* pending until this is called; once it runs, they're marked applied so
|
|
154
|
+
* subsequent calls are no-ops. Hooks added while pending hooks are running
|
|
155
|
+
* are picked up in the same pass (the array iterator re-checks length).
|
|
156
|
+
*
|
|
157
|
+
* Failures are logged through `output.error` and don't abort the loop —
|
|
158
|
+
* a broken hook can't poison the run, but its error is visible.
|
|
159
|
+
*
|
|
160
|
+
* @param {Object<string, *>} [cfg] target config (defaults to the live singleton)
|
|
161
|
+
* @return {boolean} true if any hook ran
|
|
162
|
+
*/
|
|
163
|
+
static runPendingHooks(cfg = config) {
|
|
164
|
+
let ran = false
|
|
165
|
+
for (const hook of hooks) {
|
|
166
|
+
if (hook.ran) continue
|
|
167
|
+
applyHook(hook, cfg)
|
|
168
|
+
ran = true
|
|
169
|
+
}
|
|
170
|
+
return ran
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Number of registered config hooks. Useful for snapshotting before a phase
|
|
175
|
+
* (e.g. plugin loading) and re-running only the hooks added during it.
|
|
176
|
+
* @return {number}
|
|
177
|
+
*/
|
|
178
|
+
static hooksCount() {
|
|
179
|
+
return hooks.length
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Run hooks in `[fromIndex, end)` against the given config object, mutating it.
|
|
184
|
+
* @param {number} fromIndex
|
|
185
|
+
* @param {Object<string, *>} cfg
|
|
186
|
+
*/
|
|
187
|
+
static runHooksFrom(fromIndex, cfg) {
|
|
188
|
+
for (let i = fromIndex; i < hooks.length; i++) hooks[i](cfg)
|
|
125
189
|
}
|
|
126
190
|
|
|
127
191
|
/**
|
|
@@ -150,32 +214,47 @@ async function loadConfigFile(configFile) {
|
|
|
150
214
|
const require = createRequire(import.meta.url)
|
|
151
215
|
const extensionName = path.extname(configFile)
|
|
152
216
|
|
|
217
|
+
// Populate the in-process registry that packages like @codeceptjs/configure
|
|
218
|
+
// look up at config-import time (their proxies throw if `globalThis.codeceptjs`
|
|
219
|
+
// is missing). initCodeceptGlobals sets this too, but only later during
|
|
220
|
+
// bootstrap — config files are imported here first.
|
|
221
|
+
if (!globalThis.codeceptjs) {
|
|
222
|
+
const indexModule = await import('./index.js')
|
|
223
|
+
globalThis.codeceptjs = indexModule.default || indexModule
|
|
224
|
+
}
|
|
225
|
+
|
|
153
226
|
// .conf.js config file
|
|
154
227
|
if (extensionName === '.js' || extensionName === '.ts' || extensionName === '.cjs') {
|
|
155
228
|
let configModule
|
|
156
229
|
try {
|
|
157
230
|
// For .ts files, try to compile and load as JavaScript
|
|
158
231
|
if (extensionName === '.ts') {
|
|
232
|
+
let transpileError = null
|
|
233
|
+
let tempFile = null
|
|
234
|
+
let allTempFiles = null
|
|
235
|
+
let fileMapping = null
|
|
236
|
+
|
|
159
237
|
try {
|
|
160
238
|
// Use the TypeScript transpilation utility
|
|
161
239
|
const typescript = require('typescript')
|
|
162
|
-
const
|
|
240
|
+
const result = await transpileTypeScript(configFile, typescript)
|
|
241
|
+
tempFile = result.tempFile
|
|
242
|
+
allTempFiles = result.allTempFiles
|
|
243
|
+
fileMapping = result.fileMapping
|
|
163
244
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
245
|
+
configModule = await import(tempFile)
|
|
246
|
+
cleanupTempFiles(allTempFiles)
|
|
247
|
+
} catch (err) {
|
|
248
|
+
transpileError = err
|
|
249
|
+
if (fileMapping) {
|
|
250
|
+
fixErrorStack(err, fileMapping)
|
|
170
251
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
try {
|
|
174
|
-
require('ts-node/register')
|
|
175
|
-
configModule = require(configFile)
|
|
176
|
-
} catch (tsNodeError) {
|
|
177
|
-
throw new Error(`Failed to load TypeScript config: ${tsError.message}`)
|
|
252
|
+
if (allTempFiles) {
|
|
253
|
+
cleanupTempFiles(allTempFiles)
|
|
178
254
|
}
|
|
255
|
+
// Throw immediately with the actual error - don't fall back to ts-node
|
|
256
|
+
// as it will mask the real error with "Unexpected token 'export'"
|
|
257
|
+
throw err
|
|
179
258
|
}
|
|
180
259
|
} else {
|
|
181
260
|
// Try ESM import first for JS files
|