codeceptjs 4.0.0-rc.9 → 4.0.0

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 (314) hide show
  1. package/README.md +9 -10
  2. package/bin/codecept.js +15 -2
  3. package/bin/codeceptq.js +49 -0
  4. package/bin/mcp-server.js +751 -172
  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 +743 -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 +198 -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 +7 -7
  103. package/lib/command/check.js +2 -1
  104. package/lib/command/dryRun.js +24 -5
  105. package/lib/command/generate.js +2 -0
  106. package/lib/command/gherkin/snippets.js +5 -4
  107. package/lib/command/init.js +248 -266
  108. package/lib/command/list.js +150 -10
  109. package/lib/command/query.js +218 -0
  110. package/lib/command/run-multiple.js +3 -2
  111. package/lib/command/run-workers.js +1 -14
  112. package/lib/command/run.js +3 -17
  113. package/lib/command/utils.js +14 -0
  114. package/lib/command/workers/runTests.js +11 -15
  115. package/lib/config.js +77 -4
  116. package/lib/container.js +97 -15
  117. package/lib/effects.js +17 -0
  118. package/lib/element/WebElement.js +194 -2
  119. package/lib/els.js +12 -6
  120. package/lib/globals.js +32 -19
  121. package/lib/heal.js +7 -4
  122. package/lib/helper/ApiDataFactory.js +2 -1
  123. package/lib/helper/FileSystem.js +3 -2
  124. package/lib/helper/GraphQLDataFactory.js +2 -1
  125. package/lib/helper/Playwright.js +63 -70
  126. package/lib/helper/Puppeteer.js +20 -109
  127. package/lib/helper/WebDriver.js +13 -30
  128. package/lib/helper/errors/NonFocusedType.js +8 -0
  129. package/lib/helper/extras/Download.js +45 -0
  130. package/lib/helper/extras/PlaywrightLocator.js +10 -0
  131. package/lib/helper/extras/elementSelection.js +10 -3
  132. package/lib/helper/extras/focusCheck.js +43 -0
  133. package/lib/helper/extras/richTextEditor.js +178 -0
  134. package/lib/history.js +3 -2
  135. package/lib/html.js +90 -16
  136. package/lib/index.js +9 -1
  137. package/lib/listener/config.js +6 -4
  138. package/lib/listener/emptyRun.js +2 -1
  139. package/lib/listener/helpers.js +4 -1
  140. package/lib/listener/mocha.js +2 -1
  141. package/lib/listener/pageobjects.js +43 -0
  142. package/lib/listener/result.js +3 -2
  143. package/lib/locator.js +126 -16
  144. package/lib/mocha/cli.js +4 -2
  145. package/lib/mocha/factory.js +7 -2
  146. package/lib/mocha/inject.js +1 -1
  147. package/lib/mocha/scenarioConfig.js +2 -1
  148. package/lib/mocha/ui.js +5 -6
  149. package/lib/parser.js +2 -2
  150. package/lib/pause.js +38 -4
  151. package/lib/plugin/aiTrace.js +96 -103
  152. package/lib/plugin/analyze.js +9 -9
  153. package/lib/plugin/auth.js +3 -3
  154. package/lib/plugin/browser.js +77 -0
  155. package/lib/plugin/expose.js +159 -0
  156. package/lib/plugin/heal.js +47 -3
  157. package/lib/plugin/junitReporter.js +303 -0
  158. package/lib/plugin/pageInfo.js +54 -52
  159. package/lib/plugin/pause.js +131 -0
  160. package/lib/plugin/pauseOnFail.js +11 -33
  161. package/lib/plugin/retryFailedStep.js +15 -13
  162. package/lib/plugin/screencast.js +289 -0
  163. package/lib/plugin/screenshot.js +558 -0
  164. package/lib/plugin/screenshotOnFail.js +9 -170
  165. package/lib/plugin/stepTimeout.js +3 -2
  166. package/lib/recorder.js +1 -1
  167. package/lib/rerun.js +2 -1
  168. package/lib/result.js +2 -1
  169. package/lib/step/base.js +10 -9
  170. package/lib/step/comment.js +2 -2
  171. package/lib/step/config.js +7 -0
  172. package/lib/step/helper.js +4 -4
  173. package/lib/step/meta.js +3 -3
  174. package/lib/step/record.js +5 -5
  175. package/lib/store.js +72 -3
  176. package/lib/translation.js +2 -1
  177. package/lib/utils/mask_data.js +2 -1
  178. package/lib/utils/pluginParser.js +151 -0
  179. package/lib/utils/trace.js +297 -0
  180. package/lib/utils.js +29 -3
  181. package/lib/workers.js +14 -22
  182. package/package.json +17 -14
  183. package/typings/index.d.ts +0 -5
  184. package/docs/webapi/amOnPage.mustache +0 -11
  185. package/docs/webapi/appendField.mustache +0 -16
  186. package/docs/webapi/attachFile.mustache +0 -24
  187. package/docs/webapi/blur.mustache +0 -18
  188. package/docs/webapi/checkOption.mustache +0 -13
  189. package/docs/webapi/clearCookie.mustache +0 -9
  190. package/docs/webapi/clearField.mustache +0 -14
  191. package/docs/webapi/click.mustache +0 -29
  192. package/docs/webapi/clickLink.mustache +0 -8
  193. package/docs/webapi/closeCurrentTab.mustache +0 -7
  194. package/docs/webapi/closeOtherTabs.mustache +0 -8
  195. package/docs/webapi/dontSee.mustache +0 -11
  196. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
  197. package/docs/webapi/dontSeeCookie.mustache +0 -8
  198. package/docs/webapi/dontSeeCurrentPathEquals.mustache +0 -10
  199. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
  200. package/docs/webapi/dontSeeElement.mustache +0 -12
  201. package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
  202. package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
  203. package/docs/webapi/dontSeeInField.mustache +0 -16
  204. package/docs/webapi/dontSeeInSource.mustache +0 -8
  205. package/docs/webapi/dontSeeInTitle.mustache +0 -8
  206. package/docs/webapi/dontSeeTraffic.mustache +0 -13
  207. package/docs/webapi/doubleClick.mustache +0 -13
  208. package/docs/webapi/downloadFile.mustache +0 -12
  209. package/docs/webapi/dragAndDrop.mustache +0 -9
  210. package/docs/webapi/dragSlider.mustache +0 -11
  211. package/docs/webapi/executeAsyncScript.mustache +0 -24
  212. package/docs/webapi/executeScript.mustache +0 -26
  213. package/docs/webapi/fillField.mustache +0 -21
  214. package/docs/webapi/flushNetworkTraffics.mustache +0 -5
  215. package/docs/webapi/focus.mustache +0 -13
  216. package/docs/webapi/forceClick.mustache +0 -28
  217. package/docs/webapi/forceRightClick.mustache +0 -18
  218. package/docs/webapi/grabAllWindowHandles.mustache +0 -7
  219. package/docs/webapi/grabAttributeFrom.mustache +0 -10
  220. package/docs/webapi/grabAttributeFromAll.mustache +0 -9
  221. package/docs/webapi/grabBrowserLogs.mustache +0 -9
  222. package/docs/webapi/grabCookie.mustache +0 -11
  223. package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
  224. package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
  225. package/docs/webapi/grabCurrentUrl.mustache +0 -9
  226. package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
  227. package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
  228. package/docs/webapi/grabElementBoundingRect.mustache +0 -20
  229. package/docs/webapi/grabGeoLocation.mustache +0 -8
  230. package/docs/webapi/grabHTMLFrom.mustache +0 -10
  231. package/docs/webapi/grabHTMLFromAll.mustache +0 -9
  232. package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
  233. package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
  234. package/docs/webapi/grabPageScrollPosition.mustache +0 -8
  235. package/docs/webapi/grabPopupText.mustache +0 -5
  236. package/docs/webapi/grabRecordedNetworkTraffics.mustache +0 -10
  237. package/docs/webapi/grabSource.mustache +0 -8
  238. package/docs/webapi/grabTextFrom.mustache +0 -10
  239. package/docs/webapi/grabTextFromAll.mustache +0 -9
  240. package/docs/webapi/grabTitle.mustache +0 -8
  241. package/docs/webapi/grabValueFrom.mustache +0 -9
  242. package/docs/webapi/grabValueFromAll.mustache +0 -8
  243. package/docs/webapi/grabWebElement.mustache +0 -9
  244. package/docs/webapi/grabWebElements.mustache +0 -9
  245. package/docs/webapi/moveCursorTo.mustache +0 -16
  246. package/docs/webapi/openNewTab.mustache +0 -7
  247. package/docs/webapi/pressKey.mustache +0 -12
  248. package/docs/webapi/pressKeyDown.mustache +0 -12
  249. package/docs/webapi/pressKeyUp.mustache +0 -12
  250. package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
  251. package/docs/webapi/refreshPage.mustache +0 -6
  252. package/docs/webapi/resizeWindow.mustache +0 -6
  253. package/docs/webapi/rightClick.mustache +0 -14
  254. package/docs/webapi/saveElementScreenshot.mustache +0 -10
  255. package/docs/webapi/saveScreenshot.mustache +0 -12
  256. package/docs/webapi/say.mustache +0 -10
  257. package/docs/webapi/scrollIntoView.mustache +0 -11
  258. package/docs/webapi/scrollPageToBottom.mustache +0 -6
  259. package/docs/webapi/scrollPageToTop.mustache +0 -6
  260. package/docs/webapi/scrollTo.mustache +0 -12
  261. package/docs/webapi/see.mustache +0 -11
  262. package/docs/webapi/seeAttributesOnElements.mustache +0 -9
  263. package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
  264. package/docs/webapi/seeCookie.mustache +0 -8
  265. package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
  266. package/docs/webapi/seeCurrentPathEquals.mustache +0 -10
  267. package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
  268. package/docs/webapi/seeElement.mustache +0 -12
  269. package/docs/webapi/seeElementInDOM.mustache +0 -8
  270. package/docs/webapi/seeInCurrentUrl.mustache +0 -8
  271. package/docs/webapi/seeInField.mustache +0 -17
  272. package/docs/webapi/seeInPopup.mustache +0 -8
  273. package/docs/webapi/seeInSource.mustache +0 -7
  274. package/docs/webapi/seeInTitle.mustache +0 -8
  275. package/docs/webapi/seeNumberOfElements.mustache +0 -11
  276. package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
  277. package/docs/webapi/seeTextEquals.mustache +0 -9
  278. package/docs/webapi/seeTitleEquals.mustache +0 -8
  279. package/docs/webapi/seeTraffic.mustache +0 -36
  280. package/docs/webapi/selectOption.mustache +0 -26
  281. package/docs/webapi/setCookie.mustache +0 -16
  282. package/docs/webapi/setGeoLocation.mustache +0 -12
  283. package/docs/webapi/startRecordingTraffic.mustache +0 -8
  284. package/docs/webapi/startRecordingWebSocketMessages.mustache +0 -8
  285. package/docs/webapi/stopRecordingTraffic.mustache +0 -5
  286. package/docs/webapi/stopRecordingWebSocketMessages.mustache +0 -7
  287. package/docs/webapi/switchTo.mustache +0 -9
  288. package/docs/webapi/switchToNextTab.mustache +0 -10
  289. package/docs/webapi/switchToPreviousTab.mustache +0 -10
  290. package/docs/webapi/type.mustache +0 -21
  291. package/docs/webapi/uncheckOption.mustache +0 -13
  292. package/docs/webapi/wait.mustache +0 -8
  293. package/docs/webapi/waitForClickable.mustache +0 -11
  294. package/docs/webapi/waitForCookie.mustache +0 -9
  295. package/docs/webapi/waitForDetached.mustache +0 -10
  296. package/docs/webapi/waitForDisabled.mustache +0 -6
  297. package/docs/webapi/waitForElement.mustache +0 -11
  298. package/docs/webapi/waitForEnabled.mustache +0 -6
  299. package/docs/webapi/waitForFunction.mustache +0 -17
  300. package/docs/webapi/waitForInvisible.mustache +0 -10
  301. package/docs/webapi/waitForNumberOfTabs.mustache +0 -9
  302. package/docs/webapi/waitForText.mustache +0 -13
  303. package/docs/webapi/waitForValue.mustache +0 -10
  304. package/docs/webapi/waitForVisible.mustache +0 -10
  305. package/docs/webapi/waitInUrl.mustache +0 -9
  306. package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
  307. package/docs/webapi/waitToHide.mustache +0 -10
  308. package/docs/webapi/waitUrlEquals.mustache +0 -10
  309. package/lib/helper/AI.js +0 -214
  310. package/lib/helper/Mochawesome.js +0 -96
  311. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -52
  312. package/lib/helper/extras/React.js +0 -65
  313. package/lib/plugin/stepByStepReport.js +0 -431
  314. package/lib/plugin/subtitles.js +0 -89
package/lib/els.js CHANGED
@@ -6,10 +6,11 @@ import recordStep from './step/record.js'
6
6
  import FuncStep from './step/func.js'
7
7
  import { truth } from './assert/truth.js'
8
8
  import { isAsyncFunction, humanizeFunction } from './utils.js'
9
+ import WebElement from './element/WebElement.js'
9
10
 
10
11
  function element(purpose, locator, fn) {
11
12
  let stepConfig
12
- if (arguments[arguments.length - 1] instanceof StepConfig) {
13
+ if (StepConfig.isStepConfig(arguments[arguments.length - 1])) {
13
14
  stepConfig = arguments[arguments.length - 1]
14
15
  }
15
16
 
@@ -28,7 +29,8 @@ function element(purpose, locator, fn) {
28
29
  const els = await step.helper._locate(locator)
29
30
  output.debug(`Found ${els.length} elements, using first element`)
30
31
 
31
- return fn(els[0])
32
+ const wrapped = new WebElement(els[0], step.helper)
33
+ return fn(wrapped)
32
34
  },
33
35
  stepConfig,
34
36
  )
@@ -52,7 +54,8 @@ function eachElement(purpose, locator, fn) {
52
54
  let i = 0
53
55
  for (const el of els) {
54
56
  try {
55
- await fn(el, i)
57
+ const wrapped = new WebElement(el, step.helper)
58
+ await fn(wrapped, i)
56
59
  } catch (err) {
57
60
  output.error(`eachElement: failed operation on element #${i} ${el}`)
58
61
  errs.push(err)
@@ -74,7 +77,8 @@ function expectElement(locator, fn) {
74
77
  const els = await step.helper._locate(locator)
75
78
  output.debug(`Found ${els.length} elements, first will be used for assertion`)
76
79
 
77
- const result = await fn(els[0])
80
+ const wrapped = new WebElement(els[0], step.helper)
81
+ const result = await fn(wrapped)
78
82
  const assertion = truth(`element (${locator})`, fn.toString())
79
83
  assertion.assert(result)
80
84
  })
@@ -92,7 +96,8 @@ function expectAnyElement(locator, fn) {
92
96
 
93
97
  let found = false
94
98
  for (const el of els) {
95
- const result = await fn(el)
99
+ const wrapped = new WebElement(el, step.helper)
100
+ const result = await fn(wrapped)
96
101
  if (result) {
97
102
  found = true
98
103
  break
@@ -113,7 +118,8 @@ function expectAllElements(locator, fn) {
113
118
  let i = 1
114
119
  for (const el of els) {
115
120
  output.debug(`checking element #${i}: ${el}`)
116
- const result = await fn(el)
121
+ const wrapped = new WebElement(el, step.helper)
122
+ const result = await fn(wrapped)
117
123
  const assertion = truth(`element #${i} of (${locator})`, humanizeFunction(fn))
118
124
  assertion.assert(result)
119
125
  i++
package/lib/globals.js CHANGED
@@ -8,27 +8,47 @@ import fsPath from 'path'
8
8
  import ActorFactory from './actor.js'
9
9
  import output from './output.js'
10
10
  import locator from './locator.js'
11
+ import store from './store.js'
11
12
 
12
13
  /**
13
14
  * Initialize CodeceptJS core globals
14
15
  * Called from Codecept.initGlobals()
15
16
  */
16
17
  export async function initCodeceptGlobals(dir, config, container) {
18
+ store.initialize({
19
+ codeceptDir: dir,
20
+ outputDir: fsPath.resolve(dir, config.output),
21
+ })
22
+
23
+ store.noGlobals = config.noGlobals || false
24
+ store.maskSensitiveData = config.maskSensitiveData || false
25
+
26
+ // Keep globals for backward compat with external plugins
17
27
  global.codecept_dir = dir
18
28
  global.output_dir = fsPath.resolve(dir, config.output)
19
29
 
20
- if (config.noGlobals) return;
21
- // Set up actor global - will use container when available
22
- global.actor = global.codecept_actor = (obj) => {
23
- return ActorFactory(obj, global.container || container)
24
- }
25
- global.Actor = global.actor
26
-
27
- // Use dynamic imports for modules to avoid circular dependencies
30
+ // pause/inject/share stay global even under noGlobals — they're the everyday
31
+ // debugging/wiring entry points and have no useful import alternative for
32
+ // page-object code that runs before the container is available.
28
33
  global.pause = async (...args) => {
29
34
  const pauseModule = await import('./pause.js')
30
35
  return (pauseModule.default || pauseModule)(...args)
31
36
  }
37
+ global.inject = () => container.support()
38
+ global.share = container.share
39
+
40
+ if (config.noGlobals) return;
41
+
42
+ output.print(output.styles.debug('Global functions are deprecated. Use `import { Helper, within, session } from "codeceptjs"` instead. Set `noGlobals: true` in config to disable globals.'));
43
+
44
+ const HelperModule = await import('@codeceptjs/helper')
45
+ global.Helper = global.codecept_helper = HelperModule.default || HelperModule
46
+
47
+ // Set up actor global - will use container when available
48
+ global.actor = global.codecept_actor = (obj) => {
49
+ return ActorFactory(obj, container)
50
+ }
51
+ global.Actor = global.actor
32
52
 
33
53
  global.within = async (...args) => {
34
54
  return (await import('./effects.js')).within(...args)
@@ -46,14 +66,9 @@ export async function initCodeceptGlobals(dir, config, container) {
46
66
  return locator.build(locatorQuery)
47
67
  }
48
68
 
49
- global.inject = () => container.support()
50
- global.share = container.share
51
-
52
69
  const secretModule = await import('./secret.js')
53
70
  global.secret = secretModule.secret || (secretModule.default && secretModule.default.secret)
54
71
 
55
- global.codecept_debug = output.debug
56
-
57
72
  const codeceptjsModule = await import('./index.js') // load all objects
58
73
  global.codeceptjs = codeceptjsModule.default || codeceptjsModule
59
74
 
@@ -65,9 +80,6 @@ export async function initCodeceptGlobals(dir, config, container) {
65
80
  global.Then = stepDefinitions.Then
66
81
  global.DefineParameterType = stepDefinitions.defineParameterType
67
82
 
68
- // debug mode
69
- global.debugMode = false
70
-
71
83
  // mask sensitive data
72
84
  global.maskSensitiveData = config.maskSensitiveData || false
73
85
 
@@ -78,6 +90,8 @@ export async function initCodeceptGlobals(dir, config, container) {
78
90
  * Called from mocha/ui.js pre-require event
79
91
  */
80
92
  export function initMochaGlobals(context) {
93
+ if (store.noGlobals) return;
94
+
81
95
  // Mocha test framework globals
82
96
  global.BeforeAll = context.BeforeAll
83
97
  global.AfterAll = context.AfterAll
@@ -106,6 +120,8 @@ export function getGlobalNames() {
106
120
  return [
107
121
  'codecept_dir',
108
122
  'output_dir',
123
+ 'Helper',
124
+ 'codecept_helper',
109
125
  'actor',
110
126
  'codecept_actor',
111
127
  'Actor',
@@ -117,13 +133,11 @@ export function getGlobalNames() {
117
133
  'inject',
118
134
  'share',
119
135
  'secret',
120
- 'codecept_debug',
121
136
  'codeceptjs',
122
137
  'Given',
123
138
  'When',
124
139
  'Then',
125
140
  'DefineParameterType',
126
- 'debugMode',
127
141
  'maskSensitiveData',
128
142
  'BeforeAll',
129
143
  'AfterAll',
@@ -136,6 +150,5 @@ export function getGlobalNames() {
136
150
  'After',
137
151
  'Scenario',
138
152
  'xScenario',
139
- 'container'
140
153
  ]
141
154
  }
package/lib/heal.js CHANGED
@@ -4,6 +4,7 @@ import colors from 'chalk'
4
4
  import recorder from './recorder.js'
5
5
  import output from './output.js'
6
6
  import event from './event.js'
7
+ import container from './container.js'
7
8
 
8
9
  /**
9
10
  * @class
@@ -48,12 +49,14 @@ class Heal {
48
49
  }
49
50
 
50
51
  hasCorrespondingRecipes(step) {
51
- return matchRecipes(this.recipes, this.contextName).filter(r => !r.steps || r.steps.includes(step.name)).length > 0
52
+ return matchRecipes(this.recipes, this.contextName).filter(r => !r.steps || r.steps.includes(step.title)).length > 0
52
53
  }
53
54
 
54
55
  async getCodeSuggestions(context) {
55
56
  const suggestions = []
57
+ const stepName = context.step?.title
56
58
  const recipes = matchRecipes(this.recipes, this.contextName)
59
+ .filter(r => !r.steps || !stepName || r.steps.includes(stepName))
57
60
 
58
61
  debug('Recipes', recipes)
59
62
 
@@ -69,7 +72,7 @@ class Heal {
69
72
  if (!prepareFn) continue
70
73
 
71
74
  if (context[property]) continue
72
- context[property] = await prepareFn(global.inject())
75
+ context[property] = await prepareFn(container.support())
73
76
  }
74
77
 
75
78
  output.level(currentOutputLevel)
@@ -116,10 +119,10 @@ class Heal {
116
119
  })
117
120
 
118
121
  if (typeof codeSnippet === 'string') {
119
- const I = global.container.support('I')
122
+ const I = container.support('I')
120
123
  await eval(codeSnippet)
121
124
  } else if (typeof codeSnippet === 'function') {
122
- await codeSnippet(global.container.support())
125
+ await codeSnippet(container.support())
123
126
  }
124
127
 
125
128
  this.fixes.push({
@@ -1,6 +1,7 @@
1
1
  import path from 'path'
2
2
  import Helper from '@codeceptjs/helper'
3
3
  import REST from './REST.js'
4
+ import store from '../store.js'
4
5
 
5
6
  /**
6
7
  * Helper for managing remote data using REST API.
@@ -324,7 +325,7 @@ class ApiDataFactory extends Helper {
324
325
  await import.meta.resolve(modulePath)
325
326
  } catch (e) {
326
327
  // If not found, try relative to codecept_dir
327
- modulePath = path.join(global.codecept_dir, modulePath)
328
+ modulePath = path.join(store.codeceptDir, modulePath)
328
329
  }
329
330
  // check if the new syntax `export default new Factory()` is used and loads the builder, otherwise loads the module that used old syntax `module.exports = new Factory()`.
330
331
  const module = await import(modulePath)
@@ -4,6 +4,7 @@ import fs from 'fs'
4
4
 
5
5
  import Helper from '@codeceptjs/helper'
6
6
  import { fileExists } from '../utils.js'
7
+ import store from '../store.js'
7
8
  import { fileIncludes } from '../assert/include.js'
8
9
  import { fileEquals } from '../assert/equal.js'
9
10
 
@@ -33,7 +34,7 @@ import { fileEquals } from '../assert/equal.js'
33
34
  class FileSystem extends Helper {
34
35
  constructor() {
35
36
  super()
36
- this.dir = global.codecept_dir
37
+ this.dir = store.codeceptDir
37
38
  this.file = ''
38
39
  }
39
40
 
@@ -52,7 +53,7 @@ class FileSystem extends Helper {
52
53
  * @param {string} openPath
53
54
  */
54
55
  amInPath(openPath) {
55
- this.dir = path.join(global.codecept_dir, openPath)
56
+ this.dir = path.join(store.codeceptDir, openPath)
56
57
  try {
57
58
  this.debugSection('Dir', this.dir)
58
59
  } catch (e) {
@@ -2,6 +2,7 @@ import path from 'path'
2
2
 
3
3
  import HelperModule from '@codeceptjs/helper'
4
4
  import GraphQL from './GraphQL.js'
5
+ import store from '../store.js'
5
6
 
6
7
  /**
7
8
  * Helper for managing remote data using GraphQL queries.
@@ -251,7 +252,7 @@ class GraphQLDataFactory extends Helper {
251
252
  try {
252
253
  require.resolve(modulePath)
253
254
  } catch (e) {
254
- modulePath = path.join(global.codecept_dir, modulePath)
255
+ modulePath = path.join(store.codeceptDir, modulePath)
255
256
  }
256
257
  const builder = require(modulePath)
257
258
  return builder.build(data)
@@ -7,6 +7,7 @@ import promiseRetry from 'promise-retry'
7
7
  import Locator from '../locator.js'
8
8
  import recorder from '../recorder.js'
9
9
  import store from '../store.js'
10
+ import { checkFocusBeforeType, checkFocusBeforePressKey } from './extras/focusCheck.js'
10
11
  import { includes as stringIncludes } from '../assert/include.js'
11
12
  import { urlEquals, equals } from '../assert/equal.js'
12
13
  import { empty } from '../assert/empty.js'
@@ -35,19 +36,16 @@ import MultipleElementsFound from './errors/MultipleElementsFound.js'
35
36
  import RemoteBrowserConnectionRefused from './errors/RemoteBrowserConnectionRefused.js'
36
37
  import Popup from './extras/Popup.js'
37
38
  import Console from './extras/Console.js'
38
- import { findReact, findVue, findByPlaywrightLocator } from './extras/PlaywrightReactVueLocator.js'
39
+ import { findByPlaywrightLocator } from './extras/PlaywrightLocator.js'
39
40
  import { dropFile } from './scripts/dropFile.js'
40
41
  import WebElement from '../element/WebElement.js'
41
42
  import { selectElement } from './extras/elementSelection.js'
43
+ import { fillRichEditor } from './extras/richTextEditor.js'
42
44
 
43
45
  let playwright
44
46
  let perfTiming
45
47
  let defaultSelectorEnginesInitialized = false
46
48
 
47
- // Use global object to track selector registration across workers
48
- if (typeof global.__playwrightSelectorsRegistered === 'undefined') {
49
- global.__playwrightSelectorsRegistered = false
50
- }
51
49
 
52
50
  const popupStore = new Popup()
53
51
  const consoleLogStore = new Console()
@@ -448,7 +446,7 @@ class Playwright extends Helper {
448
446
  this.options.recordVideo = { size }
449
447
  }
450
448
  if (this.options.recordVideo && !this.options.recordVideo.dir) {
451
- this.options.recordVideo.dir = `${global.output_dir}/videos/`
449
+ this.options.recordVideo.dir = `${store.outputDir}/videos/`
452
450
  }
453
451
  this.isRemoteBrowser = !!this.playwrightOptions.browserWSEndpoint
454
452
  this.isElectron = this.options.browser === 'electron'
@@ -510,18 +508,18 @@ class Playwright extends Helper {
510
508
  try {
511
509
  // Always wrap in try-catch since selectors might be registered globally across workers
512
510
  // Check global flag to avoid re-registration in worker processes
513
- if (!global.__playwrightSelectorsRegistered) {
511
+ if (!defaultSelectorEnginesInitialized) {
514
512
  try {
515
513
  await playwright.selectors.register('__value', createValueEngine)
516
514
  await playwright.selectors.register('__disabled', createDisabledEngine)
517
- global.__playwrightSelectorsRegistered = true
515
+ defaultSelectorEnginesInitialized = true
518
516
  defaultSelectorEnginesInitialized = true
519
517
  } catch (e) {
520
518
  if (!e.message.includes('already registered')) {
521
519
  throw e
522
520
  }
523
521
  // Selector already registered globally by another worker
524
- global.__playwrightSelectorsRegistered = true
522
+ defaultSelectorEnginesInitialized = true
525
523
  defaultSelectorEnginesInitialized = true
526
524
  }
527
525
  } else {
@@ -614,7 +612,7 @@ class Playwright extends Helper {
614
612
  if (this.options.recordVideo) contextOptions.recordVideo = this.options.recordVideo
615
613
  if (this.options.recordHar) {
616
614
  const harExt = this.options.recordHar.content && this.options.recordHar.content === 'attach' ? 'zip' : 'har'
617
- const fileName = `${`${global.output_dir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}`
615
+ const fileName = `${`${store.outputDir}${path.sep}har${path.sep}${uuidv4()}_${clearString(this.currentRunningTest.title)}`.slice(0, 245)}.${harExt}`
618
616
  const dir = path.dirname(fileName)
619
617
  if (!fileExists(dir)) fs.mkdirSync(dir)
620
618
  this.options.recordHar.path = fileName
@@ -757,6 +755,11 @@ class Playwright extends Helper {
757
755
  }
758
756
 
759
757
  async _afterSuite() {
758
+ // Reset leftover test-level cleanup state (e.g. screenshot failures)
759
+ // so only errors from this suite teardown are evaluated below.
760
+ this.hasCleanupError = false
761
+ this.testFailures = []
762
+
760
763
  // Stop browser after suite completes
761
764
  // For restart strategies: stop after each suite
762
765
  // For session mode (restart:false): stop after the last suite
@@ -1636,7 +1639,7 @@ class Playwright extends Helper {
1636
1639
  * @returns Promise<void>
1637
1640
  */
1638
1641
  async replayFromHar(harFilePath, opts) {
1639
- const file = path.join(global.codecept_dir, harFilePath)
1642
+ const file = path.join(store.codeceptDir, harFilePath)
1640
1643
 
1641
1644
  if (!fileExists(file)) {
1642
1645
  throw new Error(`File at ${file} cannot be found on local system`)
@@ -2047,7 +2050,7 @@ class Playwright extends Helper {
2047
2050
  const filePath = await download.path()
2048
2051
  fileName = fileName || `downloads/${path.basename(filePath)}`
2049
2052
 
2050
- const downloadPath = path.join(global.output_dir, fileName)
2053
+ const downloadPath = path.join(store.outputDir, fileName)
2051
2054
  if (!fs.existsSync(path.dirname(downloadPath))) {
2052
2055
  fs.mkdirSync(path.dirname(downloadPath), '0777')
2053
2056
  }
@@ -2078,15 +2081,6 @@ class Playwright extends Helper {
2078
2081
  return proceedClick.call(this, locator, context, options)
2079
2082
  }
2080
2083
 
2081
- /**
2082
- * Clicks link and waits for navigation (deprecated)
2083
- */
2084
- async clickLink(locator, context = null) {
2085
- console.log('clickLink deprecated: Playwright automatically waits for navigation to happen.')
2086
- console.log('Replace I.clickLink with I.click')
2087
- return this.click(locator, context)
2088
- }
2089
-
2090
2084
  /**
2091
2085
  * {{> forceClick }}
2092
2086
  */
@@ -2231,6 +2225,7 @@ class Playwright extends Helper {
2231
2225
  * {{> pressKeyWithKeyNormalization }}
2232
2226
  */
2233
2227
  async pressKey(key) {
2228
+ await checkFocusBeforePressKey(this, key)
2234
2229
  const modifiers = []
2235
2230
  if (Array.isArray(key)) {
2236
2231
  for (let k of key) {
@@ -2259,6 +2254,8 @@ class Playwright extends Helper {
2259
2254
  * {{> type }}
2260
2255
  */
2261
2256
  async type(keys, delay = null) {
2257
+ await checkFocusBeforeType(this)
2258
+
2262
2259
  // Always use page.keyboard.type for any string (including single character and national characters).
2263
2260
  if (!Array.isArray(keys)) {
2264
2261
  keys = keys.toString()
@@ -2283,11 +2280,15 @@ class Playwright extends Helper {
2283
2280
  assertElementExists(els, field, 'Field')
2284
2281
  const el = selectElement(els, field, this)
2285
2282
 
2283
+ await highlightActiveElement.call(this, el)
2284
+
2285
+ if (await fillRichEditor(this, el, value)) {
2286
+ return this._waitForAction()
2287
+ }
2288
+
2286
2289
  await el.clear()
2287
2290
  if (store.debugMode) this.debugSection('Focused', await elToString(el, 1))
2288
2291
 
2289
- await highlightActiveElement.call(this, el)
2290
-
2291
2292
  await el.type(value.toString(), { delay: this.options.pressKeyDelay })
2292
2293
 
2293
2294
  return this._waitForAction()
@@ -2343,7 +2344,7 @@ class Playwright extends Helper {
2343
2344
  *
2344
2345
  */
2345
2346
  async attachFile(locator, pathToFile, context = null) {
2346
- const file = path.join(global.codecept_dir, pathToFile)
2347
+ const file = path.join(store.codeceptDir, pathToFile)
2347
2348
 
2348
2349
  if (!fileExists(file)) {
2349
2350
  throw new Error(`File at ${file} can not be found on local system`)
@@ -2669,8 +2670,11 @@ class Playwright extends Helper {
2669
2670
  * @returns {Promise<any>}
2670
2671
  */
2671
2672
  async executeScript(fn, arg) {
2672
- if (this.context && this.context.constructor.name === 'FrameLocator') {
2673
- // switching to iframe context
2673
+ if (arg && typeof arg.getNativeElement === 'function') arg = arg.getNativeElement()
2674
+ if (arg && typeof arg.evaluate === 'function' && typeof arg.locator === 'function') {
2675
+ return arg.evaluate(fn)
2676
+ }
2677
+ if (this.context && typeof this.context.url !== 'function' && typeof this.context.innerText !== 'function') {
2674
2678
  return this.context.locator(':root').evaluate(fn, arg)
2675
2679
  }
2676
2680
  return this.page.evaluate.apply(this.page, [fn, arg])
@@ -2700,15 +2704,12 @@ class Playwright extends Helper {
2700
2704
  *
2701
2705
  */
2702
2706
  async grabTextFrom(locator) {
2703
- // Handle role locators with text/exact options
2704
- if (isRoleLocatorObject(locator)) {
2705
- const elements = await handleRoleLocator(this.page, locator)
2706
- if (elements && elements.length > 0) {
2707
- const text = await elements[0].textContent()
2708
- assertElementExists(text, JSON.stringify(locator))
2709
- this.debugSection('Text', text)
2710
- return text
2711
- }
2707
+ const roleElements = await handleRoleLocator(this.page, locator)
2708
+ if (roleElements && roleElements.length > 0) {
2709
+ const text = await roleElements[0].textContent()
2710
+ assertElementExists(text, JSON.stringify(locator))
2711
+ this.debugSection('Text', text)
2712
+ return text
2712
2713
  }
2713
2714
 
2714
2715
  const locatorObj = new Locator(locator, 'css')
@@ -2936,7 +2937,7 @@ class Playwright extends Helper {
2936
2937
  const els = await this._locate(matchedLocator)
2937
2938
  assertElementExists(els, locator)
2938
2939
  const snapshot = await els[0].ariaSnapshot()
2939
- this.debugSection('Aria Snapshot', snapshot)
2940
+ this.debugSection('Aria Snapshot', `${snapshot.split('\n').length} lines`)
2940
2941
  return snapshot
2941
2942
  }
2942
2943
 
@@ -3296,8 +3297,6 @@ class Playwright extends Helper {
3296
3297
  }
3297
3298
 
3298
3299
  /**
3299
- * This method accepts [React selectors](https://codecept.io/react).
3300
- *
3301
3300
  * {{> waitForVisible }}
3302
3301
  */
3303
3302
  async waitForVisible(locator, sec) {
@@ -3410,7 +3409,7 @@ class Playwright extends Helper {
3410
3409
  }
3411
3410
 
3412
3411
  async _getContext() {
3413
- if ((this.context && this.context.constructor.name === 'FrameLocator') || this.context) {
3412
+ if (this.context) {
3414
3413
  return this.context
3415
3414
  }
3416
3415
  if (this.frame) {
@@ -4194,25 +4193,22 @@ export function buildLocatorString(locator) {
4194
4193
  return locator.simplify()
4195
4194
  }
4196
4195
 
4197
- /**
4198
- * Checks if a locator is a role locator object (e.g., {role: 'button', text: 'Submit', exact: true})
4199
- */
4200
- function isRoleLocatorObject(locator) {
4201
- return locator && typeof locator === 'object' && locator.role && !locator.type
4202
- }
4203
-
4204
4196
  /**
4205
4197
  * Handles role locator objects by converting them to Playwright's getByRole() API
4198
+ * Accepts both raw objects ({role: 'button', text: 'Submit'}) and Locator-wrapped role objects.
4206
4199
  * Returns elements array if role locator, null otherwise
4207
4200
  */
4208
4201
  async function handleRoleLocator(context, locator) {
4209
- if (!isRoleLocatorObject(locator)) return null
4202
+ const loc = new Locator(locator)
4203
+ if (!loc.isRole()) return null
4210
4204
 
4205
+ const roleObj = loc.locator || {}
4211
4206
  const options = {}
4212
- if (locator.text) options.name = locator.text
4213
- if (locator.exact !== undefined) options.exact = locator.exact
4207
+ if (roleObj.text) options.name = roleObj.text
4208
+ if (roleObj.name) options.name = roleObj.name
4209
+ if (roleObj.exact !== undefined) options.exact = roleObj.exact
4214
4210
 
4215
- return context.getByRole(locator.role, Object.keys(options).length > 0 ? options : undefined).all()
4211
+ return context.getByRole(roleObj.role, Object.keys(options).length > 0 ? options : undefined).all()
4216
4212
  }
4217
4213
 
4218
4214
  async function findByRole(context, locator) {
@@ -4224,13 +4220,8 @@ async function findByRole(context, locator) {
4224
4220
  }
4225
4221
 
4226
4222
  async function findElements(matcher, locator) {
4227
- // Check if locator is a Locator object with react/vue type, or a raw object with react/vue property
4228
- const isReactLocator = locator.type === 'react' || (locator.locator && locator.locator.react) || locator.react
4229
- const isVueLocator = locator.type === 'vue' || (locator.locator && locator.locator.vue) || locator.vue
4230
4223
  const isPwLocator = locator.type === 'pw' || (locator.locator && locator.locator.pw) || locator.pw
4231
4224
 
4232
- if (isReactLocator) return findReact(matcher, locator)
4233
- if (isVueLocator) return findVue(matcher, locator)
4234
4225
  if (isPwLocator) return findByPlaywrightLocator.call(this, matcher, locator)
4235
4226
 
4236
4227
  // Handle role locators with text/exact options (e.g., {role: 'button', text: 'Submit', exact: true})
@@ -4245,8 +4236,6 @@ async function findElements(matcher, locator) {
4245
4236
  }
4246
4237
 
4247
4238
  async function findElement(matcher, locator) {
4248
- if (locator.react) return findReact(matcher, locator)
4249
- if (locator.vue) return findVue(matcher, locator)
4250
4239
  if (locator.pw) return findByPlaywrightLocator.call(this, matcher, locator)
4251
4240
 
4252
4241
  locator = new Locator(locator, 'css')
@@ -4281,12 +4270,13 @@ async function proceedClick(locator, context = null, options = {}) {
4281
4270
  assertElementExists(els, locator, 'Clickable element')
4282
4271
  }
4283
4272
 
4284
- const elementIndex = store.currentStep?.opts?.elementIndex
4273
+ const opts = store.currentStep?.opts
4285
4274
  let element
4286
- if (elementIndex != null) {
4275
+ if (opts?.elementIndex != null) {
4287
4276
  element = selectElement(els, locator, this)
4288
4277
  } else {
4289
- if (this.options.strict) assertOnlyOneElement(els, locator, this)
4278
+ const strict = (opts?.exact === false || opts?.strictMode === false) ? false : (this.options.strict || opts?.exact === true || opts?.strictMode === true)
4279
+ if (strict) assertOnlyOneElement(els, locator, this)
4290
4280
  element = els.length > 1 ? (await getVisibleElements(els))[0] : els[0]
4291
4281
  }
4292
4282
 
@@ -4355,7 +4345,9 @@ async function proceedSee(assertType, text, context, strict = false) {
4355
4345
  if (!context) {
4356
4346
  const el = await this.context
4357
4347
 
4358
- allText = el.constructor.name !== 'Locator' ? [await el.locator('body').innerText()] : [await el.innerText()]
4348
+ allText = typeof el.url !== 'function' && typeof el.innerText === 'function'
4349
+ ? [await el.innerText()]
4350
+ : [await el.locator('body').innerText()]
4359
4351
 
4360
4352
  description = 'web application'
4361
4353
  } else {
@@ -4425,12 +4417,9 @@ async function findFields(locator, context = null) {
4425
4417
  ? loc => findElements.call(this, contextEl, loc)
4426
4418
  : loc => this._locate(loc)
4427
4419
 
4428
- // Handle role locators with text/exact options
4429
- if (isRoleLocatorObject(locator)) {
4430
- const matcher = contextEl || (await this.page)
4431
- const roleElements = await handleRoleLocator(matcher, locator)
4432
- if (roleElements) return roleElements
4433
- }
4420
+ const matcher = contextEl || (await this.page)
4421
+ const roleElements = await handleRoleLocator(matcher, locator)
4422
+ if (roleElements) return roleElements
4434
4423
 
4435
4424
  const matchedLocator = new Locator(locator)
4436
4425
  if (!matchedLocator.isFuzzy()) {
@@ -4641,12 +4630,16 @@ async function targetCreatedHandler(page) {
4641
4630
  .catch(() => null)
4642
4631
  .then(async () => {
4643
4632
  if (this.context && this.context._type === 'Frame') {
4644
- // we are inside iframe?
4633
+ // we are inside iframe via Frame object — refresh handle
4645
4634
  const frameEl = await this.context.frameElement()
4646
4635
  this.context = await frameEl.contentFrame()
4647
4636
  this.contextLocator = null
4648
4637
  return
4649
4638
  }
4639
+ if (this.context && this.context.constructor && this.context.constructor.name === 'FrameLocator') {
4640
+ // we are inside iframe via FrameLocator — keep it across load events
4641
+ return
4642
+ }
4650
4643
  // if context element was in iframe - keep it
4651
4644
  // if (await this.context.ownerFrame()) return;
4652
4645
  this.context = page
@@ -4827,7 +4820,7 @@ async function refreshContextSession() {
4827
4820
 
4828
4821
  function saveVideoForPage(page, name) {
4829
4822
  if (!page.video()) return null
4830
- const fileName = `${`${global.output_dir}${pathSeparator}videos${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.webm`
4823
+ const fileName = `${`${store.outputDir}${pathSeparator}videos${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.webm`
4831
4824
  page
4832
4825
  .video()
4833
4826
  .saveAs(fileName)
@@ -4844,7 +4837,7 @@ async function saveTraceForContext(context, name) {
4844
4837
  if (!context) return
4845
4838
  if (!context.tracing) return
4846
4839
  try {
4847
- const fileName = `${`${global.output_dir}${pathSeparator}trace${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.zip`
4840
+ const fileName = `${`${store.outputDir}${pathSeparator}trace${pathSeparator}${uuidv4()}_${clearString(name)}`.slice(0, 245)}.zip`
4848
4841
  await context.tracing.stop({ path: fileName })
4849
4842
  return fileName
4850
4843
  } catch (err) {