codeceptjs 4.0.2-beta.9 → 4.0.2

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 (326) hide show
  1. package/README.md +39 -28
  2. package/bin/codecept.js +15 -2
  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 +6 -7
  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 -1
  112. package/lib/command/run-workers.js +2 -14
  113. package/lib/command/run.js +3 -17
  114. package/lib/command/utils.js +14 -0
  115. package/lib/command/workers/runTests.js +84 -41
  116. package/lib/config.js +96 -18
  117. package/lib/container.js +115 -17
  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 +358 -467
  128. package/lib/helper/Puppeteer.js +335 -192
  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 +4 -1
  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 +11 -1
  152. package/lib/mocha/inject.js +1 -1
  153. package/lib/mocha/scenarioConfig.js +2 -1
  154. package/lib/mocha/ui.js +5 -6
  155. package/lib/parser.js +2 -2
  156. package/lib/pause.js +38 -4
  157. package/lib/plugin/aiTrace.js +457 -0
  158. package/lib/plugin/analyze.js +9 -9
  159. package/lib/plugin/auth.js +5 -4
  160. package/lib/plugin/browser.js +77 -0
  161. package/lib/plugin/expose.js +159 -0
  162. package/lib/plugin/heal.js +47 -3
  163. package/lib/plugin/junitReporter.js +303 -0
  164. package/lib/plugin/pageInfo.js +54 -52
  165. package/lib/plugin/pause.js +131 -0
  166. package/lib/plugin/pauseOnFail.js +11 -33
  167. package/lib/plugin/retryFailedStep.js +43 -32
  168. package/lib/plugin/screencast.js +289 -0
  169. package/lib/plugin/screenshot.js +558 -0
  170. package/lib/plugin/screenshotOnFail.js +9 -170
  171. package/lib/plugin/stepTimeout.js +3 -2
  172. package/lib/recorder.js +1 -1
  173. package/lib/rerun.js +2 -1
  174. package/lib/result.js +2 -1
  175. package/lib/step/base.js +10 -9
  176. package/lib/step/comment.js +2 -2
  177. package/lib/step/config.js +15 -2
  178. package/lib/step/helper.js +4 -4
  179. package/lib/step/meta.js +3 -3
  180. package/lib/step/record.js +5 -5
  181. package/lib/store.js +72 -3
  182. package/lib/translation.js +2 -1
  183. package/lib/utils/loaderCheck.js +28 -0
  184. package/lib/utils/mask_data.js +2 -1
  185. package/lib/utils/pluginParser.js +151 -0
  186. package/lib/utils/trace.js +297 -0
  187. package/lib/utils/typescript.js +188 -23
  188. package/lib/utils.js +77 -3
  189. package/lib/workers.js +65 -40
  190. package/package.json +35 -30
  191. package/typings/index.d.ts +119 -8
  192. package/typings/promiseBasedTypes.d.ts +3158 -6065
  193. package/typings/types.d.ts +3453 -6494
  194. package/docs/webapi/amOnPage.mustache +0 -11
  195. package/docs/webapi/appendField.mustache +0 -11
  196. package/docs/webapi/attachFile.mustache +0 -12
  197. package/docs/webapi/blur.mustache +0 -18
  198. package/docs/webapi/checkOption.mustache +0 -13
  199. package/docs/webapi/clearCookie.mustache +0 -9
  200. package/docs/webapi/clearField.mustache +0 -9
  201. package/docs/webapi/click.mustache +0 -29
  202. package/docs/webapi/clickLink.mustache +0 -8
  203. package/docs/webapi/closeCurrentTab.mustache +0 -7
  204. package/docs/webapi/closeOtherTabs.mustache +0 -8
  205. package/docs/webapi/dontSee.mustache +0 -11
  206. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
  207. package/docs/webapi/dontSeeCookie.mustache +0 -8
  208. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
  209. package/docs/webapi/dontSeeElement.mustache +0 -8
  210. package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
  211. package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
  212. package/docs/webapi/dontSeeInField.mustache +0 -11
  213. package/docs/webapi/dontSeeInSource.mustache +0 -8
  214. package/docs/webapi/dontSeeInTitle.mustache +0 -8
  215. package/docs/webapi/dontSeeTraffic.mustache +0 -13
  216. package/docs/webapi/doubleClick.mustache +0 -13
  217. package/docs/webapi/downloadFile.mustache +0 -12
  218. package/docs/webapi/dragAndDrop.mustache +0 -9
  219. package/docs/webapi/dragSlider.mustache +0 -11
  220. package/docs/webapi/executeAsyncScript.mustache +0 -24
  221. package/docs/webapi/executeScript.mustache +0 -26
  222. package/docs/webapi/fillField.mustache +0 -16
  223. package/docs/webapi/flushNetworkTraffics.mustache +0 -5
  224. package/docs/webapi/focus.mustache +0 -13
  225. package/docs/webapi/forceClick.mustache +0 -28
  226. package/docs/webapi/forceRightClick.mustache +0 -18
  227. package/docs/webapi/grabAllWindowHandles.mustache +0 -7
  228. package/docs/webapi/grabAttributeFrom.mustache +0 -10
  229. package/docs/webapi/grabAttributeFromAll.mustache +0 -9
  230. package/docs/webapi/grabBrowserLogs.mustache +0 -9
  231. package/docs/webapi/grabCookie.mustache +0 -11
  232. package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
  233. package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
  234. package/docs/webapi/grabCurrentUrl.mustache +0 -9
  235. package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
  236. package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
  237. package/docs/webapi/grabElementBoundingRect.mustache +0 -20
  238. package/docs/webapi/grabGeoLocation.mustache +0 -8
  239. package/docs/webapi/grabHTMLFrom.mustache +0 -10
  240. package/docs/webapi/grabHTMLFromAll.mustache +0 -9
  241. package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
  242. package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
  243. package/docs/webapi/grabPageScrollPosition.mustache +0 -8
  244. package/docs/webapi/grabPopupText.mustache +0 -5
  245. package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
  246. package/docs/webapi/grabSource.mustache +0 -8
  247. package/docs/webapi/grabTextFrom.mustache +0 -10
  248. package/docs/webapi/grabTextFromAll.mustache +0 -9
  249. package/docs/webapi/grabTitle.mustache +0 -8
  250. package/docs/webapi/grabValueFrom.mustache +0 -9
  251. package/docs/webapi/grabValueFromAll.mustache +0 -8
  252. package/docs/webapi/grabWebElement.mustache +0 -9
  253. package/docs/webapi/grabWebElements.mustache +0 -9
  254. package/docs/webapi/moveCursorTo.mustache +0 -12
  255. package/docs/webapi/openNewTab.mustache +0 -7
  256. package/docs/webapi/pressKey.mustache +0 -12
  257. package/docs/webapi/pressKeyDown.mustache +0 -12
  258. package/docs/webapi/pressKeyUp.mustache +0 -12
  259. package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
  260. package/docs/webapi/refreshPage.mustache +0 -6
  261. package/docs/webapi/resizeWindow.mustache +0 -6
  262. package/docs/webapi/rightClick.mustache +0 -14
  263. package/docs/webapi/saveElementScreenshot.mustache +0 -10
  264. package/docs/webapi/saveScreenshot.mustache +0 -12
  265. package/docs/webapi/say.mustache +0 -10
  266. package/docs/webapi/scrollIntoView.mustache +0 -11
  267. package/docs/webapi/scrollPageToBottom.mustache +0 -6
  268. package/docs/webapi/scrollPageToTop.mustache +0 -6
  269. package/docs/webapi/scrollTo.mustache +0 -12
  270. package/docs/webapi/see.mustache +0 -11
  271. package/docs/webapi/seeAttributesOnElements.mustache +0 -9
  272. package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
  273. package/docs/webapi/seeCookie.mustache +0 -8
  274. package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
  275. package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
  276. package/docs/webapi/seeElement.mustache +0 -8
  277. package/docs/webapi/seeElementInDOM.mustache +0 -8
  278. package/docs/webapi/seeInCurrentUrl.mustache +0 -8
  279. package/docs/webapi/seeInField.mustache +0 -12
  280. package/docs/webapi/seeInPopup.mustache +0 -8
  281. package/docs/webapi/seeInSource.mustache +0 -7
  282. package/docs/webapi/seeInTitle.mustache +0 -8
  283. package/docs/webapi/seeNumberOfElements.mustache +0 -11
  284. package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
  285. package/docs/webapi/seeTextEquals.mustache +0 -9
  286. package/docs/webapi/seeTitleEquals.mustache +0 -8
  287. package/docs/webapi/seeTraffic.mustache +0 -36
  288. package/docs/webapi/selectOption.mustache +0 -21
  289. package/docs/webapi/setCookie.mustache +0 -16
  290. package/docs/webapi/setGeoLocation.mustache +0 -12
  291. package/docs/webapi/startRecordingTraffic.mustache +0 -8
  292. package/docs/webapi/startRecordingWebSocketMessages.mustache +0 -8
  293. package/docs/webapi/stopRecordingTraffic.mustache +0 -5
  294. package/docs/webapi/stopRecordingWebSocketMessages.mustache +0 -7
  295. package/docs/webapi/switchTo.mustache +0 -9
  296. package/docs/webapi/switchToNextTab.mustache +0 -10
  297. package/docs/webapi/switchToPreviousTab.mustache +0 -10
  298. package/docs/webapi/type.mustache +0 -21
  299. package/docs/webapi/uncheckOption.mustache +0 -13
  300. package/docs/webapi/wait.mustache +0 -8
  301. package/docs/webapi/waitForClickable.mustache +0 -11
  302. package/docs/webapi/waitForCookie.mustache +0 -9
  303. package/docs/webapi/waitForDetached.mustache +0 -10
  304. package/docs/webapi/waitForDisabled.mustache +0 -6
  305. package/docs/webapi/waitForElement.mustache +0 -11
  306. package/docs/webapi/waitForEnabled.mustache +0 -6
  307. package/docs/webapi/waitForFunction.mustache +0 -17
  308. package/docs/webapi/waitForInvisible.mustache +0 -10
  309. package/docs/webapi/waitForNumberOfTabs.mustache +0 -9
  310. package/docs/webapi/waitForText.mustache +0 -13
  311. package/docs/webapi/waitForValue.mustache +0 -10
  312. package/docs/webapi/waitForVisible.mustache +0 -10
  313. package/docs/webapi/waitInUrl.mustache +0 -9
  314. package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
  315. package/docs/webapi/waitToHide.mustache +0 -10
  316. package/docs/webapi/waitUrlEquals.mustache +0 -10
  317. package/lib/helper/AI.js +0 -214
  318. package/lib/helper/Mochawesome.js +0 -96
  319. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -52
  320. package/lib/helper/extras/React.js +0 -65
  321. package/lib/listener/enhancedGlobalRetry.js +0 -110
  322. package/lib/plugin/enhancedRetryFailedStep.js +0 -99
  323. package/lib/plugin/htmlReporter.js +0 -3648
  324. package/lib/plugin/stepByStepReport.js +0 -427
  325. package/lib/plugin/subtitles.js +0 -89
  326. 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} */
@@ -148,11 +149,11 @@ class Step {
148
149
  const lines = this.stack.split('\n')
149
150
  if (lines[STACK_LINE]) {
150
151
  let line = lines[STACK_LINE].trim()
151
- .replace(global.codecept_dir || '', '.')
152
+ .replace(store.codeceptDir || '', '.')
152
153
  .trim()
153
154
 
154
155
  // Map .temp.mjs back to original .ts files using container's tsFileMapping
155
- const fileMapping = global.container?.tsFileMapping?.()
156
+ const fileMapping = store.tsFileMapping
156
157
  if (line.includes('.temp.mjs') && fileMapping) {
157
158
  for (const [tsFile, mjsFile] of fileMapping.entries()) {
158
159
  if (line.includes(mjsFile)) {
@@ -179,7 +180,7 @@ class Step {
179
180
 
180
181
  /** @return {string} */
181
182
  toCode() {
182
- return `${this.prefix}${this.actor}.${this.name}(${this.humanizeArgs()})${this.suffix}`
183
+ return `${this.prefix}${this.actor}.${this.title}(${this.humanizeArgs()})${this.suffix}`
183
184
  }
184
185
 
185
186
  isMetaStep() {
@@ -222,7 +223,7 @@ class Step {
222
223
 
223
224
  return {
224
225
  opts: step.opts || {},
225
- title: step.name,
226
+ title: step.title,
226
227
  args: args,
227
228
  status: step.status,
228
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() {}
@@ -11,17 +11,17 @@ function recordStep(step, args) {
11
11
 
12
12
  // apply step configuration
13
13
  const lastArg = args[args.length - 1]
14
- if (lastArg instanceof StepConfig) {
14
+ if (StepConfig.isStepConfig(lastArg)) {
15
15
  const stepConfig = args.pop()
16
16
  const { opts, timeout, retry } = stepConfig.getConfig()
17
17
 
18
18
  if (opts) {
19
- output.debug(`Step ${step.name}: options applied ${JSON.stringify(opts)}`)
19
+ output.debug(`Step ${step.title}: options applied ${JSON.stringify(opts)}`)
20
20
  store.stepOptions = opts
21
21
  step.opts = opts
22
22
  }
23
23
  if (timeout) {
24
- output.debug(`Step ${step.name} timeout ${timeout}s`)
24
+ output.debug(`Step ${step.title} timeout ${timeout}s`)
25
25
  step.setTimeout(timeout * 1000, TIMEOUT_ORDER.codeLimitTime)
26
26
  }
27
27
  if (retry) retryStep(retry)
@@ -31,7 +31,7 @@ function recordStep(step, args) {
31
31
  // run async before step hooks
32
32
  event.emit(event.step.before, step)
33
33
 
34
- const task = `${step.name}: ${step.humanizeArgs()}`
34
+ const task = `${step.title}: ${step.humanizeArgs()}`
35
35
  let val
36
36
 
37
37
  // run step inside promise
@@ -63,7 +63,7 @@ function recordStep(step, args) {
63
63
  step.endTime = +Date.now()
64
64
 
65
65
  // Fix error stack to point to original .ts files (lazy import to avoid circular dependency)
66
- const fileMapping = global.container?.tsFileMapping?.()
66
+ const fileMapping = store.tsFileMapping
67
67
  if (fileMapping) {
68
68
  fixErrorStack(err, fileMapping)
69
69
  }
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)
@@ -106,6 +106,34 @@ Note: TypeScript config files (codecept.conf.ts) and helpers are automatically
106
106
  `
107
107
  }
108
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
+
109
137
  /**
110
138
  * Check if user is trying to run TypeScript tests without proper loader
111
139
  * @param {string[]} testFiles - Array of test file paths
@@ -1,4 +1,5 @@
1
1
  import { maskSensitiveData } from 'invisi-data'
2
+ import store from '../store.js'
2
3
 
3
4
  /**
4
5
  * Mask sensitive data utility for CodeceptJS
@@ -33,7 +34,7 @@ export function maskData(input, config) {
33
34
  * @returns {boolean|object} - Current masking configuration
34
35
  */
35
36
  export function getMaskConfig() {
36
- return global.maskSensitiveData || false
37
+ return store.maskSensitiveData || global.maskSensitiveData || false
37
38
  }
38
39
 
39
40
  /**