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,13 +1,15 @@
1
1
  import path from 'path'
2
2
  import fs from 'fs'
3
3
  import Container from '../container.js'
4
- const supportedHelpers = Container.STANDARD_ACTING_HELPERS
5
4
  import recorder from '../recorder.js'
6
5
  import event from '../event.js'
7
6
  import { scanForErrorMessages } from '../html.js'
7
+ import { captureSnapshot, pickActingHelper } from '../utils/trace.js'
8
8
  import { output } from '../index.js'
9
+ import store from '../store.js'
9
10
  import { humanizeString, ucfirst } from '../utils.js'
10
11
  import { testToFileName } from '../mocha/test.js'
12
+
11
13
  const defaultConfig = {
12
14
  errorClasses: ['error', 'warning', 'alert', 'danger'],
13
15
  browserLogs: ['error'],
@@ -36,67 +38,66 @@ const defaultConfig = {
36
38
  *
37
39
  */
38
40
  export default function (config = {}) {
39
- const helpers = Container.helpers()
40
- let helper
41
-
42
41
  config = Object.assign(defaultConfig, config)
43
42
 
44
- for (const helperName of supportedHelpers) {
45
- if (Object.keys(helpers).indexOf(helperName) > -1) {
46
- helper = helpers[helperName]
47
- }
48
- }
49
-
50
- if (!helper) return // no helpers for screenshot
51
-
52
43
  event.dispatcher.on(event.test.failed, test => {
44
+ const helper = pickActingHelper(Container.helpers())
45
+ if (!helper) return
46
+
53
47
  const pageState = {}
54
48
 
55
- recorder.add('URL of failed test', async () => {
49
+ recorder.add('pageInfo capture', async () => {
56
50
  try {
57
- const url = await helper.grabCurrentUrl()
58
- pageState.url = url
59
- } catch (err) {
60
- // not really needed
61
- }
62
- })
63
- recorder.add('HTML snapshot failed test', async () => {
64
- try {
65
- const html = await helper.grabHTMLFrom('body')
66
-
67
- if (!html) return
68
-
69
- const errors = scanForErrorMessages(html, config.errorClasses)
70
- if (errors.length) {
71
- output.debug('Detected errors in HTML code')
72
- errors.forEach(error => output.debug(error))
73
- pageState.htmlErrors = errors
51
+ const prefix = `${testToFileName(test)}.pageInfo`
52
+ const captured = await captureSnapshot(helper, {
53
+ dir: store.outputDir,
54
+ prefix,
55
+ captureScreenshot: false,
56
+ })
57
+
58
+ if (captured.url) pageState.url = captured.url
59
+
60
+ if (captured.html) {
61
+ const htmlPath = path.join(store.outputDir, captured.html)
62
+ pageState.htmlSnapshot = htmlPath
63
+ const htmlForScan = captured.htmlRaw || (() => {
64
+ try { return fs.readFileSync(htmlPath, 'utf8') } catch { return '' }
65
+ })()
66
+ if (htmlForScan) {
67
+ try {
68
+ const errors = scanForErrorMessages(htmlForScan, config.errorClasses)
69
+ if (errors.length) {
70
+ output.debug('Detected errors in HTML code')
71
+ errors.forEach(error => output.debug(error))
72
+ pageState.htmlErrors = errors
73
+ }
74
+ } catch {}
75
+ }
74
76
  }
75
- } catch (err) {
76
- // not really needed
77
- }
78
- })
79
77
 
80
- recorder.add('Browser logs for failed test', async () => {
81
- try {
82
- const logs = await helper.grabBrowserLogs()
83
-
84
- if (!logs) return
78
+ if (captured.aria) {
79
+ pageState.ariaSnapshot = path.join(store.outputDir, captured.aria)
80
+ }
85
81
 
86
- pageState.browserErrors = getBrowserErrors(logs, config.browserLogs)
87
- } catch (err) {
88
- // not really needed
89
- }
90
- })
82
+ if (captured.console) {
83
+ const consolePath = path.join(store.outputDir, captured.console)
84
+ pageState.consoleSnapshot = consolePath
85
+ try {
86
+ const logs = JSON.parse(fs.readFileSync(consolePath, 'utf8'))
87
+ pageState.browserErrors = getBrowserErrors(logs, config.browserLogs)
88
+ } catch {}
89
+ }
90
+ } catch {}
91
+ }, true)
91
92
 
92
93
  recorder.add('Save page info', () => {
93
94
  test.addNote('pageInfo', pageStateToMarkdown(pageState))
94
95
 
95
- const pageStateFileName = path.join(global.output_dir, `${testToFileName(test)}.pageInfo.md`)
96
+ const pageStateFileName = path.join(store.outputDir, `${testToFileName(test)}.pageInfo.md`)
96
97
  fs.writeFileSync(pageStateFileName, pageStateToMarkdown(pageState))
97
98
  test.artifacts.pageInfo = pageStateFileName
98
99
  return pageState
99
- })
100
+ }, true)
100
101
  })
101
102
  }
102
103
 
@@ -126,15 +127,16 @@ function pageStateToMarkdown(pageState) {
126
127
  }
127
128
 
128
129
  function getBrowserErrors(logs, type = ['error']) {
129
- // Playwright & WebDriver console messages
130
- let errors = logs
130
+ // Accepts Playwright ConsoleMessage objects, normalized {type, text}, or strings
131
+ return logs
131
132
  .map(log => {
132
133
  if (typeof log === 'string') return log
133
- if (!log.type) return null
134
- return { type: log.type(), text: log.text() }
134
+ if (!log) return null
135
+ const t = typeof log.type === 'function' ? log.type() : log.type
136
+ const text = typeof log.text === 'function' ? log.text() : log.text
137
+ if (!t) return null
138
+ return { type: t, text }
135
139
  })
136
140
  .filter(l => l && (typeof l === 'string' || type.includes(l.type)))
137
141
  .map(l => (typeof l === 'string' ? l : l.text))
138
-
139
- return errors
140
142
  }
@@ -0,0 +1,131 @@
1
+ import event from '../event.js'
2
+ import pause from '../pause.js'
3
+ import recorder from '../recorder.js'
4
+ import output from '../output.js'
5
+ import {
6
+ parsePluginArgs,
7
+ resolveTrigger,
8
+ matchStepFile,
9
+ matchUrl,
10
+ getBrowserHelper,
11
+ } from '../utils/pluginParser.js'
12
+
13
+ /**
14
+ * Pauses test execution interactively. Replaces the legacy `pauseOnFail`
15
+ * plugin. The default `on=fail` matches the old `pauseOnFail` behavior.
16
+ *
17
+ * #### Configuration
18
+ *
19
+ * ```js
20
+ * plugins: {
21
+ * pause: {
22
+ * enabled: false,
23
+ * on: 'fail',
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * #### `on=` modes
29
+ *
30
+ * * **fail** — pause when a step fails (default)
31
+ * * **test** — pause after each test
32
+ * * **step** — pause before the first step (interactive walk-through)
33
+ * * **file** — pause when execution reaches `path=...[;line=...]`
34
+ * * **url** — pause when the browser URL matches `pattern=...`
35
+ *
36
+ * CLI examples:
37
+ *
38
+ * ```
39
+ * npx codeceptjs run -p pause
40
+ * npx codeceptjs run -p pause:on=step
41
+ * npx codeceptjs run -p pause:on=file:path=tests/login_test.js;line=43
42
+ * npx codeceptjs run -p pause:on=url:pattern=/users/*
43
+ * ```
44
+ */
45
+ export default function (config = {}) {
46
+ const cliArgs = parsePluginArgs(config._args)
47
+ const trigger = resolveTrigger(cliArgs, config, { on: 'fail' }, { name: 'pause' })
48
+ if (!trigger) return
49
+
50
+ switch (trigger.on) {
51
+ case 'fail':
52
+ return initFailMode()
53
+ case 'test':
54
+ return initTestMode()
55
+ case 'step':
56
+ return initStepMode()
57
+ case 'file':
58
+ return initFileMode(trigger.path, trigger.line)
59
+ case 'url':
60
+ return initUrlMode(trigger.pattern)
61
+ }
62
+ }
63
+
64
+ function initFailMode() {
65
+ let failed = false
66
+
67
+ event.dispatcher.on(event.test.started, () => {
68
+ failed = false
69
+ })
70
+
71
+ event.dispatcher.on(event.step.failed, () => {
72
+ failed = true
73
+ })
74
+
75
+ event.dispatcher.on(event.test.after, () => {
76
+ if (failed) pause()
77
+ })
78
+ }
79
+
80
+ function initTestMode() {
81
+ event.dispatcher.on(event.test.after, () => pause())
82
+ }
83
+
84
+ function initStepMode() {
85
+ let activated = false
86
+
87
+ event.dispatcher.on(event.test.before, () => {
88
+ if (activated) return
89
+ activated = true
90
+ recorder.add('pause:step', () => pause())
91
+ })
92
+ }
93
+
94
+ function initFileMode(targetPath, targetLine) {
95
+ let paused = false
96
+
97
+ event.dispatcher.on(event.step.before, step => {
98
+ if (paused) return
99
+ if (!matchStepFile(step, targetPath, targetLine)) return
100
+ paused = true
101
+ recorder.add('pause:file', () => pause())
102
+ })
103
+ }
104
+
105
+ function initUrlMode(pattern) {
106
+ const helper = getBrowserHelper()
107
+
108
+ if (!helper) {
109
+ output.error('pause:on=url requires a browser helper (Playwright, WebDriver, Puppeteer, Appium)')
110
+ return
111
+ }
112
+
113
+ let paused = false
114
+
115
+ event.dispatcher.on(event.step.after, () => {
116
+ if (paused) return
117
+
118
+ recorder.add('pause:url check', async () => {
119
+ if (paused) return
120
+ try {
121
+ const currentUrl = await helper.grabCurrentUrl()
122
+ if (matchUrl(currentUrl, pattern)) {
123
+ paused = true
124
+ return pause()
125
+ }
126
+ } catch (err) {
127
+ // page may not be loaded yet
128
+ }
129
+ })
130
+ })
131
+ }
@@ -1,39 +1,17 @@
1
- import event from '../event.js'
1
+ import output from '../output.js'
2
+ import pause from './pause.js'
2
3
 
3
- import pause from '../pause.js'
4
+ let warned = false
4
5
 
5
6
  /**
6
- * Automatically launches [interactive pause](/basics/#pause) when a test fails.
7
- *
8
- * Useful for debugging flaky tests on local environment.
9
- * Add this plugin to config file:
10
- *
11
- * ```js
12
- * plugins: {
13
- * pauseOnFail: {},
14
- * }
15
- * ```
16
- *
17
- * Unlike other plugins, `pauseOnFail` is not recommended to be enabled by default.
18
- * Enable it manually on each run via `-p` option:
19
- *
20
- * ```
21
- * npx codeceptjs run -p pauseOnFail
22
- * ```
7
+ * Starts an interactive pause when a test fails.
23
8
  *
9
+ * **Deprecated:** use the `pause` plugin with `on: 'fail'`, which is the default behavior.
24
10
  */
25
- export default function() {
26
- let failed = false
27
-
28
- event.dispatcher.on(event.test.started, () => {
29
- failed = false
30
- })
31
-
32
- event.dispatcher.on(event.step.failed, () => {
33
- failed = true
34
- })
35
-
36
- event.dispatcher.on(event.test.after, () => {
37
- if (failed) pause()
38
- })
11
+ export default function (config = {}) {
12
+ if (!warned) {
13
+ output.error('pauseOnFail is deprecated; use the `pause` plugin (default on=fail).')
14
+ warned = true
15
+ }
16
+ return pause({ ...config, on: 'fail' })
39
17
  }
@@ -1,14 +1,27 @@
1
+ import debugModule from 'debug'
1
2
  import event from '../event.js'
2
-
3
3
  import recorder from '../recorder.js'
4
-
5
4
  import store from '../store.js'
6
5
 
6
+ const debug = debugModule('codeceptjs:retryFailedStep')
7
+
7
8
  const defaultConfig = {
8
9
  retries: 3,
9
10
  defaultIgnoredSteps: ['amOnPage', 'wait*', 'send*', 'execute*', 'run*', 'have*'],
11
+ minTimeout: 150,
12
+ maxTimeout: 10000,
10
13
  factor: 1.5,
14
+ randomize: false,
11
15
  ignoredSteps: [],
16
+ deferToScenarioRetries: true,
17
+ }
18
+
19
+ const RETRY_PRIORITIES = {
20
+ MANUAL_STEP: 100,
21
+ STEP_PLUGIN: 50,
22
+ SCENARIO_CONFIG: 30,
23
+ FEATURE_CONFIG: 20,
24
+ HOOK_CONFIG: 10,
12
25
  }
13
26
 
14
27
  /**
@@ -34,10 +47,9 @@ const defaultConfig = {
34
47
  * #### Configuration:
35
48
  *
36
49
  * * `retries` - number of retries (by default 3),
37
- * * `when` - function, when to perform a retry (accepts error as parameter)
38
50
  * * `factor` - The exponential factor to use. Default is 1.5.
39
- * * `minTimeout` - The number of milliseconds before starting the first retry. Default is 1000.
40
- * * `maxTimeout` - The maximum number of milliseconds between two retries. Default is Infinity.
51
+ * * `minTimeout` - The number of milliseconds before starting the first retry. Default is 150.
52
+ * * `maxTimeout` - The maximum number of milliseconds between two retries. Default is 10000.
41
53
  * * `randomize` - Randomizes the timeouts by multiplying with a factor from 1 to 2. Default is false.
42
54
  * * `defaultIgnoredSteps` - an array of steps to be ignored for retry. Includes:
43
55
  * * `amOnPage`
@@ -49,6 +61,7 @@ const defaultConfig = {
49
61
  * * `ignoredSteps` - an array for custom steps to ignore on retry. Use it to append custom steps to ignored list.
50
62
  * You can use step names or step prefixes ending with `*`. As such, `wait*` will match all steps starting with `wait`.
51
63
  * To append your own steps to ignore list - copy and paste a default steps list. Regexp values are accepted as well.
64
+ * * `deferToScenarioRetries` - when enabled (default), step retries are automatically disabled if scenario retries are configured to avoid excessive total retries.
52
65
  *
53
66
  * #### Example
54
67
  *
@@ -66,7 +79,7 @@ const defaultConfig = {
66
79
  *
67
80
  * #### Disable Per Test
68
81
  *
69
- * This plugin can be disabled per test. In this case you will need to stet `I.retry()` to all flaky steps:
82
+ * This plugin can be disabled per test. In this case you will need to add `step.retry()` to all flaky steps:
70
83
  *
71
84
  * Use scenario configuration to disable plugin for a test
72
85
  *
@@ -78,9 +91,8 @@ const defaultConfig = {
78
91
  *
79
92
  */
80
93
  export default function (config) {
81
- config = Object.assign(defaultConfig, config)
94
+ config = Object.assign({}, defaultConfig, config)
82
95
  config.ignoredSteps = config.ignoredSteps.concat(config.defaultIgnoredSteps)
83
- const customWhen = config.when
84
96
 
85
97
  let enableRetry = false
86
98
 
@@ -88,73 +100,72 @@ export default function (config) {
88
100
  if (!enableRetry) return
89
101
  if (store.debugMode) return false
90
102
  if (!store.autoRetries) return false
91
- // Don't retry terminal errors (e.g., frame detachment errors)
92
103
  if (err && err.isTerminal) return false
93
- // Don't retry navigation errors that are known to be terminal
94
104
  if (err && err.message && (err.message.includes('ERR_ABORTED') || err.message.includes('frame was detached') || err.message.includes('Target page, context or browser has been closed'))) return false
95
- if (customWhen) return customWhen(err)
96
105
  return true
97
106
  }
98
107
  config.when = when
99
108
 
100
- // Ensure retry options are available before any steps run
101
109
  if (!recorder.retries.find(r => r === config)) {
102
110
  recorder.retries.push(config)
103
111
  }
104
112
 
105
113
  event.dispatcher.on(event.step.started, step => {
106
- // if a step is ignored - return
114
+ if (!step.title) return
107
115
  for (const ignored of config.ignoredSteps) {
108
- if (step.name === ignored) return
116
+ if (step.title === ignored) return
109
117
  if (ignored instanceof RegExp) {
110
- if (step.name.match(ignored)) return
111
- } else if (ignored.indexOf('*') && step.name.startsWith(ignored.slice(0, -1))) return
118
+ if (step.title.match(ignored)) return
119
+ } else if (ignored.indexOf('*') !== -1 && step.title.startsWith(ignored.slice(0, -1))) return
112
120
  }
113
- enableRetry = true // enable retry for a step
121
+ enableRetry = true
114
122
  })
115
123
 
116
- // Disable retry only after a successful step; keep it enabled for failure so retry logic can act
117
124
  event.dispatcher.on(event.step.passed, () => {
118
125
  enableRetry = false
119
126
  })
120
127
 
121
128
  event.dispatcher.on(event.test.before, test => {
122
- // pass disableRetryFailedStep is a preferred way to disable retries
123
- // test.disableRetryFailedStep is used for backward compatibility
124
129
  if (!test.opts) test.opts = {}
125
130
  if (test.opts.disableRetryFailedStep || test.disableRetryFailedStep) {
126
131
  store.autoRetries = false
127
- return // disable retry when a test is not active
132
+ return
133
+ }
134
+
135
+ const scenarioRetries = typeof test.retries === 'function' ? test.retries() : -1
136
+ const stepRetryPriority = RETRY_PRIORITIES.STEP_PLUGIN
137
+ const scenarioPriority = test.opts.retryPriority || 0
138
+
139
+ if (scenarioRetries > 0 && config.deferToScenarioRetries !== false) {
140
+ store.autoRetries = false
141
+ return
128
142
  }
129
143
 
130
- // Don't apply plugin retry logic if there are already manual retries configured
131
- // Check if any retry configs exist that aren't from this plugin
132
144
  const hasManualRetries = recorder.retries.some(retry => retry !== config)
133
145
  if (hasManualRetries) {
134
146
  store.autoRetries = false
135
147
  return
136
148
  }
137
149
 
138
- // this option is used to set the retries inside _before() block of helpers
139
150
  store.autoRetries = true
140
151
  test.opts.conditionalRetries = config.retries
141
- // debug: record applied retries value for tests
142
- if (process.env.DEBUG_RETRY_PLUGIN) {
143
- // eslint-disable-next-line no-console
144
- console.log('[retryFailedStep] applying retries =', config.retries, 'for test', test.title)
145
- }
152
+ test.opts.stepRetryPriority = stepRetryPriority
153
+
154
+ debug('applying retries = %d for test %s', config.retries, test.title)
146
155
  recorder.retry(config)
147
156
  })
148
157
 
149
- // Fallback for environments where event.test.before wasn't emitted (runner scenarios)
150
158
  event.dispatcher.on(event.test.started, test => {
151
159
  if (test.opts?.disableRetryFailedStep || test.disableRetryFailedStep) return
152
160
 
153
- // Don't apply plugin retry logic if there are already manual retries configured
154
- // Check if any retry configs exist that aren't from this plugin
155
161
  const hasManualRetries = recorder.retries.some(retry => retry !== config)
156
162
  if (hasManualRetries) return
157
163
 
164
+ const scenarioRetries = typeof test.retries === 'function' ? test.retries() : -1
165
+ if (scenarioRetries > 0 && config.deferToScenarioRetries !== false) {
166
+ return
167
+ }
168
+
158
169
  if (!store.autoRetries) {
159
170
  store.autoRetries = true
160
171
  test.opts.conditionalRetries = test.opts.conditionalRetries || config.retries