codeceptjs 3.4.1 → 3.5.1-2.beta.7

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 (281) hide show
  1. package/README.md +31 -30
  2. package/bin/codecept.js +1 -1
  3. package/lib/actor.js +6 -3
  4. package/lib/ai.js +180 -0
  5. package/lib/cli.js +13 -3
  6. package/lib/codecept.js +8 -0
  7. package/lib/colorUtils.js +10 -0
  8. package/lib/command/definitions.js +2 -7
  9. package/lib/command/dryRun.js +11 -2
  10. package/lib/command/generate.js +46 -3
  11. package/lib/command/info.js +24 -0
  12. package/lib/command/init.js +64 -6
  13. package/lib/command/interactive.js +15 -1
  14. package/lib/command/run-multiple/collection.js +17 -5
  15. package/lib/command/run-multiple.js +4 -2
  16. package/lib/command/run-workers.js +68 -5
  17. package/lib/command/run.js +7 -0
  18. package/lib/command/workers/runTests.js +39 -0
  19. package/lib/container.js +13 -3
  20. package/lib/data/context.js +14 -6
  21. package/lib/event.js +4 -0
  22. package/lib/helper/ApiDataFactory.js +2 -1
  23. package/lib/helper/Appium.js +116 -29
  24. package/lib/helper/Expect.js +422 -0
  25. package/lib/helper/FileSystem.js +1 -1
  26. package/lib/helper/GraphQL.js +25 -0
  27. package/lib/helper/JSONResponse.js +4 -4
  28. package/lib/helper/Nightmare.js +10 -5
  29. package/lib/helper/OpenAI.js +126 -0
  30. package/lib/helper/Playwright.js +1298 -229
  31. package/lib/helper/Protractor.js +12 -7
  32. package/lib/helper/Puppeteer.js +204 -64
  33. package/lib/helper/REST.js +15 -5
  34. package/lib/helper/TestCafe.js +45 -10
  35. package/lib/helper/WebDriver.js +252 -83
  36. package/lib/helper/errors/ElementNotFound.js +2 -1
  37. package/lib/helper/extras/PlaywrightReactVueLocator.js +38 -0
  38. package/lib/helper/scripts/blurElement.js +17 -0
  39. package/lib/helper/scripts/focusElement.js +17 -0
  40. package/lib/helper/scripts/highlightElement.js +20 -0
  41. package/lib/html.js +258 -0
  42. package/lib/interfaces/bdd.js +1 -1
  43. package/lib/interfaces/gherkin.js +37 -3
  44. package/lib/interfaces/scenarioConfig.js +1 -0
  45. package/lib/listener/retry.js +2 -1
  46. package/lib/locator.js +17 -4
  47. package/lib/mochaFactory.js +2 -1
  48. package/lib/output.js +1 -1
  49. package/lib/pause.js +78 -19
  50. package/lib/plugin/autoLogin.js +45 -10
  51. package/lib/plugin/debugErrors.js +67 -0
  52. package/lib/plugin/fakerTransform.js +4 -6
  53. package/lib/plugin/heal.js +209 -0
  54. package/lib/plugin/retryFailedStep.js +10 -1
  55. package/lib/plugin/retryTo.js +2 -4
  56. package/lib/plugin/screenshotOnFail.js +11 -2
  57. package/lib/plugin/selenoid.js +6 -1
  58. package/lib/plugin/standardActingHelpers.js +0 -2
  59. package/lib/plugin/stepByStepReport.js +2 -2
  60. package/lib/plugin/tryTo.js +5 -7
  61. package/lib/plugin/wdio.js +0 -1
  62. package/lib/recorder.js +22 -11
  63. package/lib/secret.js +5 -4
  64. package/lib/session.js +1 -1
  65. package/lib/step.js +36 -12
  66. package/lib/ui.js +5 -3
  67. package/lib/utils.js +22 -1
  68. package/lib/workers.js +83 -10
  69. package/package.json +117 -95
  70. package/translations/de-DE.js +5 -0
  71. package/translations/fr-FR.js +14 -1
  72. package/translations/it-IT.js +1 -0
  73. package/translations/ja-JP.js +14 -9
  74. package/translations/pl-PL.js +5 -0
  75. package/translations/pt-BR.js +1 -0
  76. package/translations/ru-RU.js +1 -0
  77. package/translations/zh-CN.js +5 -0
  78. package/translations/zh-TW.js +5 -0
  79. package/typings/index.d.ts +51 -15
  80. package/typings/promiseBasedTypes.d.ts +864 -802
  81. package/typings/types.d.ts +1339 -744
  82. package/CHANGELOG.md +0 -2427
  83. package/docs/advanced.md +0 -351
  84. package/docs/api.md +0 -323
  85. package/docs/basics.md +0 -980
  86. package/docs/bdd.md +0 -535
  87. package/docs/best.md +0 -237
  88. package/docs/books.md +0 -37
  89. package/docs/bootstrap.md +0 -135
  90. package/docs/build/ApiDataFactory.js +0 -409
  91. package/docs/build/Appium.js +0 -1938
  92. package/docs/build/FileSystem.js +0 -228
  93. package/docs/build/GraphQL.js +0 -204
  94. package/docs/build/GraphQLDataFactory.js +0 -309
  95. package/docs/build/JSONResponse.js +0 -338
  96. package/docs/build/Mochawesome.js +0 -71
  97. package/docs/build/Nightmare.js +0 -2145
  98. package/docs/build/Playwright.js +0 -3986
  99. package/docs/build/Polly.js +0 -42
  100. package/docs/build/Protractor.js +0 -2699
  101. package/docs/build/Puppeteer.js +0 -3710
  102. package/docs/build/REST.js +0 -334
  103. package/docs/build/SeleniumWebdriver.js +0 -76
  104. package/docs/build/TestCafe.js +0 -2057
  105. package/docs/build/WebDriver.js +0 -4017
  106. package/docs/changelog.md +0 -2436
  107. package/docs/commands.md +0 -254
  108. package/docs/community-helpers.md +0 -58
  109. package/docs/configuration.md +0 -157
  110. package/docs/continuous-integration.md +0 -22
  111. package/docs/custom-helpers.md +0 -306
  112. package/docs/data.md +0 -375
  113. package/docs/detox.md +0 -235
  114. package/docs/docker.md +0 -137
  115. package/docs/email.md +0 -183
  116. package/docs/examples.md +0 -149
  117. package/docs/helpers/ApiDataFactory.md +0 -266
  118. package/docs/helpers/Appium.md +0 -1312
  119. package/docs/helpers/Detox.md +0 -586
  120. package/docs/helpers/FileSystem.md +0 -152
  121. package/docs/helpers/GraphQL.md +0 -130
  122. package/docs/helpers/GraphQLDataFactory.md +0 -226
  123. package/docs/helpers/JSONResponse.md +0 -254
  124. package/docs/helpers/Mochawesome.md +0 -8
  125. package/docs/helpers/MockRequest.md +0 -377
  126. package/docs/helpers/Nightmare.md +0 -1256
  127. package/docs/helpers/Playwright.md +0 -2208
  128. package/docs/helpers/Polly.md +0 -44
  129. package/docs/helpers/Puppeteer-firefox.md +0 -86
  130. package/docs/helpers/Puppeteer.md +0 -2141
  131. package/docs/helpers/REST.md +0 -217
  132. package/docs/helpers/TestCafe.md +0 -1222
  133. package/docs/helpers/WebDriver.md +0 -2319
  134. package/docs/hooks.md +0 -340
  135. package/docs/index.md +0 -111
  136. package/docs/installation.md +0 -75
  137. package/docs/internal-api.md +0 -265
  138. package/docs/locators.md +0 -331
  139. package/docs/mobile-react-native-locators.md +0 -67
  140. package/docs/mobile.md +0 -297
  141. package/docs/nightmare.md +0 -223
  142. package/docs/pageobjects.md +0 -291
  143. package/docs/parallel.md +0 -232
  144. package/docs/playwright.md +0 -609
  145. package/docs/plugins.md +0 -1171
  146. package/docs/puppeteer.md +0 -316
  147. package/docs/quickstart.md +0 -163
  148. package/docs/react.md +0 -69
  149. package/docs/reports.md +0 -392
  150. package/docs/secrets.md +0 -30
  151. package/docs/shadow.md +0 -68
  152. package/docs/shared/keys.mustache +0 -31
  153. package/docs/shared/react.mustache +0 -1
  154. package/docs/testcafe.md +0 -174
  155. package/docs/translation.md +0 -247
  156. package/docs/tutorial.md +0 -271
  157. package/docs/typescript.md +0 -180
  158. package/docs/ui.md +0 -59
  159. package/docs/videos.md +0 -28
  160. package/docs/visual.md +0 -202
  161. package/docs/vue.md +0 -121
  162. package/docs/webapi/amOnPage.mustache +0 -11
  163. package/docs/webapi/appendField.mustache +0 -9
  164. package/docs/webapi/attachFile.mustache +0 -12
  165. package/docs/webapi/checkOption.mustache +0 -13
  166. package/docs/webapi/clearCookie.mustache +0 -10
  167. package/docs/webapi/clearField.mustache +0 -9
  168. package/docs/webapi/click.mustache +0 -25
  169. package/docs/webapi/clickLink.mustache +0 -8
  170. package/docs/webapi/closeCurrentTab.mustache +0 -7
  171. package/docs/webapi/closeOtherTabs.mustache +0 -8
  172. package/docs/webapi/dontSee.mustache +0 -11
  173. package/docs/webapi/dontSeeCheckboxIsChecked.mustache +0 -10
  174. package/docs/webapi/dontSeeCookie.mustache +0 -8
  175. package/docs/webapi/dontSeeCurrentUrlEquals.mustache +0 -10
  176. package/docs/webapi/dontSeeElement.mustache +0 -8
  177. package/docs/webapi/dontSeeElementInDOM.mustache +0 -8
  178. package/docs/webapi/dontSeeInCurrentUrl.mustache +0 -4
  179. package/docs/webapi/dontSeeInField.mustache +0 -11
  180. package/docs/webapi/dontSeeInSource.mustache +0 -8
  181. package/docs/webapi/dontSeeInTitle.mustache +0 -8
  182. package/docs/webapi/doubleClick.mustache +0 -13
  183. package/docs/webapi/downloadFile.mustache +0 -12
  184. package/docs/webapi/dragAndDrop.mustache +0 -9
  185. package/docs/webapi/dragSlider.mustache +0 -11
  186. package/docs/webapi/executeAsyncScript.mustache +0 -24
  187. package/docs/webapi/executeScript.mustache +0 -26
  188. package/docs/webapi/fillField.mustache +0 -16
  189. package/docs/webapi/forceClick.mustache +0 -28
  190. package/docs/webapi/forceRightClick.mustache +0 -18
  191. package/docs/webapi/grabAllWindowHandles.mustache +0 -7
  192. package/docs/webapi/grabAttributeFrom.mustache +0 -10
  193. package/docs/webapi/grabAttributeFromAll.mustache +0 -9
  194. package/docs/webapi/grabBrowserLogs.mustache +0 -9
  195. package/docs/webapi/grabCookie.mustache +0 -11
  196. package/docs/webapi/grabCssPropertyFrom.mustache +0 -11
  197. package/docs/webapi/grabCssPropertyFromAll.mustache +0 -10
  198. package/docs/webapi/grabCurrentUrl.mustache +0 -9
  199. package/docs/webapi/grabCurrentWindowHandle.mustache +0 -6
  200. package/docs/webapi/grabDataFromPerformanceTiming.mustache +0 -20
  201. package/docs/webapi/grabElementBoundingRect.mustache +0 -20
  202. package/docs/webapi/grabGeoLocation.mustache +0 -8
  203. package/docs/webapi/grabHTMLFrom.mustache +0 -10
  204. package/docs/webapi/grabHTMLFromAll.mustache +0 -9
  205. package/docs/webapi/grabNumberOfOpenTabs.mustache +0 -8
  206. package/docs/webapi/grabNumberOfVisibleElements.mustache +0 -9
  207. package/docs/webapi/grabPageScrollPosition.mustache +0 -8
  208. package/docs/webapi/grabPopupText.mustache +0 -5
  209. package/docs/webapi/grabSource.mustache +0 -8
  210. package/docs/webapi/grabTextFrom.mustache +0 -10
  211. package/docs/webapi/grabTextFromAll.mustache +0 -9
  212. package/docs/webapi/grabTitle.mustache +0 -8
  213. package/docs/webapi/grabValueFrom.mustache +0 -9
  214. package/docs/webapi/grabValueFromAll.mustache +0 -8
  215. package/docs/webapi/moveCursorTo.mustache +0 -12
  216. package/docs/webapi/openNewTab.mustache +0 -7
  217. package/docs/webapi/pressKey.mustache +0 -12
  218. package/docs/webapi/pressKeyDown.mustache +0 -12
  219. package/docs/webapi/pressKeyUp.mustache +0 -12
  220. package/docs/webapi/pressKeyWithKeyNormalization.mustache +0 -60
  221. package/docs/webapi/refreshPage.mustache +0 -6
  222. package/docs/webapi/resizeWindow.mustache +0 -6
  223. package/docs/webapi/rightClick.mustache +0 -14
  224. package/docs/webapi/saveElementScreenshot.mustache +0 -10
  225. package/docs/webapi/saveScreenshot.mustache +0 -12
  226. package/docs/webapi/say.mustache +0 -10
  227. package/docs/webapi/scrollIntoView.mustache +0 -11
  228. package/docs/webapi/scrollPageToBottom.mustache +0 -6
  229. package/docs/webapi/scrollPageToTop.mustache +0 -6
  230. package/docs/webapi/scrollTo.mustache +0 -12
  231. package/docs/webapi/see.mustache +0 -11
  232. package/docs/webapi/seeAttributesOnElements.mustache +0 -9
  233. package/docs/webapi/seeCheckboxIsChecked.mustache +0 -10
  234. package/docs/webapi/seeCookie.mustache +0 -8
  235. package/docs/webapi/seeCssPropertiesOnElements.mustache +0 -9
  236. package/docs/webapi/seeCurrentUrlEquals.mustache +0 -11
  237. package/docs/webapi/seeElement.mustache +0 -8
  238. package/docs/webapi/seeElementInDOM.mustache +0 -8
  239. package/docs/webapi/seeInCurrentUrl.mustache +0 -8
  240. package/docs/webapi/seeInField.mustache +0 -12
  241. package/docs/webapi/seeInPopup.mustache +0 -8
  242. package/docs/webapi/seeInSource.mustache +0 -7
  243. package/docs/webapi/seeInTitle.mustache +0 -8
  244. package/docs/webapi/seeNumberOfElements.mustache +0 -11
  245. package/docs/webapi/seeNumberOfVisibleElements.mustache +0 -10
  246. package/docs/webapi/seeTextEquals.mustache +0 -9
  247. package/docs/webapi/seeTitleEquals.mustache +0 -8
  248. package/docs/webapi/selectOption.mustache +0 -21
  249. package/docs/webapi/setCookie.mustache +0 -16
  250. package/docs/webapi/setGeoLocation.mustache +0 -12
  251. package/docs/webapi/switchTo.mustache +0 -9
  252. package/docs/webapi/switchToNextTab.mustache +0 -10
  253. package/docs/webapi/switchToPreviousTab.mustache +0 -10
  254. package/docs/webapi/type.mustache +0 -18
  255. package/docs/webapi/uncheckOption.mustache +0 -13
  256. package/docs/webapi/wait.mustache +0 -8
  257. package/docs/webapi/waitForClickable.mustache +0 -11
  258. package/docs/webapi/waitForDetached.mustache +0 -10
  259. package/docs/webapi/waitForElement.mustache +0 -11
  260. package/docs/webapi/waitForEnabled.mustache +0 -6
  261. package/docs/webapi/waitForFunction.mustache +0 -17
  262. package/docs/webapi/waitForInvisible.mustache +0 -10
  263. package/docs/webapi/waitForText.mustache +0 -13
  264. package/docs/webapi/waitForValue.mustache +0 -10
  265. package/docs/webapi/waitForVisible.mustache +0 -10
  266. package/docs/webapi/waitInUrl.mustache +0 -9
  267. package/docs/webapi/waitNumberOfVisibleElements.mustache +0 -10
  268. package/docs/webapi/waitToHide.mustache +0 -10
  269. package/docs/webapi/waitUrlEquals.mustache +0 -10
  270. package/docs/webdriver.md +0 -657
  271. package/docs/wiki/Books-&-Posts.md +0 -27
  272. package/docs/wiki/Community-Helpers-&-Plugins.md +0 -49
  273. package/docs/wiki/Converting-Playwright-to-Istanbul-Coverage.md +0 -29
  274. package/docs/wiki/Examples.md +0 -139
  275. package/docs/wiki/Google-Summer-of-Code-(GSoC)-2020.md +0 -68
  276. package/docs/wiki/Home.md +0 -16
  277. package/docs/wiki/Release-Process.md +0 -24
  278. package/docs/wiki/Roadmap.md +0 -23
  279. package/docs/wiki/Tests.md +0 -1393
  280. package/docs/wiki/Upgrading-to-CodeceptJS-3.md +0 -153
  281. package/docs/wiki/Videos.md +0 -19
package/README.md CHANGED
@@ -2,15 +2,19 @@
2
2
 
3
3
 
4
4
 
5
- [<img src="https://img.shields.io/badge/slack-@codeceptjs-purple.svg?logo=slack">](https://join.slack.com/t/codeceptjs/shared_invite/enQtMzA5OTM4NDM2MzA4LWE4MThhN2NmYTgxNTU5MTc4YzAyYWMwY2JkMmZlYWI5MWQ2MDM5MmRmYzZmYmNiNmY5NTAzM2EwMGIwOTNhOGQ) [<img src="https://img.shields.io/badge/discourse-codeceptjs-purple">](https://codecept.discourse.group) [![NPM version][npm-image]][npm-url]
6
- [![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
5
+ [<img src="https://img.shields.io/badge/slack-@codeceptjs-purple.svg?logo=slack">](https://join.slack.com/t/codeceptjs/shared_invite/enQtMzA5OTM4NDM2MzA4LWE4MThhN2NmYTgxNTU5MTc4YzAyYWMwY2JkMmZlYWI5MWQ2MDM5MmRmYzZmYmNiNmY5NTAzM2EwMGIwOTNhOGQ) [<img src="https://img.shields.io/badge/discourse-codeceptjs-purple">](https://codecept.discourse.group) [![NPM version][npm-image]][npm-url] [<img src="https://img.shields.io/badge/dockerhub-images-blue.svg?logo=codeceptjs">](https://hub.docker.com/r/codeceptjs/codeceptjs)
6
+ [![AI features](https://img.shields.io/badge/AI-features?logo=openai&logoColor=white)](https://github.com/codeceptjs/CodeceptJS/edit/3.x/docs/ai.md) [![StandWithUkraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
7
7
 
8
8
  Build Status:
9
9
 
10
+ Appium Helper:
11
+ [![Appium V2 Tests - Android](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_Android.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_Android.yml)
12
+ [![Appium V2 Tests - iOS](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_iOS.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appiumV2_iOS.yml)
13
+
14
+ Web Helper:
10
15
  [![Playwright Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/playwright.yml)
11
16
  [![Puppeteer Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/puppeteer.yml)
12
17
  [![WebDriver Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/webdriver.yml)
13
- [![Appium Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/appium.yml)
14
18
  [![TestCafe Tests](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml/badge.svg)](https://github.com/codeceptjs/CodeceptJS/actions/workflows/testcafe.yml)
15
19
 
16
20
  # CodeceptJS [![Made in Ukraine](https://img.shields.io/badge/made_in-ukraine-ffd700.svg?labelColor=0057b7)](https://stand-with-ukraine.pp.ua)
@@ -20,7 +24,7 @@ Reference: [Helpers API](https://github.com/codeceptjs/CodeceptJS/tree/master/do
20
24
  ## Supercharged E2E Testing
21
25
 
22
26
  CodeceptJS is a new testing framework for end-to-end testing with WebDriver (or others).
23
- It abstracts browser interaction to simple steps that are written from a user perspective.
27
+ It abstracts browser interaction to simple steps that are written from a user's perspective.
24
28
  A simple test that verifies the "Welcome" text is present on a main page of a site will look like:
25
29
 
26
30
  ```js
@@ -36,15 +40,14 @@ CodeceptJS tests are:
36
40
 
37
41
  * **Synchronous**. You don't need to care about callbacks or promises or test scenarios which are linear. But, your tests should be linear.
38
42
  * Written from **user's perspective**. Every action is a method of `I`. That makes test easy to read, write and maintain even for non-tech persons.
39
- * Backend **API agnostic**. We don't know which WebDriver implementation is running this test. We can easily switch from WebDriverIO to Protractor or PhantomJS.
43
+ * Backend **API agnostic**. We don't know which WebDriver implementation is running this test.
40
44
 
41
- CodeceptJS uses **Helper** modules to provide actions to `I` object. Currently CodeceptJS has these helpers:
45
+ CodeceptJS uses **Helper** modules to provide actions to `I` object. Currently, CodeceptJS has these helpers:
42
46
 
43
47
  * [**Playwright**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Playwright.md) - is a Node library to automate the Chromium, WebKit and Firefox browsers with a single API.
44
48
  * [**Puppeteer**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Puppeteer.md) - uses Google Chrome's Puppeteer for fast headless testing.
45
49
  * [**WebDriver**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) - uses [webdriverio](http://webdriver.io/) to run tests via WebDriver protocol.
46
50
  * [**TestCafe**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/TestCafe.md) - cheap and fast cross-browser test automation.
47
- * [**Nightmare**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Nightmare.md) - uses Electron and NightmareJS to run tests.
48
51
  * [**Appium**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Appium.md) - for **mobile testing** with Appium
49
52
  * [**Detox**](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/Detox.md) - This is a wrapper on top of Detox library, aimed to unify testing experience for CodeceptJS framework. Detox provides a grey box testing for mobile applications, playing especially well for React Native apps.
50
53
 
@@ -54,25 +57,27 @@ And more to come...
54
57
 
55
58
  CodeceptJS is a successor of [Codeception](http://codeception.com), a popular full-stack testing framework for PHP.
56
59
  With CodeceptJS your scenario-driven functional and acceptance tests will be as simple and clean as they can be.
57
- You don't need to worry about asynchronous nature of NodeJS or about various APIs of Selenium, Puppeteer, Protractor, TestCafe, etc. as CodeceptJS unifies them and makes them work as they are synchronous.
60
+ You don't need to worry about asynchronous nature of NodeJS or about various APIs of Playwright, Selenium, Puppeteer, TestCafe, etc. as CodeceptJS unifies them and makes them work as they are synchronous.
58
61
 
59
- ## Features
60
62
 
61
- * Based on [Mocha](https://mochajs.org/) testing framework.
62
- * Designed for scenario driven acceptance testing in BDD-style
63
- * Uses ES6 natively without transpiler.
63
+ ## Features
64
+
65
+ * 🪄 **AI-powered** with GPT features to assist and heal failing tests.
66
+ * ☕ Based on [Mocha](https://mochajs.org/) testing framework.
67
+ * 💼 Designed for scenario driven acceptance testing in BDD-style.
68
+ * 💻 Uses ES6 natively without transpiler.
64
69
  * Also plays nice with TypeScript.
65
- * Smart locators: use names, labels, matching text, CSS or XPath to locate elements.
66
- * Interactive debugging shell: pause test at any point and try different commands in a browser.
70
+ * </> Smart locators: use names, labels, matching text, CSS or XPath to locate elements.
71
+ * 🌐 Interactive debugging shell: pause test at any point and try different commands in a browser.
67
72
  * Easily create tests, pageobjects, stepobjects with CLI generators.
68
73
 
69
- ## Install
74
+ ## Installation
70
75
 
71
76
  ```sh
72
77
  npm i codeceptjs --save
73
78
  ```
74
79
 
75
- Move to directory where you'd like to have your tests (and codeceptjs config) stored, and execute
80
+ Move to directory where you'd like to have your tests (and CodeceptJS config) stored, and execute:
76
81
 
77
82
  ```sh
78
83
  npx codeceptjs init
@@ -126,8 +131,8 @@ Scenario('test some forms', ({ I }) => {
126
131
  });
127
132
  ```
128
133
 
129
- All actions are performed by I object; assertions functions start with `see` function.
130
- In this examples all methods of `I` are taken from WebDriver helper, see [reference](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) to learn how to use them.
134
+ All actions are performed by `I` object; assertions functions start with `see` function.
135
+ In these examples all methods of `I` are taken from WebDriver helper, see [reference](https://github.com/codeceptjs/CodeceptJS/blob/master/docs/helpers/WebDriver.md) to learn how to use them.
131
136
 
132
137
  Let's execute this test with `run` command. Additional option `--steps` will show us the running process. We recommend use `--steps` or `--debug` during development.
133
138
 
@@ -193,17 +198,15 @@ In this case 'User is valid' string will be searched only inside elements locate
193
198
  ### Grabbers
194
199
 
195
200
  In case you need to return a value from a webpage and use it directly in test, you should use methods with `grab` prefix.
196
- They are expected to be used inside async/await functions, and their results will be available in test:
201
+ They are expected to be used inside `async/await` functions, and their results will be available in test:
197
202
 
198
203
  ```js
199
- const assert = require('assert');
200
-
201
204
  Feature('CodeceptJS Demonstration');
202
205
 
203
206
  Scenario('test page title', async ({ I }) => {
204
207
  I.amOnPage('http://simple-form-bootstrap.plataformatec.com.br/documentation');
205
208
  const title = await I.grabTitle();
206
- assert.equal(title, 'Example application with SimpleForm and Twitter Bootstrap');
209
+ I.expectEqual(title, 'Example application with SimpleForm and Twitter Bootstrap'); // Avaiable with Expect helper. -> https://codecept.io/helpers/Expect/
207
210
  });
208
211
  ```
209
212
 
@@ -294,35 +297,33 @@ Thanks all to those who are and will have contributing to this awesome project!
294
297
 
295
298
  [//]: contributor-faces
296
299
  <a href="https://github.com/DavertMik"><img src="https://avatars.githubusercontent.com/u/220264?v=4" title="DavertMik" width="80" height="80"></a>
297
- <a href="https://github.com/PeterNgTr"><img src="https://avatars.githubusercontent.com/u/7845001?v=4" title="PeterNgTr" width="80" height="80"></a>
300
+ <a href="https://github.com/kobenguyent"><img src="https://avatars.githubusercontent.com/u/7845001?v=4" title="kobenguyent" width="80" height="80"></a>
298
301
  <a href="https://github.com/Vorobeyko"><img src="https://avatars.githubusercontent.com/u/11293201?v=4" title="Vorobeyko" width="80" height="80"></a>
299
302
  <a href="https://github.com/reubenmiller"><img src="https://avatars.githubusercontent.com/u/3029781?v=4" title="reubenmiller" width="80" height="80"></a>
300
303
  <a href="https://github.com/Arhell"><img src="https://avatars.githubusercontent.com/u/26163841?v=4" title="Arhell" width="80" height="80"></a>
301
304
  <a href="https://github.com/APshenkin"><img src="https://avatars.githubusercontent.com/u/14344430?v=4" title="APshenkin" width="80" height="80"></a>
302
305
  <a href="https://github.com/fabioel"><img src="https://avatars.githubusercontent.com/u/9824235?v=4" title="fabioel" width="80" height="80"></a>
303
306
  <a href="https://github.com/pablopaul"><img src="https://avatars.githubusercontent.com/u/635526?v=4" title="pablopaul" width="80" height="80"></a>
304
- <a href="https://github.com/Georgegriff"><img src="https://avatars.githubusercontent.com/u/9056958?v=4" title="Georgegriff" width="80" height="80"></a>
305
307
  <a href="https://github.com/mirao"><img src="https://avatars.githubusercontent.com/u/12584138?v=4" title="mirao" width="80" height="80"></a>
308
+ <a href="https://github.com/Georgegriff"><img src="https://avatars.githubusercontent.com/u/9056958?v=4" title="Georgegriff" width="80" height="80"></a>
306
309
  <a href="https://github.com/KMKoushik"><img src="https://avatars.githubusercontent.com/u/24666922?v=4" title="KMKoushik" width="80" height="80"></a>
307
310
  <a href="https://github.com/nikocanvacom"><img src="https://avatars.githubusercontent.com/u/83254493?v=4" title="nikocanvacom" width="80" height="80"></a>
308
311
  <a href="https://github.com/elukoyanov"><img src="https://avatars.githubusercontent.com/u/11647141?v=4" title="elukoyanov" width="80" height="80"></a>
309
312
  <a href="https://github.com/gkushang"><img src="https://avatars.githubusercontent.com/u/3663389?v=4" title="gkushang" width="80" height="80"></a>
310
313
  <a href="https://github.com/tsuemura"><img src="https://avatars.githubusercontent.com/u/17092259?v=4" title="tsuemura" width="80" height="80"></a>
314
+ <a href="https://github.com/EgorBodnar"><img src="https://avatars.githubusercontent.com/u/63167966?v=4" title="EgorBodnar" width="80" height="80"></a>
311
315
  <a href="https://github.com/VikalpP"><img src="https://avatars.githubusercontent.com/u/11846339?v=4" title="VikalpP" width="80" height="80"></a>
312
316
  <a href="https://github.com/BorisOsipov"><img src="https://avatars.githubusercontent.com/u/6514276?v=4" title="BorisOsipov" width="80" height="80"></a>
313
317
  <a href="https://github.com/elaichenkov"><img src="https://avatars.githubusercontent.com/u/29764053?v=4" title="elaichenkov" width="80" height="80"></a>
314
318
  <a href="https://github.com/nitschSB"><img src="https://avatars.githubusercontent.com/u/39341455?v=4" title="nitschSB" width="80" height="80"></a>
315
319
  <a href="https://github.com/hubidu"><img src="https://avatars.githubusercontent.com/u/13134082?v=4" title="hubidu" width="80" height="80"></a>
316
320
  <a href="https://github.com/jploskonka"><img src="https://avatars.githubusercontent.com/u/669483?v=4" title="jploskonka" width="80" height="80"></a>
321
+ <a href="https://github.com/ngraf"><img src="https://avatars.githubusercontent.com/u/7094389?v=4" title="ngraf" width="80" height="80"></a>
317
322
  <a href="https://github.com/maojunxyz"><img src="https://avatars.githubusercontent.com/u/28778042?v=4" title="maojunxyz" width="80" height="80"></a>
318
323
  <a href="https://github.com/abhimanyupandian"><img src="https://avatars.githubusercontent.com/u/36107381?v=4" title="abhimanyupandian" width="80" height="80"></a>
319
324
  <a href="https://github.com/martomo"><img src="https://avatars.githubusercontent.com/u/1850135?v=4" title="martomo" width="80" height="80"></a>
320
- <a href="https://github.com/denis-sokolov"><img src="https://avatars.githubusercontent.com/u/113721?v=4" title="denis-sokolov" width="80" height="80"></a>
321
- <a href="https://github.com/lennym"><img src="https://avatars.githubusercontent.com/u/117398?v=4" title="lennym" width="80" height="80"></a>
322
- <a href="https://github.com/petehouston"><img src="https://avatars.githubusercontent.com/u/9006720?v=4" title="petehouston" width="80" height="80"></a>
323
- <a href="https://github.com/Holorium"><img src="https://avatars.githubusercontent.com/u/10815542?v=4" title="Holorium" width="80" height="80"></a>
324
- <a href="https://github.com/jancorvus"><img src="https://avatars.githubusercontent.com/u/67001310?v=4" title="jancorvus" width="80" height="80"></a>
325
- <a href="https://github.com/ngraf"><img src="https://avatars.githubusercontent.com/u/7094389?v=4" title="ngraf" width="80" height="80"></a>
325
+ <a href="https://github.com/hatufacci"><img src="https://avatars.githubusercontent.com/u/4963181?v=4" title="hatufacci" width="80" height="80"></a>
326
+ <a href="https://github.com/johnyb"><img src="https://avatars.githubusercontent.com/u/86358?v=4" title="johnyb" width="80" height="80"></a>
326
327
 
327
328
  [//]: contributor-faces
328
329
 
package/bin/codecept.js CHANGED
@@ -130,7 +130,7 @@ program.command('run [test]')
130
130
  .option('--child <string>', 'option for child processes')
131
131
  .action(errorHandler(require('../lib/command/run')));
132
132
 
133
- program.command('run-workers <workers>')
133
+ program.command('run-workers <workers> [selectedRuns...]')
134
134
  .description('Executes tests in workers')
135
135
  .option('-c, --config [file]', 'configuration file to be used')
136
136
  .option('-g, --grep <pattern>', 'only run tests matching <pattern>')
package/lib/actor.js CHANGED
@@ -13,15 +13,18 @@ const output = require('./output');
13
13
  */
14
14
  class Actor {
15
15
  /**
16
- * add print comment method`
16
+ * Print the comment on log. Also, adding a step in the `Test.steps` object
17
17
  * @param {string} msg
18
18
  * @param {string} color
19
19
  * @inner
20
20
  *
21
21
  * ⚠️ returns a promise which is synchronized internally by recorder
22
22
  */
23
- say(msg, color = 'cyan') {
24
- return recorder.add(`say ${msg}`, () => {
23
+ async say(msg, color = 'cyan') {
24
+ const step = new Step('say', 'say');
25
+ step.status = 'passed';
26
+ return recordStep(step, [msg]).then(() => {
27
+ // this is backward compatibility as this event may be used somewhere
25
28
  event.emit(event.step.comment, msg);
26
29
  output.say(msg, `${color}`);
27
30
  });
package/lib/ai.js ADDED
@@ -0,0 +1,180 @@
1
+ const { Configuration, OpenAIApi } = require('openai');
2
+ const debug = require('debug')('codeceptjs:ai');
3
+ const config = require('./config');
4
+ const output = require('./output');
5
+ const { removeNonInteractiveElements, minifyHtml, splitByChunks } = require('./html');
6
+
7
+ const defaultConfig = {
8
+ model: 'gpt-3.5-turbo-16k',
9
+ temperature: 0.1,
10
+ };
11
+
12
+ const htmlConfig = {
13
+ maxLength: 50000,
14
+ simplify: true,
15
+ minify: true,
16
+ html: {},
17
+ };
18
+
19
+ const aiInstance = null;
20
+
21
+ class AiAssistant {
22
+ constructor() {
23
+ this.config = config.get('ai', defaultConfig);
24
+ this.htmlConfig = Object.assign(htmlConfig, this.config.html);
25
+ delete this.config.html;
26
+ this.html = null;
27
+ this.response = null;
28
+
29
+ this.isEnabled = !!process.env.OPENAI_API_KEY;
30
+
31
+ if (!this.isEnabled) {
32
+ debug('No OpenAI API key provided. AI assistant is disabled.');
33
+ return;
34
+ }
35
+
36
+ const configuration = new Configuration({
37
+ apiKey: process.env.OPENAI_API_KEY,
38
+ });
39
+
40
+ this.openai = new OpenAIApi(configuration);
41
+ }
42
+
43
+ static getInstance() {
44
+ return aiInstance || new AiAssistant();
45
+ }
46
+
47
+ async setHtmlContext(html) {
48
+ let processedHTML = html;
49
+
50
+ if (this.htmlConfig.simplify) {
51
+ processedHTML = removeNonInteractiveElements(processedHTML, this.htmlConfig);
52
+ }
53
+ if (this.htmlConfig.minify) processedHTML = await minifyHtml(processedHTML);
54
+ if (this.htmlConfig.maxLength) processedHTML = splitByChunks(processedHTML, this.htmlConfig.maxLength)[0];
55
+
56
+ debug(processedHTML);
57
+
58
+ this.html = processedHTML;
59
+ }
60
+
61
+ getResponse() {
62
+ return this.response || '';
63
+ }
64
+
65
+ mockResponse(response) {
66
+ this.mockedResponse = response;
67
+ }
68
+
69
+ async createCompletion(messages) {
70
+ if (!this.openai) return;
71
+
72
+ debug(messages);
73
+
74
+ if (this.mockedResponse) return this.mockedResponse;
75
+
76
+ this.response = null;
77
+
78
+ try {
79
+ const completion = await this.openai.createChatCompletion({
80
+ ...this.config,
81
+ messages,
82
+ });
83
+
84
+ this.response = completion?.data?.choices[0]?.message?.content;
85
+
86
+ debug(this.response);
87
+
88
+ return this.response;
89
+ } catch (err) {
90
+ debug(err.response);
91
+ output.print('');
92
+ output.error(`OpenAI error: ${err.message}`);
93
+ output.error(err?.response?.data?.error?.code);
94
+ output.error(err?.response?.data?.error?.message);
95
+ return '';
96
+ }
97
+ }
98
+
99
+ async healFailedStep(step, err, test) {
100
+ if (!this.isEnabled) return [];
101
+ if (!this.html) throw new Error('No HTML context provided');
102
+
103
+ const messages = [
104
+ { role: 'user', content: 'As a test automation engineer I am testing web application using CodeceptJS.' },
105
+ { role: 'user', content: `I want to heal a test that fails. Here is the list of executed steps: ${test.steps.join(', ')}` },
106
+ { role: 'user', content: `Propose how to adjust ${step.toCode()} step to fix the test.` },
107
+ { role: 'user', content: 'Use locators in order of preference: semantic locator by text, CSS, XPath. Use codeblocks marked with ```.' },
108
+ { role: 'user', content: `Here is the error message: ${err.message}` },
109
+ { role: 'user', content: `Here is HTML code of a page where the failure has happened: \n\n${this.html}` },
110
+ ];
111
+
112
+ const response = await this.createCompletion(messages);
113
+ if (!response) return [];
114
+
115
+ return parseCodeBlocks(response);
116
+ }
117
+
118
+ async writeSteps(input) {
119
+ if (!this.isEnabled) return;
120
+ if (!this.html) throw new Error('No HTML context provided');
121
+
122
+ const snippets = [];
123
+
124
+ const messages = [
125
+ {
126
+ role: 'user',
127
+ content: `I am test engineer writing test in CodeceptJS
128
+ I have opened web page and I want to use CodeceptJS to ${input} on this page
129
+ Provide me valid CodeceptJS code to accomplish it
130
+ Use only locators from this HTML: \n\n${this.html}`,
131
+ },
132
+ { role: 'user', content: 'Propose only CodeceptJS steps code. Do not include Scenario or Feature into response' },
133
+
134
+ // old prompt
135
+ // { role: 'user', content: 'I want to click button Submit using CodeceptJS on this HTML page: <html><body><button>Submit</button></body></html>' },
136
+ // { role: 'assistant', content: '```js\nI.click("Submit");\n```' },
137
+ // { role: 'user', content: 'I want to click button Submit using CodeceptJS on this HTML page: <html><body><button>Login</button></body></html>' },
138
+ // { role: 'assistant', content: 'No suggestions' },
139
+ // { role: 'user', content: `Now I want to ${input} on this HTML page using CodeceptJS code` },
140
+ // { role: 'user', content: `Provide me with CodeceptJS code to achieve this on THIS page.` },
141
+ ];
142
+ const response = await this.createCompletion(messages);
143
+ if (!response) return;
144
+ snippets.push(...parseCodeBlocks(response));
145
+
146
+ debug(snippets[0]);
147
+
148
+ return snippets[0];
149
+ }
150
+ }
151
+
152
+ function parseCodeBlocks(response) {
153
+ // Regular expression pattern to match code snippets
154
+ const codeSnippetPattern = /```(?:javascript|js|typescript|ts)?\n([\s\S]+?)\n```/g;
155
+
156
+ // Array to store extracted code snippets
157
+ const codeSnippets = [];
158
+
159
+ response = response.split('\n').map(line => line.trim()).join('\n');
160
+
161
+ // Iterate over matches and extract code snippets
162
+ let match;
163
+ while ((match = codeSnippetPattern.exec(response)) !== null) {
164
+ codeSnippets.push(match[1]);
165
+ }
166
+
167
+ // Remove "Scenario", "Feature", and "require()" lines
168
+ const modifiedSnippets = codeSnippets.map(snippet => {
169
+ const lines = snippet.split('\n');
170
+
171
+ const filteredLines = lines.filter(line => !line.includes('I.amOnPage') && !line.startsWith('Scenario') && !line.startsWith('Feature') && !line.includes('= require('));
172
+
173
+ return filteredLines.join('\n');
174
+ // remove snippets that move from current url
175
+ }); // .filter(snippet => !line.includes('I.amOnPage'));
176
+
177
+ return modifiedSnippets.filter(snippet => !!snippet);
178
+ }
179
+
180
+ module.exports = AiAssistant;
package/lib/cli.js CHANGED
@@ -81,6 +81,11 @@ class Cli extends Base {
81
81
  if (!codeceptjsEventDispatchersRegistered) {
82
82
  codeceptjsEventDispatchersRegistered = true;
83
83
 
84
+ event.dispatcher.on(event.bddStep.started, (step) => {
85
+ output.stepShift = 2;
86
+ output.step(step);
87
+ });
88
+
84
89
  event.dispatcher.on(event.step.started, (step) => {
85
90
  let processingStep = step;
86
91
  const metaSteps = [];
@@ -93,12 +98,17 @@ class Cli extends Base {
93
98
  for (let i = 0; i < Math.max(currentMetaStep.length, metaSteps.length); i++) {
94
99
  if (currentMetaStep[i] !== metaSteps[i]) {
95
100
  output.stepShift = 3 + 2 * i;
96
- if (metaSteps[i]) output.step(metaSteps[i]);
101
+ if (!metaSteps[i]) continue;
102
+ // bdd steps are handled by bddStep.started
103
+ if (metaSteps[i].isBDD()) continue;
104
+ output.step(metaSteps[i]);
97
105
  }
98
106
  }
99
107
  currentMetaStep = metaSteps;
100
108
  output.stepShift = 3 + 2 * shift;
101
- output.step(step);
109
+ if (step.helper.constructor.name !== 'ExpectHelper') {
110
+ output.step(step);
111
+ }
102
112
  });
103
113
 
104
114
  event.dispatcher.on(event.step.finished, () => {
@@ -168,7 +178,7 @@ class Cli extends Base {
168
178
  }
169
179
 
170
180
  // display artifacts in debug mode
171
- if (test.artifacts && Object.keys(test.artifacts).length) {
181
+ if (test?.artifacts && Object.keys(test.artifacts).length) {
172
182
  log += `\n${output.styles.bold('Artifacts:')}`;
173
183
  for (const artifact of Object.keys(test.artifacts)) {
174
184
  log += `\n- ${artifact}: ${test.artifacts[artifact]}`;
package/lib/codecept.js CHANGED
@@ -8,6 +8,7 @@ const Config = require('./config');
8
8
  const event = require('./event');
9
9
  const runHook = require('./hooks');
10
10
  const output = require('./output');
11
+ const { emptyFolder } = require('./utils');
11
12
 
12
13
  /**
13
14
  * CodeceptJS runner
@@ -66,6 +67,8 @@ class Codecept {
66
67
  global.codecept_dir = dir;
67
68
  global.output_dir = fsPath.resolve(dir, this.config.output);
68
69
 
70
+ if (this.config.emptyOutputFolder) emptyFolder(global.output_dir);
71
+
69
72
  if (!this.config.noGlobals) {
70
73
  global.Helper = global.codecept_helper = require('@codeceptjs/helper');
71
74
  global.actor = global.codecept_actor = require('./actor');
@@ -86,6 +89,9 @@ class Codecept {
86
89
  global.When = stepDefinitions.When;
87
90
  global.Then = stepDefinitions.Then;
88
91
  global.DefineParameterType = stepDefinitions.defineParameterType;
92
+
93
+ // debug mode
94
+ global.debugMode = false;
89
95
  }
90
96
  }
91
97
 
@@ -158,6 +164,7 @@ class Codecept {
158
164
 
159
165
  for (pattern of patterns) {
160
166
  glob.sync(pattern, options).forEach((file) => {
167
+ if (file.includes('node_modules')) return;
161
168
  if (!fsPath.isAbsolute(file)) {
162
169
  file = fsPath.join(global.codecept_dir, file);
163
170
  }
@@ -172,6 +179,7 @@ class Codecept {
172
179
  * Run a specific test or all loaded tests.
173
180
  *
174
181
  * @param {string} [test]
182
+ * @returns {Promise<void>}
175
183
  */
176
184
  async run(test) {
177
185
  return new Promise((resolve, reject) => {
package/lib/colorUtils.js CHANGED
@@ -226,15 +226,25 @@ function isColorProperty(prop) {
226
226
  'color',
227
227
  'background',
228
228
  'backgroundColor',
229
+ 'background-color',
229
230
  'borderColor',
231
+ 'border-color',
230
232
  'borderBottomColor',
233
+ 'border-bottom-color',
231
234
  'borderLeftColor',
235
+ 'border-left-color',
232
236
  'borderRightColor',
233
237
  'borderTopColor',
234
238
  'caretColor',
235
239
  'columnRuleColor',
236
240
  'outlineColor',
237
241
  'textDecorationColor',
242
+ 'border-right-color',
243
+ 'border-top-color',
244
+ 'caret-color',
245
+ 'column-rule-color',
246
+ 'outline-color',
247
+ 'text-decoration-color',
238
248
  ].indexOf(prop) > -1;
239
249
  }
240
250
 
@@ -17,7 +17,6 @@ const actingHelpers = [...require('../plugin/standardActingHelpers'), 'REST'];
17
17
  * @param {Map} params.supportObject
18
18
  * @param {Array<string>} params.helperNames
19
19
  * @param {Array<string>} params.importPaths
20
- * @param {Array<string>} params.customHelpers
21
20
  * @param params.translations
22
21
  *
23
22
  * @returns {string}
@@ -29,15 +28,13 @@ const getDefinitionsFileContent = ({
29
28
  supportObject,
30
29
  importPaths,
31
30
  translations,
32
- customHelpers,
33
31
  }) => {
34
32
  const getHelperListFragment = ({
35
33
  hasCustomHelper,
36
34
  hasCustomStepsFile,
37
- customHelpers,
38
35
  }) => {
39
36
  if (hasCustomHelper && hasCustomStepsFile) {
40
- return `${['ReturnType<steps_file>', ...customHelpers].join(', ')}`;
37
+ return `${['ReturnType<steps_file>', 'WithTranslation<Methods>'].join(', ')}`;
41
38
  }
42
39
 
43
40
  if (hasCustomStepsFile) {
@@ -50,7 +47,6 @@ const getDefinitionsFileContent = ({
50
47
  const helpersListFragment = getHelperListFragment({
51
48
  hasCustomHelper,
52
49
  hasCustomStepsFile,
53
- customHelpers,
54
50
  });
55
51
 
56
52
  const importPathsFragment = importPaths.join('\n');
@@ -143,7 +139,7 @@ module.exports = function (genPath, options) {
143
139
  }
144
140
 
145
141
  if (!actingHelpers.includes(name)) {
146
- customHelpers.push(`WithTranslation<${name}>`);
142
+ customHelpers.push(name);
147
143
  }
148
144
  }
149
145
 
@@ -186,7 +182,6 @@ module.exports = function (genPath, options) {
186
182
  translations,
187
183
  hasCustomStepsFile,
188
184
  hasCustomHelper,
189
- customHelpers,
190
185
  });
191
186
 
192
187
  // add aliases for translations
@@ -7,6 +7,7 @@ const store = require('../store');
7
7
  const Container = require('../container');
8
8
 
9
9
  module.exports = async function (test, options) {
10
+ if (options.grep) process.env.grep = options.grep.toLowerCase();
10
11
  const configFile = options.config;
11
12
  let codecept;
12
13
 
@@ -19,7 +20,8 @@ module.exports = async function (test, options) {
19
20
  if (config.plugins) {
20
21
  // disable all plugins by default, they can be enabled with -p option
21
22
  for (const plugin in config.plugins) {
22
- config.plugins[plugin].enabled = false;
23
+ // if `-p all` is passed, then enabling all plugins, otherwise plugins could be enabled by `-p customLocator,commentStep,tryTo`
24
+ config.plugins[plugin].enabled = options.plugins === 'all';
23
25
  }
24
26
  }
25
27
 
@@ -57,9 +59,16 @@ function printTests(files) {
57
59
 
58
60
  let numOfTests = 0;
59
61
  let numOfSuites = 0;
62
+ const filteredSuites = [];
60
63
 
61
64
  for (const suite of mocha.suite.suites) {
62
- output.print(`${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')}`);
65
+ if (process.env.grep && suite.title.toLowerCase().includes(process.env.grep)) {
66
+ filteredSuites.push(suite);
67
+ }
68
+ }
69
+ const displayedSuites = process.env.grep ? filteredSuites : mocha.suite.suites;
70
+ for (const suite of displayedSuites) {
71
+ output.print(`${colors.white.bold(suite.title)} -- ${output.styles.log(suite.file || '')} -- ${suite.tests.length} tests`);
63
72
  numOfSuites++;
64
73
 
65
74
  for (const test of suite.tests) {
@@ -24,6 +24,7 @@ Scenario('test something', async ({ {{actor}} }) => {
24
24
  // generates empty test
25
25
  module.exports.test = function (genPath) {
26
26
  const testsPath = getTestRoot(genPath);
27
+ global.codecept_dir = testsPath;
27
28
  const config = getConfig(testsPath);
28
29
  if (!config) return;
29
30
 
@@ -83,6 +84,29 @@ module.exports = {
83
84
  }
84
85
  `;
85
86
 
87
+ const poModuleTemplateTS = `const { I } = inject();
88
+
89
+ export = {
90
+
91
+ // insert your locators and methods here
92
+ }
93
+ `;
94
+
95
+ const poClassTemplate = `const { I } = inject();
96
+
97
+ class {{name}} {
98
+ constructor() {
99
+ //insert your locators
100
+ // this.button = '#button'
101
+ }
102
+ // insert your methods here
103
+ }
104
+
105
+ // For inheritance
106
+ module.exports = new {{name}}();
107
+ export = {{name}};
108
+ `;
109
+
86
110
  module.exports.pageObject = function (genPath, opts) {
87
111
  const testsPath = getTestRoot(genPath);
88
112
  const config = getConfig(testsPath);
@@ -110,7 +134,15 @@ module.exports.pageObject = function (genPath, opts) {
110
134
  name: 'filename',
111
135
  message: 'Where should it be stored',
112
136
  default: answers => `./${kind}s/${answers.name}.${extension}`,
113
- }]).then((result) => {
137
+ },
138
+ {
139
+ type: 'list',
140
+ name: 'objectType',
141
+ message: 'What is your preferred object type',
142
+ choices: ['module', 'class'],
143
+ default: 'module',
144
+ },
145
+ ]).then((result) => {
114
146
  const pageObjectFile = path.join(testsPath, result.filename);
115
147
  const dir = path.dirname(pageObjectFile);
116
148
  if (!fileExists(dir)) fs.mkdirSync(dir);
@@ -124,13 +156,24 @@ module.exports.pageObject = function (genPath, opts) {
124
156
  }
125
157
  actor = `require('${actorPath}')`;
126
158
  }
127
- if (!safeFileWrite(pageObjectFile, pageObjectTemplate.replace('{{actor}}', actor))) return;
159
+
128
160
  const name = lcfirst(result.name) + ucfirst(kind);
161
+ if (result.objectType === 'module' && extension === 'ts') {
162
+ if (!safeFileWrite(pageObjectFile, poModuleTemplateTS.replace('{{actor}}', actor))) return;
163
+ } else if (result.objectType === 'module' && extension === 'js') {
164
+ if (!safeFileWrite(pageObjectFile, pageObjectTemplate.replace('{{actor}}', actor))) return;
165
+ } else if (result.objectType === 'class') {
166
+ const content = poClassTemplate.replace(/{{actor}}/g, actor).replace(/{{name}}/g, name);
167
+ if (!safeFileWrite(pageObjectFile, content)) return;
168
+ }
169
+
129
170
  let data = readConfig(configFile);
130
171
  config.include[name] = result.filename;
172
+
173
+ if (!data) throw Error('Config file is empty');
131
174
  const currentInclude = `${data.match(/include:[\s\S][^\}]*/i)[0]}\n ${name}:${JSON.stringify(config.include[name])}`;
132
175
 
133
- data = data.replace(/include:[\s\S][^\}]*/i, `${currentInclude}`);
176
+ data = data.replace(/include:[\s\S][^\}]*/i, `${currentInclude},`);
134
177
 
135
178
  fs.writeFileSync(configFile, beautify(data), 'utf-8');
136
179