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.
Files changed (179) hide show
  1. package/README.md +0 -45
  2. package/bin/codecept.js +46 -57
  3. package/lib/actor.js +15 -11
  4. package/lib/ai.js +6 -5
  5. package/lib/assert/empty.js +9 -8
  6. package/lib/assert/equal.js +15 -17
  7. package/lib/assert/error.js +2 -2
  8. package/lib/assert/include.js +9 -11
  9. package/lib/assert/throws.js +1 -1
  10. package/lib/assert/truth.js +8 -5
  11. package/lib/assert.js +18 -18
  12. package/lib/codecept.js +66 -107
  13. package/lib/colorUtils.js +48 -50
  14. package/lib/command/check.js +32 -27
  15. package/lib/command/configMigrate.js +11 -10
  16. package/lib/command/definitions.js +16 -10
  17. package/lib/command/dryRun.js +16 -16
  18. package/lib/command/generate.js +29 -26
  19. package/lib/command/gherkin/init.js +36 -38
  20. package/lib/command/gherkin/snippets.js +14 -14
  21. package/lib/command/gherkin/steps.js +21 -18
  22. package/lib/command/info.js +8 -8
  23. package/lib/command/init.js +34 -31
  24. package/lib/command/interactive.js +11 -10
  25. package/lib/command/list.js +10 -9
  26. package/lib/command/run-multiple/chunk.js +5 -5
  27. package/lib/command/run-multiple/collection.js +5 -5
  28. package/lib/command/run-multiple/run.js +3 -3
  29. package/lib/command/run-multiple.js +16 -13
  30. package/lib/command/run-rerun.js +6 -7
  31. package/lib/command/run-workers.js +10 -24
  32. package/lib/command/run.js +8 -8
  33. package/lib/command/utils.js +20 -18
  34. package/lib/command/workers/runTests.js +117 -269
  35. package/lib/config.js +111 -49
  36. package/lib/container.js +299 -102
  37. package/lib/data/context.js +6 -5
  38. package/lib/data/dataScenarioConfig.js +1 -1
  39. package/lib/data/dataTableArgument.js +1 -1
  40. package/lib/data/table.js +1 -1
  41. package/lib/effects.js +94 -10
  42. package/lib/els.js +11 -9
  43. package/lib/event.js +11 -10
  44. package/lib/globals.js +141 -0
  45. package/lib/heal.js +12 -12
  46. package/lib/helper/AI.js +1 -1
  47. package/lib/helper/ApiDataFactory.js +16 -13
  48. package/lib/helper/FileSystem.js +32 -12
  49. package/lib/helper/GraphQL.js +1 -1
  50. package/lib/helper/GraphQLDataFactory.js +1 -1
  51. package/lib/helper/JSONResponse.js +19 -30
  52. package/lib/helper/Mochawesome.js +9 -28
  53. package/lib/helper/Playwright.js +668 -265
  54. package/lib/helper/Puppeteer.js +284 -169
  55. package/lib/helper/REST.js +29 -12
  56. package/lib/helper/WebDriver.js +191 -71
  57. package/lib/helper/errors/ConnectionRefused.js +6 -6
  58. package/lib/helper/errors/ElementAssertion.js +11 -16
  59. package/lib/helper/errors/ElementNotFound.js +5 -9
  60. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  61. package/lib/helper/extras/Console.js +11 -11
  62. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  63. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  64. package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
  65. package/lib/helper/extras/Popup.js +1 -1
  66. package/lib/helper/extras/React.js +29 -30
  67. package/lib/helper/network/actions.js +33 -48
  68. package/lib/helper/network/utils.js +76 -83
  69. package/lib/helper/scripts/blurElement.js +6 -6
  70. package/lib/helper/scripts/focusElement.js +6 -6
  71. package/lib/helper/scripts/highlightElement.js +9 -9
  72. package/lib/helper/scripts/isElementClickable.js +34 -34
  73. package/lib/helper.js +2 -1
  74. package/lib/history.js +23 -20
  75. package/lib/hooks.js +10 -10
  76. package/lib/html.js +90 -100
  77. package/lib/index.js +48 -21
  78. package/lib/listener/config.js +8 -9
  79. package/lib/listener/emptyRun.js +6 -7
  80. package/lib/listener/exit.js +4 -3
  81. package/lib/listener/globalRetry.js +5 -5
  82. package/lib/listener/globalTimeout.js +11 -10
  83. package/lib/listener/helpers.js +33 -14
  84. package/lib/listener/mocha.js +3 -4
  85. package/lib/listener/result.js +4 -5
  86. package/lib/listener/steps.js +7 -18
  87. package/lib/listener/store.js +3 -3
  88. package/lib/locator.js +213 -192
  89. package/lib/mocha/asyncWrapper.js +108 -75
  90. package/lib/mocha/bdd.js +99 -13
  91. package/lib/mocha/cli.js +60 -27
  92. package/lib/mocha/factory.js +75 -19
  93. package/lib/mocha/featureConfig.js +1 -1
  94. package/lib/mocha/gherkin.js +57 -25
  95. package/lib/mocha/hooks.js +12 -3
  96. package/lib/mocha/index.js +13 -4
  97. package/lib/mocha/inject.js +22 -5
  98. package/lib/mocha/scenarioConfig.js +2 -2
  99. package/lib/mocha/suite.js +9 -2
  100. package/lib/mocha/test.js +10 -13
  101. package/lib/mocha/ui.js +28 -31
  102. package/lib/output.js +11 -9
  103. package/lib/parser.js +44 -44
  104. package/lib/pause.js +15 -16
  105. package/lib/plugin/analyze.js +19 -12
  106. package/lib/plugin/auth.js +20 -21
  107. package/lib/plugin/autoDelay.js +12 -8
  108. package/lib/plugin/coverage.js +12 -8
  109. package/lib/plugin/customLocator.js +3 -3
  110. package/lib/plugin/customReporter.js +3 -2
  111. package/lib/plugin/heal.js +14 -9
  112. package/lib/plugin/pageInfo.js +10 -10
  113. package/lib/plugin/pauseOnFail.js +4 -3
  114. package/lib/plugin/retryFailedStep.js +47 -5
  115. package/lib/plugin/screenshotOnFail.js +75 -37
  116. package/lib/plugin/stepByStepReport.js +14 -14
  117. package/lib/plugin/stepTimeout.js +4 -3
  118. package/lib/plugin/subtitles.js +6 -5
  119. package/lib/recorder.js +33 -23
  120. package/lib/rerun.js +69 -26
  121. package/lib/result.js +4 -4
  122. package/lib/secret.js +18 -17
  123. package/lib/session.js +95 -89
  124. package/lib/step/base.js +6 -6
  125. package/lib/step/config.js +1 -1
  126. package/lib/step/func.js +3 -3
  127. package/lib/step/helper.js +3 -3
  128. package/lib/step/meta.js +4 -4
  129. package/lib/step/record.js +11 -11
  130. package/lib/step/retry.js +3 -3
  131. package/lib/step/section.js +3 -3
  132. package/lib/step.js +7 -10
  133. package/lib/steps.js +9 -5
  134. package/lib/store.js +1 -1
  135. package/lib/timeout.js +1 -7
  136. package/lib/transform.js +8 -8
  137. package/lib/translation.js +32 -18
  138. package/lib/utils.js +68 -97
  139. package/lib/workerStorage.js +16 -17
  140. package/lib/workers.js +145 -171
  141. package/package.json +63 -57
  142. package/translations/de-DE.js +2 -2
  143. package/translations/fr-FR.js +2 -2
  144. package/translations/index.js +23 -10
  145. package/translations/it-IT.js +2 -2
  146. package/translations/ja-JP.js +2 -2
  147. package/translations/nl-NL.js +2 -2
  148. package/translations/pl-PL.js +2 -2
  149. package/translations/pt-BR.js +2 -2
  150. package/translations/ru-RU.js +2 -2
  151. package/translations/utils.js +11 -2
  152. package/translations/zh-CN.js +2 -2
  153. package/translations/zh-TW.js +2 -2
  154. package/typings/index.d.ts +7 -18
  155. package/typings/promiseBasedTypes.d.ts +3769 -5450
  156. package/typings/types.d.ts +3953 -5778
  157. package/bin/test-server.js +0 -53
  158. package/lib/element/WebElement.js +0 -327
  159. package/lib/helper/Nightmare.js +0 -1486
  160. package/lib/helper/Protractor.js +0 -1840
  161. package/lib/helper/TestCafe.js +0 -1391
  162. package/lib/helper/clientscripts/nightmare.js +0 -213
  163. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
  164. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  165. package/lib/helper/testcafe/testcafe-utils.js +0 -61
  166. package/lib/listener/retryEnhancer.js +0 -85
  167. package/lib/plugin/allure.js +0 -15
  168. package/lib/plugin/autoLogin.js +0 -5
  169. package/lib/plugin/commentStep.js +0 -141
  170. package/lib/plugin/eachElement.js +0 -127
  171. package/lib/plugin/fakerTransform.js +0 -49
  172. package/lib/plugin/htmlReporter.js +0 -1947
  173. package/lib/plugin/retryTo.js +0 -16
  174. package/lib/plugin/selenoid.js +0 -364
  175. package/lib/plugin/standardActingHelpers.js +0 -6
  176. package/lib/plugin/tryTo.js +0 -16
  177. package/lib/plugin/wdio.js +0 -247
  178. package/lib/test-server.js +0 -323
  179. package/lib/within.js +0 -90
@@ -1,4 +1,4 @@
1
- const tty = require('tty')
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
- const { parentPort, workerData } = require('worker_threads')
11
- const event = require('../../event')
12
- const container = require('../../container')
13
- const { getConfig } = require('../utils')
14
- const { tryOrDefault, deepMerge } = require('../../utils')
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
- // Requiring of Codecept need to be after tty.getWindowSize is available.
21
- const Codecept = require(process.env.CODECEPT_CLASS_PATH || '../../codecept')
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, poolMode } = workerData
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
- if (poolMode) {
54
- await runPoolTests()
55
- } else if (mocha.suite.total()) {
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
- // Clear suites and tests but preserve other mocha settings
204
- mocha.suite.suites = []
205
- mocha.suite.tests = []
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
- const originalTests = [...suite.tests]
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
- // Clean up empty suites again
260
- mocha.suite.suites = mocha.suite.suites.filter(suite => suite.tests.length > 0)
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
- function filterTests() {
265
- const files = codecept.testFiles
266
- mocha.files = files
267
- mocha.loadFiles()
70
+ filterTests()
268
71
 
269
- for (const suite of mocha.suite.suites) {
270
- suite.tests = suite.tests.filter(test => tests.indexOf(test.uid) >= 0)
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 => sendToParentThread({ event: event.suite.before, workerIndex, data: suite.simplify() }))
277
- event.dispatcher.on(event.suite.after, suite => sendToParentThread({ event: event.suite.after, workerIndex, data: suite.simplify() }))
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 => sendToParentThread({ event: event.test.before, workerIndex, data: test.simplify() }))
284
- event.dispatcher.on(event.test.after, test => sendToParentThread({ event: event.test.after, workerIndex, data: test.simplify() }))
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) => sendToParentThread({ event: event.test.finished, workerIndex, data: { ...test.simplify(), err } }))
287
- event.dispatcher.on(event.test.failed, (test, err) => sendToParentThread({ event: event.test.failed, workerIndex, data: { ...test.simplify(), err } }))
288
- event.dispatcher.on(event.test.passed, (test, err) => sendToParentThread({ event: event.test.passed, workerIndex, data: { ...test.simplify(), err } }))
289
- event.dispatcher.on(event.test.started, test => sendToParentThread({ event: event.test.started, workerIndex, data: test.simplify() }))
290
- event.dispatcher.on(event.test.skipped, test => sendToParentThread({ event: event.test.skipped, workerIndex, data: test.simplify() }))
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 => sendToParentThread({ event: event.step.finished, workerIndex, data: step.simplify() }))
294
- event.dispatcher.on(event.step.started, step => sendToParentThread({ event: event.step.started, workerIndex, data: step.simplify() }))
295
- event.dispatcher.on(event.step.passed, step => sendToParentThread({ event: event.step.passed, workerIndex, data: step.simplify() }))
296
- event.dispatcher.on(event.step.failed, step => sendToParentThread({ event: event.step.failed, workerIndex, data: step.simplify() }))
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
- event.dispatcher.on(event.hook.failed, (hook, err) => sendToParentThread({ event: event.hook.failed, workerIndex, data: { ...hook.simplify(), err } }))
299
- event.dispatcher.on(event.hook.passed, hook => sendToParentThread({ event: event.hook.passed, workerIndex, data: hook.simplify() }))
300
- event.dispatcher.on(event.hook.finished, hook => sendToParentThread({ event: event.hook.finished, workerIndex, data: hook.simplify() }))
125
+ function disablePause() {
126
+ global.pause = () => {}
127
+ }
301
128
 
302
- if (!poolMode) {
303
- // In regular mode, close worker after all tests are complete
304
- event.dispatcher.once(event.all.after, () => {
305
- sendToParentThread({ event: event.all.after, workerIndex, data: container.result().simplify() })
306
- })
307
- // all
308
- event.dispatcher.once(event.all.result, () => {
309
- sendToParentThread({ event: event.all.result, workerIndex, data: container.result().simplify() })
310
- parentPort?.close()
311
- })
312
- } else {
313
- // In pool mode, don't send result events for individual tests
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 disablePause() {
319
- global.pause = () => {}
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
- if (!poolMode) {
328
- parentPort?.on('message', eventData => {
329
- container.append({ support: eventData.data })
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
- const fs = require('fs');
2
- const path = require('path');
3
- const {
4
- fileExists,
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
- module.exports = Config;
135
+ export default Config
146
136
 
147
- function loadConfigFile(configFile) {
148
- const extensionName = path.extname(configFile);
137
+ async function loadConfigFile(configFile) {
138
+ const require = createRequire(import.meta.url)
139
+ const extensionName = path.extname(configFile)
149
140
 
150
- if (extensionName === '.ts') {
141
+ // .conf.js config file
142
+ if (extensionName === '.js' || extensionName === '.ts' || extensionName === '.cjs') {
143
+ let configModule
151
144
  try {
152
- require('ts-node/register');
153
- } catch (err) {
154
- console.log('ts-node package is required to parse codecept.conf.ts config correctly');
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
- // .conf.js config file
159
- if (extensionName === '.js' || extensionName === '.ts' || extensionName === '.cjs') {
160
- return Config.create(require(configFile).config);
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
  }