codeceptjs 4.0.1-beta.9 → 4.0.1

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