codeceptjs 4.0.0-beta.2 → 4.0.0-beta.20
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 +133 -120
- package/bin/codecept.js +107 -96
- package/bin/test-server.js +64 -0
- package/docs/webapi/clearCookie.mustache +1 -1
- package/docs/webapi/click.mustache +5 -1
- package/lib/actor.js +71 -103
- package/lib/ai.js +159 -188
- package/lib/assert/empty.js +22 -24
- package/lib/assert/equal.js +30 -37
- package/lib/assert/error.js +14 -14
- package/lib/assert/include.js +43 -48
- package/lib/assert/throws.js +11 -11
- package/lib/assert/truth.js +22 -22
- package/lib/assert.js +20 -18
- package/lib/codecept.js +262 -162
- package/lib/colorUtils.js +50 -52
- package/lib/command/check.js +206 -0
- package/lib/command/configMigrate.js +56 -51
- package/lib/command/definitions.js +96 -109
- package/lib/command/dryRun.js +77 -79
- package/lib/command/generate.js +234 -194
- package/lib/command/gherkin/init.js +42 -33
- package/lib/command/gherkin/snippets.js +76 -74
- package/lib/command/gherkin/steps.js +20 -17
- package/lib/command/info.js +74 -38
- package/lib/command/init.js +301 -290
- package/lib/command/interactive.js +41 -32
- package/lib/command/list.js +28 -27
- package/lib/command/run-multiple/chunk.js +51 -48
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +5 -1
- package/lib/command/run-multiple.js +97 -97
- package/lib/command/run-rerun.js +19 -25
- package/lib/command/run-workers.js +68 -92
- package/lib/command/run.js +39 -27
- package/lib/command/utils.js +80 -64
- package/lib/command/workers/runTests.js +388 -226
- package/lib/config.js +109 -50
- package/lib/container.js +641 -261
- package/lib/data/context.js +60 -61
- package/lib/data/dataScenarioConfig.js +47 -47
- package/lib/data/dataTableArgument.js +32 -32
- package/lib/data/table.js +22 -22
- package/lib/effects.js +307 -0
- package/lib/element/WebElement.js +327 -0
- package/lib/els.js +160 -0
- package/lib/event.js +173 -163
- package/lib/globals.js +141 -0
- package/lib/heal.js +89 -85
- package/lib/helper/AI.js +131 -41
- package/lib/helper/ApiDataFactory.js +107 -75
- package/lib/helper/Appium.js +542 -404
- package/lib/helper/FileSystem.js +100 -79
- package/lib/helper/GraphQL.js +44 -43
- package/lib/helper/GraphQLDataFactory.js +52 -52
- package/lib/helper/JSONResponse.js +126 -88
- package/lib/helper/Mochawesome.js +54 -29
- package/lib/helper/Playwright.js +2547 -1316
- package/lib/helper/Puppeteer.js +1578 -1181
- package/lib/helper/REST.js +209 -68
- package/lib/helper/WebDriver.js +1482 -1342
- 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 +17 -8
- package/lib/helper/extras/PlaywrightRestartOpts.js +25 -11
- package/lib/helper/extras/Popup.js +22 -22
- package/lib/helper/extras/React.js +27 -28
- package/lib/helper/network/actions.js +36 -42
- package/lib/helper/network/utils.js +78 -84
- package/lib/helper/scripts/blurElement.js +5 -5
- package/lib/helper/scripts/focusElement.js +5 -5
- package/lib/helper/scripts/highlightElement.js +8 -8
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -3
- package/lib/history.js +23 -19
- package/lib/hooks.js +8 -8
- package/lib/html.js +94 -104
- package/lib/index.js +38 -27
- package/lib/listener/config.js +30 -23
- package/lib/listener/emptyRun.js +54 -0
- package/lib/listener/enhancedGlobalRetry.js +110 -0
- package/lib/listener/exit.js +16 -18
- package/lib/listener/globalRetry.js +70 -0
- package/lib/listener/globalTimeout.js +181 -0
- package/lib/listener/helpers.js +76 -51
- package/lib/listener/mocha.js +10 -11
- package/lib/listener/result.js +11 -0
- package/lib/listener/retryEnhancer.js +85 -0
- package/lib/listener/steps.js +71 -59
- package/lib/listener/store.js +20 -0
- package/lib/locator.js +214 -197
- package/lib/mocha/asyncWrapper.js +274 -0
- package/lib/mocha/bdd.js +167 -0
- package/lib/mocha/cli.js +341 -0
- package/lib/mocha/factory.js +163 -0
- package/lib/mocha/featureConfig.js +89 -0
- package/lib/mocha/gherkin.js +231 -0
- package/lib/mocha/hooks.js +121 -0
- package/lib/mocha/index.js +21 -0
- package/lib/mocha/inject.js +46 -0
- package/lib/{interfaces → mocha}/scenarioConfig.js +58 -34
- package/lib/mocha/suite.js +89 -0
- package/lib/mocha/test.js +184 -0
- package/lib/mocha/types.d.ts +42 -0
- package/lib/mocha/ui.js +242 -0
- package/lib/output.js +141 -71
- package/lib/parser.js +47 -44
- package/lib/pause.js +173 -145
- package/lib/plugin/analyze.js +403 -0
- package/lib/plugin/{autoLogin.js → auth.js} +178 -79
- package/lib/plugin/autoDelay.js +36 -40
- package/lib/plugin/coverage.js +131 -78
- package/lib/plugin/customLocator.js +22 -21
- package/lib/plugin/customReporter.js +53 -0
- package/lib/plugin/enhancedRetryFailedStep.js +99 -0
- package/lib/plugin/heal.js +101 -110
- package/lib/plugin/htmlReporter.js +3648 -0
- package/lib/plugin/pageInfo.js +140 -0
- package/lib/plugin/pauseOnFail.js +12 -11
- package/lib/plugin/retryFailedStep.js +82 -47
- package/lib/plugin/screenshotOnFail.js +111 -92
- package/lib/plugin/stepByStepReport.js +159 -101
- package/lib/plugin/stepTimeout.js +20 -25
- package/lib/plugin/subtitles.js +38 -38
- package/lib/recorder.js +193 -130
- package/lib/rerun.js +94 -49
- package/lib/result.js +238 -0
- package/lib/retryCoordinator.js +207 -0
- package/lib/secret.js +20 -18
- package/lib/session.js +95 -89
- package/lib/step/base.js +239 -0
- package/lib/step/comment.js +10 -0
- package/lib/step/config.js +50 -0
- package/lib/step/func.js +46 -0
- package/lib/step/helper.js +50 -0
- package/lib/step/meta.js +99 -0
- package/lib/step/record.js +74 -0
- package/lib/step/retry.js +11 -0
- package/lib/step/section.js +55 -0
- package/lib/step.js +18 -329
- package/lib/steps.js +54 -0
- package/lib/store.js +38 -7
- package/lib/template/heal.js +3 -12
- 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 +334 -0
- package/lib/timeout.js +60 -0
- package/lib/transform.js +8 -8
- package/lib/translation.js +34 -21
- package/lib/utils/loaderCheck.js +124 -0
- package/lib/utils/mask_data.js +47 -0
- package/lib/utils/typescript.js +237 -0
- package/lib/utils.js +411 -228
- package/lib/workerStorage.js +37 -34
- package/lib/workers.js +532 -296
- package/package.json +124 -95
- package/translations/de-DE.js +5 -3
- package/translations/fr-FR.js +5 -4
- package/translations/index.js +22 -12
- package/translations/it-IT.js +4 -3
- package/translations/ja-JP.js +4 -3
- package/translations/nl-NL.js +76 -0
- package/translations/pl-PL.js +4 -3
- package/translations/pt-BR.js +4 -3
- package/translations/ru-RU.js +4 -3
- package/translations/utils.js +10 -0
- package/translations/zh-CN.js +4 -3
- package/translations/zh-TW.js +4 -3
- package/typings/index.d.ts +546 -185
- package/typings/promiseBasedTypes.d.ts +150 -875
- package/typings/types.d.ts +547 -992
- package/lib/cli.js +0 -249
- package/lib/dirname.js +0 -5
- package/lib/helper/Expect.js +0 -425
- package/lib/helper/ExpectHelper.js +0 -399
- package/lib/helper/MockServer.js +0 -223
- package/lib/helper/Nightmare.js +0 -1411
- package/lib/helper/Protractor.js +0 -1835
- package/lib/helper/SoftExpectHelper.js +0 -381
- package/lib/helper/TestCafe.js +0 -1410
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -63
- package/lib/interfaces/bdd.js +0 -98
- package/lib/interfaces/featureConfig.js +0 -69
- package/lib/interfaces/gherkin.js +0 -195
- package/lib/listener/artifacts.js +0 -19
- package/lib/listener/retry.js +0 -68
- package/lib/listener/timeout.js +0 -109
- package/lib/mochaFactory.js +0 -110
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/commentStep.js +0 -136
- package/lib/plugin/debugErrors.js +0 -67
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/retryTo.js +0 -121
- package/lib/plugin/selenoid.js +0 -371
- package/lib/plugin/standardActingHelpers.js +0 -9
- package/lib/plugin/tryTo.js +0 -105
- package/lib/plugin/wdio.js +0 -246
- package/lib/scenario.js +0 -222
- package/lib/ui.js +0 -238
- package/lib/within.js +0 -70
|
@@ -1,292 +1,454 @@
|
|
|
1
|
-
import tty from 'tty'
|
|
2
|
-
import { parentPort, workerData } from 'worker_threads';
|
|
3
|
-
import * as event from '../../event.js';
|
|
4
|
-
import Container from '../../container.js';
|
|
5
|
-
import { getConfig } from '../utils.js';
|
|
6
|
-
import { deepMerge, tryOrDefault } from '../../utils.js';
|
|
7
|
-
import Codecept from '../../codecept.js';
|
|
1
|
+
import tty from 'tty'
|
|
8
2
|
|
|
9
3
|
if (!tty.getWindowSize) {
|
|
10
4
|
// this is really old method, long removed from Node, but Mocha
|
|
11
5
|
// reporters fall back on it if they cannot use `process.stdout.getWindowSize`
|
|
12
6
|
// we need to polyfill it.
|
|
13
|
-
tty.getWindowSize = () => [40, 80]
|
|
7
|
+
tty.getWindowSize = () => [40, 80]
|
|
14
8
|
}
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
let stdout = '';
|
|
18
|
-
/* eslint-enable no-unused-vars */
|
|
19
|
-
const stderr = '';
|
|
10
|
+
import { parentPort, workerData } from 'worker_threads'
|
|
20
11
|
|
|
21
|
-
//
|
|
22
|
-
//
|
|
12
|
+
// Delay imports to avoid ES Module loader race conditions in Node 22.x worker threads
|
|
13
|
+
// These will be imported dynamically when needed
|
|
14
|
+
let event, container, Codecept, getConfig, tryOrDefault, deepMerge
|
|
23
15
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
let stdout = ''
|
|
17
|
+
|
|
18
|
+
const stderr = ''
|
|
19
|
+
|
|
20
|
+
const { options, tests, testRoot, workerIndex, poolMode } = workerData
|
|
27
21
|
|
|
28
22
|
// hide worker output
|
|
29
|
-
|
|
23
|
+
// In pool mode, only suppress output if debug is NOT enabled
|
|
24
|
+
// In regular mode, hide result output but allow step output in verbose/debug
|
|
25
|
+
if (poolMode && !options.debug) {
|
|
26
|
+
// In pool mode without debug, allow test names and important output but suppress verbose details
|
|
27
|
+
const originalWrite = process.stdout.write
|
|
28
|
+
process.stdout.write = string => {
|
|
29
|
+
// Allow test names (✔ or ✖), Scenario Steps, failures, and important markers
|
|
30
|
+
if (
|
|
31
|
+
string.includes('✔') ||
|
|
32
|
+
string.includes('✖') ||
|
|
33
|
+
string.includes('Scenario Steps:') ||
|
|
34
|
+
string.includes('◯ Scenario Steps:') ||
|
|
35
|
+
string.includes('-- FAILURES:') ||
|
|
36
|
+
string.includes('AssertionError:') ||
|
|
37
|
+
string.includes('Feature(')
|
|
38
|
+
) {
|
|
39
|
+
return originalWrite.call(process.stdout, string)
|
|
40
|
+
}
|
|
41
|
+
// Suppress result summaries to avoid duplicates
|
|
42
|
+
if (string.includes(' FAIL |') || string.includes(' OK |') || string.includes('◯ File:')) {
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
return originalWrite.call(process.stdout, string)
|
|
46
|
+
}
|
|
47
|
+
} else if (!poolMode && !options.debug && !options.verbose) {
|
|
48
|
+
process.stdout.write = string => {
|
|
49
|
+
stdout += string
|
|
50
|
+
return true
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// In verbose/debug mode for test/suite modes, show step details
|
|
54
|
+
// but suppress individual worker result summaries to avoid duplicate output
|
|
55
|
+
const originalWrite = process.stdout.write
|
|
56
|
+
const originalConsoleLog = console.log
|
|
57
|
+
|
|
58
|
+
process.stdout.write = string => {
|
|
59
|
+
// Suppress individual worker result summaries and failure reports
|
|
60
|
+
if (string.includes(' FAIL |') || string.includes(' OK |') || string.includes('-- FAILURES:') || string.includes('AssertionError:') || string.includes('◯ File:') || string.includes('◯ Scenario Steps:')) {
|
|
61
|
+
return true
|
|
62
|
+
}
|
|
63
|
+
return originalWrite.call(process.stdout, string)
|
|
64
|
+
}
|
|
30
65
|
|
|
31
|
-
|
|
66
|
+
// Override console.log to catch result summaries
|
|
67
|
+
console.log = (...args) => {
|
|
68
|
+
const fullMessage = args.join(' ')
|
|
69
|
+
if (fullMessage.includes(' FAIL |') || fullMessage.includes(' OK |') || fullMessage.includes('-- FAILURES:')) {
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
return originalConsoleLog.apply(console, args)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
32
75
|
|
|
33
|
-
//
|
|
34
|
-
|
|
76
|
+
// Declare codecept and mocha at module level so they can be accessed by functions
|
|
77
|
+
let codecept
|
|
78
|
+
let mocha
|
|
79
|
+
let initPromise
|
|
80
|
+
let config
|
|
35
81
|
|
|
36
82
|
// Load test and run
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
83
|
+
initPromise = (async function () {
|
|
84
|
+
try {
|
|
85
|
+
// Import modules dynamically to avoid ES Module loader race conditions in Node 22.x
|
|
86
|
+
const eventModule = await import('../../event.js')
|
|
87
|
+
const containerModule = await import('../../container.js')
|
|
88
|
+
const utilsModule = await import('../utils.js')
|
|
89
|
+
const coreUtilsModule = await import('../../utils.js')
|
|
90
|
+
const CodeceptModule = await import('../../codecept.js')
|
|
91
|
+
|
|
92
|
+
event = eventModule.default
|
|
93
|
+
container = containerModule.default
|
|
94
|
+
getConfig = utilsModule.getConfig
|
|
95
|
+
tryOrDefault = coreUtilsModule.tryOrDefault
|
|
96
|
+
deepMerge = coreUtilsModule.deepMerge
|
|
97
|
+
Codecept = CodeceptModule.default
|
|
98
|
+
|
|
99
|
+
const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {})
|
|
100
|
+
|
|
101
|
+
// IMPORTANT: await is required here since getConfig is async
|
|
102
|
+
const baseConfig = await getConfig(options.config || testRoot)
|
|
103
|
+
|
|
104
|
+
// important deep merge so dynamic things e.g. functions on config are not overridden
|
|
105
|
+
config = deepMerge(baseConfig, overrideConfigs)
|
|
106
|
+
|
|
107
|
+
codecept = new Codecept(config, options)
|
|
108
|
+
await codecept.init(testRoot)
|
|
109
|
+
codecept.loadTests()
|
|
110
|
+
mocha = container.mocha()
|
|
111
|
+
|
|
112
|
+
if (poolMode) {
|
|
113
|
+
// In pool mode, don't filter tests upfront - wait for assignments
|
|
114
|
+
// We'll reload test files fresh for each test request
|
|
115
|
+
} else {
|
|
116
|
+
// Legacy mode - filter tests upfront
|
|
117
|
+
filterTests()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// run tests
|
|
121
|
+
if (poolMode) {
|
|
122
|
+
await runPoolTests()
|
|
123
|
+
} else if (mocha.suite.total()) {
|
|
124
|
+
await runTests()
|
|
125
|
+
} else {
|
|
126
|
+
// No tests to run, close the worker
|
|
127
|
+
parentPort?.close()
|
|
128
|
+
}
|
|
129
|
+
} catch (err) {
|
|
130
|
+
console.error('Error in worker initialization:', err)
|
|
131
|
+
process.exit(1)
|
|
46
132
|
}
|
|
47
|
-
}()
|
|
133
|
+
})()
|
|
134
|
+
|
|
135
|
+
let globalStats = { passes: 0, failures: 0, tests: 0, pending: 0, failedHooks: 0 }
|
|
48
136
|
|
|
49
137
|
async function runTests() {
|
|
50
138
|
try {
|
|
51
|
-
await codecept.bootstrap()
|
|
139
|
+
await codecept.bootstrap()
|
|
52
140
|
} catch (err) {
|
|
53
|
-
throw new Error(`Error while running bootstrap file :${err}`)
|
|
141
|
+
throw new Error(`Error while running bootstrap file :${err}`)
|
|
54
142
|
}
|
|
55
|
-
listenToParentThread()
|
|
56
|
-
initializeListeners()
|
|
57
|
-
disablePause()
|
|
143
|
+
listenToParentThread()
|
|
144
|
+
initializeListeners()
|
|
145
|
+
disablePause()
|
|
58
146
|
try {
|
|
59
|
-
await codecept.run()
|
|
147
|
+
await codecept.run()
|
|
60
148
|
} finally {
|
|
61
|
-
await codecept.teardown()
|
|
149
|
+
await codecept.teardown()
|
|
62
150
|
}
|
|
63
151
|
}
|
|
64
152
|
|
|
65
|
-
function
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
for (const suite of mocha.suite.suites) {
|
|
71
|
-
suite.tests = suite.tests.filter(test => tests.indexOf(test.uid) >= 0);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function initializeListeners() {
|
|
76
|
-
function simplifyError(error) {
|
|
77
|
-
if (error) {
|
|
78
|
-
const {
|
|
79
|
-
stack,
|
|
80
|
-
uncaught,
|
|
81
|
-
message,
|
|
82
|
-
actual,
|
|
83
|
-
expected,
|
|
84
|
-
} = error;
|
|
85
|
-
|
|
86
|
-
return {
|
|
87
|
-
stack,
|
|
88
|
-
uncaught,
|
|
89
|
-
message,
|
|
90
|
-
actual,
|
|
91
|
-
expected,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return null;
|
|
153
|
+
async function runPoolTests() {
|
|
154
|
+
try {
|
|
155
|
+
await codecept.bootstrap()
|
|
156
|
+
} catch (err) {
|
|
157
|
+
throw new Error(`Error while running bootstrap file :${err}`)
|
|
96
158
|
}
|
|
97
|
-
function simplifyTest(test, err = null) {
|
|
98
|
-
test = { ...test };
|
|
99
159
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
160
|
+
initializeListeners()
|
|
161
|
+
disablePause()
|
|
162
|
+
|
|
163
|
+
// Emit event.all.before once at the start of pool mode
|
|
164
|
+
event.dispatcher.emit(event.all.before, codecept)
|
|
165
|
+
|
|
166
|
+
// Accumulate results across all tests in pool mode
|
|
167
|
+
let consolidatedStats = { passes: 0, failures: 0, tests: 0, pending: 0, failedHooks: 0 }
|
|
168
|
+
let allTests = []
|
|
169
|
+
let allFailures = []
|
|
170
|
+
let previousStats = { passes: 0, failures: 0, tests: 0, pending: 0, failedHooks: 0 }
|
|
171
|
+
|
|
172
|
+
// Keep requesting tests until no more available
|
|
173
|
+
while (true) {
|
|
174
|
+
// Request a test assignment and wait for response
|
|
175
|
+
const testResult = await new Promise((resolve, reject) => {
|
|
176
|
+
// Set up pool mode message handler FIRST before sending request
|
|
177
|
+
const messageHandler = async eventData => {
|
|
178
|
+
// Remove handler immediately to prevent duplicate processing
|
|
179
|
+
parentPort?.off('message', messageHandler)
|
|
180
|
+
|
|
181
|
+
if (eventData.type === 'TEST_ASSIGNED') {
|
|
182
|
+
// In pool mode with ESM, we receive test FILE paths instead of UIDs
|
|
183
|
+
// because UIDs are not stable across different mocha instances
|
|
184
|
+
const testIdentifier = eventData.test
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
// Create a fresh Mocha instance for each test file
|
|
188
|
+
container.createMocha()
|
|
189
|
+
const mocha = container.mocha()
|
|
190
|
+
|
|
191
|
+
// Load only the assigned test file
|
|
192
|
+
mocha.files = [testIdentifier]
|
|
193
|
+
mocha.loadFiles()
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
require('fs').appendFileSync('/tmp/config_listener_debug.log', `${new Date().toISOString()} [POOL] Loaded ${testIdentifier}, tests: ${mocha.suite.total()}\n`)
|
|
197
|
+
} catch (e) { /* ignore */ }
|
|
198
|
+
|
|
199
|
+
if (mocha.suite.total() > 0) {
|
|
200
|
+
// Run only the tests in the current mocha suite
|
|
201
|
+
// Don't use codecept.run() as it overwrites mocha.files with ALL test files
|
|
202
|
+
await new Promise((resolve, reject) => {
|
|
203
|
+
mocha.run(() => {
|
|
204
|
+
try {
|
|
205
|
+
require('fs').appendFileSync('/tmp/config_listener_debug.log', `${new Date().toISOString()} [POOL] Finished ${testIdentifier}\n`)
|
|
206
|
+
} catch (e) { /* ignore */ }
|
|
207
|
+
resolve()
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
// Get the results from this specific test run
|
|
212
|
+
const result = container.result()
|
|
213
|
+
const currentStats = result.stats || {}
|
|
214
|
+
|
|
215
|
+
// Calculate the difference from previous accumulated stats
|
|
216
|
+
const newPasses = Math.max(0, (currentStats.passes || 0) - previousStats.passes)
|
|
217
|
+
const newFailures = Math.max(0, (currentStats.failures || 0) - previousStats.failures)
|
|
218
|
+
const newTests = Math.max(0, (currentStats.tests || 0) - previousStats.tests)
|
|
219
|
+
const newPending = Math.max(0, (currentStats.pending || 0) - previousStats.pending)
|
|
220
|
+
const newFailedHooks = Math.max(0, (currentStats.failedHooks || 0) - previousStats.failedHooks)
|
|
221
|
+
|
|
222
|
+
// Add only the new results
|
|
223
|
+
consolidatedStats.passes += newPasses
|
|
224
|
+
consolidatedStats.failures += newFailures
|
|
225
|
+
consolidatedStats.tests += newTests
|
|
226
|
+
consolidatedStats.pending += newPending
|
|
227
|
+
consolidatedStats.failedHooks += newFailedHooks
|
|
228
|
+
|
|
229
|
+
// Update previous stats for next comparison
|
|
230
|
+
previousStats = { ...currentStats }
|
|
231
|
+
|
|
232
|
+
// Add new failures to consolidated collections
|
|
233
|
+
if (result.failures && result.failures.length > allFailures.length) {
|
|
234
|
+
const newFailures = result.failures.slice(allFailures.length)
|
|
235
|
+
allFailures.push(...newFailures)
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Signal test completed
|
|
240
|
+
resolve('TEST_COMPLETED')
|
|
241
|
+
} catch (err) {
|
|
242
|
+
reject(err)
|
|
243
|
+
}
|
|
244
|
+
} else if (eventData.type === 'NO_MORE_TESTS') {
|
|
245
|
+
// No tests available, exit worker
|
|
246
|
+
resolve('NO_MORE_TESTS')
|
|
247
|
+
} else {
|
|
248
|
+
// Handle other message types (support messages, etc.)
|
|
249
|
+
container.append({ support: eventData.data })
|
|
250
|
+
// Don't re-add handler - each test request creates its own one-time handler
|
|
251
|
+
}
|
|
252
|
+
}
|
|
104
253
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
const parent = {};
|
|
113
|
-
if (test.parent) {
|
|
114
|
-
parent.title = test.parent.title;
|
|
115
|
-
}
|
|
254
|
+
// Set up handler BEFORE sending request to avoid race condition
|
|
255
|
+
parentPort?.on('message', messageHandler)
|
|
256
|
+
|
|
257
|
+
// Now send the request
|
|
258
|
+
sendToParentThread({ type: 'REQUEST_TEST', workerIndex })
|
|
259
|
+
})
|
|
116
260
|
|
|
117
|
-
if
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
if (typeof test.opts[k] === 'function') delete test.opts[k];
|
|
121
|
-
});
|
|
261
|
+
// Exit if no more tests
|
|
262
|
+
if (testResult === 'NO_MORE_TESTS') {
|
|
263
|
+
break
|
|
122
264
|
}
|
|
123
|
-
|
|
124
|
-
return {
|
|
125
|
-
opts: test.opts || {},
|
|
126
|
-
tags: test.tags || [],
|
|
127
|
-
uid: test.uid,
|
|
128
|
-
workerIndex,
|
|
129
|
-
retries: test._retries,
|
|
130
|
-
title: test.title,
|
|
131
|
-
status: test.status,
|
|
132
|
-
duration: test.duration || 0,
|
|
133
|
-
err,
|
|
134
|
-
parent,
|
|
135
|
-
steps: test.steps && test.steps.length > 0 ? simplifyStepsInTestObject(test.steps, err) : [],
|
|
136
|
-
};
|
|
137
265
|
}
|
|
138
266
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const _steps = [];
|
|
142
|
-
|
|
143
|
-
for (step of steps) {
|
|
144
|
-
const _args = [];
|
|
145
|
-
|
|
146
|
-
if (step.args) {
|
|
147
|
-
for (const arg of step.args) {
|
|
148
|
-
// check if arg is a JOI object
|
|
149
|
-
if (arg && arg.$_root) {
|
|
150
|
-
_args.push(JSON.stringify(arg).slice(0, 300));
|
|
151
|
-
// check if arg is a function
|
|
152
|
-
} else if (arg && typeof arg === 'function') {
|
|
153
|
-
_args.push(arg.name);
|
|
154
|
-
} else {
|
|
155
|
-
_args.push(arg);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
267
|
+
// Emit event.all.after once at the end of pool mode
|
|
268
|
+
event.dispatcher.emit(event.all.after, codecept)
|
|
159
269
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
startTime: step.startTime,
|
|
167
|
-
endTime: step.endTime,
|
|
168
|
-
finishedAt: step.finishedAt,
|
|
169
|
-
duration: step.duration,
|
|
170
|
-
err,
|
|
171
|
-
});
|
|
172
|
-
}
|
|
270
|
+
try {
|
|
271
|
+
await codecept.teardown()
|
|
272
|
+
} catch (err) {
|
|
273
|
+
// Log teardown errors but don't fail
|
|
274
|
+
console.error('Teardown error:', err)
|
|
275
|
+
}
|
|
173
276
|
|
|
174
|
-
|
|
277
|
+
// Send final consolidated results for the entire worker
|
|
278
|
+
const finalResult = {
|
|
279
|
+
hasFailed: consolidatedStats.failures > 0,
|
|
280
|
+
stats: consolidatedStats,
|
|
281
|
+
duration: 0, // Pool mode doesn't track duration per worker
|
|
282
|
+
tests: [], // Keep tests empty to avoid serialization issues - stats are sufficient
|
|
283
|
+
failures: allFailures, // Include all failures for error reporting
|
|
175
284
|
}
|
|
176
285
|
|
|
177
|
-
|
|
178
|
-
|
|
286
|
+
sendToParentThread({ event: event.all.after, workerIndex, data: finalResult })
|
|
287
|
+
sendToParentThread({ event: event.all.result, workerIndex, data: finalResult })
|
|
179
288
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
step.duration = end - step.startTime;
|
|
183
|
-
}
|
|
289
|
+
// Add longer delay to ensure messages are delivered before closing
|
|
290
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
184
291
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
} else if (err) {
|
|
189
|
-
err = simplifyError(err);
|
|
190
|
-
step.status = 'failed';
|
|
191
|
-
}
|
|
292
|
+
// Close worker thread when pool mode is complete
|
|
293
|
+
parentPort?.close()
|
|
294
|
+
}
|
|
192
295
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
296
|
+
function filterTestById(testUid) {
|
|
297
|
+
// In pool mode with ESM, test files are already loaded once at initialization
|
|
298
|
+
// We just need to filter the existing mocha suite to only include the target test
|
|
299
|
+
|
|
300
|
+
// Get the existing mocha instance
|
|
301
|
+
const mocha = container.mocha()
|
|
302
|
+
|
|
303
|
+
// Save reference to all suites before clearing
|
|
304
|
+
const allSuites = [...mocha.suite.suites]
|
|
305
|
+
|
|
306
|
+
// Clear suites and tests but preserve other mocha settings
|
|
307
|
+
mocha.suite.suites = []
|
|
308
|
+
mocha.suite.tests = []
|
|
309
|
+
|
|
310
|
+
// Find and add only the suite containing our target test
|
|
311
|
+
let foundTest = false
|
|
312
|
+
for (const suite of allSuites) {
|
|
313
|
+
const originalTests = [...suite.tests]
|
|
314
|
+
|
|
315
|
+
// Check if this suite has our target test
|
|
316
|
+
const targetTest = originalTests.find(test => test.uid === testUid)
|
|
317
|
+
|
|
318
|
+
if (targetTest) {
|
|
319
|
+
// Create a filtered suite with only the target test
|
|
320
|
+
suite.tests = [targetTest]
|
|
321
|
+
mocha.suite.suites.push(suite)
|
|
322
|
+
foundTest = true
|
|
323
|
+
break // Only include one test
|
|
196
324
|
}
|
|
325
|
+
}
|
|
197
326
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
});
|
|
203
|
-
}
|
|
327
|
+
if (!foundTest) {
|
|
328
|
+
console.error(`WARNING: Test with UID ${testUid} not found in mocha suites`)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
204
331
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
parent,
|
|
213
|
-
test: simplifyTest(step.test),
|
|
214
|
-
};
|
|
332
|
+
function filterTests() {
|
|
333
|
+
const files = codecept.testFiles
|
|
334
|
+
mocha.files = files
|
|
335
|
+
mocha.loadFiles()
|
|
336
|
+
|
|
337
|
+
for (const suite of mocha.suite.suites) {
|
|
338
|
+
suite.tests = suite.tests.filter(test => tests.indexOf(test.uid) >= 0)
|
|
215
339
|
}
|
|
340
|
+
}
|
|
216
341
|
|
|
217
|
-
|
|
342
|
+
function initializeListeners() {
|
|
218
343
|
// suite
|
|
219
|
-
event.dispatcher.on(event.suite.before, suite =>
|
|
220
|
-
event.dispatcher.on(event.suite.after, suite =>
|
|
344
|
+
event.dispatcher.on(event.suite.before, suite => safelySendToParent({ event: event.suite.before, workerIndex, data: suite.simplify() }))
|
|
345
|
+
event.dispatcher.on(event.suite.after, suite => safelySendToParent({ event: event.suite.after, workerIndex, data: suite.simplify() }))
|
|
221
346
|
|
|
222
347
|
// calculate duration
|
|
223
|
-
event.dispatcher.on(event.test.started, test => test.start = new Date())
|
|
348
|
+
event.dispatcher.on(event.test.started, test => (test.start = new Date()))
|
|
224
349
|
|
|
225
350
|
// tests
|
|
226
|
-
event.dispatcher.on(event.test.before, test =>
|
|
227
|
-
event.dispatcher.on(event.test.after, test =>
|
|
351
|
+
event.dispatcher.on(event.test.before, test => safelySendToParent({ event: event.test.before, workerIndex, data: test.simplify() }))
|
|
352
|
+
event.dispatcher.on(event.test.after, test => safelySendToParent({ event: event.test.after, workerIndex, data: test.simplify() }))
|
|
228
353
|
// we should force-send correct errors to prevent race condition
|
|
229
|
-
event.dispatcher.on(event.test.finished, (test, err) =>
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
354
|
+
event.dispatcher.on(event.test.finished, (test, err) => {
|
|
355
|
+
const simplifiedData = test.simplify()
|
|
356
|
+
const serializableErr = serializeError(err)
|
|
357
|
+
safelySendToParent({ event: event.test.finished, workerIndex, data: { ...simplifiedData, err: serializableErr } })
|
|
358
|
+
})
|
|
359
|
+
event.dispatcher.on(event.test.failed, (test, err, hookName) => {
|
|
360
|
+
const simplifiedData = test.simplify()
|
|
361
|
+
const serializableErr = serializeError(err)
|
|
362
|
+
// Include hookName to identify hook failures
|
|
363
|
+
safelySendToParent({ event: event.test.failed, workerIndex, data: { ...simplifiedData, err: serializableErr, hookName } })
|
|
364
|
+
})
|
|
365
|
+
event.dispatcher.on(event.test.passed, (test, err) => safelySendToParent({ event: event.test.passed, workerIndex, data: { ...test.simplify(), err } }))
|
|
366
|
+
event.dispatcher.on(event.test.started, test => safelySendToParent({ event: event.test.started, workerIndex, data: test.simplify() }))
|
|
367
|
+
event.dispatcher.on(event.test.skipped, test => safelySendToParent({ event: event.test.skipped, workerIndex, data: test.simplify() }))
|
|
234
368
|
|
|
235
369
|
// steps
|
|
236
|
-
event.dispatcher.on(event.step.finished, step =>
|
|
237
|
-
event.dispatcher.on(event.step.started, step =>
|
|
238
|
-
event.dispatcher.on(event.step.passed, step =>
|
|
239
|
-
event.dispatcher.on(event.step.failed, step =>
|
|
240
|
-
|
|
241
|
-
event.dispatcher.on(event.hook.failed, (
|
|
242
|
-
event.dispatcher.on(event.hook.passed,
|
|
243
|
-
event.dispatcher.on(event.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
370
|
+
event.dispatcher.on(event.step.finished, step => safelySendToParent({ event: event.step.finished, workerIndex, data: step.simplify() }))
|
|
371
|
+
event.dispatcher.on(event.step.started, step => safelySendToParent({ event: event.step.started, workerIndex, data: step.simplify() }))
|
|
372
|
+
event.dispatcher.on(event.step.passed, step => safelySendToParent({ event: event.step.passed, workerIndex, data: step.simplify() }))
|
|
373
|
+
event.dispatcher.on(event.step.failed, step => safelySendToParent({ event: event.step.failed, workerIndex, data: step.simplify() }))
|
|
374
|
+
|
|
375
|
+
event.dispatcher.on(event.hook.failed, (hook, err) => sendToParentThread({ event: event.hook.failed, workerIndex, data: { ...hook.simplify(), err } }))
|
|
376
|
+
event.dispatcher.on(event.hook.passed, hook => sendToParentThread({ event: event.hook.passed, workerIndex, data: hook.simplify() }))
|
|
377
|
+
event.dispatcher.on(event.hook.finished, hook => sendToParentThread({ event: event.hook.finished, workerIndex, data: hook.simplify() }))
|
|
378
|
+
|
|
379
|
+
if (!poolMode) {
|
|
380
|
+
// In regular mode, close worker after all tests are complete
|
|
381
|
+
event.dispatcher.once(event.all.after, () => {
|
|
382
|
+
sendToParentThread({ event: event.all.after, workerIndex, data: container.result().simplify() })
|
|
383
|
+
})
|
|
384
|
+
// all
|
|
385
|
+
event.dispatcher.once(event.all.result, () => {
|
|
386
|
+
sendToParentThread({ event: event.all.result, workerIndex, data: container.result().simplify() })
|
|
387
|
+
parentPort?.close()
|
|
388
|
+
})
|
|
389
|
+
} else {
|
|
390
|
+
// In pool mode, don't send result events for individual tests
|
|
391
|
+
// Results will be sent once when the worker completes all tests
|
|
392
|
+
}
|
|
247
393
|
}
|
|
248
394
|
|
|
249
395
|
function disablePause() {
|
|
250
|
-
global.pause = () => {}
|
|
396
|
+
global.pause = () => {}
|
|
251
397
|
}
|
|
252
398
|
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
stats.skipped++;
|
|
263
|
-
});
|
|
264
|
-
event.dispatcher.on(event.test.passed, () => {
|
|
265
|
-
stats.passes++;
|
|
266
|
-
});
|
|
267
|
-
event.dispatcher.on(event.test.failed, (test) => {
|
|
268
|
-
if (test.ctx._runnable.title.includes('hook: AfterSuite')) {
|
|
269
|
-
stats.failedHooks += 1;
|
|
399
|
+
function serializeError(err) {
|
|
400
|
+
if (!err) return null
|
|
401
|
+
try {
|
|
402
|
+
return {
|
|
403
|
+
message: err.message,
|
|
404
|
+
stack: err.stack,
|
|
405
|
+
name: err.name,
|
|
406
|
+
actual: err.actual,
|
|
407
|
+
expected: err.expected,
|
|
270
408
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
409
|
+
} catch {
|
|
410
|
+
return { message: 'Error could not be serialized', name: 'Error' }
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function safelySendToParent(data) {
|
|
415
|
+
try {
|
|
416
|
+
parentPort?.postMessage(data)
|
|
417
|
+
} catch (cloneError) {
|
|
418
|
+
// Fallback for non-serializable data
|
|
419
|
+
const fallbackData = { ...data }
|
|
420
|
+
|
|
421
|
+
// Try to serialize error objects if present
|
|
422
|
+
if (fallbackData.data && fallbackData.data.err) {
|
|
423
|
+
fallbackData.data.err = serializeError(fallbackData.data.err)
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// If still fails, send minimal data
|
|
427
|
+
try {
|
|
428
|
+
parentPort?.postMessage(fallbackData)
|
|
429
|
+
} catch (finalError) {
|
|
430
|
+
parentPort?.postMessage({
|
|
431
|
+
event: data.event,
|
|
432
|
+
workerIndex,
|
|
433
|
+
data: {
|
|
434
|
+
title: fallbackData.data?.title || 'Unknown',
|
|
435
|
+
state: fallbackData.data?.state || 'error',
|
|
436
|
+
err: { message: 'Data could not be serialized' },
|
|
437
|
+
},
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
}
|
|
282
441
|
}
|
|
283
442
|
|
|
284
443
|
function sendToParentThread(data) {
|
|
285
|
-
parentPort
|
|
444
|
+
parentPort?.postMessage(data)
|
|
286
445
|
}
|
|
287
446
|
|
|
288
447
|
function listenToParentThread() {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
448
|
+
if (!poolMode) {
|
|
449
|
+
parentPort?.on('message', eventData => {
|
|
450
|
+
container.append({ support: eventData.data })
|
|
451
|
+
})
|
|
452
|
+
}
|
|
453
|
+
// In pool mode, message handling is done in runPoolTests()
|
|
292
454
|
}
|