codeceptjs 4.0.2-beta.8 → 4.0.2
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 +15 -2
- 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 +6 -7
- 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 -1
- package/lib/command/run-workers.js +2 -14
- package/lib/command/run.js +3 -17
- package/lib/command/utils.js +14 -0
- package/lib/command/workers/runTests.js +91 -37
- package/lib/config.js +96 -18
- package/lib/container.js +115 -17
- 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 +358 -467
- package/lib/helper/Puppeteer.js +335 -192
- 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 +4 -1
- 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 +11 -1
- package/lib/mocha/inject.js +1 -1
- package/lib/mocha/scenarioConfig.js +2 -1
- package/lib/mocha/ui.js +5 -6
- 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 +10 -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 +5 -5
- package/lib/store.js +72 -3
- package/lib/translation.js +2 -1
- package/lib/utils/loaderCheck.js +28 -0
- 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 +188 -23
- package/lib/utils.js +77 -3
- package/lib/workers.js +65 -40
- package/package.json +35 -30
- package/typings/index.d.ts +119 -8
- package/typings/promiseBasedTypes.d.ts +3158 -6065
- package/typings/types.d.ts +3453 -6494
- 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
package/lib/utils/typescript.js
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
import fs from 'fs'
|
|
2
2
|
import path from 'path'
|
|
3
|
+
import { pathToFileURL } from 'url'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Load tsconfig.json if it exists
|
|
7
|
+
* @param {string} tsConfigPath - Path to tsconfig.json
|
|
8
|
+
* @returns {object|null} - Parsed tsconfig or null
|
|
9
|
+
*/
|
|
10
|
+
function loadTsConfig(tsConfigPath) {
|
|
11
|
+
if (!fs.existsSync(tsConfigPath)) {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const tsConfigContent = fs.readFileSync(tsConfigPath, 'utf8')
|
|
17
|
+
return JSON.parse(tsConfigContent)
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return null
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolve TypeScript path alias to actual file path
|
|
25
|
+
* @param {string} importPath - Import path with alias (e.g., '#config/urls')
|
|
26
|
+
* @param {object} tsConfig - Parsed tsconfig.json
|
|
27
|
+
* @param {string} configDir - Directory containing tsconfig.json
|
|
28
|
+
* @returns {string|null} - Resolved file path or null if not an alias
|
|
29
|
+
*/
|
|
30
|
+
function resolveTsPathAlias(importPath, tsConfig, configDir) {
|
|
31
|
+
if (!tsConfig || !tsConfig.compilerOptions || !tsConfig.compilerOptions.paths) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const paths = tsConfig.compilerOptions.paths
|
|
36
|
+
|
|
37
|
+
for (const [pattern, targets] of Object.entries(paths)) {
|
|
38
|
+
if (!targets || targets.length === 0) {
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const patternRegex = new RegExp(
|
|
43
|
+
'^' + pattern.replace(/\*/g, '(.*)') + '$'
|
|
44
|
+
)
|
|
45
|
+
const match = importPath.match(patternRegex)
|
|
46
|
+
|
|
47
|
+
if (match) {
|
|
48
|
+
const wildcard = match[1] || ''
|
|
49
|
+
const target = targets[0]
|
|
50
|
+
const resolvedTarget = target.replace(/\*/g, wildcard)
|
|
51
|
+
|
|
52
|
+
return path.resolve(configDir, resolvedTarget)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null
|
|
57
|
+
}
|
|
3
58
|
|
|
4
59
|
/**
|
|
5
60
|
* Transpile TypeScript files to ES modules with CommonJS shim support
|
|
@@ -108,6 +163,22 @@ const __dirname = __dirname_fn(__filename);
|
|
|
108
163
|
const transpiledFiles = new Map()
|
|
109
164
|
const baseDir = path.dirname(mainFilePath)
|
|
110
165
|
|
|
166
|
+
// Try to find tsconfig.json by walking up the directory tree
|
|
167
|
+
let tsConfigPath = path.join(baseDir, 'tsconfig.json')
|
|
168
|
+
let configDir = baseDir
|
|
169
|
+
let searchDir = baseDir
|
|
170
|
+
|
|
171
|
+
while (!fs.existsSync(tsConfigPath) && searchDir !== path.dirname(searchDir)) {
|
|
172
|
+
searchDir = path.dirname(searchDir)
|
|
173
|
+
tsConfigPath = path.join(searchDir, 'tsconfig.json')
|
|
174
|
+
if (fs.existsSync(tsConfigPath)) {
|
|
175
|
+
configDir = searchDir
|
|
176
|
+
break
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const tsConfig = loadTsConfig(tsConfigPath)
|
|
181
|
+
|
|
111
182
|
// Recursive function to transpile a file and all its TypeScript dependencies
|
|
112
183
|
const transpileFileAndDeps = (filePath) => {
|
|
113
184
|
// Already transpiled, skip
|
|
@@ -118,21 +189,36 @@ const __dirname = __dirname_fn(__filename);
|
|
|
118
189
|
// Transpile this file
|
|
119
190
|
let jsContent = transpileTS(filePath)
|
|
120
191
|
|
|
121
|
-
// Find all
|
|
122
|
-
const importRegex = /from\s+['"](
|
|
192
|
+
// Find all TypeScript imports in this file (both ESM imports and require() calls)
|
|
193
|
+
const importRegex = /from\s+['"]([^'"]+?)['"]/g
|
|
194
|
+
const requireRegex = /require\s*\(\s*['"]([^'"]+?)['"]\s*\)/g
|
|
123
195
|
let match
|
|
124
196
|
const imports = []
|
|
125
197
|
|
|
126
198
|
while ((match = importRegex.exec(jsContent)) !== null) {
|
|
127
|
-
imports.push(match[1])
|
|
199
|
+
imports.push({ path: match[1], type: 'import' })
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
while ((match = requireRegex.exec(jsContent)) !== null) {
|
|
203
|
+
imports.push({ path: match[1], type: 'require' })
|
|
128
204
|
}
|
|
129
205
|
|
|
130
206
|
// Get the base directory for this file
|
|
131
207
|
const fileBaseDir = path.dirname(filePath)
|
|
132
208
|
|
|
133
209
|
// Recursively transpile each imported TypeScript file
|
|
134
|
-
for (const
|
|
135
|
-
let importedPath =
|
|
210
|
+
for (const { path: importPath } of imports) {
|
|
211
|
+
let importedPath = importPath
|
|
212
|
+
|
|
213
|
+
// Check if this is a path alias
|
|
214
|
+
const resolvedAlias = resolveTsPathAlias(importPath, tsConfig, configDir)
|
|
215
|
+
if (resolvedAlias) {
|
|
216
|
+
importedPath = resolvedAlias
|
|
217
|
+
} else if (importPath.startsWith('.')) {
|
|
218
|
+
importedPath = path.resolve(fileBaseDir, importPath)
|
|
219
|
+
} else {
|
|
220
|
+
continue
|
|
221
|
+
}
|
|
136
222
|
|
|
137
223
|
// Handle .js extensions that might actually be .ts files
|
|
138
224
|
if (importedPath.endsWith('.js')) {
|
|
@@ -153,11 +239,17 @@ const __dirname = __dirname_fn(__filename);
|
|
|
153
239
|
if (fs.existsSync(tsPath)) {
|
|
154
240
|
importedPath = tsPath
|
|
155
241
|
} else {
|
|
156
|
-
// Try .
|
|
157
|
-
const
|
|
158
|
-
if (fs.existsSync(
|
|
159
|
-
|
|
160
|
-
|
|
242
|
+
// Try index.ts for directory imports
|
|
243
|
+
const indexTsPath = path.join(importedPath, 'index.ts')
|
|
244
|
+
if (fs.existsSync(indexTsPath)) {
|
|
245
|
+
importedPath = indexTsPath
|
|
246
|
+
} else {
|
|
247
|
+
// Try .js extension as well
|
|
248
|
+
const jsPath = importedPath + '.js'
|
|
249
|
+
if (fs.existsSync(jsPath)) {
|
|
250
|
+
// Skip .js files, they don't need transpilation
|
|
251
|
+
continue
|
|
252
|
+
}
|
|
161
253
|
}
|
|
162
254
|
}
|
|
163
255
|
}
|
|
@@ -170,24 +262,45 @@ const __dirname = __dirname_fn(__filename);
|
|
|
170
262
|
|
|
171
263
|
// After all dependencies are transpiled, rewrite imports in this file
|
|
172
264
|
jsContent = jsContent.replace(
|
|
173
|
-
/from\s+['"](
|
|
265
|
+
/from\s+['"]([^'"]+?)['"]/g,
|
|
174
266
|
(match, importPath) => {
|
|
175
|
-
let resolvedPath =
|
|
267
|
+
let resolvedPath = importPath
|
|
176
268
|
const originalExt = path.extname(importPath)
|
|
177
269
|
|
|
270
|
+
// Check if this is a path alias
|
|
271
|
+
const resolvedAlias = resolveTsPathAlias(importPath, tsConfig, configDir)
|
|
272
|
+
if (resolvedAlias) {
|
|
273
|
+
resolvedPath = resolvedAlias
|
|
274
|
+
} else if (importPath.startsWith('.')) {
|
|
275
|
+
resolvedPath = path.resolve(fileBaseDir, importPath)
|
|
276
|
+
} else {
|
|
277
|
+
return match
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// If resolved path is a directory, try index.ts
|
|
281
|
+
if (fs.existsSync(resolvedPath) && fs.statSync(resolvedPath).isDirectory()) {
|
|
282
|
+
const indexPath = path.join(resolvedPath, 'index.ts')
|
|
283
|
+
if (fs.existsSync(indexPath) && transpiledFiles.has(indexPath)) {
|
|
284
|
+
const tempFile = transpiledFiles.get(indexPath)
|
|
285
|
+
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
|
|
286
|
+
if (!relPath.startsWith('.')) {
|
|
287
|
+
return `from './${relPath}'`
|
|
288
|
+
}
|
|
289
|
+
return `from '${relPath}'`
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
178
293
|
// Handle .js extension that might be .ts
|
|
179
294
|
if (resolvedPath.endsWith('.js')) {
|
|
180
295
|
const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
|
|
181
296
|
if (transpiledFiles.has(tsVersion)) {
|
|
182
297
|
const tempFile = transpiledFiles.get(tsVersion)
|
|
183
298
|
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
|
|
184
|
-
// Ensure the path starts with ./
|
|
185
299
|
if (!relPath.startsWith('.')) {
|
|
186
300
|
return `from './${relPath}'`
|
|
187
301
|
}
|
|
188
302
|
return `from '${relPath}'`
|
|
189
303
|
}
|
|
190
|
-
// Keep .js extension as-is (might be a real .js file)
|
|
191
304
|
return match
|
|
192
305
|
}
|
|
193
306
|
|
|
@@ -198,18 +311,24 @@ const __dirname = __dirname_fn(__filename);
|
|
|
198
311
|
if (transpiledFiles.has(tsPath)) {
|
|
199
312
|
const tempFile = transpiledFiles.get(tsPath)
|
|
200
313
|
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
|
|
201
|
-
// Ensure the path starts with ./
|
|
202
314
|
if (!relPath.startsWith('.')) {
|
|
203
315
|
return `from './${relPath}'`
|
|
204
316
|
}
|
|
205
317
|
return `from '${relPath}'`
|
|
206
318
|
}
|
|
207
319
|
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
320
|
+
// Try index.ts for directory imports
|
|
321
|
+
const indexTsPath = path.join(resolvedPath, 'index.ts')
|
|
322
|
+
if (transpiledFiles.has(indexTsPath)) {
|
|
323
|
+
const tempFile = transpiledFiles.get(indexTsPath)
|
|
324
|
+
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
|
|
325
|
+
if (!relPath.startsWith('.')) {
|
|
326
|
+
return `from './${relPath}'`
|
|
327
|
+
}
|
|
328
|
+
return `from '${relPath}'`
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// If the import doesn't have a standard module extension, add .js for ESM compatibility
|
|
213
332
|
const standardExtensions = ['.js', '.mjs', '.cjs', '.json', '.node']
|
|
214
333
|
const hasStandardExtension = standardExtensions.includes(originalExt.toLowerCase())
|
|
215
334
|
|
|
@@ -217,7 +336,50 @@ const __dirname = __dirname_fn(__filename);
|
|
|
217
336
|
return match.replace(importPath, importPath + '.js')
|
|
218
337
|
}
|
|
219
338
|
|
|
220
|
-
|
|
339
|
+
return match
|
|
340
|
+
}
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
// Also rewrite require() calls to point to transpiled TypeScript files
|
|
344
|
+
jsContent = jsContent.replace(
|
|
345
|
+
/require\s*\(\s*['"]([^'"]+?)['"]\s*\)/g,
|
|
346
|
+
(match, requirePath) => {
|
|
347
|
+
let resolvedPath = requirePath
|
|
348
|
+
|
|
349
|
+
// Check if this is a path alias
|
|
350
|
+
const resolvedAlias = resolveTsPathAlias(requirePath, tsConfig, configDir)
|
|
351
|
+
if (resolvedAlias) {
|
|
352
|
+
resolvedPath = resolvedAlias
|
|
353
|
+
} else if (requirePath.startsWith('.')) {
|
|
354
|
+
resolvedPath = path.resolve(fileBaseDir, requirePath)
|
|
355
|
+
} else {
|
|
356
|
+
return match
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Handle .js extension that might be .ts
|
|
360
|
+
if (resolvedPath.endsWith('.js')) {
|
|
361
|
+
const tsVersion = resolvedPath.replace(/\.js$/, '.ts')
|
|
362
|
+
if (transpiledFiles.has(tsVersion)) {
|
|
363
|
+
const tempFile = transpiledFiles.get(tsVersion)
|
|
364
|
+
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
|
|
365
|
+
const finalPath = relPath.startsWith('.') ? relPath : './' + relPath
|
|
366
|
+
return `require('${finalPath}')`
|
|
367
|
+
}
|
|
368
|
+
return match
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Try with .ts extension
|
|
372
|
+
const tsPath = resolvedPath.endsWith('.ts') ? resolvedPath : resolvedPath + '.ts'
|
|
373
|
+
|
|
374
|
+
// If we transpiled this file, use the temp file
|
|
375
|
+
if (transpiledFiles.has(tsPath)) {
|
|
376
|
+
const tempFile = transpiledFiles.get(tsPath)
|
|
377
|
+
const relPath = path.relative(fileBaseDir, tempFile).replace(/\\/g, '/')
|
|
378
|
+
const finalPath = relPath.startsWith('.') ? relPath : './' + relPath
|
|
379
|
+
return `require('${finalPath}')`
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Otherwise, keep the require as-is
|
|
221
383
|
return match
|
|
222
384
|
}
|
|
223
385
|
)
|
|
@@ -234,10 +396,13 @@ const __dirname = __dirname_fn(__filename);
|
|
|
234
396
|
// Get the main transpiled file
|
|
235
397
|
const tempJsFile = transpiledFiles.get(mainFilePath)
|
|
236
398
|
|
|
237
|
-
//
|
|
399
|
+
// Convert to file:// URL for dynamic import() (required on Windows)
|
|
400
|
+
const tempFileUrl = pathToFileURL(tempJsFile).href
|
|
401
|
+
|
|
402
|
+
// Store all temp files for cleanup (keep as paths, not URLs)
|
|
238
403
|
const allTempFiles = Array.from(transpiledFiles.values())
|
|
239
404
|
|
|
240
|
-
return { tempFile:
|
|
405
|
+
return { tempFile: tempFileUrl, allTempFiles, fileMapping: transpiledFiles }
|
|
241
406
|
}
|
|
242
407
|
|
|
243
408
|
/**
|
package/lib/utils.js
CHANGED
|
@@ -7,6 +7,7 @@ import getFunctionArguments from 'fn-args'
|
|
|
7
7
|
import deepClone from 'lodash.clonedeep'
|
|
8
8
|
import merge from 'lodash.merge'
|
|
9
9
|
import { convertColorToRGBA, isColorProperty } from './colorUtils.js'
|
|
10
|
+
import store from './store.js'
|
|
10
11
|
import Fuse from 'fuse.js'
|
|
11
12
|
import crypto from 'crypto'
|
|
12
13
|
import jsBeautify from 'js-beautify'
|
|
@@ -150,6 +151,24 @@ export const decodeUrl = function (url) {
|
|
|
150
151
|
return decodeURIComponent(decodeURIComponent(decodeURIComponent(url)))
|
|
151
152
|
}
|
|
152
153
|
|
|
154
|
+
export const normalizePath = function (path) {
|
|
155
|
+
if (path === '' || path === '/') return '/'
|
|
156
|
+
return path
|
|
157
|
+
.replace(/\/+/g, '/')
|
|
158
|
+
.replace(/\/$/, '') || '/'
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const resolveUrl = function (url, baseUrl) {
|
|
162
|
+
if (!url) return url
|
|
163
|
+
if (url.indexOf('http') === 0) return url
|
|
164
|
+
if (!baseUrl) return url
|
|
165
|
+
try {
|
|
166
|
+
return new URL(url, baseUrl).href
|
|
167
|
+
} catch (e) {
|
|
168
|
+
return url
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
153
172
|
export const xpathLocator = {
|
|
154
173
|
/**
|
|
155
174
|
* @param {string} string
|
|
@@ -317,13 +336,13 @@ export const screenshotOutputFolder = function (fileName) {
|
|
|
317
336
|
const fileSep = path.sep
|
|
318
337
|
|
|
319
338
|
if (!fileName.includes(fileSep) || fileName.includes('record_')) {
|
|
320
|
-
return path.resolve(
|
|
339
|
+
return path.resolve(store.outputDir, fileName)
|
|
321
340
|
}
|
|
322
|
-
return path.resolve(
|
|
341
|
+
return path.resolve(store.codeceptDir, fileName)
|
|
323
342
|
}
|
|
324
343
|
|
|
325
344
|
export const relativeDir = function (fileName) {
|
|
326
|
-
return fileName.replace(
|
|
345
|
+
return fileName.replace(store.codeceptDir, '').replace(/^\//, '')
|
|
327
346
|
}
|
|
328
347
|
|
|
329
348
|
export const beautify = function (code) {
|
|
@@ -598,6 +617,12 @@ function createCircularSafeReplacer(keysToSkip = []) {
|
|
|
598
617
|
return undefined
|
|
599
618
|
}
|
|
600
619
|
|
|
620
|
+
// Coerce types that JSON.stringify can't handle natively
|
|
621
|
+
if (typeof value === 'function') return `[Function: ${value.name || 'anonymous'}]`
|
|
622
|
+
if (typeof value === 'bigint') return `${value.toString()}n`
|
|
623
|
+
if (typeof value === 'symbol') return value.toString()
|
|
624
|
+
if (value instanceof Error) return { name: value.name, message: value.message, stack: value.stack }
|
|
625
|
+
|
|
601
626
|
if (value === null || typeof value !== 'object') {
|
|
602
627
|
return value
|
|
603
628
|
}
|
|
@@ -628,6 +653,25 @@ export const safeStringify = function (obj, keysToSkip = [], space = 0) {
|
|
|
628
653
|
}
|
|
629
654
|
}
|
|
630
655
|
|
|
656
|
+
/**
|
|
657
|
+
* Truncate a string at a byte cap, returning structured info.
|
|
658
|
+
* @param {string} str
|
|
659
|
+
* @param {number} maxBytes
|
|
660
|
+
* @returns {{ value: string, truncated: boolean, fullLength: number }}
|
|
661
|
+
*/
|
|
662
|
+
export const truncateString = function (str, maxBytes) {
|
|
663
|
+
if (typeof str !== 'string') str = String(str)
|
|
664
|
+
if (str.length <= maxBytes) {
|
|
665
|
+
return { value: str, truncated: false, fullLength: str.length }
|
|
666
|
+
}
|
|
667
|
+
const dropped = str.length - maxBytes
|
|
668
|
+
return {
|
|
669
|
+
value: `${str.slice(0, maxBytes)}\n...[truncated ${dropped} more chars]`,
|
|
670
|
+
truncated: true,
|
|
671
|
+
fullLength: str.length,
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
631
675
|
export const serializeError = function (error) {
|
|
632
676
|
if (error) {
|
|
633
677
|
const { stack, uncaught, message, actual, expected } = error
|
|
@@ -640,6 +684,36 @@ export const base64EncodeFile = function (filePath) {
|
|
|
640
684
|
return Buffer.from(fs.readFileSync(filePath)).toString('base64')
|
|
641
685
|
}
|
|
642
686
|
|
|
687
|
+
export const getMimeType = function (fileName) {
|
|
688
|
+
const ext = path.extname(fileName).toLowerCase()
|
|
689
|
+
const mimeTypes = {
|
|
690
|
+
'.jpg': 'image/jpeg',
|
|
691
|
+
'.jpeg': 'image/jpeg',
|
|
692
|
+
'.png': 'image/png',
|
|
693
|
+
'.gif': 'image/gif',
|
|
694
|
+
'.bmp': 'image/bmp',
|
|
695
|
+
'.svg': 'image/svg+xml',
|
|
696
|
+
'.webp': 'image/webp',
|
|
697
|
+
'.pdf': 'application/pdf',
|
|
698
|
+
'.txt': 'text/plain',
|
|
699
|
+
'.html': 'text/html',
|
|
700
|
+
'.css': 'text/css',
|
|
701
|
+
'.js': 'application/javascript',
|
|
702
|
+
'.json': 'application/json',
|
|
703
|
+
'.xml': 'application/xml',
|
|
704
|
+
'.zip': 'application/zip',
|
|
705
|
+
'.csv': 'text/csv',
|
|
706
|
+
'.doc': 'application/msword',
|
|
707
|
+
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
708
|
+
'.xls': 'application/vnd.ms-excel',
|
|
709
|
+
'.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
710
|
+
'.mp3': 'audio/mpeg',
|
|
711
|
+
'.mp4': 'video/mp4',
|
|
712
|
+
'.wav': 'audio/wav',
|
|
713
|
+
}
|
|
714
|
+
return mimeTypes[ext] || 'application/octet-stream'
|
|
715
|
+
}
|
|
716
|
+
|
|
643
717
|
export const markdownToAnsi = function (markdown) {
|
|
644
718
|
return (
|
|
645
719
|
markdown
|
package/lib/workers.js
CHANGED
|
@@ -20,6 +20,7 @@ import event from './event.js'
|
|
|
20
20
|
import { deserializeTest } from './mocha/test.js'
|
|
21
21
|
import { deserializeSuite } from './mocha/suite.js'
|
|
22
22
|
import recorder from './recorder.js'
|
|
23
|
+
import store from './store.js'
|
|
23
24
|
import runHook from './hooks.js'
|
|
24
25
|
import WorkerStorage from './workerStorage.js'
|
|
25
26
|
import { createRuns } from './command/run-multiple/collection.js'
|
|
@@ -28,7 +29,7 @@ const pathToWorker = path.join(__dirname, 'command', 'workers', 'runTests.js')
|
|
|
28
29
|
|
|
29
30
|
const initializeCodecept = async (configPath, options = {}) => {
|
|
30
31
|
const config = await mainConfig.load(configPath || '.')
|
|
31
|
-
const codecept = new Codecept(config, options)
|
|
32
|
+
const codecept = new Codecept(config, { ...options, skipDefaultListeners: true })
|
|
32
33
|
await codecept.init(getTestRoot(configPath))
|
|
33
34
|
codecept.loadTests()
|
|
34
35
|
|
|
@@ -117,11 +118,9 @@ const createWorkerObjects = (testGroups, config, testRoot, options, selectedRuns
|
|
|
117
118
|
const workersToExecute = []
|
|
118
119
|
|
|
119
120
|
const currentOutputFolder = config.output
|
|
120
|
-
let currentMochawesomeReportDir
|
|
121
121
|
let currentMochaJunitReporterFile
|
|
122
122
|
|
|
123
123
|
if (config.mocha && config.mocha.reporterOptions) {
|
|
124
|
-
currentMochawesomeReportDir = config.mocha.reporterOptions?.mochawesome.options.reportDir
|
|
125
124
|
currentMochaJunitReporterFile = config.mocha.reporterOptions['mocha-junit-reporter'].options.mochaFile
|
|
126
125
|
}
|
|
127
126
|
|
|
@@ -131,8 +130,6 @@ const createWorkerObjects = (testGroups, config, testRoot, options, selectedRuns
|
|
|
131
130
|
let workerName = worker.name.replace(':', '_')
|
|
132
131
|
_config.output = `${currentOutputFolder}${separator}${workerName}`
|
|
133
132
|
if (config.mocha && config.mocha.reporterOptions) {
|
|
134
|
-
_config.mocha.reporterOptions.mochawesome.options.reportDir = `${currentMochawesomeReportDir}${separator}${workerName}`
|
|
135
|
-
|
|
136
133
|
const _tempArray = currentMochaJunitReporterFile.split(separator)
|
|
137
134
|
_tempArray.splice(
|
|
138
135
|
_tempArray.findIndex(item => item.includes('.xml')),
|
|
@@ -224,7 +221,8 @@ class WorkerObject {
|
|
|
224
221
|
const oldConfig = JSON.parse(this.options.override || '{}')
|
|
225
222
|
|
|
226
223
|
// Remove customLocatorStrategies from both old and new config before JSON serialization
|
|
227
|
-
// since functions cannot be serialized and will be lost, causing workers to have empty strategies
|
|
224
|
+
// since functions cannot be serialized and will be lost, causing workers to have empty strategies.
|
|
225
|
+
// Note: Only WebDriver helper supports customLocatorStrategies
|
|
228
226
|
const configWithoutFunctions = { ...config }
|
|
229
227
|
|
|
230
228
|
// Clean both old and new config
|
|
@@ -503,6 +501,7 @@ class Workers extends EventEmitter {
|
|
|
503
501
|
await this._ensureInitialized()
|
|
504
502
|
recorder.startUnlessRunning()
|
|
505
503
|
event.dispatcher.emit(event.workers.before)
|
|
504
|
+
store.workerMode = true
|
|
506
505
|
process.env.RUNS_WITH_WORKERS = 'true'
|
|
507
506
|
|
|
508
507
|
// Create workers and set up message handlers immediately (not in recorder queue)
|
|
@@ -518,22 +517,8 @@ class Workers extends EventEmitter {
|
|
|
518
517
|
// Workers are already running, this is just a placeholder step
|
|
519
518
|
})
|
|
520
519
|
|
|
521
|
-
// Add overall timeout to prevent infinite hanging
|
|
522
|
-
const overallTimeout = setTimeout(() => {
|
|
523
|
-
console.error('[Main] Overall timeout reached (10 minutes). Force terminating remaining workers...')
|
|
524
|
-
workerThreads.forEach(w => {
|
|
525
|
-
try {
|
|
526
|
-
w.terminate()
|
|
527
|
-
} catch (e) {
|
|
528
|
-
// ignore
|
|
529
|
-
}
|
|
530
|
-
})
|
|
531
|
-
this._finishRun()
|
|
532
|
-
}, 600000) // 10 minutes
|
|
533
|
-
|
|
534
520
|
return new Promise(resolve => {
|
|
535
521
|
this.on('end', () => {
|
|
536
|
-
clearTimeout(overallTimeout)
|
|
537
522
|
resolve()
|
|
538
523
|
})
|
|
539
524
|
})
|
|
@@ -558,11 +543,12 @@ class Workers extends EventEmitter {
|
|
|
558
543
|
if (this.isPoolMode) {
|
|
559
544
|
this.activeWorkers.set(worker, { available: true, workerIndex: null })
|
|
560
545
|
}
|
|
561
|
-
|
|
546
|
+
|
|
562
547
|
// Track last activity time to detect hanging workers
|
|
563
548
|
let lastActivity = Date.now()
|
|
564
549
|
let currentTest = null
|
|
565
|
-
|
|
550
|
+
let autoTerminated = false
|
|
551
|
+
const workerTimeout = process.env.CODECEPT_WORKER_TIMEOUT ? ms(process.env.CODECEPT_WORKER_TIMEOUT) : ms('5m')
|
|
566
552
|
|
|
567
553
|
const timeoutChecker = setInterval(() => {
|
|
568
554
|
const elapsed = Date.now() - lastActivity
|
|
@@ -582,11 +568,6 @@ class Workers extends EventEmitter {
|
|
|
582
568
|
// Track current test
|
|
583
569
|
if (message.event === event.test.started && message.data) {
|
|
584
570
|
currentTest = message.data.title || message.data.fullTitle
|
|
585
|
-
console.log(`[Worker ${message.workerIndex}] Started: ${currentTest}`)
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
if (message.event === event.test.finished && message.data) {
|
|
589
|
-
console.log(`[Worker ${message.workerIndex}] Finished: ${message.data.title || currentTest}`)
|
|
590
571
|
}
|
|
591
572
|
|
|
592
573
|
output.process(message.workerIndex)
|
|
@@ -627,15 +608,41 @@ class Workers extends EventEmitter {
|
|
|
627
608
|
})
|
|
628
609
|
}
|
|
629
610
|
|
|
611
|
+
const exitTimeout = parseInt(process.env.CODECEPT_AUTO_EXIT_TIMEOUT, 10)
|
|
612
|
+
if (exitTimeout === 0) break
|
|
613
|
+
setTimeout(() => {
|
|
614
|
+
autoTerminated = true
|
|
615
|
+
worker.terminate()
|
|
616
|
+
}, exitTimeout || 2000)
|
|
617
|
+
|
|
630
618
|
break
|
|
631
619
|
case event.suite.before:
|
|
632
|
-
|
|
620
|
+
{
|
|
621
|
+
const suite = deserializeSuite(message.data)
|
|
622
|
+
this.emit(event.suite.before, suite)
|
|
623
|
+
event.dispatcher.emit(event.suite.before, suite)
|
|
624
|
+
}
|
|
625
|
+
break
|
|
626
|
+
case event.suite.after:
|
|
627
|
+
{
|
|
628
|
+
const suite = deserializeSuite(message.data)
|
|
629
|
+
this.emit(event.suite.after, suite)
|
|
630
|
+
event.dispatcher.emit(event.suite.after, suite)
|
|
631
|
+
}
|
|
633
632
|
break
|
|
634
633
|
case event.test.before:
|
|
635
|
-
|
|
634
|
+
{
|
|
635
|
+
const test = deserializeTest(message.data)
|
|
636
|
+
this.emit(event.test.before, test)
|
|
637
|
+
event.dispatcher.emit(event.test.before, test)
|
|
638
|
+
}
|
|
636
639
|
break
|
|
637
640
|
case event.test.started:
|
|
638
|
-
|
|
641
|
+
{
|
|
642
|
+
const test = deserializeTest(message.data)
|
|
643
|
+
this.emit(event.test.started, test)
|
|
644
|
+
event.dispatcher.emit(event.test.started, test)
|
|
645
|
+
}
|
|
639
646
|
break
|
|
640
647
|
case event.test.failed:
|
|
641
648
|
// For hook failures, emit immediately as there won't be a test.finished event
|
|
@@ -649,7 +656,11 @@ class Workers extends EventEmitter {
|
|
|
649
656
|
// Skip individual passed events - we'll emit based on finished state
|
|
650
657
|
break
|
|
651
658
|
case event.test.skipped:
|
|
652
|
-
|
|
659
|
+
{
|
|
660
|
+
const test = deserializeTest(message.data)
|
|
661
|
+
this.emit(event.test.skipped, test)
|
|
662
|
+
event.dispatcher.emit(event.test.skipped, test)
|
|
663
|
+
}
|
|
653
664
|
break
|
|
654
665
|
case event.test.finished:
|
|
655
666
|
// Handle different types of test completion properly
|
|
@@ -678,28 +689,47 @@ class Workers extends EventEmitter {
|
|
|
678
689
|
}
|
|
679
690
|
}
|
|
680
691
|
|
|
681
|
-
|
|
692
|
+
const test = deserializeTest(data)
|
|
693
|
+
this.emit(event.test.finished, test)
|
|
694
|
+
event.dispatcher.emit(event.test.finished, test)
|
|
682
695
|
}
|
|
683
696
|
break
|
|
684
697
|
case event.test.after:
|
|
685
|
-
|
|
698
|
+
{
|
|
699
|
+
const test = deserializeTest(message.data)
|
|
700
|
+
this.emit(event.test.after, test)
|
|
701
|
+
event.dispatcher.emit(event.test.after, test)
|
|
702
|
+
}
|
|
686
703
|
break
|
|
687
704
|
case event.step.finished:
|
|
688
705
|
this.emit(event.step.finished, message.data)
|
|
706
|
+
event.dispatcher.emit(event.step.finished, message.data)
|
|
689
707
|
break
|
|
690
708
|
case event.step.started:
|
|
691
709
|
this.emit(event.step.started, message.data)
|
|
710
|
+
event.dispatcher.emit(event.step.started, message.data)
|
|
692
711
|
break
|
|
693
712
|
case event.step.passed:
|
|
694
713
|
this.emit(event.step.passed, message.data)
|
|
714
|
+
event.dispatcher.emit(event.step.passed, message.data)
|
|
695
715
|
break
|
|
696
716
|
case event.step.failed:
|
|
697
717
|
this.emit(event.step.failed, message.data, message.data.error)
|
|
718
|
+
event.dispatcher.emit(event.step.failed, message.data, message.data.error)
|
|
698
719
|
break
|
|
699
720
|
case event.hook.failed:
|
|
700
721
|
// Hook failures are already reported as test failures by the worker
|
|
701
722
|
// Just emit the hook.failed event for listeners
|
|
702
723
|
this.emit(event.hook.failed, message.data)
|
|
724
|
+
event.dispatcher.emit(event.hook.failed, message.data)
|
|
725
|
+
break
|
|
726
|
+
case event.hook.passed:
|
|
727
|
+
this.emit(event.hook.passed, message.data)
|
|
728
|
+
event.dispatcher.emit(event.hook.passed, message.data)
|
|
729
|
+
break
|
|
730
|
+
case event.hook.finished:
|
|
731
|
+
this.emit(event.hook.finished, message.data)
|
|
732
|
+
event.dispatcher.emit(event.hook.finished, message.data)
|
|
703
733
|
break
|
|
704
734
|
}
|
|
705
735
|
})
|
|
@@ -715,8 +745,8 @@ class Workers extends EventEmitter {
|
|
|
715
745
|
worker.on('exit', (code) => {
|
|
716
746
|
clearInterval(timeoutChecker)
|
|
717
747
|
this.closedWorkers += 1
|
|
718
|
-
|
|
719
|
-
if (code !== 0) {
|
|
748
|
+
|
|
749
|
+
if (code !== 0 && !autoTerminated) {
|
|
720
750
|
console.error(`[Main] Worker exited with code ${code}`)
|
|
721
751
|
if (currentTest) {
|
|
722
752
|
console.error(`[Main] Last test running: ${currentTest}`)
|
|
@@ -724,8 +754,6 @@ class Workers extends EventEmitter {
|
|
|
724
754
|
// Mark as failed
|
|
725
755
|
process.exitCode = 1
|
|
726
756
|
}
|
|
727
|
-
|
|
728
|
-
console.log(`[Main] Workers closed: ${this.closedWorkers}/${this.numberOfWorkers}`)
|
|
729
757
|
|
|
730
758
|
if (this.isPoolMode) {
|
|
731
759
|
// Pool mode: finish when all workers have exited and no more tests
|
|
@@ -740,7 +768,6 @@ class Workers extends EventEmitter {
|
|
|
740
768
|
}
|
|
741
769
|
|
|
742
770
|
_finishRun() {
|
|
743
|
-
console.log('[Main] Finishing test run...')
|
|
744
771
|
event.dispatcher.emit(event.workers.after, { tests: this.workers.map(worker => worker.tests) })
|
|
745
772
|
if (Container.result().hasFailed || this.errors.length > 0) {
|
|
746
773
|
process.exitCode = 1
|
|
@@ -782,10 +809,8 @@ class Workers extends EventEmitter {
|
|
|
782
809
|
this._testStates.clear()
|
|
783
810
|
}
|
|
784
811
|
|
|
785
|
-
console.log('[Main] Emitting final results...')
|
|
786
812
|
this.emit(event.all.result, Container.result())
|
|
787
813
|
event.dispatcher.emit(event.workers.result, Container.result())
|
|
788
|
-
console.log('[Main] Emitting end event...')
|
|
789
814
|
this.emit('end') // internal event
|
|
790
815
|
}
|
|
791
816
|
|