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
@@ -1,178 +1,17 @@
1
- import fs from 'fs'
2
- import path from 'path'
3
-
4
- import Container from '../container.js'
5
-
6
- import recorder from '../recorder.js'
7
-
8
- import event from '../event.js'
9
-
10
1
  import output from '../output.js'
2
+ import screenshot from './screenshot.js'
11
3
 
12
- import { fileExists } from '../utils.js'
13
- import Codeceptjs from '../index.js'
14
- import { testToFileName } from '../mocha/test.js'
15
-
16
- const defaultConfig = {
17
- uniqueScreenshotNames: false,
18
- disableScreenshots: false,
19
- fullPageScreenshots: false,
20
- }
21
-
22
- const supportedHelpers = Container.STANDARD_ACTING_HELPERS
4
+ let warned = false
23
5
 
24
6
  /**
25
- * Creates screenshot on failure. Screenshot is saved into `output` directory.
26
- *
27
- * Initially this functionality was part of corresponding helper but has been moved into plugin since 1.4
28
- *
29
- * This plugin is **enabled by default**.
30
- *
31
- * #### Configuration
32
- *
33
- * Configuration can either be taken from a corresponding helper (deprecated) or a from plugin config (recommended).
34
- *
35
- * ```js
36
- * plugins: {
37
- * screenshotOnFail: {
38
- * enabled: true
39
- * }
40
- * }
41
- * ```
42
- *
43
- * Possible config options:
44
- *
45
- * * `uniqueScreenshotNames`: use unique names for screenshot. Default: false.
46
- * * `fullPageScreenshots`: make full page screenshots. Default: false.
47
- *
7
+ * Saves a screenshot when a test fails.
48
8
  *
9
+ * **Deprecated:** use the `screenshot` plugin with `on: 'fail'`, which is the default behavior.
49
10
  */
50
- export default function (config) {
51
- const helpers = Container.helpers()
52
- let helper
53
-
54
- for (const helperName of supportedHelpers) {
55
- if (Object.keys(helpers).indexOf(helperName) > -1) {
56
- helper = helpers[helperName]
57
- }
58
- }
59
-
60
- if (!helper) return // no helpers for screenshot
61
-
62
- const options = Object.assign(defaultConfig, helper.options, config)
63
-
64
- if (helpers.Mochawesome) {
65
- if (helpers.Mochawesome.config) {
66
- options.uniqueScreenshotNames = helpers.Mochawesome.config.uniqueScreenshotNames
67
- }
68
- }
69
-
70
- if (Codeceptjs.container.mocha()) {
71
- options.reportDir = Codeceptjs.container.mocha()?.options?.reporterOptions && Codeceptjs.container.mocha()?.options?.reporterOptions?.reportDir
11
+ export default function (config = {}) {
12
+ if (!warned) {
13
+ output.error('screenshotOnFail is deprecated; use the `screenshot` plugin (default on=fail).')
14
+ warned = true
72
15
  }
73
-
74
- if (options.disableScreenshots) {
75
- // old version of disabling screenshots
76
- return
77
- }
78
-
79
- event.dispatcher.on(event.test.failed, (test, _err, hookName) => {
80
- if (hookName === 'BeforeSuite' || hookName === 'AfterSuite') {
81
- // no browser here
82
- return
83
- }
84
-
85
- recorder.add(
86
- 'screenshot of failed test',
87
- async () => {
88
- const dataType = 'image/png'
89
- // This prevents data driven to be included in the failed screenshot file name
90
- let fileName
91
-
92
- if (options.uniqueScreenshotNames && test) {
93
- fileName = `${testToFileName(test, { suffix: '', unique: true })}.failed.png`
94
- } else {
95
- fileName = `${testToFileName(test, { suffix: '', unique: false })}.failed.png`
96
- }
97
- const quietMode = !('output_dir' in global) || !global.output_dir
98
- if (!quietMode) {
99
- output.plugin('screenshotOnFail', 'Test failed, try to save a screenshot')
100
- }
101
-
102
- // Re-check helpers at runtime in case they weren't ready during plugin init
103
- const runtimeHelpers = Container.helpers()
104
- let runtimeHelper = null
105
- for (const helperName of supportedHelpers) {
106
- if (Object.keys(runtimeHelpers).indexOf(helperName) > -1) {
107
- runtimeHelper = runtimeHelpers[helperName]
108
- break
109
- }
110
- }
111
-
112
- if (runtimeHelper && typeof runtimeHelper.saveScreenshot === 'function') {
113
- helper = runtimeHelper
114
- }
115
-
116
- try {
117
- if (options.reportDir) {
118
- fileName = path.join(options.reportDir, fileName)
119
- const mochaReportDir = path.resolve(process.cwd(), options.reportDir)
120
- if (!fileExists(mochaReportDir)) {
121
- fs.mkdirSync(mochaReportDir)
122
- }
123
- }
124
-
125
- // Check if browser/page is still available before attempting screenshot
126
- if (helper.page && helper.page.isClosed && helper.page.isClosed()) {
127
- throw new Error('Browser page has been closed')
128
- }
129
- if (helper.browser && helper.browser.isConnected && !helper.browser.isConnected()) {
130
- throw new Error('Browser has been disconnected')
131
- }
132
-
133
- // Add timeout wrapper to prevent hanging with shorter timeout for ESM
134
- const screenshotPromise = helper.saveScreenshot(fileName, options.fullPageScreenshots)
135
- const timeoutPromise = new Promise((_, reject) => {
136
- setTimeout(() => reject(new Error('Screenshot timeout after 5 seconds')), 5000)
137
- })
138
-
139
- await Promise.race([screenshotPromise, timeoutPromise])
140
-
141
- if (!test.artifacts) test.artifacts = {}
142
- // Some unit tests may not define global.output_dir; avoid throwing when it is undefined
143
- // Detect output directory safely (may not be initialized in narrow unit tests)
144
- const baseOutputDir = 'output_dir' in global && typeof global.output_dir === 'string' && global.output_dir ? global.output_dir : null
145
- if (baseOutputDir) {
146
- test.artifacts.screenshot = path.join(baseOutputDir, fileName)
147
- if (Container.mocha().options.reporterOptions['mocha-junit-reporter'] && Container.mocha().options.reporterOptions['mocha-junit-reporter'].options.attachments) {
148
- test.attachments = [path.join(baseOutputDir, fileName)]
149
- }
150
- } else {
151
- // Fallback: just store the file name to keep tests stable without triggering path errors
152
- test.artifacts.screenshot = fileName
153
- }
154
- } catch (err) {
155
- if (!quietMode) {
156
- output.plugin('screenshotOnFail', `Failed to save screenshot: ${err.message}`)
157
- }
158
- // Enhanced error handling for browser closed scenarios
159
- if (
160
- err &&
161
- ((err.message &&
162
- (err.message.includes('Target page, context or browser has been closed') ||
163
- err.message.includes('Browser page has been closed') ||
164
- err.message.includes('Browser has been disconnected') ||
165
- err.message.includes('was terminated due to') ||
166
- err.message.includes('no such window: target window already closed') ||
167
- err.message.includes('Screenshot timeout after'))) ||
168
- (err.type && err.type === 'RuntimeError'))
169
- ) {
170
- output.log(`Can't make screenshot, ${err.message}`)
171
- helper.isRunning = false
172
- }
173
- }
174
- },
175
- true,
176
- )
177
- })
16
+ return screenshot({ ...config, on: 'fail' })
178
17
  }
@@ -32,7 +32,7 @@ const defaultConfig = {
32
32
  * #### Configuration:
33
33
  *
34
34
  * * `timeout` - global step timeout, default 150 seconds
35
- * * `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with I.limitTime(x).action(...), default false
35
+ * * `overrideStepLimits` - whether to use timeouts set in plugin config to override step timeouts set in code with `I.action(..., step.timeout(x))`, default false
36
36
  * * `noTimeoutSteps` - an array of steps with no timeout. Default:
37
37
  * * `amOnPage`
38
38
  * * `wait*`
@@ -68,6 +68,7 @@ export default function(config) {
68
68
  config.customTimeoutSteps = config.customTimeoutSteps.concat(config.noTimeoutSteps).concat(config.customTimeoutSteps)
69
69
 
70
70
  event.dispatcher.on(event.step.before, step => {
71
+ if (!step.title) return
71
72
  let stepTimeout
72
73
  for (let stepRule of config.customTimeoutSteps) {
73
74
  let customTimeout = 0
@@ -75,7 +76,7 @@ export default function(config) {
75
76
  if (stepRule.length > 1) customTimeout = stepRule[1]
76
77
  stepRule = stepRule[0]
77
78
  }
78
- if (stepRule instanceof RegExp ? step.name.match(stepRule) : step.name === stepRule || (stepRule.indexOf('*') && step.name.startsWith(stepRule.slice(0, -1)))) {
79
+ if (stepRule instanceof RegExp ? step.title.match(stepRule) : step.title === stepRule || (stepRule.indexOf('*') && step.title.startsWith(stepRule.slice(0, -1)))) {
79
80
  stepTimeout = customTimeout
80
81
  break
81
82
  }
package/lib/recorder.js CHANGED
@@ -217,7 +217,7 @@ export default {
217
217
  }
218
218
 
219
219
  const retryRules = this.retries.slice().reverse()
220
- return promiseRetry(Object.assign(defaultRetryOptions, retryOpts), (retry, number) => {
220
+ return promiseRetry(Object.assign({}, defaultRetryOptions, retryOpts), (retry, number) => {
221
221
  if (number > 1) output.log(`${currentQueue()}Retrying... Attempt #${number}`)
222
222
  const [promise, timer] = getTimeoutPromise(timeout, taskName)
223
223
  return Promise.race([promise, Promise.resolve(res).then(fn)])
package/lib/rerun.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import fsPath from 'path'
2
+ import store from './store.js'
2
3
  import container from './container.js'
3
4
  import event from './event.js'
4
5
  import BaseCodecept from './codecept.js'
@@ -29,7 +30,7 @@ class CodeceptRerunner extends BaseCodecept {
29
30
  let filesToRun = this.testFiles
30
31
  if (test) {
31
32
  if (!fsPath.isAbsolute(test)) {
32
- test = fsPath.join(global.codecept_dir, test)
33
+ test = fsPath.join(store.codeceptDir, test)
33
34
  }
34
35
  filesToRun = this.testFiles.filter(t => fsPath.basename(t, '.js') === test || t === test)
35
36
  }
package/lib/result.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from 'fs'
2
2
  import path from 'path'
3
3
  import { serializeTest } from './mocha/test.js'
4
+ import store from './store.js'
4
5
 
5
6
  /**
6
7
  * @typedef {Object} Stats Statistics for a test result.
@@ -212,7 +213,7 @@ class Result {
212
213
  */
213
214
  save(fileName) {
214
215
  if (!fileName) fileName = 'result.json'
215
- fs.writeFileSync(path.join(global.output_dir, fileName), JSON.stringify(this.simplify(), null, 2))
216
+ fs.writeFileSync(path.join(store.outputDir, fileName), JSON.stringify(this.simplify(), null, 2))
216
217
  }
217
218
 
218
219
  /**
package/lib/step/base.js CHANGED
@@ -3,18 +3,19 @@ import Secret from '../secret.js'
3
3
  import { getCurrentTimeout } from '../timeout.js'
4
4
  import { ucfirst, humanizeString, serializeError } from '../utils.js'
5
5
  import recordStep from './record.js'
6
+ import store from '../store.js'
6
7
 
7
8
  const STACK_LINE = 5
8
9
 
9
10
  /**
10
11
  * Each command in test executed through `I.` object is wrapped in Step.
11
12
  * Step allows logging executed commands and triggers hook before and after step execution.
12
- * @param {string} name
13
+ * @param {string} title
13
14
  */
14
15
  class Step {
15
- constructor(name) {
16
+ constructor(title) {
16
17
  /** @member {string} */
17
- this.name = name
18
+ this.title = title
18
19
  /** @member {Map<number, number>} */
19
20
  this.timeouts = new Map()
20
21
 
@@ -42,7 +43,7 @@ class Step {
42
43
  /** @member {any} */
43
44
  this.helper = null
44
45
  /** @member {string} */
45
- this.helperMethod = name
46
+ this.helperMethod = title
46
47
 
47
48
  this.startTime = 0
48
49
  this.endTime = 0
@@ -102,7 +103,7 @@ class Step {
102
103
 
103
104
  /** @return {string} */
104
105
  humanize() {
105
- return humanizeString(this.name)
106
+ return humanizeString(this.title)
106
107
  }
107
108
 
108
109
  /** @return {string} */
@@ -147,9 +148,22 @@ class Step {
147
148
  line() {
148
149
  const lines = this.stack.split('\n')
149
150
  if (lines[STACK_LINE]) {
150
- return lines[STACK_LINE].trim()
151
- .replace(global.codecept_dir || '', '.')
151
+ let line = lines[STACK_LINE].trim()
152
+ .replace(store.codeceptDir || '', '.')
152
153
  .trim()
154
+
155
+ // Map .temp.mjs back to original .ts files using container's tsFileMapping
156
+ const fileMapping = store.tsFileMapping
157
+ if (line.includes('.temp.mjs') && fileMapping) {
158
+ for (const [tsFile, mjsFile] of fileMapping.entries()) {
159
+ if (line.includes(mjsFile)) {
160
+ line = line.replace(mjsFile, tsFile)
161
+ break
162
+ }
163
+ }
164
+ }
165
+
166
+ return line
153
167
  }
154
168
  return ''
155
169
  }
@@ -166,7 +180,7 @@ class Step {
166
180
 
167
181
  /** @return {string} */
168
182
  toCode() {
169
- return `${this.prefix}${this.actor}.${this.name}(${this.humanizeArgs()})${this.suffix}`
183
+ return `${this.prefix}${this.actor}.${this.title}(${this.humanizeArgs()})${this.suffix}`
170
184
  }
171
185
 
172
186
  isMetaStep() {
@@ -209,7 +223,7 @@ class Step {
209
223
 
210
224
  return {
211
225
  opts: step.opts || {},
212
- title: step.name,
226
+ title: step.title,
213
227
  args: args,
214
228
  status: step.status,
215
229
  startTime: step.startTime,
@@ -1,8 +1,8 @@
1
1
  import FuncStep from './func.js'
2
2
 
3
3
  class CommentStep extends FuncStep {
4
- constructor(name, comment) {
5
- super(name)
4
+ constructor(title, comment) {
5
+ super(title)
6
6
  this.fn = () => {}
7
7
  }
8
8
  }
@@ -1,20 +1,33 @@
1
+ /**
2
+ * @typedef {Object} StepOptions
3
+ * @property {number|'first'|'last'} [elementIndex] - Select a specific element when multiple match. 1-based positive index, negative from end, or 'first'/'last'.
4
+ * @property {boolean} [exact] - Enable strict mode for this step. Throws if multiple elements match.
5
+ * @property {boolean} [strictMode] - Alias for exact.
6
+ * @property {boolean} [ignoreCase] - Perform case-insensitive text matching.
7
+ */
8
+
1
9
  /**
2
10
  * StepConfig is a configuration object for a step.
3
11
  * It is used to create a new step that is a combination of other steps.
4
12
  */
5
13
  class StepConfig {
6
14
  constructor(opts = {}) {
7
- /** @member {{ opts: Record<string, any>, timeout: number|undefined, retry: number|undefined }} */
15
+ /** @member {{ opts: StepOptions, timeout: number|undefined, retry: number|undefined }} */
8
16
  this.config = {
9
17
  opts,
10
18
  timeout: undefined,
11
19
  retry: undefined,
12
20
  }
21
+ this.__isStepConfig = true
22
+ }
23
+
24
+ static isStepConfig(obj) {
25
+ return obj && (obj instanceof StepConfig || obj.__isStepConfig === true)
13
26
  }
14
27
 
15
28
  /**
16
29
  * Set the options for the step.
17
- * @param {object} opts - The options for the step.
30
+ * @param {StepOptions} opts - The options for the step.
18
31
  * @returns {StepConfig} - The step configuration object.
19
32
  */
20
33
  opts(opts) {
@@ -2,12 +2,12 @@ import Step from './base.js'
2
2
  import store from '../store.js'
3
3
 
4
4
  class HelperStep extends Step {
5
- constructor(helper, name) {
6
- super(name)
5
+ constructor(helper, title) {
6
+ super(title)
7
7
  /** @member {CodeceptJS.Helper} helper corresponding helper */
8
8
  this.helper = helper
9
- /** @member {string} helperMethod name of method to be executed */
10
- this.helperMethod = name
9
+ /** @member {string} helperMethod title of method to be executed */
10
+ this.helperMethod = title
11
11
  }
12
12
 
13
13
  /**
package/lib/step/meta.js CHANGED
@@ -29,7 +29,7 @@ class MetaStep extends Step {
29
29
  const actorText = this.actor
30
30
 
31
31
  if (this.isBDD()) {
32
- return `${this.prefix}${actorText} ${this.name} "${this.humanizeArgs()}${this.suffix}"`
32
+ return `${this.prefix}${actorText} ${this.title} "${this.humanizeArgs()}${this.suffix}"`
33
33
  }
34
34
 
35
35
  if (actorText === 'I') {
@@ -37,14 +37,14 @@ class MetaStep extends Step {
37
37
  }
38
38
 
39
39
  if (!this.actor) {
40
- return `${this.name} ${this.humanizeArgs()}${this.suffix}`.trim()
40
+ return `${this.title} ${this.humanizeArgs()}${this.suffix}`.trim()
41
41
  }
42
42
 
43
43
  return `On ${this.prefix}${actorText}: ${this.humanize()} ${this.humanizeArgs()}${this.suffix}`.trim()
44
44
  }
45
45
 
46
46
  humanize() {
47
- return humanizeString(this.name)
47
+ return humanizeString(this.title)
48
48
  }
49
49
 
50
50
  setTrace() {}
@@ -5,22 +5,23 @@ import output from '../output.js'
5
5
  import store from '../store.js'
6
6
  import { TIMEOUT_ORDER } from '../timeout.js'
7
7
  import retryStep from './retry.js'
8
+ import { fixErrorStack } from '../utils/typescript.js'
8
9
  function recordStep(step, args) {
9
10
  step.status = 'queued'
10
11
 
11
12
  // apply step configuration
12
13
  const lastArg = args[args.length - 1]
13
- if (lastArg instanceof StepConfig) {
14
+ if (StepConfig.isStepConfig(lastArg)) {
14
15
  const stepConfig = args.pop()
15
16
  const { opts, timeout, retry } = stepConfig.getConfig()
16
17
 
17
18
  if (opts) {
18
- output.debug(`Step ${step.name}: options applied ${JSON.stringify(opts)}`)
19
+ output.debug(`Step ${step.title}: options applied ${JSON.stringify(opts)}`)
19
20
  store.stepOptions = opts
20
21
  step.opts = opts
21
22
  }
22
23
  if (timeout) {
23
- output.debug(`Step ${step.name} timeout ${timeout}s`)
24
+ output.debug(`Step ${step.title} timeout ${timeout}s`)
24
25
  step.setTimeout(timeout * 1000, TIMEOUT_ORDER.codeLimitTime)
25
26
  }
26
27
  if (retry) retryStep(retry)
@@ -30,7 +31,7 @@ function recordStep(step, args) {
30
31
  // run async before step hooks
31
32
  event.emit(event.step.before, step)
32
33
 
33
- const task = `${step.name}: ${step.humanizeArgs()}`
34
+ const task = `${step.title}: ${step.humanizeArgs()}`
34
35
  let val
35
36
 
36
37
  // run step inside promise
@@ -60,6 +61,13 @@ function recordStep(step, args) {
60
61
  recorder.catch(err => {
61
62
  step.status = 'failed'
62
63
  step.endTime = +Date.now()
64
+
65
+ // Fix error stack to point to original .ts files (lazy import to avoid circular dependency)
66
+ const fileMapping = store.tsFileMapping
67
+ if (fileMapping) {
68
+ fixErrorStack(err, fileMapping)
69
+ }
70
+
63
71
  event.emit(event.step.failed, step, err)
64
72
  event.emit(event.step.finished, step)
65
73
  throw err
package/lib/store.js CHANGED
@@ -1,8 +1,34 @@
1
1
  /**
2
- * global values for current session
2
+ * Global store for current session
3
3
  * @namespace
4
4
  */
5
5
  const store = {
6
+ // --- Required (set once via initialize(), immutable after) ---
7
+
8
+ /** @type {string | null} */
9
+ _codeceptDir: null,
10
+ /** @type {string | null} */
11
+ _outputDir: null,
12
+
13
+ get codeceptDir() {
14
+ return this._codeceptDir || global.codecept_dir || null
15
+ },
16
+ set codeceptDir(val) {
17
+ this._codeceptDir = val
18
+ },
19
+
20
+ get outputDir() {
21
+ return this._outputDir || global.output_dir || null
22
+ },
23
+ set outputDir(val) {
24
+ this._outputDir = val
25
+ },
26
+
27
+ /** @type {boolean} */
28
+ workerMode: false,
29
+
30
+ // --- Session config (per-session, mutable, set at session start) ---
31
+
6
32
  /**
7
33
  * If we are in --debug mode
8
34
  * @type {boolean}
@@ -27,20 +53,63 @@ const store = {
27
53
  * @type {boolean}
28
54
  */
29
55
  dryRun: false,
56
+
57
+ /**
58
+ * Feature.only() was used
59
+ * @type {boolean}
60
+ */
61
+ featureOnly: false,
62
+
63
+ /**
64
+ * Scenario.only() was used
65
+ * @type {boolean}
66
+ */
67
+ scenarioOnly: false,
68
+
69
+ /**
70
+ * Mask sensitive data config
71
+ * @type {boolean|object}
72
+ */
73
+ maskSensitiveData: false,
74
+
75
+ /**
76
+ * noGlobals mode — user imports everything
77
+ * @type {boolean}
78
+ */
79
+ noGlobals: false,
80
+
81
+ // --- State (tracks current execution, changes constantly) ---
82
+
30
83
  /**
31
84
  * If we are in pause mode
32
85
  * @type {boolean}
33
86
  */
34
87
  onPause: false,
35
88
 
36
- // current object states
37
-
38
89
  /** @type {CodeceptJS.Test | null} */
39
90
  currentTest: null,
40
91
  /** @type {CodeceptJS.Step | null} */
41
92
  currentStep: null,
42
93
  /** @type {CodeceptJS.Suite | null} */
43
94
  currentSuite: null,
95
+
96
+ /** @type {Map<string, string> | null} */
97
+ tsFileMapping: null,
98
+
99
+ /**
100
+ * Initialize required store fields.
101
+ * These values cannot be overwritten after initialization.
102
+ * @param {object} opts
103
+ * @param {string} opts.codeceptDir - root directory of tests
104
+ * @param {string} opts.outputDir - resolved output directory
105
+ */
106
+ initialize(opts) {
107
+ if (!opts.codeceptDir) throw new Error('codeceptDir is required')
108
+ if (!opts.outputDir) throw new Error('outputDir is required')
109
+
110
+ this._codeceptDir = opts.codeceptDir
111
+ this._outputDir = opts.outputDir
112
+ },
44
113
  }
45
114
 
46
115
  export default store
@@ -1,6 +1,7 @@
1
1
  import merge from 'lodash.merge'
2
2
  import path from 'path'
3
3
  import { createRequire } from 'module'
4
+ import store from './store.js'
4
5
 
5
6
  const defaultVocabulary = {
6
7
  I: 'I',
@@ -15,7 +16,7 @@ class Translation {
15
16
 
16
17
  loadVocabulary(vocabularyFile) {
17
18
  if (!vocabularyFile) return
18
- const filePath = path.join(global.codecept_dir, vocabularyFile)
19
+ const filePath = path.join(store.codeceptDir, vocabularyFile)
19
20
 
20
21
  try {
21
22
  const require = createRequire(import.meta.url)
@@ -65,9 +65,18 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
65
65
  ✅ Complete: Handles all TypeScript features
66
66
 
67
67
  ┌─────────────────────────────────────────────────────────────────────────────┐
68
- │ Option 2: ts-node/esm (Alternative - Established, Requires Config)
68
+ │ Option 2: ts-node/esm (Not Recommended - Has Module Resolution Issues)
69
69
  └─────────────────────────────────────────────────────────────────────────────┘
70
70
 
71
+ ⚠️ ts-node/esm has significant limitations and is not recommended:
72
+ - Doesn't work with "type": "module" in package.json
73
+ - Module resolution doesn't work like standard TypeScript ESM
74
+ - Import statements must use explicit file paths
75
+
76
+ We strongly recommend using tsx/cjs instead.
77
+
78
+ If you still want to use ts-node/esm:
79
+
71
80
  Installation:
72
81
  npm install --save-dev ts-node
73
82
 
@@ -84,11 +93,12 @@ CodeceptJS 4.x uses ES Modules (ESM) and requires a loader to run TypeScript tes
84
93
  "esModuleInterop": true
85
94
  },
86
95
  "ts-node": {
87
- "esm": true,
88
- "experimentalSpecifierResolution": "node"
96
+ "esm": true
89
97
  }
90
98
  }
91
99
 
100
+ 3. Do NOT use "type": "module" in package.json
101
+
92
102
  📚 Documentation: https://codecept.io/typescript
93
103
 
94
104
  Note: TypeScript config files (codecept.conf.ts) and helpers are automatically
@@ -96,6 +106,34 @@ Note: TypeScript config files (codecept.conf.ts) and helpers are automatically
96
106
  `
97
107
  }
98
108
 
109
+ /**
110
+ * Get warning message if ts-node/esm is being used
111
+ * @param {string[]} requiredModules - Array of required modules from config
112
+ * @returns {string|null} Warning message or null
113
+ */
114
+ export function getTSNodeESMWarning(requiredModules = []) {
115
+ if (!requiredModules.includes('ts-node/esm')) {
116
+ return null
117
+ }
118
+
119
+ return `
120
+ ⚠️ Warning: ts-node/esm with "module": "esnext" requires explicit file extensions in all imports.
121
+
122
+ This is a known limitation. Use tsx/cjs instead to write imports without extensions.
123
+
124
+ Examples:
125
+
126
+ ❌ Incorrect (will fail):
127
+ import loginPage from "./pages/Login";
128
+
129
+ ✅ Correct (must include .ts extension):
130
+ import loginPage from "./pages/Login.ts";
131
+
132
+ 📚 Documentation: https://codecept.io/typescript
133
+
134
+ `
135
+ }
136
+
99
137
  /**
100
138
  * Check if user is trying to run TypeScript tests without proper loader
101
139
  * @param {string[]} testFiles - Array of test file paths