codeceptjs 4.0.0-beta.5 → 4.0.0-beta.6.esm-aria
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -45
- package/bin/codecept.js +46 -57
- package/lib/actor.js +15 -11
- package/lib/ai.js +6 -5
- package/lib/assert/empty.js +9 -8
- package/lib/assert/equal.js +15 -17
- package/lib/assert/error.js +2 -2
- package/lib/assert/include.js +9 -11
- package/lib/assert/throws.js +1 -1
- package/lib/assert/truth.js +8 -5
- package/lib/assert.js +18 -18
- package/lib/codecept.js +66 -107
- package/lib/colorUtils.js +48 -50
- package/lib/command/check.js +32 -27
- package/lib/command/configMigrate.js +11 -10
- package/lib/command/definitions.js +16 -10
- package/lib/command/dryRun.js +16 -16
- package/lib/command/generate.js +29 -26
- package/lib/command/gherkin/init.js +36 -38
- package/lib/command/gherkin/snippets.js +14 -14
- package/lib/command/gherkin/steps.js +21 -18
- package/lib/command/info.js +8 -8
- package/lib/command/init.js +34 -31
- package/lib/command/interactive.js +11 -10
- package/lib/command/list.js +10 -9
- package/lib/command/run-multiple/chunk.js +5 -5
- package/lib/command/run-multiple/collection.js +5 -5
- package/lib/command/run-multiple/run.js +3 -3
- package/lib/command/run-multiple.js +16 -13
- package/lib/command/run-rerun.js +6 -7
- package/lib/command/run-workers.js +10 -24
- package/lib/command/run.js +8 -8
- package/lib/command/utils.js +20 -18
- package/lib/command/workers/runTests.js +117 -269
- package/lib/config.js +111 -49
- package/lib/container.js +299 -102
- package/lib/data/context.js +6 -5
- package/lib/data/dataScenarioConfig.js +1 -1
- package/lib/data/dataTableArgument.js +1 -1
- package/lib/data/table.js +1 -1
- package/lib/effects.js +94 -10
- package/lib/els.js +11 -9
- package/lib/event.js +11 -10
- package/lib/globals.js +141 -0
- package/lib/heal.js +12 -12
- package/lib/helper/AI.js +1 -1
- package/lib/helper/ApiDataFactory.js +16 -13
- package/lib/helper/FileSystem.js +32 -12
- package/lib/helper/GraphQL.js +1 -1
- package/lib/helper/GraphQLDataFactory.js +1 -1
- package/lib/helper/JSONResponse.js +19 -30
- package/lib/helper/Mochawesome.js +9 -28
- package/lib/helper/Playwright.js +668 -265
- package/lib/helper/Puppeteer.js +284 -169
- package/lib/helper/REST.js +29 -12
- package/lib/helper/WebDriver.js +191 -71
- package/lib/helper/errors/ConnectionRefused.js +6 -6
- package/lib/helper/errors/ElementAssertion.js +11 -16
- package/lib/helper/errors/ElementNotFound.js +5 -9
- package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
- package/lib/helper/extras/Console.js +11 -11
- package/lib/helper/extras/PlaywrightLocator.js +110 -0
- package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
- package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
- package/lib/helper/extras/Popup.js +1 -1
- package/lib/helper/extras/React.js +29 -30
- package/lib/helper/network/actions.js +33 -48
- package/lib/helper/network/utils.js +76 -83
- package/lib/helper/scripts/blurElement.js +6 -6
- package/lib/helper/scripts/focusElement.js +6 -6
- package/lib/helper/scripts/highlightElement.js +9 -9
- package/lib/helper/scripts/isElementClickable.js +34 -34
- package/lib/helper.js +2 -1
- package/lib/history.js +23 -20
- package/lib/hooks.js +10 -10
- package/lib/html.js +90 -100
- package/lib/index.js +48 -21
- package/lib/listener/config.js +8 -9
- package/lib/listener/emptyRun.js +6 -7
- package/lib/listener/exit.js +4 -3
- package/lib/listener/globalRetry.js +5 -5
- package/lib/listener/globalTimeout.js +11 -10
- package/lib/listener/helpers.js +33 -14
- package/lib/listener/mocha.js +3 -4
- package/lib/listener/result.js +4 -5
- package/lib/listener/steps.js +7 -18
- package/lib/listener/store.js +3 -3
- package/lib/locator.js +213 -192
- package/lib/mocha/asyncWrapper.js +108 -75
- package/lib/mocha/bdd.js +99 -13
- package/lib/mocha/cli.js +60 -27
- package/lib/mocha/factory.js +75 -19
- package/lib/mocha/featureConfig.js +1 -1
- package/lib/mocha/gherkin.js +57 -25
- package/lib/mocha/hooks.js +12 -3
- package/lib/mocha/index.js +13 -4
- package/lib/mocha/inject.js +22 -5
- package/lib/mocha/scenarioConfig.js +2 -2
- package/lib/mocha/suite.js +9 -2
- package/lib/mocha/test.js +10 -13
- package/lib/mocha/ui.js +28 -31
- package/lib/output.js +11 -9
- package/lib/parser.js +44 -44
- package/lib/pause.js +15 -16
- package/lib/plugin/analyze.js +19 -12
- package/lib/plugin/auth.js +20 -21
- package/lib/plugin/autoDelay.js +12 -8
- package/lib/plugin/coverage.js +12 -8
- package/lib/plugin/customLocator.js +3 -3
- package/lib/plugin/customReporter.js +3 -2
- package/lib/plugin/heal.js +14 -9
- package/lib/plugin/pageInfo.js +10 -10
- package/lib/plugin/pauseOnFail.js +4 -3
- package/lib/plugin/retryFailedStep.js +47 -5
- package/lib/plugin/screenshotOnFail.js +75 -37
- package/lib/plugin/stepByStepReport.js +14 -14
- package/lib/plugin/stepTimeout.js +4 -3
- package/lib/plugin/subtitles.js +6 -5
- package/lib/recorder.js +33 -23
- package/lib/rerun.js +69 -26
- package/lib/result.js +4 -4
- package/lib/secret.js +18 -17
- package/lib/session.js +95 -89
- package/lib/step/base.js +6 -6
- package/lib/step/config.js +1 -1
- package/lib/step/func.js +3 -3
- package/lib/step/helper.js +3 -3
- package/lib/step/meta.js +4 -4
- package/lib/step/record.js +11 -11
- package/lib/step/retry.js +3 -3
- package/lib/step/section.js +3 -3
- package/lib/step.js +7 -10
- package/lib/steps.js +9 -5
- package/lib/store.js +1 -1
- package/lib/timeout.js +1 -7
- package/lib/transform.js +8 -8
- package/lib/translation.js +32 -18
- package/lib/utils.js +68 -97
- package/lib/workerStorage.js +16 -17
- package/lib/workers.js +145 -171
- package/package.json +63 -57
- package/translations/de-DE.js +2 -2
- package/translations/fr-FR.js +2 -2
- package/translations/index.js +23 -10
- package/translations/it-IT.js +2 -2
- package/translations/ja-JP.js +2 -2
- package/translations/nl-NL.js +2 -2
- package/translations/pl-PL.js +2 -2
- package/translations/pt-BR.js +2 -2
- package/translations/ru-RU.js +2 -2
- package/translations/utils.js +11 -2
- package/translations/zh-CN.js +2 -2
- package/translations/zh-TW.js +2 -2
- package/typings/index.d.ts +7 -18
- package/typings/promiseBasedTypes.d.ts +3769 -5450
- package/typings/types.d.ts +3953 -5778
- package/bin/test-server.js +0 -53
- package/lib/element/WebElement.js +0 -327
- package/lib/helper/Nightmare.js +0 -1486
- package/lib/helper/Protractor.js +0 -1840
- package/lib/helper/TestCafe.js +0 -1391
- package/lib/helper/clientscripts/nightmare.js +0 -213
- package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
- package/lib/helper/testcafe/testControllerHolder.js +0 -42
- package/lib/helper/testcafe/testcafe-utils.js +0 -61
- package/lib/listener/retryEnhancer.js +0 -85
- package/lib/plugin/allure.js +0 -15
- package/lib/plugin/autoLogin.js +0 -5
- package/lib/plugin/commentStep.js +0 -141
- package/lib/plugin/eachElement.js +0 -127
- package/lib/plugin/fakerTransform.js +0 -49
- package/lib/plugin/htmlReporter.js +0 -1947
- package/lib/plugin/retryTo.js +0 -16
- package/lib/plugin/selenoid.js +0 -364
- package/lib/plugin/standardActingHelpers.js +0 -6
- package/lib/plugin/tryTo.js +0 -16
- package/lib/plugin/wdio.js +0 -247
- package/lib/test-server.js +0 -323
- package/lib/within.js +0 -90
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import tty from 'tty'
|
|
2
2
|
|
|
3
3
|
if (!tty.getWindowSize) {
|
|
4
4
|
// this is really old method, long removed from Node, but Mocha
|
|
@@ -7,20 +7,20 @@ if (!tty.getWindowSize) {
|
|
|
7
7
|
tty.getWindowSize = () => [40, 80]
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
import { parentPort, workerData } from 'worker_threads'
|
|
11
|
+
import event from '../../event.js'
|
|
12
|
+
import container from '../../container.js'
|
|
13
|
+
import { getConfig } from '../utils.js'
|
|
14
|
+
import { tryOrDefault, deepMerge } from '../../utils.js'
|
|
15
15
|
|
|
16
16
|
let stdout = ''
|
|
17
17
|
|
|
18
18
|
const stderr = ''
|
|
19
19
|
|
|
20
|
-
//
|
|
21
|
-
|
|
20
|
+
// Importing of Codecept need to be after tty.getWindowSize is available.
|
|
21
|
+
import Codecept from '../../codecept.js'
|
|
22
22
|
|
|
23
|
-
const { options, tests, testRoot, workerIndex
|
|
23
|
+
const { options, tests, testRoot, workerIndex } = workerData
|
|
24
24
|
|
|
25
25
|
// hide worker output
|
|
26
26
|
if (!options.debug && !options.verbose)
|
|
@@ -35,288 +35,139 @@ const overrideConfigs = tryOrDefault(() => JSON.parse(options.override), {})
|
|
|
35
35
|
const config = deepMerge(getConfig(options.config || testRoot), overrideConfigs)
|
|
36
36
|
|
|
37
37
|
// Load test and run
|
|
38
|
-
const codecept = new Codecept(config, options)
|
|
39
|
-
codecept.init(testRoot)
|
|
40
|
-
codecept.loadTests()
|
|
41
|
-
const mocha = container.mocha()
|
|
42
|
-
|
|
43
|
-
if (poolMode) {
|
|
44
|
-
// In pool mode, don't filter tests upfront - wait for assignments
|
|
45
|
-
// We'll reload test files fresh for each test request
|
|
46
|
-
} else {
|
|
47
|
-
// Legacy mode - filter tests upfront
|
|
48
|
-
filterTests()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// run tests
|
|
52
38
|
;(async function () {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
await runTests()
|
|
57
|
-
}
|
|
58
|
-
})()
|
|
59
|
-
|
|
60
|
-
let globalStats = { passes: 0, failures: 0, tests: 0, pending: 0, failedHooks: 0 }
|
|
61
|
-
|
|
62
|
-
async function runTests() {
|
|
63
|
-
try {
|
|
64
|
-
await codecept.bootstrap()
|
|
65
|
-
} catch (err) {
|
|
66
|
-
throw new Error(`Error while running bootstrap file :${err}`)
|
|
67
|
-
}
|
|
68
|
-
listenToParentThread()
|
|
69
|
-
initializeListeners()
|
|
70
|
-
disablePause()
|
|
71
|
-
try {
|
|
72
|
-
await codecept.run()
|
|
73
|
-
} finally {
|
|
74
|
-
await codecept.teardown()
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function runPoolTests() {
|
|
79
|
-
try {
|
|
80
|
-
await codecept.bootstrap()
|
|
81
|
-
} catch (err) {
|
|
82
|
-
throw new Error(`Error while running bootstrap file :${err}`)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
initializeListeners()
|
|
86
|
-
disablePause()
|
|
87
|
-
|
|
88
|
-
// Accumulate results across all tests in pool mode
|
|
89
|
-
let consolidatedStats = { passes: 0, failures: 0, tests: 0, pending: 0, failedHooks: 0 }
|
|
90
|
-
let allTests = []
|
|
91
|
-
let allFailures = []
|
|
92
|
-
let previousStats = { passes: 0, failures: 0, tests: 0, pending: 0, failedHooks: 0 }
|
|
93
|
-
|
|
94
|
-
// Keep requesting tests until no more available
|
|
95
|
-
while (true) {
|
|
96
|
-
// Request a test assignment
|
|
97
|
-
sendToParentThread({ type: 'REQUEST_TEST', workerIndex })
|
|
98
|
-
|
|
99
|
-
const testResult = await new Promise((resolve, reject) => {
|
|
100
|
-
// Set up pool mode message handler
|
|
101
|
-
const messageHandler = async eventData => {
|
|
102
|
-
if (eventData.type === 'TEST_ASSIGNED') {
|
|
103
|
-
const testUid = eventData.test
|
|
104
|
-
|
|
105
|
-
try {
|
|
106
|
-
// In pool mode, we need to create a fresh Mocha instance for each test
|
|
107
|
-
// because Mocha instances become disposed after running tests
|
|
108
|
-
container.createMocha() // Create fresh Mocha instance
|
|
109
|
-
filterTestById(testUid)
|
|
110
|
-
const mocha = container.mocha()
|
|
111
|
-
|
|
112
|
-
if (mocha.suite.total() > 0) {
|
|
113
|
-
// Run the test and complete
|
|
114
|
-
await codecept.run()
|
|
115
|
-
|
|
116
|
-
// Get the results from this specific test run
|
|
117
|
-
const result = container.result()
|
|
118
|
-
const currentStats = result.stats || {}
|
|
119
|
-
|
|
120
|
-
// Calculate the difference from previous accumulated stats
|
|
121
|
-
const newPasses = Math.max(0, (currentStats.passes || 0) - previousStats.passes)
|
|
122
|
-
const newFailures = Math.max(0, (currentStats.failures || 0) - previousStats.failures)
|
|
123
|
-
const newTests = Math.max(0, (currentStats.tests || 0) - previousStats.tests)
|
|
124
|
-
const newPending = Math.max(0, (currentStats.pending || 0) - previousStats.pending)
|
|
125
|
-
const newFailedHooks = Math.max(0, (currentStats.failedHooks || 0) - previousStats.failedHooks)
|
|
126
|
-
|
|
127
|
-
// Add only the new results
|
|
128
|
-
consolidatedStats.passes += newPasses
|
|
129
|
-
consolidatedStats.failures += newFailures
|
|
130
|
-
consolidatedStats.tests += newTests
|
|
131
|
-
consolidatedStats.pending += newPending
|
|
132
|
-
consolidatedStats.failedHooks += newFailedHooks
|
|
133
|
-
|
|
134
|
-
// Update previous stats for next comparison
|
|
135
|
-
previousStats = { ...currentStats }
|
|
136
|
-
|
|
137
|
-
// Add new failures to consolidated collections
|
|
138
|
-
if (result.failures && result.failures.length > allFailures.length) {
|
|
139
|
-
const newFailures = result.failures.slice(allFailures.length)
|
|
140
|
-
allFailures.push(...newFailures)
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Signal test completed and request next
|
|
145
|
-
parentPort?.off('message', messageHandler)
|
|
146
|
-
resolve('TEST_COMPLETED')
|
|
147
|
-
} catch (err) {
|
|
148
|
-
parentPort?.off('message', messageHandler)
|
|
149
|
-
reject(err)
|
|
150
|
-
}
|
|
151
|
-
} else if (eventData.type === 'NO_MORE_TESTS') {
|
|
152
|
-
// No tests available, exit worker
|
|
153
|
-
parentPort?.off('message', messageHandler)
|
|
154
|
-
resolve('NO_MORE_TESTS')
|
|
155
|
-
} else {
|
|
156
|
-
// Handle other message types (support messages, etc.)
|
|
157
|
-
container.append({ support: eventData.data })
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
parentPort?.on('message', messageHandler)
|
|
162
|
-
})
|
|
163
|
-
|
|
164
|
-
// Exit if no more tests
|
|
165
|
-
if (testResult === 'NO_MORE_TESTS') {
|
|
166
|
-
break
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
await codecept.teardown()
|
|
172
|
-
} catch (err) {
|
|
173
|
-
// Log teardown errors but don't fail
|
|
174
|
-
console.error('Teardown error:', err)
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Send final consolidated results for the entire worker
|
|
178
|
-
const finalResult = {
|
|
179
|
-
hasFailed: consolidatedStats.failures > 0,
|
|
180
|
-
stats: consolidatedStats,
|
|
181
|
-
duration: 0, // Pool mode doesn't track duration per worker
|
|
182
|
-
tests: [], // Keep tests empty to avoid serialization issues - stats are sufficient
|
|
183
|
-
failures: allFailures, // Include all failures for error reporting
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
sendToParentThread({ event: event.all.after, workerIndex, data: finalResult })
|
|
187
|
-
sendToParentThread({ event: event.all.result, workerIndex, data: finalResult })
|
|
188
|
-
|
|
189
|
-
// Add longer delay to ensure messages are delivered before closing
|
|
190
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
191
|
-
|
|
192
|
-
// Close worker thread when pool mode is complete
|
|
193
|
-
parentPort?.close()
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
function filterTestById(testUid) {
|
|
197
|
-
// Reload test files fresh for each test in pool mode
|
|
198
|
-
const files = codecept.testFiles
|
|
199
|
-
|
|
200
|
-
// Get the existing mocha instance
|
|
39
|
+
const codecept = new Codecept(config, options)
|
|
40
|
+
await codecept.init(testRoot)
|
|
41
|
+
codecept.loadTests()
|
|
201
42
|
const mocha = container.mocha()
|
|
202
43
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// Clear require cache for test files to ensure fresh loading
|
|
208
|
-
files.forEach(file => {
|
|
209
|
-
delete require.cache[require.resolve(file)]
|
|
210
|
-
})
|
|
211
|
-
|
|
212
|
-
// Set files and load them
|
|
213
|
-
mocha.files = files
|
|
214
|
-
mocha.loadFiles()
|
|
215
|
-
|
|
216
|
-
// Now filter to only the target test - use a more robust approach
|
|
217
|
-
let foundTest = false
|
|
218
|
-
for (const suite of mocha.suite.suites) {
|
|
219
|
-
const originalTests = [...suite.tests]
|
|
220
|
-
suite.tests = []
|
|
221
|
-
|
|
222
|
-
for (const test of originalTests) {
|
|
223
|
-
if (test.uid === testUid) {
|
|
224
|
-
suite.tests.push(test)
|
|
225
|
-
foundTest = true
|
|
226
|
-
break // Only add one matching test
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// If no tests found in this suite, remove it
|
|
231
|
-
if (suite.tests.length === 0) {
|
|
232
|
-
suite.parent.suites = suite.parent.suites.filter(s => s !== suite)
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Filter out empty suites from the root
|
|
237
|
-
mocha.suite.suites = mocha.suite.suites.filter(suite => suite.tests.length > 0)
|
|
238
|
-
|
|
239
|
-
if (!foundTest) {
|
|
240
|
-
// If testUid doesn't match, maybe it's a simple test name - try fallback
|
|
241
|
-
mocha.suite.suites = []
|
|
242
|
-
mocha.suite.tests = []
|
|
44
|
+
function filterTests() {
|
|
45
|
+
const files = codecept.testFiles
|
|
46
|
+
mocha.files = files
|
|
243
47
|
mocha.loadFiles()
|
|
244
48
|
|
|
245
|
-
// Try matching by title
|
|
246
49
|
for (const suite of mocha.suite.suites) {
|
|
247
|
-
|
|
248
|
-
suite.tests = []
|
|
249
|
-
|
|
250
|
-
for (const test of originalTests) {
|
|
251
|
-
if (test.title === testUid || test.fullTitle() === testUid || test.uid === testUid) {
|
|
252
|
-
suite.tests.push(test)
|
|
253
|
-
foundTest = true
|
|
254
|
-
break
|
|
255
|
-
}
|
|
256
|
-
}
|
|
50
|
+
suite.tests = suite.tests.filter(test => tests.indexOf(test.uid) >= 0)
|
|
257
51
|
}
|
|
52
|
+
}
|
|
258
53
|
|
|
259
|
-
|
|
260
|
-
|
|
54
|
+
async function runTests() {
|
|
55
|
+
try {
|
|
56
|
+
await codecept.bootstrap()
|
|
57
|
+
} catch (err) {
|
|
58
|
+
throw new Error(`Error while running bootstrap file :${err}`)
|
|
59
|
+
}
|
|
60
|
+
listenToParentThread()
|
|
61
|
+
initializeListeners()
|
|
62
|
+
disablePause()
|
|
63
|
+
try {
|
|
64
|
+
await codecept.run()
|
|
65
|
+
} finally {
|
|
66
|
+
await codecept.teardown()
|
|
67
|
+
}
|
|
261
68
|
}
|
|
262
|
-
}
|
|
263
69
|
|
|
264
|
-
|
|
265
|
-
const files = codecept.testFiles
|
|
266
|
-
mocha.files = files
|
|
267
|
-
mocha.loadFiles()
|
|
70
|
+
filterTests()
|
|
268
71
|
|
|
269
|
-
|
|
270
|
-
|
|
72
|
+
// run tests
|
|
73
|
+
if (mocha.suite.total()) {
|
|
74
|
+
await runTests()
|
|
271
75
|
}
|
|
272
|
-
}
|
|
76
|
+
})()
|
|
273
77
|
|
|
274
78
|
function initializeListeners() {
|
|
275
79
|
// suite
|
|
276
|
-
event.dispatcher.on(event.suite.before, suite =>
|
|
277
|
-
event.dispatcher.on(event.suite.after, suite =>
|
|
80
|
+
event.dispatcher.on(event.suite.before, suite => safelySendToParent({ event: event.suite.before, workerIndex, data: suite.simplify() }))
|
|
81
|
+
event.dispatcher.on(event.suite.after, suite => safelySendToParent({ event: event.suite.after, workerIndex, data: suite.simplify() }))
|
|
278
82
|
|
|
279
83
|
// calculate duration
|
|
280
84
|
event.dispatcher.on(event.test.started, test => (test.start = new Date()))
|
|
281
85
|
|
|
282
86
|
// tests
|
|
283
|
-
event.dispatcher.on(event.test.before, test =>
|
|
284
|
-
event.dispatcher.on(event.test.after, test =>
|
|
87
|
+
event.dispatcher.on(event.test.before, test => safelySendToParent({ event: event.test.before, workerIndex, data: test.simplify() }))
|
|
88
|
+
event.dispatcher.on(event.test.after, test => safelySendToParent({ event: event.test.after, workerIndex, data: test.simplify() }))
|
|
285
89
|
// we should force-send correct errors to prevent race condition
|
|
286
|
-
event.dispatcher.on(event.test.finished, (test, err) =>
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
90
|
+
event.dispatcher.on(event.test.finished, (test, err) => {
|
|
91
|
+
const simplifiedData = test.simplify()
|
|
92
|
+
const serializableErr = serializeError(err)
|
|
93
|
+
safelySendToParent({ event: event.test.finished, workerIndex, data: { ...simplifiedData, err: serializableErr } })
|
|
94
|
+
})
|
|
95
|
+
event.dispatcher.on(event.test.failed, (test, err) => {
|
|
96
|
+
const simplifiedData = test.simplify()
|
|
97
|
+
const serializableErr = serializeError(err)
|
|
98
|
+
safelySendToParent({ event: event.test.failed, workerIndex, data: { ...simplifiedData, err: serializableErr } })
|
|
99
|
+
})
|
|
100
|
+
event.dispatcher.on(event.test.passed, (test, err) => safelySendToParent({ event: event.test.passed, workerIndex, data: { ...test.simplify(), err } }))
|
|
101
|
+
event.dispatcher.on(event.test.started, test => safelySendToParent({ event: event.test.started, workerIndex, data: test.simplify() }))
|
|
102
|
+
event.dispatcher.on(event.test.skipped, test => safelySendToParent({ event: event.test.skipped, workerIndex, data: test.simplify() }))
|
|
291
103
|
|
|
292
104
|
// steps
|
|
293
|
-
event.dispatcher.on(event.step.finished, step =>
|
|
294
|
-
event.dispatcher.on(event.step.started, step =>
|
|
295
|
-
event.dispatcher.on(event.step.passed, step =>
|
|
296
|
-
event.dispatcher.on(event.step.failed, step =>
|
|
105
|
+
event.dispatcher.on(event.step.finished, step => safelySendToParent({ event: event.step.finished, workerIndex, data: step.simplify() }))
|
|
106
|
+
event.dispatcher.on(event.step.started, step => safelySendToParent({ event: event.step.started, workerIndex, data: step.simplify() }))
|
|
107
|
+
event.dispatcher.on(event.step.passed, step => safelySendToParent({ event: event.step.passed, workerIndex, data: step.simplify() }))
|
|
108
|
+
event.dispatcher.on(event.step.failed, step => safelySendToParent({ event: event.step.failed, workerIndex, data: step.simplify() }))
|
|
109
|
+
|
|
110
|
+
event.dispatcher.on(event.hook.failed, (hook, err) => {
|
|
111
|
+
const serializableErr = serializeError(err)
|
|
112
|
+
safelySendToParent({ event: event.hook.failed, workerIndex, data: { ...hook.simplify(), err: serializableErr } })
|
|
113
|
+
})
|
|
114
|
+
event.dispatcher.on(event.hook.passed, hook => safelySendToParent({ event: event.hook.passed, workerIndex, data: hook.simplify() }))
|
|
115
|
+
event.dispatcher.on(event.hook.finished, hook => safelySendToParent({ event: event.hook.finished, workerIndex, data: hook.simplify() }))
|
|
116
|
+
|
|
117
|
+
event.dispatcher.once(event.all.after, () => safelySendToParent({ event: event.all.after, workerIndex, data: container.result().simplify() }))
|
|
118
|
+
// all
|
|
119
|
+
event.dispatcher.once(event.all.result, () => {
|
|
120
|
+
safelySendToParent({ event: event.all.result, workerIndex, data: container.result().simplify() })
|
|
121
|
+
parentPort?.close()
|
|
122
|
+
})
|
|
123
|
+
}
|
|
297
124
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
125
|
+
function disablePause() {
|
|
126
|
+
global.pause = () => {}
|
|
127
|
+
}
|
|
301
128
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Results will be sent once when the worker completes all tests
|
|
129
|
+
function serializeError(err) {
|
|
130
|
+
if (!err) return null
|
|
131
|
+
try {
|
|
132
|
+
return {
|
|
133
|
+
message: err.message,
|
|
134
|
+
stack: err.stack,
|
|
135
|
+
name: err.name,
|
|
136
|
+
actual: err.actual,
|
|
137
|
+
expected: err.expected,
|
|
138
|
+
}
|
|
139
|
+
} catch {
|
|
140
|
+
return { message: 'Error could not be serialized', name: 'Error' }
|
|
315
141
|
}
|
|
316
142
|
}
|
|
317
143
|
|
|
318
|
-
function
|
|
319
|
-
|
|
144
|
+
function safelySendToParent(data) {
|
|
145
|
+
try {
|
|
146
|
+
parentPort?.postMessage(data)
|
|
147
|
+
} catch (cloneError) {
|
|
148
|
+
// Fallback for non-serializable data
|
|
149
|
+
const fallbackData = { ...data }
|
|
150
|
+
|
|
151
|
+
// Try to serialize error objects if present
|
|
152
|
+
if (fallbackData.data && fallbackData.data.err) {
|
|
153
|
+
fallbackData.data.err = serializeError(fallbackData.data.err)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// If still fails, send minimal data
|
|
157
|
+
try {
|
|
158
|
+
parentPort?.postMessage(fallbackData)
|
|
159
|
+
} catch (finalError) {
|
|
160
|
+
parentPort?.postMessage({
|
|
161
|
+
event: data.event,
|
|
162
|
+
workerIndex,
|
|
163
|
+
data: {
|
|
164
|
+
title: fallbackData.data?.title || 'Unknown',
|
|
165
|
+
state: fallbackData.data?.state || 'error',
|
|
166
|
+
err: { message: 'Data could not be serialized' },
|
|
167
|
+
},
|
|
168
|
+
})
|
|
169
|
+
}
|
|
170
|
+
}
|
|
320
171
|
}
|
|
321
172
|
|
|
322
173
|
function sendToParentThread(data) {
|
|
@@ -324,10 +175,7 @@ function sendToParentThread(data) {
|
|
|
324
175
|
}
|
|
325
176
|
|
|
326
177
|
function listenToParentThread() {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
})
|
|
331
|
-
}
|
|
332
|
-
// In pool mode, message handling is done in runPoolTests()
|
|
178
|
+
parentPort?.on('message', eventData => {
|
|
179
|
+
container.append({ support: eventData.data })
|
|
180
|
+
})
|
|
333
181
|
}
|
package/lib/config.js
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
isFile,
|
|
6
|
-
deepMerge,
|
|
7
|
-
deepClone,
|
|
8
|
-
} = require('./utils');
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { createRequire } from 'module'
|
|
4
|
+
import { fileExists, isFile, deepMerge, deepClone } from './utils.js'
|
|
9
5
|
|
|
10
6
|
const defaultConfig = {
|
|
11
7
|
output: './_output',
|
|
@@ -33,18 +29,12 @@ const defaultConfig = {
|
|
|
33
29
|
timeout: 0,
|
|
34
30
|
},
|
|
35
31
|
],
|
|
36
|
-
}
|
|
32
|
+
}
|
|
37
33
|
|
|
38
|
-
let hooks = []
|
|
39
|
-
let config = {}
|
|
34
|
+
let hooks = []
|
|
35
|
+
let config = {}
|
|
40
36
|
|
|
41
|
-
const configFileNames = [
|
|
42
|
-
'codecept.config.js',
|
|
43
|
-
'codecept.conf.js',
|
|
44
|
-
'codecept.js',
|
|
45
|
-
'codecept.config.ts',
|
|
46
|
-
'codecept.conf.ts',
|
|
47
|
-
];
|
|
37
|
+
const configFileNames = ['codecept.config.js', 'codecept.conf.js', 'codecept.js', 'codecept.config.ts', 'codecept.conf.ts']
|
|
48
38
|
|
|
49
39
|
/**
|
|
50
40
|
* Current configuration
|
|
@@ -57,9 +47,9 @@ class Config {
|
|
|
57
47
|
* @return {Object<string, *>}
|
|
58
48
|
*/
|
|
59
49
|
static create(newConfig) {
|
|
60
|
-
config = deepMerge(deepClone(defaultConfig), newConfig)
|
|
61
|
-
hooks.forEach(f => f(config))
|
|
62
|
-
return config
|
|
50
|
+
config = deepMerge(deepClone(defaultConfig), newConfig)
|
|
51
|
+
hooks.forEach(f => f(config))
|
|
52
|
+
return config
|
|
63
53
|
}
|
|
64
54
|
|
|
65
55
|
/**
|
|
@@ -75,34 +65,34 @@ class Config {
|
|
|
75
65
|
* @param {string} configFile
|
|
76
66
|
* @return {*}
|
|
77
67
|
*/
|
|
78
|
-
static load(configFile) {
|
|
79
|
-
configFile = path.resolve(configFile || '.')
|
|
68
|
+
static async load(configFile) {
|
|
69
|
+
configFile = path.resolve(configFile || '.')
|
|
80
70
|
|
|
81
71
|
if (!fileExists(configFile)) {
|
|
82
|
-
configFile = configFile.replace('.js', '.ts')
|
|
72
|
+
configFile = configFile.replace('.js', '.ts')
|
|
83
73
|
|
|
84
74
|
if (!fileExists(configFile)) {
|
|
85
|
-
throw new Error(`Config file ${configFile} does not exist. Execute 'codeceptjs init' to create config`)
|
|
75
|
+
throw new Error(`Config file ${configFile} does not exist. Execute 'codeceptjs init' to create config`)
|
|
86
76
|
}
|
|
87
77
|
}
|
|
88
78
|
|
|
89
79
|
// is config file
|
|
90
80
|
if (isFile(configFile)) {
|
|
91
|
-
return loadConfigFile(configFile)
|
|
81
|
+
return await loadConfigFile(configFile)
|
|
92
82
|
}
|
|
93
83
|
|
|
94
84
|
for (const name of configFileNames) {
|
|
95
85
|
// is path to directory
|
|
96
|
-
const jsConfig = path.join(configFile, name)
|
|
86
|
+
const jsConfig = path.join(configFile, name)
|
|
97
87
|
|
|
98
88
|
if (isFile(jsConfig)) {
|
|
99
|
-
return loadConfigFile(jsConfig)
|
|
89
|
+
return await loadConfigFile(jsConfig)
|
|
100
90
|
}
|
|
101
91
|
}
|
|
102
92
|
|
|
103
|
-
const configPaths = configFileNames.map(name => path.join(configFile, name)).join(' or ')
|
|
93
|
+
const configPaths = configFileNames.map(name => path.join(configFile, name)).join(' or ')
|
|
104
94
|
|
|
105
|
-
throw new Error(`Can not load config from ${configPaths}\nCodeceptJS is not initialized in this dir. Execute 'codeceptjs init' to start`)
|
|
95
|
+
throw new Error(`Can not load config from ${configPaths}\nCodeceptJS is not initialized in this dir. Execute 'codeceptjs init' to start`)
|
|
106
96
|
}
|
|
107
97
|
|
|
108
98
|
/**
|
|
@@ -113,13 +103,13 @@ class Config {
|
|
|
113
103
|
*/
|
|
114
104
|
static get(key, val) {
|
|
115
105
|
if (key) {
|
|
116
|
-
return config[key] || val
|
|
106
|
+
return config[key] || val
|
|
117
107
|
}
|
|
118
|
-
return config
|
|
108
|
+
return config
|
|
119
109
|
}
|
|
120
110
|
|
|
121
111
|
static addHook(fn) {
|
|
122
|
-
hooks.push(fn)
|
|
112
|
+
hooks.push(fn)
|
|
123
113
|
}
|
|
124
114
|
|
|
125
115
|
/**
|
|
@@ -129,7 +119,7 @@ class Config {
|
|
|
129
119
|
* @return {Object<string, *>}
|
|
130
120
|
*/
|
|
131
121
|
static append(additionalConfig) {
|
|
132
|
-
return config = deepMerge(config, additionalConfig)
|
|
122
|
+
return (config = deepMerge(config, additionalConfig))
|
|
133
123
|
}
|
|
134
124
|
|
|
135
125
|
/**
|
|
@@ -137,33 +127,105 @@ class Config {
|
|
|
137
127
|
* @return {Object<string, *>}
|
|
138
128
|
*/
|
|
139
129
|
static reset() {
|
|
140
|
-
hooks = []
|
|
141
|
-
return config = { ...defaultConfig }
|
|
130
|
+
hooks = []
|
|
131
|
+
return (config = { ...defaultConfig })
|
|
142
132
|
}
|
|
143
133
|
}
|
|
144
134
|
|
|
145
|
-
|
|
135
|
+
export default Config
|
|
146
136
|
|
|
147
|
-
function loadConfigFile(configFile) {
|
|
148
|
-
const
|
|
137
|
+
async function loadConfigFile(configFile) {
|
|
138
|
+
const require = createRequire(import.meta.url)
|
|
139
|
+
const extensionName = path.extname(configFile)
|
|
149
140
|
|
|
150
|
-
|
|
141
|
+
// .conf.js config file
|
|
142
|
+
if (extensionName === '.js' || extensionName === '.ts' || extensionName === '.cjs') {
|
|
143
|
+
let configModule
|
|
151
144
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
// For .ts files, try to compile and load as JavaScript
|
|
146
|
+
if (extensionName === '.ts') {
|
|
147
|
+
try {
|
|
148
|
+
// Try to load ts-node and compile the file
|
|
149
|
+
const { transpile } = require('typescript')
|
|
150
|
+
const tsContent = fs.readFileSync(configFile, 'utf8')
|
|
151
|
+
|
|
152
|
+
// Transpile TypeScript to JavaScript with ES module output
|
|
153
|
+
const jsContent = transpile(tsContent, {
|
|
154
|
+
module: 99, // ModuleKind.ESNext
|
|
155
|
+
target: 99, // ScriptTarget.ESNext
|
|
156
|
+
esModuleInterop: true,
|
|
157
|
+
allowSyntheticDefaultImports: true,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
// Create a temporary JS file with .mjs extension to force ES module treatment
|
|
161
|
+
const tempJsFile = configFile.replace('.ts', '.temp.mjs')
|
|
162
|
+
fs.writeFileSync(tempJsFile, jsContent)
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
configModule = await import(tempJsFile)
|
|
166
|
+
// Clean up temp file
|
|
167
|
+
fs.unlinkSync(tempJsFile)
|
|
168
|
+
} catch (err) {
|
|
169
|
+
// Clean up temp file even on error
|
|
170
|
+
if (fs.existsSync(tempJsFile)) {
|
|
171
|
+
fs.unlinkSync(tempJsFile)
|
|
172
|
+
}
|
|
173
|
+
throw err
|
|
174
|
+
}
|
|
175
|
+
} catch (tsError) {
|
|
176
|
+
// If TypeScript compilation fails, fallback to ts-node
|
|
177
|
+
try {
|
|
178
|
+
require('ts-node/register')
|
|
179
|
+
configModule = require(configFile)
|
|
180
|
+
} catch (tsNodeError) {
|
|
181
|
+
throw new Error(`Failed to load TypeScript config: ${tsError.message}`)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
// Try ESM import first for JS files
|
|
186
|
+
configModule = await import(configFile)
|
|
187
|
+
}
|
|
188
|
+
} catch (importError) {
|
|
189
|
+
try {
|
|
190
|
+
// Fall back to CommonJS require for .js/.cjs files
|
|
191
|
+
if (extensionName !== '.ts') {
|
|
192
|
+
configModule = require(configFile)
|
|
193
|
+
} else {
|
|
194
|
+
throw importError
|
|
195
|
+
}
|
|
196
|
+
} catch (requireError) {
|
|
197
|
+
throw new Error(`Failed to load config file ${configFile}: ${importError.message}`)
|
|
198
|
+
}
|
|
155
199
|
}
|
|
156
|
-
}
|
|
157
200
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
201
|
+
const rawConfig = configModule.config || configModule.default?.config || configModule
|
|
202
|
+
|
|
203
|
+
// Process helpers to extract imported classes
|
|
204
|
+
if (rawConfig.helpers) {
|
|
205
|
+
const processedHelpers = {}
|
|
206
|
+
for (const [helperName, helperConfig] of Object.entries(rawConfig.helpers)) {
|
|
207
|
+
// Check if the helper name itself is a class (ESM import)
|
|
208
|
+
if (typeof helperName === 'function' && helperName.prototype) {
|
|
209
|
+
// This is an imported class, use its constructor name
|
|
210
|
+
const className = helperName.name
|
|
211
|
+
processedHelpers[className] = {
|
|
212
|
+
...helperConfig,
|
|
213
|
+
_helperClass: helperName,
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
processedHelpers[helperName] = helperConfig
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
rawConfig.helpers = processedHelpers
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return Config.create(rawConfig)
|
|
161
223
|
}
|
|
162
224
|
|
|
163
225
|
// json config provided
|
|
164
226
|
if (extensionName === '.json') {
|
|
165
|
-
return Config.create(JSON.parse(fs.readFileSync(configFile, 'utf8')))
|
|
227
|
+
return Config.create(JSON.parse(fs.readFileSync(configFile, 'utf8')))
|
|
166
228
|
}
|
|
167
229
|
|
|
168
|
-
throw new Error(`Config file ${configFile} can't be loaded`)
|
|
230
|
+
throw new Error(`Config file ${configFile} can't be loaded`)
|
|
169
231
|
}
|