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
package/lib/container.js CHANGED
@@ -5,7 +5,7 @@ import debugModule from 'debug'
5
5
  const debug = debugModule('codeceptjs:container')
6
6
  import { MetaStep } from './step.js'
7
7
  import { methodsOfObject, fileExists, isFunction, isAsyncFunction, installedLocally, deepMerge } from './utils.js'
8
- import { transpileTypeScript, cleanupTempFiles } from './utils/typescript.js'
8
+ import { transpileTypeScript, cleanupTempFiles, fixErrorStack } from './utils/typescript.js'
9
9
  import Translation from './translation.js'
10
10
  import MochaFactory from './mocha/factory.js'
11
11
  import recorder from './recorder.js'
@@ -15,9 +15,15 @@ import store from './store.js'
15
15
  import Result from './result.js'
16
16
  import ai from './ai.js'
17
17
  import actorFactory from './actor.js'
18
+ import Config from './config.js'
18
19
 
19
20
  let asyncHelperPromise
20
21
 
22
+ let beforeCalledSet = new Set()
23
+
24
+ export function getBeforeCalledSet() { return beforeCalledSet }
25
+ export function resetBeforeCalledSet() { beforeCalledSet = new Set() }
26
+
21
27
  let container = {
22
28
  helpers: {},
23
29
  support: {},
@@ -34,6 +40,7 @@ let container = {
34
40
  /** @type {Result | null} */
35
41
  result: null,
36
42
  sharedKeys: new Set(), // Track keys shared via share() function
43
+ tsFileMapping: null, // TypeScript file mapping for error stack fixing
37
44
  }
38
45
 
39
46
  /**
@@ -88,7 +95,7 @@ class Container {
88
95
  container.support.I = mod
89
96
  }
90
97
  } catch (e) {
91
- throw new Error(`Could not include object I: ${e.message}`)
98
+ throw e
92
99
  }
93
100
  } else {
94
101
  // Create default actor - this sets up the callback in asyncHelperPromise
@@ -115,6 +122,18 @@ class Container {
115
122
  // Wait for all async helpers to finish loading and populate the actor
116
123
  await asyncHelperPromise
117
124
 
125
+ // Plugins may have registered Config hooks during their boot. Run anything
126
+ // that hasn't been applied yet and re-feed the mutated helper config to the
127
+ // already-instantiated helpers.
128
+ if (Config.runPendingHooks(config)) {
129
+ for (const name of Object.keys(container.helpers)) {
130
+ const helper = container.helpers[name]
131
+ if (helper && typeof helper._setConfig === 'function' && config.helpers && config.helpers[name]) {
132
+ helper._setConfig(config.helpers[name])
133
+ }
134
+ }
135
+ }
136
+
118
137
  if (opts && opts.ai) ai.enable(config.ai) // enable AI Assistant
119
138
  if (config.gherkin) await loadGherkinStepsAsync(config.gherkin.steps || [])
120
139
  if (opts && typeof opts.timeouts === 'boolean') store.timeouts = opts.timeouts
@@ -149,10 +168,23 @@ class Container {
149
168
  if (!name) {
150
169
  return container.proxySupport
151
170
  }
152
- // Always return the proxy to ensure MetaStep creation works
171
+ if (typeof container.support[name] === 'function') {
172
+ return container.support[name]
173
+ }
153
174
  return container.proxySupport[name]
154
175
  }
155
176
 
177
+ /**
178
+ * Get raw (non-proxied) support objects for direct access.
179
+ * Used by listeners to call lifecycle hooks without MetaStep wrapping.
180
+ *
181
+ * @api
182
+ * @returns {object}
183
+ */
184
+ static supportObjects() {
185
+ return container.support
186
+ }
187
+
156
188
  /**
157
189
  * Get all helpers or get a helper by name
158
190
  *
@@ -176,6 +208,15 @@ class Container {
176
208
  return container.translation
177
209
  }
178
210
 
211
+ /**
212
+ * Get TypeScript file mapping for error stack fixing
213
+ *
214
+ * @api
215
+ */
216
+ static tsFileMapping() {
217
+ return store.tsFileMapping
218
+ }
219
+
179
220
  /**
180
221
  * Get Mocha instance
181
222
  *
@@ -398,20 +439,66 @@ async function requireHelperFromModule(helperName, config, HelperClass) {
398
439
  throw err
399
440
  }
400
441
  } else {
442
+ // Handle TypeScript files
443
+ let importPath = moduleName
444
+ let tempJsFile = null
445
+ let fileMapping = null
446
+ const ext = path.extname(moduleName)
447
+
448
+ if (ext === '.ts') {
449
+ try {
450
+ // Use the TypeScript transpilation utility
451
+ const typescript = await import('typescript')
452
+ const { tempFile, allTempFiles, fileMapping: mapping } = await transpileTypeScript(importPath, typescript)
453
+
454
+ debug(`Transpiled TypeScript helper: ${importPath} -> ${tempFile}`)
455
+
456
+ importPath = tempFile
457
+ tempJsFile = allTempFiles
458
+ fileMapping = mapping
459
+ // Store file mapping in container for runtime error fixing (merge with existing)
460
+ if (!store.tsFileMapping) {
461
+ store.tsFileMapping = new Map()
462
+ }
463
+ for (const [key, value] of mapping.entries()) {
464
+ store.tsFileMapping.set(key, value)
465
+ }
466
+ } catch (tsError) {
467
+ throw new Error(`Failed to load TypeScript helper ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
468
+ }
469
+ }
470
+
401
471
  // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
402
472
  try {
403
473
  // Try dynamic import for both CommonJS and ESM modules
404
- const mod = await import(moduleName)
474
+ const mod = await import(importPath)
405
475
  if (!mod && !mod.default) {
406
476
  throw new Error(`Helper module '${moduleName}' was not found. Make sure you have installed the package correctly.`)
407
477
  }
408
478
  HelperClass = mod.default || mod
479
+
480
+ // Clean up temp files if created
481
+ if (tempJsFile) {
482
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
483
+ cleanupTempFiles(filesToClean)
484
+ }
409
485
  } catch (err) {
486
+ // Fix error stack to point to original .ts files
487
+ if (fileMapping) {
488
+ fixErrorStack(err, fileMapping)
489
+ }
490
+
491
+ // Clean up temp files before rethrowing
492
+ if (tempJsFile) {
493
+ const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
494
+ cleanupTempFiles(filesToClean)
495
+ }
496
+
410
497
  if (err.code === 'ERR_REQUIRE_ESM' || (err.message && err.message.includes('ES module'))) {
411
498
  // This is an ESM module, use dynamic import
412
499
  try {
413
500
  const pathModule = await import('path')
414
- const absolutePath = pathModule.default.resolve(moduleName)
501
+ const absolutePath = pathModule.default.resolve(importPath)
415
502
  const mod = await import(absolutePath)
416
503
  HelperClass = mod.default || mod
417
504
  debug(`helper ${helperName} loaded via ESM import`)
@@ -486,6 +573,19 @@ function createSupportObjects(config) {
486
573
  let currentValue = currentObject[prop]
487
574
 
488
575
  if (isFunction(currentValue) || isAsyncFunction(currentValue)) {
576
+ if (prop.toString().charAt(0) !== '_' && currentObject._before && !beforeCalledSet.has(name)) {
577
+ beforeCalledSet.add(name)
578
+ const originalValue = currentValue
579
+ const wrappedValue = async function (...args) {
580
+ await currentObject._before()
581
+ return originalValue.apply(currentObject, args)
582
+ }
583
+ const ms = new MetaStep(name, prop)
584
+ ms.setContext(currentObject)
585
+ debug(`metastep is created for ${name}.${prop.toString()}() (with _before)`)
586
+ return ms.run.bind(ms, asyncWrapper(wrappedValue))
587
+ }
588
+
489
589
  const ms = new MetaStep(name, prop)
490
590
  ms.setContext(currentObject)
491
591
  if (isAsyncFunction(currentValue)) currentValue = asyncWrapper(currentValue)
@@ -544,6 +644,8 @@ function createSupportObjects(config) {
544
644
  let value
545
645
  if (container.sharedKeys.has(prop) && prop in container.support) {
546
646
  value = container.support[prop]
647
+ } else if (prop in container.support && typeof container.support[prop] === 'function') {
648
+ value = container.support[prop]
547
649
  } else {
548
650
  value = lazyLoad(prop)
549
651
  }
@@ -558,6 +660,9 @@ function createSupportObjects(config) {
558
660
  if (container.sharedKeys.has(key) && key in container.support) {
559
661
  return container.support[key]
560
662
  }
663
+ if (key in container.support && typeof container.support[key] === 'function') {
664
+ return container.support[key]
665
+ }
561
666
  return lazyLoad(key)
562
667
  },
563
668
  },
@@ -598,26 +703,55 @@ async function loadPluginFallback(modulePath, config) {
598
703
  async function createPlugins(config, options = {}) {
599
704
  const plugins = {}
600
705
 
601
- const enabledPluginsByOptions = (options.plugins || '').split(',')
706
+ const pluginOptionMap = new Map()
707
+ for (const token of (options.plugins || '').split(',').filter(Boolean)) {
708
+ const parts = token.split(':')
709
+ pluginOptionMap.set(parts[0], parts.slice(1))
710
+ }
711
+
712
+ for (const [name] of pluginOptionMap) {
713
+ if (!config[name]) config[name] = {}
714
+ }
715
+
602
716
  for (const pluginName in config) {
603
717
  if (!config[pluginName]) config[pluginName] = {}
604
- if (!config[pluginName].enabled && enabledPluginsByOptions.indexOf(pluginName) < 0) {
718
+ const pluginConfig = config[pluginName]
719
+ const enabledByCli = pluginOptionMap.has(pluginName)
720
+ if (!pluginConfig.enabled && !enabledByCli) {
605
721
  continue // plugin is disabled
606
722
  }
723
+
724
+ if (enabledByCli && pluginOptionMap.get(pluginName).length > 0) {
725
+ pluginConfig._args = pluginOptionMap.get(pluginName)
726
+ }
727
+
728
+ // Generic workers gate:
729
+ // - runInWorker / runInWorkers controls plugin execution inside worker threads.
730
+ // - runInParent / runInMain can disable plugin in workers parent process.
731
+ const runInWorker = pluginConfig.runInWorker ?? pluginConfig.runInWorkers ?? (pluginName === 'testomatio' ? false : true)
732
+ const runInParent = pluginConfig.runInParent ?? pluginConfig.runInMain ?? true
733
+
734
+ if (options.child && !runInWorker) {
735
+ continue
736
+ }
737
+
738
+ if (!options.child && store.workerMode && !runInParent) {
739
+ continue
740
+ }
607
741
  let module
608
742
  try {
609
- if (config[pluginName].require) {
610
- module = config[pluginName].require
743
+ if (pluginConfig.require) {
744
+ module = pluginConfig.require
611
745
  if (module.startsWith('.')) {
612
746
  // local
613
- module = path.resolve(global.codecept_dir, module) // custom plugin
747
+ module = path.resolve(store.codeceptDir, module) // custom plugin
614
748
  }
615
749
  } else {
616
750
  module = `./plugin/${pluginName}.js`
617
751
  }
618
752
 
619
753
  // Use async loading for all plugins (ESM and CJS)
620
- plugins[pluginName] = await loadPluginAsync(module, config[pluginName])
754
+ plugins[pluginName] = await loadPluginAsync(module, pluginConfig)
621
755
  debug(`plugin ${pluginName} loaded via async import`)
622
756
  } catch (err) {
623
757
  throw new Error(`Could not load plugin ${pluginName} from module '${module}':\n${err.message}\n${err.stack}`)
@@ -627,12 +761,24 @@ async function createPlugins(config, options = {}) {
627
761
  }
628
762
 
629
763
  async function loadGherkinStepsAsync(paths) {
764
+ // Import BDD module to access step file tracking functions and step DSL
765
+ const bddModule = await import('./mocha/bdd.js')
766
+
630
767
  global.Before = fn => event.dispatcher.on(event.test.started, fn)
631
768
  global.After = fn => event.dispatcher.on(event.test.finished, fn)
632
769
  global.Fail = fn => event.dispatcher.on(event.test.failed, fn)
633
770
 
634
- // Import BDD module to access step file tracking functions
635
- const bddModule = await import('./mocha/bdd.js')
771
+ // Scope-inject Given/When/Then/And while loading step files so they work
772
+ // with noGlobals: true. When noGlobals: false, globals.js has already set
773
+ // them as permanent globals — skip to avoid deleting them at the end.
774
+ const injectStepDsl = !!store.noGlobals
775
+ if (injectStepDsl) {
776
+ global.Given = bddModule.Given
777
+ global.When = bddModule.When
778
+ global.Then = bddModule.Then
779
+ global.And = bddModule.And
780
+ global.DefineParameterType = bddModule.defineParameterType
781
+ }
636
782
 
637
783
  // If gherkin.steps is string, then this will iterate through that folder and send all step def js files to loadSupportObject
638
784
  // If gherkin.steps is Array, it will go the old way
@@ -645,7 +791,7 @@ async function loadGherkinStepsAsync(paths) {
645
791
  bddModule.clearCurrentStepFile()
646
792
  }
647
793
  } else {
648
- const folderPath = paths.startsWith('.') ? normalizeAndJoin(global.codecept_dir, paths) : ''
794
+ const folderPath = paths.startsWith('.') ? normalizeAndJoin(store.codeceptDir, paths) : ''
649
795
  if (folderPath !== '') {
650
796
  const files = globSync(folderPath)
651
797
  for (const file of files) {
@@ -660,6 +806,13 @@ async function loadGherkinStepsAsync(paths) {
660
806
  delete global.Before
661
807
  delete global.After
662
808
  delete global.Fail
809
+ if (injectStepDsl) {
810
+ delete global.Given
811
+ delete global.When
812
+ delete global.Then
813
+ delete global.And
814
+ delete global.DefineParameterType
815
+ }
663
816
  }
664
817
 
665
818
  function loadGherkinSteps(paths) {
@@ -693,12 +846,13 @@ async function loadSupportObject(modulePath, supportObjectName) {
693
846
  }
694
847
  }
695
848
  if (typeof modulePath === 'string' && modulePath.charAt(0) === '.') {
696
- modulePath = path.join(global.codecept_dir, modulePath)
849
+ modulePath = path.join(store.codeceptDir, modulePath)
697
850
  }
698
851
  try {
699
852
  // Use dynamic import for both ESM and CJS modules
700
853
  let importPath = modulePath
701
854
  let tempJsFile = null
855
+ let fileMapping = null
702
856
 
703
857
  if (typeof importPath === 'string') {
704
858
  const ext = path.extname(importPath)
@@ -708,7 +862,7 @@ async function loadSupportObject(modulePath, supportObjectName) {
708
862
  try {
709
863
  // Use the TypeScript transpilation utility
710
864
  const typescript = await import('typescript')
711
- const { tempFile, allTempFiles } = await transpileTypeScript(importPath, typescript)
865
+ const { tempFile, allTempFiles, fileMapping: mapping } = await transpileTypeScript(importPath, typescript)
712
866
 
713
867
  debug(`Transpiled TypeScript file: ${importPath} -> ${tempFile}`)
714
868
 
@@ -716,6 +870,14 @@ async function loadSupportObject(modulePath, supportObjectName) {
716
870
  importPath = tempFile
717
871
  // Store temp files list in a way that cleanup can access them
718
872
  tempJsFile = allTempFiles
873
+ fileMapping = mapping
874
+ // Store file mapping in container for runtime error fixing (merge with existing)
875
+ if (!container.tsFileMapping) {
876
+ container.tsFileMapping = new Map()
877
+ }
878
+ for (const [key, value] of mapping.entries()) {
879
+ container.tsFileMapping.set(key, value)
880
+ }
719
881
  } catch (tsError) {
720
882
  throw new Error(`Failed to load TypeScript file ${importPath}: ${tsError.message}. Make sure 'typescript' package is installed.`)
721
883
  }
@@ -729,6 +891,11 @@ async function loadSupportObject(modulePath, supportObjectName) {
729
891
  try {
730
892
  obj = await import(importPath)
731
893
  } catch (importError) {
894
+ // Fix error stack to point to original .ts files
895
+ if (fileMapping) {
896
+ fixErrorStack(importError, fileMapping)
897
+ }
898
+
732
899
  // Clean up temp files if created before rethrowing
733
900
  if (tempJsFile) {
734
901
  const filesToClean = Array.isArray(tempJsFile) ? tempJsFile : [tempJsFile]
@@ -777,7 +944,9 @@ async function loadSupportObject(modulePath, supportObjectName) {
777
944
 
778
945
  throw new Error(`Support object "${supportObjectName}" should be an object, class, or function, but got ${typeof actualObj}`)
779
946
  } catch (err) {
780
- throw new Error(`Could not include object ${supportObjectName} from module '${modulePath}'\n${err.message}\n${err.stack}`)
947
+ const newErr = new Error(`Could not include object ${supportObjectName} from module '${modulePath}': ${err.message}`)
948
+ newErr.stack = err.stack
949
+ throw newErr
781
950
  }
782
951
  }
783
952
 
@@ -801,7 +970,7 @@ async function loadTranslation(locale, vocabularies) {
801
970
  const langs = await Translation.getLangs()
802
971
  if (langs[locale]) {
803
972
  translation = new Translation(langs[locale])
804
- } else if (fileExists(path.join(global.codecept_dir, locale))) {
973
+ } else if (fileExists(path.join(store.codeceptDir, locale))) {
805
974
  // get from a provided file instead
806
975
  translation = Translation.createDefault()
807
976
  translation.loadVocabulary(locale)
@@ -818,7 +987,7 @@ function getHelperModuleName(helperName, config) {
818
987
  // classical require
819
988
  if (config[helperName].require) {
820
989
  if (config[helperName].require.startsWith('.')) {
821
- let helperPath = path.resolve(global.codecept_dir, config[helperName].require)
990
+ let helperPath = path.resolve(store.codeceptDir, config[helperName].require)
822
991
  // Add .js extension if not present for ESM compatibility
823
992
  if (!path.extname(helperPath)) {
824
993
  helperPath += '.js'
package/lib/effects.js CHANGED
@@ -4,6 +4,7 @@ import store from './store.js'
4
4
  import event from './event.js'
5
5
  import container from './container.js'
6
6
  import MetaStep from './step/meta.js'
7
+ import { empty } from './assert/empty.js'
7
8
  import { isAsyncFunction } from './utils.js'
8
9
 
9
10
  /**
@@ -111,6 +112,11 @@ class WithinStep extends MetaStep {
111
112
  *
112
113
  * @throws Will handle errors that occur during the callback execution. Errors are logged and attached as notes to the test.
113
114
  */
115
+ let hopeThatFailures = []
116
+ event.dispatcher.on(event.test.before, () => {
117
+ hopeThatFailures = []
118
+ })
119
+
114
120
  async function hopeThat(callback) {
115
121
  if (store.dryRun) return
116
122
  const sessionName = 'hopeThat'
@@ -131,6 +137,7 @@ async function hopeThat(callback) {
131
137
  result = false
132
138
  const msg = err.inspect ? err.inspect() : err.toString()
133
139
  output.debug(`Unsuccessful assertion > ${msg}`)
140
+ hopeThatFailures.push(msg)
134
141
  event.dispatcher.once(event.test.finished, test => {
135
142
  if (!test.notes) test.notes = []
136
143
  test.notes.push({ type: 'conditionalError', text: msg })
@@ -153,6 +160,16 @@ async function hopeThat(callback) {
153
160
  )
154
161
  }
155
162
 
163
+ /**
164
+ * Asserts that no `hopeThat` soft assertion has failed in the current test.
165
+ * Call once at the end of a scenario to fail it when any soft assertion failed.
166
+ */
167
+ hopeThat.noErrors = function () {
168
+ const failures = hopeThatFailures
169
+ hopeThatFailures = []
170
+ empty('soft assertions').assert(failures)
171
+ }
172
+
156
173
  /**
157
174
  * A CodeceptJS utility function to retry a step or callback multiple times with a specified polling interval.
158
175
  *