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
@@ -1,2057 +0,0 @@
1
- // @ts-nocheck
2
- const fs = require('fs');
3
- const assert = require('assert');
4
- const path = require('path');
5
- const qrcode = require('qrcode-terminal');
6
- const createTestCafe = require('testcafe');
7
- const { Selector, ClientFunction } = require('testcafe');
8
-
9
- const Helper = require('@codeceptjs/helper');
10
- const ElementNotFound = require('./errors/ElementNotFound');
11
- const testControllerHolder = require('./testcafe/testControllerHolder');
12
- const {
13
- mapError,
14
- createTestFile,
15
- createClientFunction,
16
- } = require('./testcafe/testcafe-utils');
17
-
18
- const stringIncludes = require('../assert/include').includes;
19
- const { urlEquals } = require('../assert/equal');
20
- const { empty } = require('../assert/empty');
21
- const { truth } = require('../assert/truth');
22
- const {
23
- xpathLocator,
24
- } = require('../utils');
25
- const Locator = require('../locator');
26
-
27
- /**
28
- * Client Functions
29
- */
30
- const getPageUrl = t => ClientFunction(() => document.location.href).with({ boundTestRun: t });
31
- const getHtmlSource = t => ClientFunction(() => document.getElementsByTagName('html')[0].innerHTML).with({ boundTestRun: t });
32
-
33
- /**
34
- * Uses [TestCafe](https://github.com/DevExpress/testcafe) library to run cross-browser tests.
35
- * The browser version you want to use in tests must be installed on your system.
36
- *
37
- * Requires `testcafe` package to be installed.
38
- *
39
- * ```
40
- * npm i testcafe --save-dev
41
- * ```
42
- *
43
- * ## Configuration
44
- *
45
- * This helper should be configured in codecept.conf.ts or codecept.conf.js
46
- *
47
- * * `url`: base url of website to be tested
48
- * * `show`: (optional, default: false) - show browser window.
49
- * * `windowSize`: (optional) - set browser window width and height
50
- * * `getPageTimeout` (optional, default: '30000') config option to set maximum navigation time in milliseconds.
51
- * * `waitForTimeout`: (optional) default wait* timeout in ms. Default: 5000.
52
- * * `browser`: (optional, default: chrome) - See https://devexpress.github.io/testcafe/documentation/using-testcafe/common-concepts/browsers/browser-support.html
53
- *
54
- *
55
- * #### Example #1: Show chrome browser window
56
- *
57
- * ```js
58
- * {
59
- * helpers: {
60
- * TestCafe : {
61
- * url: "http://localhost",
62
- * waitForTimeout: 15000,
63
- * show: true,
64
- * browser: "chrome"
65
- * }
66
- * }
67
- * }
68
- * ```
69
- *
70
- * To use remote device you can provide 'remote' as browser parameter this will display a link with QR Code
71
- * See https://devexpress.github.io/testcafe/documentation/recipes/test-on-remote-computers-and-mobile-devices.html
72
- * #### Example #2: Remote browser connection
73
- *
74
- * ```js
75
- * {
76
- * helpers: {
77
- * TestCafe : {
78
- * url: "http://localhost",
79
- * waitForTimeout: 15000,
80
- * browser: "remote"
81
- * }
82
- * }
83
- * }
84
- * ```
85
- *
86
- * ## Access From Helpers
87
- *
88
- * Call Testcafe methods directly using the testcafe controller.
89
- *
90
- * ```js
91
- * const testcafeTestController = this.helpers['TestCafe'].t;
92
- * const comboBox = Selector('.combo-box');
93
- * await testcafeTestController
94
- * .hover(comboBox) // hover over combo box
95
- * .click('#i-prefer-both') // click some other element
96
- * ```
97
- *
98
- * ## Methods
99
- */
100
- class TestCafe extends Helper {
101
- constructor(config) {
102
- super(config);
103
-
104
- this.testcafe = undefined; // testcafe instance
105
- this.t = undefined; // testcafe test controller
106
- this.dummyTestcafeFile; // generated testcafe test file
107
-
108
- // context is used for within() function.
109
- // It requires to have _withinBeginand _withinEnd implemented.
110
- // Inside _withinBegin we should define that all next element calls should be started from a specific element (this.context).
111
- this.context = undefined; // TODO Not sure if this applies to testcafe
112
-
113
- this.options = {
114
- url: 'http://localhost',
115
- show: false,
116
- browser: 'chrome',
117
- restart: true, // TODO Test if restart false works
118
- manualStart: false,
119
- keepBrowserState: false,
120
- waitForTimeout: 5000,
121
- getPageTimeout: 30000,
122
- fullPageScreenshots: false,
123
- disableScreenshots: false,
124
- windowSize: undefined,
125
- ...config,
126
- };
127
- }
128
-
129
- // TOOD Do a requirements check
130
- static _checkRequirements() {
131
- try {
132
- require('testcafe');
133
- } catch (e) {
134
- return ['testcafe@^1.1.0'];
135
- }
136
- }
137
-
138
- static _config() {
139
- return [
140
- { name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' },
141
- { name: 'browser', message: 'Browser to be used', default: 'chrome' },
142
- {
143
- name: 'show', message: 'Show browser window', default: true, type: 'confirm',
144
- },
145
- ];
146
- }
147
-
148
- async _configureAndStartBrowser() {
149
- this.dummyTestcafeFile = createTestFile(global.output_dir); // create a dummy test file to get hold of the test controller
150
-
151
- this.iteration += 2; // Use different ports for each test run
152
- // @ts-ignore
153
- this.testcafe = await createTestCafe('', null, null);
154
-
155
- this.debugSection('_before', 'Starting testcafe browser...');
156
-
157
- this.isRunning = true;
158
-
159
- // TODO Do we have to cleanup the runner?
160
- const runner = this.testcafe.createRunner();
161
-
162
- this.options.browser !== 'remote' ? this._startBrowser(runner) : this._startRemoteBrowser(runner);
163
-
164
- this.t = await testControllerHolder.get();
165
- assert(this.t, 'Expected to have the testcafe test controller');
166
-
167
- if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
168
- const dimensions = this.options.windowSize.split('x');
169
- await this.t.resizeWindow(parseInt(dimensions[0], 10), parseInt(dimensions[1], 10));
170
- }
171
- }
172
-
173
- async _startBrowser(runner) {
174
- runner
175
- .src(this.dummyTestcafeFile)
176
- .screenshots(global.output_dir, !this.options.disableScreenshots)
177
- // .video(global.output_dir) // TODO Make this configurable
178
- .browsers(this.options.show ? this.options.browser : `${this.options.browser}:headless`)
179
- .reporter('minimal')
180
- .run({
181
- skipJsErrors: true,
182
- skipUncaughtErrors: true,
183
- quarantineMode: false,
184
- // debugMode: true,
185
- // debugOnFail: true,
186
- // developmentMode: true,
187
- pageLoadTimeout: this.options.getPageTimeout,
188
- selectorTimeout: this.options.waitForTimeout,
189
- assertionTimeout: this.options.waitForTimeout,
190
- takeScreenshotsOnFails: true,
191
- })
192
- .catch((err) => {
193
- this.debugSection('_before', `Error ${err.toString()}`);
194
- this.isRunning = false;
195
- this.testcafe.close();
196
- });
197
- }
198
-
199
- async _startRemoteBrowser(runner) {
200
- const remoteConnection = await this.testcafe.createBrowserConnection();
201
- console.log('Connect your device to the following URL or scan QR Code: ', remoteConnection.url);
202
- qrcode.generate(remoteConnection.url);
203
- remoteConnection.once('ready', () => {
204
- runner
205
- .src(this.dummyTestcafeFile)
206
- .browsers(remoteConnection)
207
- .reporter('minimal')
208
- .run({
209
- selectorTimeout: this.options.waitForTimeout,
210
- skipJsErrors: true,
211
- skipUncaughtErrors: true,
212
- })
213
- .catch((err) => {
214
- this.debugSection('_before', `Error ${err.toString()}`);
215
- this.isRunning = false;
216
- this.testcafe.close();
217
- });
218
- });
219
- }
220
-
221
- async _stopBrowser() {
222
- this.debugSection('_after', 'Stopping testcafe browser...');
223
-
224
- testControllerHolder.free();
225
- if (this.testcafe) {
226
- this.testcafe.close();
227
- }
228
-
229
- fs.unlinkSync(this.dummyTestcafeFile); // remove the dummy test
230
- this.t = undefined;
231
-
232
- this.isRunning = false;
233
- }
234
-
235
- _init() {
236
- }
237
-
238
- async _beforeSuite() {
239
- if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
240
- this.debugSection('Session', 'Starting singleton browser session');
241
- return this._configureAndStartBrowser();
242
- }
243
- }
244
-
245
- async _before() {
246
- if (this.options.restart && !this.options.manualStart) return this._configureAndStartBrowser();
247
- if (!this.isRunning && !this.options.manualStart) return this._configureAndStartBrowser();
248
- this.context = null;
249
- }
250
-
251
- async _after() {
252
- if (!this.isRunning) return;
253
-
254
- if (this.options.restart) {
255
- this.isRunning = false;
256
- return this._stopBrowser();
257
- }
258
-
259
- if (this.options.keepBrowserState) return;
260
-
261
- if (!this.options.keepCookies) {
262
- this.debugSection('Session', 'cleaning cookies and localStorage');
263
- await this.clearCookie();
264
-
265
- // TODO IMHO that should only happen when
266
- await this.executeScript(() => localStorage.clear())
267
- .catch((err) => {
268
- if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err;
269
- });
270
- }
271
- }
272
-
273
- _afterSuite() {
274
- }
275
-
276
- async _finishTest() {
277
- if (!this.options.restart && this.isRunning) return this._stopBrowser();
278
- }
279
-
280
- /**
281
- * Use [TestCafe](https://devexpress.github.io/testcafe/documentation/test-api/) API inside a test.
282
- *
283
- * First argument is a description of an action.
284
- * Second argument is async function that gets this helper as parameter.
285
- *
286
- * { [`t`](https://devexpress.github.io/testcafe/documentation/test-api/test-code-structure.html#test-controller)) } object from TestCafe API is available.
287
- *
288
- * ```js
289
- * I.useTestCafeTo('handle browser dialog', async ({ t }) {
290
- * await t.setNativeDialogHandler(() => true);
291
- * });
292
- * ```
293
- *
294
- *
295
- *
296
- * @param {string} description used to show in logs.
297
- * @param {function} fn async functuion that executed with TestCafe helper as argument
298
- */
299
- useTestCafeTo(description, fn) {
300
- return this._useTo(...arguments);
301
- }
302
-
303
- /**
304
- * Get elements by different locator types, including strict locator
305
- * Should be used in custom helpers:
306
- *
307
- * ```js
308
- * const elements = await this.helpers['TestCafe']._locate('.item');
309
- * ```
310
- *
311
- */
312
- async _locate(locator) {
313
- return findElements.call(this, this.context, locator).catch(mapError);
314
- }
315
-
316
- async _withinBegin(locator) {
317
- const els = await this._locate(locator);
318
- assertElementExists(els, locator);
319
- this.context = await els.nth(0);
320
- }
321
-
322
- async _withinEnd() {
323
- this.context = null;
324
- }
325
-
326
- /**
327
- * Opens a web page in a browser. Requires relative or absolute url.
328
- * If url starts with `/`, opens a web page of a site defined in `url` config parameter.
329
- *
330
- * ```js
331
- * I.amOnPage('/'); // opens main page of website
332
- * I.amOnPage('https://github.com'); // opens github
333
- * I.amOnPage('/login'); // opens a login page
334
- * ```
335
- *
336
- * @param {string} url url path or global url.
337
- * @return {void} automatically synchronized promise with recorder #!
338
- */
339
- async amOnPage(url) {
340
- if (!(/^\w+\:\/\//.test(url))) {
341
- url = this.options.url + url;
342
- }
343
-
344
- return this.t.navigateTo(url)
345
- .catch(mapError);
346
- }
347
-
348
- /**
349
- * Resize the current window to provided width and height.
350
- * First parameter can be set to `maximize`.
351
- *
352
- * @param {number} width width in pixels or `maximize`.
353
- * @param {number} height height in pixels.
354
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
355
- *
356
- */
357
- async resizeWindow(width, height) {
358
- if (width === 'maximize') {
359
- return this.t.maximizeWindow().catch(mapError);
360
- }
361
-
362
- return this.t.resizeWindow(width, height).catch(mapError);
363
- }
364
-
365
- /**
366
- * Perform a click on a link or a button, given by a locator.
367
- * If a fuzzy locator is given, the page will be searched for a button, link, or image matching the locator string.
368
- * For buttons, the "value" attribute, "name" attribute, and inner text are searched. For links, the link text is searched.
369
- * For images, the "alt" attribute and inner text of any parent links are searched.
370
- *
371
- * The second parameter is a context (CSS or XPath locator) to narrow the search.
372
- *
373
- * ```js
374
- * // simple link
375
- * I.click('Logout');
376
- * // button of form
377
- * I.click('Submit');
378
- * // CSS button
379
- * I.click('#form input[type=submit]');
380
- * // XPath
381
- * I.click('//form/*[@type=submit]');
382
- * // link in context
383
- * I.click('Logout', '#nav');
384
- * // using strict locator
385
- * I.click({css: 'nav a.login'});
386
- * ```
387
- *
388
- * @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
389
- * @param {?CodeceptJS.LocatorOrString | null} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
390
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
391
- *
392
- *
393
- */
394
- async click(locator, context = null) {
395
- return proceedClick.call(this, locator, context);
396
- }
397
-
398
- /**
399
- * Reload the current page.
400
- *
401
- * ```js
402
- * I.refreshPage();
403
- * ```
404
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
405
- *
406
- */
407
- async refreshPage() {
408
- // eslint-disable-next-line no-restricted-globals
409
- return this.t.eval(() => location.reload(true), { boundTestRun: this.t }).catch(mapError);
410
- }
411
-
412
- /**
413
- * Waits for an element to become visible on a page (by default waits for 1sec).
414
- * Element can be located by CSS or XPath.
415
- *
416
- * ```js
417
- * I.waitForVisible('#popup');
418
- * ```
419
- *
420
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
421
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
422
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
423
- *
424
- *
425
- */
426
- async waitForVisible(locator, sec) {
427
- const timeout = sec ? sec * 1000 : undefined;
428
-
429
- return (await findElements.call(this, this.context, locator))
430
- .with({ visibilityCheck: true, timeout })()
431
- .catch(mapError);
432
- }
433
-
434
- /**
435
- * Fills a text field or textarea, after clearing its value, with the given string.
436
- * Field is located by name, label, CSS, or XPath.
437
- *
438
- * ```js
439
- * // by label
440
- * I.fillField('Email', 'hello@world.com');
441
- * // by name
442
- * I.fillField('password', secret('123456'));
443
- * // by CSS
444
- * I.fillField('form#login input[name=username]', 'John');
445
- * // or by strict locator
446
- * I.fillField({css: 'form#login input[name=username]'}, 'John');
447
- * ```
448
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
449
- * @param {CodeceptJS.StringOrSecret} value text value to fill.
450
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
451
- *
452
- */
453
- async fillField(field, value) {
454
- const els = await findFields.call(this, field);
455
- assertElementExists(els, field, 'Field');
456
- const el = await els.nth(0);
457
- return this.t
458
- .typeText(el, value.toString(), { replace: true })
459
- .catch(mapError);
460
- }
461
-
462
- /**
463
- * Clears a `<textarea>` or text `<input>` element's value.
464
- *
465
- * ```js
466
- * I.clearField('Email');
467
- * I.clearField('user[email]');
468
- * I.clearField('#email');
469
- * ```
470
- * @param {LocatorOrString} editable field located by label|name|CSS|XPath|strict locator.
471
- * ⚠️ returns a _promise_ which is synchronized internally by recorder.
472
- *
473
- */
474
- async clearField(field) {
475
- const els = await findFields.call(this, field);
476
- assertElementExists(els, field, 'Field');
477
- const el = await els.nth(0);
478
-
479
- const res = await this.t
480
- .selectText(el)
481
- .pressKey('delete');
482
- return res;
483
- }
484
-
485
- /**
486
- * Appends text to a input field or textarea.
487
- * Field is located by name, label, CSS or XPath
488
- *
489
- * ```js
490
- * I.appendField('#myTextField', 'appended');
491
- * ```
492
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator
493
- * @param {string} value text value to append.
494
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
495
- *
496
- *
497
- */
498
- async appendField(field, value) {
499
- const els = await findFields.call(this, field);
500
- assertElementExists(els, field, 'Field');
501
- const el = await els.nth(0);
502
-
503
- return this.t
504
- .typeText(el, value, { replace: false })
505
- .catch(mapError);
506
- }
507
-
508
- /**
509
- * Attaches a file to element located by label, name, CSS or XPath
510
- * Path to file is relative current codecept directory (where codecept.conf.ts or codecept.conf.js is located).
511
- * File will be uploaded to remote system (if tests are running remotely).
512
- *
513
- * ```js
514
- * I.attachFile('Avatar', 'data/avatar.jpg');
515
- * I.attachFile('form input[name=avatar]', 'data/avatar.jpg');
516
- * ```
517
- *
518
- * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
519
- * @param {string} pathToFile local file path relative to codecept.conf.ts or codecept.conf.js config file.
520
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
521
- *
522
- *
523
- */
524
- async attachFile(field, pathToFile) {
525
- const els = await findFields.call(this, field);
526
- assertElementExists(els, field, 'Field');
527
- const el = await els.nth(0);
528
- const file = path.join(global.codecept_dir, pathToFile);
529
-
530
- return this.t
531
- .setFilesToUpload(el, [file])
532
- .catch(mapError);
533
- }
534
-
535
- /**
536
- * Presses a key on a focused element.
537
- * Special keys like 'Enter', 'Control', [etc](https://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/element/:id/value)
538
- * will be replaced with corresponding unicode.
539
- * If modifier key is used (Control, Command, Alt, Shift) in array, it will be released afterwards.
540
- *
541
- * ```js
542
- * I.pressKey('Enter');
543
- * I.pressKey(['Control','a']);
544
- * ```
545
- *
546
- * @param {string|string[]} key key or array of keys to press.
547
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
548
- *
549
- *
550
- * {{ keys }}
551
- */
552
- async pressKey(key) {
553
- assert(key, 'Expected a sequence of keys or key combinations');
554
-
555
- return this.t
556
- .pressKey(key.toLowerCase()) // testcafe keys are lowercase
557
- .catch(mapError);
558
- }
559
-
560
- /**
561
- * Moves cursor to element matched by locator.
562
- * Extra shift can be set with offsetX and offsetY options.
563
- *
564
- * ```js
565
- * I.moveCursorTo('.tooltip');
566
- * I.moveCursorTo('#submit', 5,5);
567
- * ```
568
- *
569
- * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
570
- * @param {number} [offsetX=0] (optional, `0` by default) X-axis offset.
571
- * @param {number} [offsetY=0] (optional, `0` by default) Y-axis offset.
572
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
573
- *
574
- *
575
- */
576
- async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
577
- const els = (await findElements.call(this, this.context, locator)).filterVisible();
578
- await assertElementExists(els, locator);
579
-
580
- return this.t
581
- .hover(els.nth(0), { offsetX, offsetY })
582
- .catch(mapError);
583
- }
584
-
585
- /**
586
- * Performs a double-click on an element matched by link|button|label|CSS or XPath.
587
- * Context can be specified as second parameter to narrow search.
588
- *
589
- * ```js
590
- * I.doubleClick('Edit');
591
- * I.doubleClick('Edit', '.actions');
592
- * I.doubleClick({css: 'button.accept'});
593
- * I.doubleClick('.btn.edit');
594
- * ```
595
- *
596
- * @param {CodeceptJS.LocatorOrString} locator clickable link or button located by text, or any element located by CSS|XPath|strict locator.
597
- * @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element to search in CSS|XPath|Strict locator.
598
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
599
- *
600
- *
601
- */
602
- async doubleClick(locator, context = null) {
603
- let matcher;
604
- if (context) {
605
- const els = await this._locate(context);
606
- await assertElementExists(els, context);
607
- matcher = await els.nth(0);
608
- }
609
-
610
- const els = (await findClickable.call(this, matcher, locator)).filterVisible();
611
- return this.t
612
- .doubleClick(els.nth(0))
613
- .catch(mapError);
614
- }
615
-
616
- /**
617
- * Performs right click on a clickable element matched by semantic locator, CSS or XPath.
618
- *
619
- * ```js
620
- * // right click element with id el
621
- * I.rightClick('#el');
622
- * // right click link or button with text "Click me"
623
- * I.rightClick('Click me');
624
- * // right click button with text "Click me" inside .context
625
- * I.rightClick('Click me', '.context');
626
- * ```
627
- *
628
- * @param {CodeceptJS.LocatorOrString} locator clickable element located by CSS|XPath|strict locator.
629
- * @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS|XPath|strict locator.
630
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
631
- *
632
- *
633
- */
634
- async rightClick(locator, context = null) {
635
- let matcher;
636
- if (context) {
637
- const els = await this._locate(context);
638
- await assertElementExists(els, context);
639
- matcher = await els.nth(0);
640
- }
641
- const els = (await findClickable.call(this, matcher, locator)).filterVisible();
642
- assertElementExists(els, locator);
643
- return this.t
644
- .rightClick(els.nth(0))
645
- .catch(mapError);
646
- }
647
-
648
- /**
649
- * Selects a checkbox or radio button.
650
- * Element is located by label or name or CSS or XPath.
651
- *
652
- * The second parameter is a context (CSS or XPath locator) to narrow the search.
653
- *
654
- * ```js
655
- * I.checkOption('#agree');
656
- * I.checkOption('I Agree to Terms and Conditions');
657
- * I.checkOption('agree', '//form');
658
- * ```
659
- * @param {CodeceptJS.LocatorOrString} field checkbox located by label | name | CSS | XPath | strict locator.
660
- * @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
661
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
662
- *
663
- */
664
- async checkOption(field, context = null) {
665
- const el = await findCheckable.call(this, field, context);
666
-
667
- return this.t
668
- .click(el)
669
- .catch(mapError);
670
- }
671
-
672
- /**
673
- * Unselects a checkbox or radio button.
674
- * Element is located by label or name or CSS or XPath.
675
- *
676
- * The second parameter is a context (CSS or XPath locator) to narrow the search.
677
- *
678
- * ```js
679
- * I.uncheckOption('#agree');
680
- * I.uncheckOption('I Agree to Terms and Conditions');
681
- * I.uncheckOption('agree', '//form');
682
- * ```
683
- * @param {CodeceptJS.LocatorOrString} field checkbox located by label | name | CSS | XPath | strict locator.
684
- * @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS | XPath | strict locator.
685
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
686
- *
687
- */
688
- async uncheckOption(field, context = null) {
689
- const el = await findCheckable.call(this, field, context);
690
-
691
- if (await el.checked) {
692
- return this.t
693
- .click(el)
694
- .catch(mapError);
695
- }
696
- }
697
-
698
- /**
699
- * Verifies that the specified checkbox is checked.
700
- *
701
- * ```js
702
- * I.seeCheckboxIsChecked('Agree');
703
- * I.seeCheckboxIsChecked('#agree'); // I suppose user agreed to terms
704
- * I.seeCheckboxIsChecked({css: '#signup_form input[type=checkbox]'});
705
- * ```
706
- *
707
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
708
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
709
- *
710
- */
711
- async seeCheckboxIsChecked(field) {
712
- return proceedIsChecked.call(this, 'assert', field);
713
- }
714
-
715
- /**
716
- * Verifies that the specified checkbox is not checked.
717
- *
718
- * ```js
719
- * I.dontSeeCheckboxIsChecked('#agree'); // located by ID
720
- * I.dontSeeCheckboxIsChecked('I agree to terms'); // located by label
721
- * I.dontSeeCheckboxIsChecked('agree'); // located by name
722
- * ```
723
- *
724
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
725
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
726
- *
727
- */
728
- async dontSeeCheckboxIsChecked(field) {
729
- return proceedIsChecked.call(this, 'negate', field);
730
- }
731
-
732
- /**
733
- * Selects an option in a drop-down select.
734
- * Field is searched by label | name | CSS | XPath.
735
- * Option is selected by visible text or by value.
736
- *
737
- * ```js
738
- * I.selectOption('Choose Plan', 'Monthly'); // select by label
739
- * I.selectOption('subscription', 'Monthly'); // match option by text
740
- * I.selectOption('subscription', '0'); // or by value
741
- * I.selectOption('//form/select[@name=account]','Premium');
742
- * I.selectOption('form select[name=account]', 'Premium');
743
- * I.selectOption({css: 'form select[name=account]'}, 'Premium');
744
- * ```
745
- *
746
- * Provide an array for the second argument to select multiple options.
747
- *
748
- * ```js
749
- * I.selectOption('Which OS do you use?', ['Android', 'iOS']);
750
- * ```
751
- * @param {LocatorOrString} select field located by label|name|CSS|XPath|strict locator.
752
- * @param {string|Array<*>} option visible text or value of option.
753
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
754
- *
755
- */
756
- async selectOption(select, option) {
757
- const els = await findFields.call(this, select);
758
- assertElementExists(els, select, 'Selectable field');
759
-
760
- const el = await els.filterVisible().nth(0);
761
-
762
- if ((await el.tagName).toLowerCase() !== 'select') {
763
- throw new Error('Element is not <select>');
764
- }
765
- if (!Array.isArray(option)) option = [option];
766
-
767
- // TODO As far as I understand the testcafe docs this should do a multi-select
768
- // but it does not work
769
- // const clickOpts = { ctrl: option.length > 1 };
770
- await this.t.click(el).catch(mapError);
771
-
772
- for (const key of option) {
773
- const opt = key;
774
-
775
- let optEl;
776
- try {
777
- optEl = el.child('option').withText(opt);
778
- if (await optEl.count) {
779
- await this.t.click(optEl).catch(mapError);
780
- continue;
781
- }
782
- // eslint-disable-next-line no-empty
783
- } catch (err) {
784
- }
785
-
786
- try {
787
- const sel = `[value="${opt}"]`;
788
- optEl = el.find(sel);
789
- if (await optEl.count) {
790
- await this.t.click(optEl).catch(mapError);
791
- }
792
- // eslint-disable-next-line no-empty
793
- } catch (err) {
794
- }
795
- }
796
- }
797
-
798
- /**
799
- * Checks that current url contains a provided fragment.
800
- *
801
- * ```js
802
- * I.seeInCurrentUrl('/register'); // we are on registration page
803
- * ```
804
- *
805
- * @param {string} url a fragment to check
806
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
807
- *
808
- */
809
- async seeInCurrentUrl(url) {
810
- stringIncludes('url').assert(url, await getPageUrl(this.t)().catch(mapError));
811
- }
812
-
813
- /**
814
- * Checks that current url does not contain a provided fragment.
815
- *
816
- * @param {string} url value to check.
817
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
818
- *
819
- */
820
- async dontSeeInCurrentUrl(url) {
821
- stringIncludes('url').negate(url, await getPageUrl(this.t)().catch(mapError));
822
- }
823
-
824
- /**
825
- * Checks that current url is equal to provided one.
826
- * If a relative url provided, a configured url will be prepended to it.
827
- * So both examples will work:
828
- *
829
- * ```js
830
- * I.seeCurrentUrlEquals('/register');
831
- * I.seeCurrentUrlEquals('http://my.site.com/register');
832
- * ```
833
- *
834
- * @param {string} url value to check.
835
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
836
- *
837
- */
838
- async seeCurrentUrlEquals(url) {
839
- urlEquals(this.options.url).assert(url, await getPageUrl(this.t)().catch(mapError));
840
- }
841
-
842
- /**
843
- * Checks that current url is not equal to provided one.
844
- * If a relative url provided, a configured url will be prepended to it.
845
- *
846
- * ```js
847
- * I.dontSeeCurrentUrlEquals('/login'); // relative url are ok
848
- * I.dontSeeCurrentUrlEquals('http://mysite.com/login'); // absolute urls are also ok
849
- * ```
850
- *
851
- * @param {string} url value to check.
852
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
853
- *
854
- */
855
- async dontSeeCurrentUrlEquals(url) {
856
- urlEquals(this.options.url).negate(url, await getPageUrl(this.t)().catch(mapError));
857
- }
858
-
859
- /**
860
- * Checks that a page contains a visible text.
861
- * Use context parameter to narrow down the search.
862
- *
863
- * ```js
864
- * I.see('Welcome'); // text welcome on a page
865
- * I.see('Welcome', '.content'); // text inside .content div
866
- * I.see('Register', {css: 'form.register'}); // use strict locator
867
- * ```
868
- * @param {string} text expected on page.
869
- * @param {?CodeceptJS.LocatorOrString} [context=null] (optional, `null` by default) element located by CSS|Xpath|strict locator in which to search for text.
870
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
871
- *
872
- *
873
- */
874
- async see(text, context = null) {
875
- let els;
876
- if (context) {
877
- els = (await findElements.call(this, this.context, context)).withText(text);
878
- } else {
879
- els = (await findElements.call(this, this.context, '*')).withText(text);
880
- }
881
-
882
- return this.t
883
- .expect(els.filterVisible().count).gt(0, `No element with text "${text}" found`)
884
- .catch(mapError);
885
- }
886
-
887
- /**
888
- * Opposite to `see`. Checks that a text is not present on a page.
889
- * Use context parameter to narrow down the search.
890
- *
891
- * ```js
892
- * I.dontSee('Login'); // assume we are already logged in.
893
- * I.dontSee('Login', '.nav'); // no login inside .nav element
894
- * ```
895
- *
896
- * @param {string} text which is not present.
897
- * @param {CodeceptJS.LocatorOrString} [context] (optional) element located by CSS|XPath|strict locator in which to perfrom search.
898
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
899
- *
900
- *
901
- */
902
- async dontSee(text, context = null) {
903
- let els;
904
- if (context) {
905
- els = (await findElements.call(this, this.context, context)).withText(text);
906
- } else {
907
- els = (await findElements.call(this, this.context, 'body')).withText(text);
908
- }
909
-
910
- return this.t
911
- .expect(els.filterVisible().count).eql(0, `Element with text "${text}" can still be seen`)
912
- .catch(mapError);
913
- }
914
-
915
- /**
916
- * Checks that a given Element is visible
917
- * Element is located by CSS or XPath.
918
- *
919
- * ```js
920
- * I.seeElement('#modal');
921
- * ```
922
- * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
923
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
924
- *
925
- */
926
- async seeElement(locator) {
927
- const exists = (await findElements.call(this, this.context, locator)).filterVisible().exists;
928
- return this.t
929
- .expect(exists).ok(`No element "${(new Locator(locator))}" found`)
930
- .catch(mapError);
931
- }
932
-
933
- /**
934
- * Opposite to `seeElement`. Checks that element is not visible (or in DOM)
935
- *
936
- * ```js
937
- * I.dontSeeElement('.modal'); // modal is not shown
938
- * ```
939
- *
940
- * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|Strict locator.
941
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
942
- *
943
- */
944
- async dontSeeElement(locator) {
945
- const exists = (await findElements.call(this, this.context, locator)).filterVisible().exists;
946
- return this.t
947
- .expect(exists).notOk(`Element "${(new Locator(locator))}" is still visible`)
948
- .catch(mapError);
949
- }
950
-
951
- /**
952
- * Checks that a given Element is present in the DOM
953
- * Element is located by CSS or XPath.
954
- *
955
- * ```js
956
- * I.seeElementInDOM('#modal');
957
- * ```
958
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
959
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
960
- *
961
- */
962
- async seeElementInDOM(locator) {
963
- const exists = (await findElements.call(this, this.context, locator)).exists;
964
- return this.t
965
- .expect(exists).ok(`No element "${(new Locator(locator))}" found in DOM`)
966
- .catch(mapError);
967
- }
968
-
969
- /**
970
- * Opposite to `seeElementInDOM`. Checks that element is not on page.
971
- *
972
- * ```js
973
- * I.dontSeeElementInDOM('.nav'); // checks that element is not on page visible or not
974
- * ```
975
- *
976
- * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|Strict locator.
977
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
978
- *
979
- */
980
- async dontSeeElementInDOM(locator) {
981
- const exists = (await findElements.call(this, this.context, locator)).exists;
982
- return this.t
983
- .expect(exists).notOk(`Element "${(new Locator(locator))}" is still in DOM`)
984
- .catch(mapError);
985
- }
986
-
987
- /**
988
- * Asserts that an element is visible a given number of times.
989
- * Element is located by CSS or XPath.
990
- *
991
- * ```js
992
- * I.seeNumberOfVisibleElements('.buttons', 3);
993
- * ```
994
- *
995
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
996
- * @param {number} num number of elements.
997
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
998
- *
999
- *
1000
- */
1001
- async seeNumberOfVisibleElements(locator, num) {
1002
- const count = (await findElements.call(this, this.context, locator)).filterVisible().count;
1003
- return this.t
1004
- .expect(count).eql(num)
1005
- .catch(mapError);
1006
- }
1007
-
1008
- /**
1009
- * Grab number of visible elements by locator.
1010
- * Resumes test execution, so **should be used inside async function with `await`** operator.
1011
- *
1012
- * ```js
1013
- * let numOfElements = await I.grabNumberOfVisibleElements('p');
1014
- * ```
1015
- *
1016
- * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
1017
- * @returns {Promise<number>} number of visible elements
1018
- */
1019
- async grabNumberOfVisibleElements(locator) {
1020
- const count = (await findElements.call(this, this.context, locator)).filterVisible().count;
1021
- return count;
1022
- }
1023
-
1024
- /**
1025
- * Checks that the given input field or textarea equals to given value.
1026
- * For fuzzy locators, fields are matched by label text, the "name" attribute, CSS, and XPath.
1027
- *
1028
- * ```js
1029
- * I.seeInField('Username', 'davert');
1030
- * I.seeInField({css: 'form textarea'},'Type your comment here');
1031
- * I.seeInField('form input[type=hidden]','hidden_value');
1032
- * I.seeInField('#searchform input','Search');
1033
- * ```
1034
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1035
- * @param {string} value value to check.
1036
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1037
- *
1038
- */
1039
- async seeInField(field, value) {
1040
- // const expectedValue = findElements.call(this, this.context, field).value;
1041
- const els = await findFields.call(this, field);
1042
- assertElementExists(els, field, 'Field');
1043
- const el = await els.nth(0);
1044
-
1045
- return this.t
1046
- .expect(await el.value).eql(value)
1047
- .catch(mapError);
1048
- }
1049
-
1050
- /**
1051
- * Checks that value of input field or textarea doesn't equal to given value
1052
- * Opposite to `seeInField`.
1053
- *
1054
- * ```js
1055
- * I.dontSeeInField('email', 'user@user.com'); // field by name
1056
- * I.dontSeeInField({ css: 'form input.email' }, 'user@user.com'); // field by CSS
1057
- * ```
1058
- *
1059
- * @param {CodeceptJS.LocatorOrString} field located by label|name|CSS|XPath|strict locator.
1060
- * @param {string} value value to check.
1061
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1062
- *
1063
- */
1064
- async dontSeeInField(field, value) {
1065
- // const expectedValue = findElements.call(this, this.context, field).value;
1066
- const els = await findFields.call(this, field);
1067
- assertElementExists(els, field, 'Field');
1068
- const el = await els.nth(0);
1069
-
1070
- return this.t
1071
- .expect(el.value).notEql(value)
1072
- .catch(mapError);
1073
- }
1074
-
1075
- /**
1076
- * Checks that text is equal to provided one.
1077
- *
1078
- * ```js
1079
- * I.seeTextEquals('text', 'h1');
1080
- * ```
1081
- */
1082
- async seeTextEquals(text, context = null) {
1083
- const expectedText = findElements.call(this, context, undefined).textContent;
1084
- return this.t
1085
- .expect(expectedText).eql(text)
1086
- .catch(mapError);
1087
- }
1088
-
1089
- /**
1090
- * Checks that the current page contains the given string in its raw source code.
1091
- *
1092
- * ```js
1093
- * I.seeInSource('<h1>Green eggs &amp; ham</h1>');
1094
- * ```
1095
- * @param {string} text value to check.
1096
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1097
- *
1098
- */
1099
- async seeInSource(text) {
1100
- const source = await getHtmlSource(this.t)();
1101
- stringIncludes('HTML source of a page').assert(text, source);
1102
- }
1103
-
1104
- /**
1105
- * Checks that the current page does not contains the given string in its raw source code.
1106
- *
1107
- * ```js
1108
- * I.dontSeeInSource('<!--'); // no comments in source
1109
- * ```
1110
- *
1111
- * @param {string} value to check.
1112
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1113
- *
1114
- */
1115
- async dontSeeInSource(text) {
1116
- const source = await getHtmlSource(this.t)();
1117
- stringIncludes('HTML source of a page').negate(text, source);
1118
- }
1119
-
1120
- /**
1121
- * Saves screenshot of the specified locator to ouput folder (set in codecept.conf.ts or codecept.conf.js).
1122
- * Filename is relative to output folder.
1123
- *
1124
- * ```js
1125
- * I.saveElementScreenshot(`#submit`,'debug.png');
1126
- * ```
1127
- *
1128
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1129
- * @param {string} fileName file name to save.
1130
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1131
- *
1132
- *
1133
- */
1134
- async saveElementScreenshot(locator, fileName) {
1135
- const outputFile = path.join(global.output_dir, fileName);
1136
-
1137
- const sel = await findElements.call(this, this.context, locator);
1138
- assertElementExists(sel, locator);
1139
- const firstElement = await sel.filterVisible().nth(0);
1140
-
1141
- this.debug(`Screenshot of ${(new Locator(locator))} element has been saved to ${outputFile}`);
1142
- return this.t.takeElementScreenshot(firstElement, fileName);
1143
- }
1144
-
1145
- /**
1146
- * Saves a screenshot to ouput folder (set in codecept.conf.ts or codecept.conf.js).
1147
- * Filename is relative to output folder.
1148
- * Optionally resize the window to the full available page `scrollHeight` and `scrollWidth` to capture the entire page by passing `true` in as the second argument.
1149
- *
1150
- * ```js
1151
- * I.saveScreenshot('debug.png');
1152
- * I.saveScreenshot('debug.png', true) //resizes to available scrollHeight and scrollWidth before taking screenshot
1153
- * ```
1154
- *
1155
- * @param {string} fileName file name to save.
1156
- * @param {boolean} [fullPage=false] (optional, `false` by default) flag to enable fullscreen screenshot mode.
1157
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1158
- *
1159
- */
1160
- // TODO Implement full page screenshots
1161
- async saveScreenshot(fileName) {
1162
- const outputFile = path.join(global.output_dir, fileName);
1163
- this.debug(`Screenshot is saving to ${outputFile}`);
1164
-
1165
- // TODO testcafe automatically creates thumbnail images (which cant be turned off)
1166
- return this.t.takeScreenshot(fileName);
1167
- }
1168
-
1169
- /**
1170
- * Pauses execution for a number of seconds.
1171
- *
1172
- * ```js
1173
- * I.wait(2); // wait 2 secs
1174
- * ```
1175
- *
1176
- * @param {number} sec number of second to wait.
1177
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1178
- *
1179
- */
1180
- async wait(sec) {
1181
- return new Promise(((done) => {
1182
- setTimeout(done, sec * 1000);
1183
- }));
1184
- }
1185
-
1186
- /**
1187
- * Executes sync script on a page.
1188
- * Pass arguments to function as additional parameters.
1189
- * Will return execution result to a test.
1190
- * In this case you should use async function and await to receive results.
1191
- *
1192
- * Example with jQuery DatePicker:
1193
- *
1194
- * ```js
1195
- * // change date of jQuery DatePicker
1196
- * I.executeScript(function() {
1197
- * // now we are inside browser context
1198
- * $('date').datetimepicker('setDate', new Date());
1199
- * });
1200
- * ```
1201
- * Can return values. Don't forget to use `await` to get them.
1202
- *
1203
- * ```js
1204
- * let date = await I.executeScript(function(el) {
1205
- * // only basic types can be returned
1206
- * return $(el).datetimepicker('getDate').toString();
1207
- * }, '#date'); // passing jquery selector
1208
- * ```
1209
- *
1210
- * @param {string|function} fn function to be executed in browser context.
1211
- * @param {...any} args to be passed to function.
1212
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1213
- *
1214
- *
1215
- * If a function returns a Promise It will wait for it resolution.
1216
- */
1217
- async executeScript(fn, ...args) {
1218
- const browserFn = createClientFunction(fn, args).with({ boundTestRun: this.t });
1219
- return browserFn();
1220
- }
1221
-
1222
- /**
1223
- * Retrieves all texts from an element located by CSS or XPath and returns it to test.
1224
- * Resumes test execution, so **should be used inside async with `await`** operator.
1225
- *
1226
- * ```js
1227
- * let pins = await I.grabTextFromAll('#pin li');
1228
- * ```
1229
- *
1230
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1231
- * @returns {Promise<string[]>} attribute value
1232
- *
1233
- */
1234
- async grabTextFromAll(locator) {
1235
- const sel = await findElements.call(this, this.context, locator);
1236
- const length = await sel.count;
1237
- const texts = [];
1238
- for (let i = 0; i < length; i++) {
1239
- texts.push(await sel.nth(i).innerText);
1240
- }
1241
-
1242
- return texts;
1243
- }
1244
-
1245
- /**
1246
- * Retrieves a text from an element located by CSS or XPath and returns it to test.
1247
- * Resumes test execution, so **should be used inside async with `await`** operator.
1248
- *
1249
- * ```js
1250
- * let pin = await I.grabTextFrom('#pin');
1251
- * ```
1252
- * If multiple elements found returns first element.
1253
- *
1254
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1255
- * @returns {Promise<string>} attribute value
1256
- *
1257
- */
1258
- async grabTextFrom(locator) {
1259
- const sel = await findElements.call(this, this.context, locator);
1260
- assertElementExists(sel, locator);
1261
- const texts = await this.grabTextFromAll(locator);
1262
- if (texts.length > 1) {
1263
- this.debugSection('GrabText', `Using first element out of ${texts.length}`);
1264
- }
1265
-
1266
- return texts[0];
1267
- }
1268
-
1269
- /**
1270
- * Retrieves an attribute from an element located by CSS or XPath and returns it to test.
1271
- * Resumes test execution, so **should be used inside async with `await`** operator.
1272
- * If more than one element is found - attribute of first element is returned.
1273
- *
1274
- * ```js
1275
- * let hint = await I.grabAttributeFrom('#tooltip', 'title');
1276
- * ```
1277
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1278
- * @param {string} attr attribute name.
1279
- * @returns {Promise<string>} attribute value
1280
- *
1281
- */
1282
- async grabAttributeFromAll(locator, attr) {
1283
- const sel = await findElements.call(this, this.context, locator);
1284
- const length = await sel.count;
1285
- const attrs = [];
1286
- for (let i = 0; i < length; i++) {
1287
- attrs.push(await (await sel.nth(i)).getAttribute(attr));
1288
- }
1289
-
1290
- return attrs;
1291
- }
1292
-
1293
- /**
1294
- * Retrieves an attribute from an element located by CSS or XPath and returns it to test.
1295
- * Resumes test execution, so **should be used inside async with `await`** operator.
1296
- * If more than one element is found - attribute of first element is returned.
1297
- *
1298
- * ```js
1299
- * let hint = await I.grabAttributeFrom('#tooltip', 'title');
1300
- * ```
1301
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1302
- * @param {string} attr attribute name.
1303
- * @returns {Promise<string>} attribute value
1304
- *
1305
- */
1306
- async grabAttributeFrom(locator, attr) {
1307
- const sel = await findElements.call(this, this.context, locator);
1308
- assertElementExists(sel, locator);
1309
- const attrs = await this.grabAttributeFromAll(locator, attr);
1310
- if (attrs.length > 1) {
1311
- this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`);
1312
- }
1313
-
1314
- return attrs[0];
1315
- }
1316
-
1317
- /**
1318
- * Retrieves an array of value from a form located by CSS or XPath and returns it to test.
1319
- * Resumes test execution, so **should be used inside async function with `await`** operator.
1320
- *
1321
- * ```js
1322
- * let inputs = await I.grabValueFromAll('//form/input');
1323
- * ```
1324
- * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
1325
- * @returns {Promise<string[]>} attribute value
1326
- *
1327
- */
1328
- async grabValueFromAll(locator) {
1329
- const sel = await findElements.call(this, this.context, locator);
1330
- const length = await sel.count;
1331
- const values = [];
1332
- for (let i = 0; i < length; i++) {
1333
- values.push(await (await sel.nth(i)).value);
1334
- }
1335
-
1336
- return values;
1337
- }
1338
-
1339
- /**
1340
- * Retrieves a value from a form element located by CSS or XPath and returns it to test.
1341
- * Resumes test execution, so **should be used inside async function with `await`** operator.
1342
- * If more than one element is found - value of first element is returned.
1343
- *
1344
- * ```js
1345
- * let email = await I.grabValueFrom('input[name=email]');
1346
- * ```
1347
- * @param {CodeceptJS.LocatorOrString} locator field located by label|name|CSS|XPath|strict locator.
1348
- * @returns {Promise<string>} attribute value
1349
- *
1350
- */
1351
- async grabValueFrom(locator) {
1352
- const sel = await findElements.call(this, this.context, locator);
1353
- assertElementExists(sel, locator);
1354
- const values = await this.grabValueFromAll(locator);
1355
- if (values.length > 1) {
1356
- this.debugSection('GrabValue', `Using first element out of ${values.length}`);
1357
- }
1358
-
1359
- return values[0];
1360
- }
1361
-
1362
- /**
1363
- * Retrieves page source and returns it to test.
1364
- * Resumes test execution, so **should be used inside async function with `await`** operator.
1365
- *
1366
- * ```js
1367
- * let pageSource = await I.grabSource();
1368
- * ```
1369
- *
1370
- * @returns {Promise<string>} source code
1371
- */
1372
- async grabSource() {
1373
- return ClientFunction(() => document.documentElement.innerHTML).with({ boundTestRun: this.t })();
1374
- }
1375
-
1376
- /**
1377
- * Get JS log from browser.
1378
- *
1379
- * ```js
1380
- * let logs = await I.grabBrowserLogs();
1381
- * console.log(JSON.stringify(logs))
1382
- * ```
1383
- */
1384
- async grabBrowserLogs() {
1385
- // TODO Must map?
1386
- return this.t.getBrowserConsoleMessages();
1387
- }
1388
-
1389
- /**
1390
- * Get current URL from browser.
1391
- * Resumes test execution, so should be used inside an async function.
1392
- *
1393
- * ```js
1394
- * let url = await I.grabCurrentUrl();
1395
- * console.log(`Current URL is [${url}]`);
1396
- * ```
1397
- *
1398
- * @returns {Promise<string>} current URL
1399
- */
1400
- async grabCurrentUrl() {
1401
- return ClientFunction(() => document.location.href).with({ boundTestRun: this.t })();
1402
- }
1403
-
1404
- /**
1405
- * Retrieves a page scroll position and returns it to test.
1406
- * Resumes test execution, so **should be used inside an async function with `await`** operator.
1407
- *
1408
- * ```js
1409
- * let { x, y } = await I.grabPageScrollPosition();
1410
- * ```
1411
- *
1412
- * @returns {Promise<PageScrollPosition>} scroll position
1413
- *
1414
- */
1415
- async grabPageScrollPosition() {
1416
- return ClientFunction(() => ({ x: window.pageXOffset, y: window.pageYOffset })).with({ boundTestRun: this.t })();
1417
- }
1418
-
1419
- /**
1420
- * Scroll page to the top.
1421
- *
1422
- * ```js
1423
- * I.scrollPageToTop();
1424
- * ```
1425
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1426
- *
1427
- */
1428
- scrollPageToTop() {
1429
- return ClientFunction(() => window.scrollTo(0, 0)).with({ boundTestRun: this.t })().catch(mapError);
1430
- }
1431
-
1432
- /**
1433
- * Scroll page to the bottom.
1434
- *
1435
- * ```js
1436
- * I.scrollPageToBottom();
1437
- * ```
1438
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1439
- *
1440
- */
1441
- scrollPageToBottom() {
1442
- return ClientFunction(() => {
1443
- const body = document.body;
1444
- const html = document.documentElement;
1445
- window.scrollTo(0, Math.max(
1446
- body.scrollHeight, body.offsetHeight,
1447
- html.clientHeight, html.scrollHeight, html.offsetHeight,
1448
- ));
1449
- }).with({ boundTestRun: this.t })().catch(mapError);
1450
- }
1451
-
1452
- /**
1453
- * Scrolls to element matched by locator.
1454
- * Extra shift can be set with offsetX and offsetY options.
1455
- *
1456
- * ```js
1457
- * I.scrollTo('footer');
1458
- * I.scrollTo('#submit', 5, 5);
1459
- * ```
1460
- *
1461
- * @param {CodeceptJS.LocatorOrString} locator located by CSS|XPath|strict locator.
1462
- * @param {number} [offsetX=0] (optional, `0` by default) X-axis offset.
1463
- * @param {number} [offsetY=0] (optional, `0` by default) Y-axis offset.
1464
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1465
- *
1466
- */
1467
- async scrollTo(locator, offsetX = 0, offsetY = 0) {
1468
- if (typeof locator === 'number' && typeof offsetX === 'number') {
1469
- offsetY = offsetX;
1470
- offsetX = locator;
1471
- locator = null;
1472
- }
1473
-
1474
- const scrollBy = ClientFunction((offset) => {
1475
- if (window && window.scrollBy && offset) {
1476
- window.scrollBy(offset.x, offset.y);
1477
- }
1478
- }).with({ boundTestRun: this.t });
1479
-
1480
- if (locator) {
1481
- const els = await this._locate(locator);
1482
- assertElementExists(els, locator, 'Element');
1483
- const el = await els.nth(0);
1484
- const x = (await el.offsetLeft) + offsetX;
1485
- const y = (await el.offsetTop) + offsetY;
1486
-
1487
- return scrollBy({ x, y }).catch(mapError);
1488
- }
1489
-
1490
- const x = offsetX;
1491
- const y = offsetY;
1492
- return scrollBy({ x, y }).catch(mapError);
1493
- }
1494
-
1495
- /**
1496
- * Switches frame or in case of null locator reverts to parent.
1497
- *
1498
- * ```js
1499
- * I.switchTo('iframe'); // switch to first iframe
1500
- * I.switchTo(); // switch back to main page
1501
- * ```
1502
- *
1503
- * @param {?CodeceptJS.LocatorOrString} [locator=null] (optional, `null` by default) element located by CSS|XPath|strict locator.
1504
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1505
- *
1506
- */
1507
- async switchTo(locator) {
1508
- if (Number.isInteger(locator)) {
1509
- throw new Error('Not supported switching to iframe by number');
1510
- }
1511
-
1512
- if (!locator) {
1513
- return this.t.switchToMainWindow();
1514
- }
1515
-
1516
- const el = await findElements.call(this, this.context, locator);
1517
- return this.t.switchToIframe(el);
1518
- }
1519
-
1520
- // TODO Add url assertions
1521
-
1522
- /**
1523
- * Sets cookie(s).
1524
- *
1525
- * Can be a single cookie object or an array of cookies:
1526
- *
1527
- * ```js
1528
- * I.setCookie({name: 'auth', value: true});
1529
- *
1530
- * // as array
1531
- * I.setCookie([
1532
- * {name: 'auth', value: true},
1533
- * {name: 'agree', value: true}
1534
- * ]);
1535
- * ```
1536
- *
1537
- * @param {Cookie|Array<Cookie>} cookie a cookie object or array of cookie objects.
1538
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1539
- *
1540
- */
1541
- async setCookie(cookie) {
1542
- if (Array.isArray(cookie)) {
1543
- throw new Error('cookie array is not supported');
1544
- }
1545
-
1546
- cookie.path = cookie.path || '/';
1547
- // cookie.expires = cookie.expires || (new Date()).toUTCString();
1548
-
1549
- const setCookie = ClientFunction(() => {
1550
- document.cookie = `${cookie.name}=${cookie.value};path=${cookie.path};expires=${cookie.expires};`;
1551
- }, { dependencies: { cookie } }).with({ boundTestRun: this.t });
1552
-
1553
- return setCookie();
1554
- }
1555
-
1556
- /**
1557
- * Checks that cookie with given name exists.
1558
- *
1559
- * ```js
1560
- * I.seeCookie('Auth');
1561
- * ```
1562
- *
1563
- * @param {string} name cookie name.
1564
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1565
- *
1566
- *
1567
- */
1568
- async seeCookie(name) {
1569
- const cookie = await this.grabCookie(name);
1570
- empty(`cookie ${name} to be set`).negate(cookie);
1571
- }
1572
-
1573
- /**
1574
- * Checks that cookie with given name does not exist.
1575
- *
1576
- * ```js
1577
- * I.dontSeeCookie('auth'); // no auth cookie
1578
- * ```
1579
- *
1580
- * @param {string} name cookie name.
1581
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1582
- *
1583
- */
1584
- async dontSeeCookie(name) {
1585
- const cookie = await this.grabCookie(name);
1586
- empty(`cookie ${name} not to be set`).assert(cookie);
1587
- }
1588
-
1589
- /**
1590
- * Gets a cookie object by name.
1591
- * If none provided gets all cookies.
1592
- * Resumes test execution, so **should be used inside async function with `await`** operator.
1593
- *
1594
- * ```js
1595
- * let cookie = await I.grabCookie('auth');
1596
- * assert(cookie.value, '123456');
1597
- * ```
1598
- *
1599
- * @param {?string} [name=null] cookie name.
1600
- * @returns {Promise<string>|Promise<string[]>} attribute value
1601
- *
1602
- *
1603
- * Returns cookie in JSON format. If name not passed returns all cookies for this domain.
1604
- */
1605
- async grabCookie(name) {
1606
- if (!name) {
1607
- const getCookie = ClientFunction(() => {
1608
- return document.cookie.split(';').map(c => c.split('='));
1609
- }).with({ boundTestRun: this.t });
1610
- const cookies = await getCookie();
1611
- return cookies.map(cookie => ({ name: cookie[0].trim(), value: cookie[1] }));
1612
- }
1613
- const getCookie = ClientFunction(() => {
1614
- // eslint-disable-next-line prefer-template
1615
- const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)');
1616
- return v ? v[2] : null;
1617
- }, { dependencies: { name } }).with({ boundTestRun: this.t });
1618
- const value = await getCookie();
1619
- if (value) return { name, value };
1620
- }
1621
-
1622
- /**
1623
- * Clears a cookie by name,
1624
- * if none provided clears all cookies.
1625
- *
1626
- * ```js
1627
- * I.clearCookie();
1628
- * I.clearCookie('test');
1629
- * ```
1630
- *
1631
- * @param {?string} [cookie=null] (optional, `null` by default) cookie name
1632
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1633
- *
1634
- */
1635
- async clearCookie(cookieName) {
1636
- const clearCookies = ClientFunction(() => {
1637
- const cookies = document.cookie.split(';');
1638
-
1639
- for (let i = 0; i < cookies.length; i++) {
1640
- const cookie = cookies[i];
1641
- const eqPos = cookie.indexOf('=');
1642
- const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
1643
- if (cookieName === undefined || name === cookieName) {
1644
- document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT`;
1645
- }
1646
- }
1647
- }, { dependencies: { cookieName } }).with({ boundTestRun: this.t });
1648
-
1649
- return clearCookies();
1650
- }
1651
-
1652
- /**
1653
- * Waiting for the part of the URL to match the expected. Useful for SPA to understand that page was changed.
1654
- *
1655
- * ```js
1656
- * I.waitInUrl('/info', 2);
1657
- * ```
1658
- *
1659
- * @param {string} urlPart value to check.
1660
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
1661
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1662
- *
1663
- */
1664
- async waitInUrl(urlPart, sec = null) {
1665
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1666
-
1667
- const clientFn = createClientFunction((urlPart) => {
1668
- const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)));
1669
- return currUrl.indexOf(urlPart) > -1;
1670
- }, [urlPart]).with({ boundTestRun: this.t });
1671
-
1672
- return waitForFunction(clientFn, waitTimeout).catch(async () => {
1673
- const currUrl = await this.grabCurrentUrl();
1674
- throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`);
1675
- });
1676
- }
1677
-
1678
- /**
1679
- * Waits for the entire URL to match the expected
1680
- *
1681
- * ```js
1682
- * I.waitUrlEquals('/info', 2);
1683
- * I.waitUrlEquals('http://127.0.0.1:8000/info');
1684
- * ```
1685
- *
1686
- * @param {string} urlPart value to check.
1687
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
1688
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1689
- *
1690
- */
1691
- async waitUrlEquals(urlPart, sec = null) {
1692
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1693
-
1694
- const baseUrl = this.options.url;
1695
- if (urlPart.indexOf('http') < 0) {
1696
- urlPart = baseUrl + urlPart;
1697
- }
1698
-
1699
- const clientFn = createClientFunction((urlPart) => {
1700
- const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)));
1701
- return currUrl === urlPart;
1702
- }, [urlPart]).with({ boundTestRun: this.t });
1703
-
1704
- return waitForFunction(clientFn, waitTimeout).catch(async () => {
1705
- const currUrl = await this.grabCurrentUrl();
1706
- throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`);
1707
- });
1708
- }
1709
-
1710
- /**
1711
- * Waits for a function to return true (waits for 1 sec by default).
1712
- * Running in browser context.
1713
- *
1714
- * ```js
1715
- * I.waitForFunction(fn[, [args[, timeout]])
1716
- * ```
1717
- *
1718
- * ```js
1719
- * I.waitForFunction(() => window.requests == 0);
1720
- * I.waitForFunction(() => window.requests == 0, 5); // waits for 5 sec
1721
- * I.waitForFunction((count) => window.requests == count, [3], 5) // pass args and wait for 5 sec
1722
- * ```
1723
- *
1724
- * @param {string|function} fn to be executed in browser context.
1725
- * @param {any[]|number} [argsOrSec] (optional, `1` by default) arguments for function or seconds.
1726
- * @param {number} [sec] (optional, `1` by default) time in seconds to wait
1727
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1728
- *
1729
- */
1730
- async waitForFunction(fn, argsOrSec = null, sec = null) {
1731
- let args = [];
1732
- if (argsOrSec) {
1733
- if (Array.isArray(argsOrSec)) {
1734
- args = argsOrSec;
1735
- } else if (typeof argsOrSec === 'number') {
1736
- sec = argsOrSec;
1737
- }
1738
- }
1739
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1740
-
1741
- const clientFn = createClientFunction(fn, args).with({ boundTestRun: this.t });
1742
-
1743
- return waitForFunction(clientFn, waitTimeout);
1744
- }
1745
-
1746
- /**
1747
- * Waits for a specified number of elements on the page.
1748
- *
1749
- * ```js
1750
- * I.waitNumberOfVisibleElements('a', 3);
1751
- * ```
1752
- *
1753
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1754
- * @param {number} num number of elements.
1755
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
1756
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1757
- *
1758
- */
1759
- async waitNumberOfVisibleElements(locator, num, sec) {
1760
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1761
-
1762
- return this.t
1763
- .expect(createSelector(locator).with({ boundTestRun: this.t }).filterVisible().count)
1764
- .eql(num, `The number of elements (${(new Locator(locator))}) is not ${num} after ${sec} sec`, { timeout: waitTimeout })
1765
- .catch(mapError);
1766
- }
1767
-
1768
- /**
1769
- * Waits for element to be present on page (by default waits for 1sec).
1770
- * Element can be located by CSS or XPath.
1771
- *
1772
- * ```js
1773
- * I.waitForElement('.btn.continue');
1774
- * I.waitForElement('.btn.continue', 5); // wait for 5 secs
1775
- * ```
1776
- *
1777
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1778
- * @param {number} [sec] (optional, `1` by default) time in seconds to wait
1779
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1780
- *
1781
- */
1782
- async waitForElement(locator, sec) {
1783
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1784
-
1785
- return this.t
1786
- .expect(createSelector(locator).with({ boundTestRun: this.t }).exists)
1787
- .ok({ timeout: waitTimeout });
1788
- }
1789
-
1790
- /**
1791
- * Waits for an element to hide (by default waits for 1sec).
1792
- * Element can be located by CSS or XPath.
1793
- *
1794
- * ```js
1795
- * I.waitToHide('#popup');
1796
- * ```
1797
- *
1798
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1799
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
1800
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1801
- *
1802
- */
1803
- async waitToHide(locator, sec) {
1804
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1805
-
1806
- return this.t
1807
- .expect(createSelector(locator).filterHidden().with({ boundTestRun: this.t }).exists)
1808
- .notOk({ timeout: waitTimeout });
1809
- }
1810
-
1811
- /**
1812
- * Waits for an element to be removed or become invisible on a page (by default waits for 1sec).
1813
- * Element can be located by CSS or XPath.
1814
- *
1815
- * ```js
1816
- * I.waitForInvisible('#popup');
1817
- * ```
1818
- *
1819
- * @param {CodeceptJS.LocatorOrString} locator element located by CSS|XPath|strict locator.
1820
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
1821
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1822
- *
1823
- */
1824
- async waitForInvisible(locator, sec) {
1825
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1826
-
1827
- return this.t
1828
- .expect(createSelector(locator).filterVisible().with({ boundTestRun: this.t }).exists)
1829
- .ok({ timeout: waitTimeout });
1830
- }
1831
-
1832
- /**
1833
- * Waits for a text to appear (by default waits for 1sec).
1834
- * Element can be located by CSS or XPath.
1835
- * Narrow down search results by providing context.
1836
- *
1837
- * ```js
1838
- * I.waitForText('Thank you, form has been submitted');
1839
- * I.waitForText('Thank you, form has been submitted', 5, '#modal');
1840
- * ```
1841
- *
1842
- * @param {string }text to wait for.
1843
- * @param {number} [sec=1] (optional, `1` by default) time in seconds to wait
1844
- * @param {CodeceptJS.LocatorOrString} [context] (optional) element located by CSS|XPath|strict locator.
1845
- * ⚠️ returns a _promise_ which is synchronized internally by recorder
1846
- *
1847
- *
1848
- */
1849
- async waitForText(text, sec = null, context = null) {
1850
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout;
1851
-
1852
- let els;
1853
- if (context) {
1854
- els = (await findElements.call(this, this.context, context));
1855
- await this.t
1856
- .expect(els.exists)
1857
- .ok(`Context element ${context} not found`, { timeout: waitTimeout });
1858
- } else {
1859
- els = (await findElements.call(this, this.context, '*'));
1860
- }
1861
-
1862
- return this.t
1863
- .expect(els.withText(text).filterVisible().exists)
1864
- .ok(`No element with text "${text}" found in ${context || 'body'}`, { timeout: waitTimeout })
1865
- .catch(mapError);
1866
- }
1867
- }
1868
-
1869
- async function waitForFunction(browserFn, waitTimeout) {
1870
- const pause = () => new Promise((done => setTimeout(done, 50)));
1871
-
1872
- const start = Date.now();
1873
- // eslint-disable-next-line no-constant-condition
1874
- while (true) {
1875
- let result;
1876
- try {
1877
- result = await browserFn();
1878
- // eslint-disable-next-line no-empty
1879
- } catch (err) {
1880
- throw new Error(`Error running function ${err.toString()}`);
1881
- }
1882
-
1883
- if (result) return result;
1884
-
1885
- const duration = (Date.now() - start);
1886
- if (duration > waitTimeout) {
1887
- throw new Error('waitForFunction timed out');
1888
- }
1889
- await pause(); // make polling
1890
- }
1891
- }
1892
-
1893
- const createSelector = (locator) => {
1894
- locator = new Locator(locator, 'css');
1895
- if (locator.isXPath()) return elementByXPath(locator.value);
1896
- return Selector(locator.simplify());
1897
- };
1898
-
1899
- const elementByXPath = (xpath) => {
1900
- assert(xpath, 'xpath is required');
1901
-
1902
- return Selector(() => {
1903
- const iterator = document.evaluate(xpath, document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
1904
- const items = [];
1905
-
1906
- let item = iterator.iterateNext();
1907
-
1908
- while (item) {
1909
- items.push(item);
1910
- item = iterator.iterateNext();
1911
- }
1912
-
1913
- return items;
1914
- }, { dependencies: { xpath } });
1915
- };
1916
-
1917
- const assertElementExists = async (res, locator, prefix, suffix) => {
1918
- if (!res || !(await res.count) || !(await res.nth(0).tagName)) {
1919
- throw new ElementNotFound(locator, prefix, suffix);
1920
- }
1921
- };
1922
-
1923
- async function findElements(matcher, locator) {
1924
- if (locator && locator.react) throw new Error('react locators are not yet supported');
1925
-
1926
- locator = new Locator(locator, 'css');
1927
-
1928
- if (!locator.isXPath()) {
1929
- return matcher
1930
- ? matcher.find(locator.simplify())
1931
- : Selector(locator.simplify()).with({ timeout: 0, boundTestRun: this.t });
1932
- }
1933
-
1934
- if (!matcher) return elementByXPath(locator.value).with({ timeout: 0, boundTestRun: this.t });
1935
-
1936
- return matcher.find((node, idx, originNode) => {
1937
- const found = document.evaluate(xpath, originNode, null, 5, null);
1938
- let current = null;
1939
- while (current = found.iterateNext()) {
1940
- if (current === node) return true;
1941
- }
1942
- return false;
1943
- }, { xpath: locator.value });
1944
- }
1945
-
1946
- async function proceedClick(locator, context = null) {
1947
- let matcher;
1948
-
1949
- if (context) {
1950
- const els = await this._locate(context);
1951
- await assertElementExists(els, context);
1952
- matcher = await els.nth(0);
1953
- }
1954
-
1955
- const els = await findClickable.call(this, matcher, locator);
1956
- if (context) {
1957
- await assertElementExists(els, locator, 'Clickable element', `was not found inside element ${new Locator(context).toString()}`);
1958
- } else {
1959
- await assertElementExists(els, locator, 'Clickable element');
1960
- }
1961
-
1962
- const firstElement = await els.filterVisible().nth(0);
1963
-
1964
- return this.t
1965
- .click(firstElement)
1966
- .catch(mapError);
1967
- }
1968
-
1969
- async function findClickable(matcher, locator) {
1970
- if (locator && locator.react) throw new Error('react locators are not yet supported');
1971
-
1972
- locator = new Locator(locator);
1973
- if (!locator.isFuzzy()) return (await findElements.call(this, matcher, locator)).filterVisible();
1974
-
1975
- let els;
1976
-
1977
- // try to use native TestCafe locator
1978
- els = matcher ? matcher.find('a,button') : createSelector('a,button');
1979
- els = await els.withExactText(locator.value).with({ timeout: 0, boundTestRun: this.t });
1980
- if (await els.count) return els;
1981
-
1982
- const literal = xpathLocator.literal(locator.value);
1983
-
1984
- els = (await findElements.call(this, matcher, Locator.clickable.narrow(literal))).filterVisible();
1985
- if (await els.count) return els;
1986
-
1987
- els = (await findElements.call(this, matcher, Locator.clickable.wide(literal))).filterVisible();
1988
- if (await els.count) return els;
1989
-
1990
- els = (await findElements.call(this, matcher, Locator.clickable.self(literal))).filterVisible();
1991
- if (await els.count) return els;
1992
-
1993
- return findElements.call(this, matcher, locator.value); // by css or xpath
1994
- }
1995
-
1996
- async function proceedIsChecked(assertType, option) {
1997
- const els = await findCheckable.call(this, option);
1998
- assertElementExists(els, option, 'Checkable');
1999
-
2000
- const selected = await els.checked;
2001
-
2002
- return truth(`checkable ${option}`, 'to be checked')[assertType](selected);
2003
- }
2004
-
2005
- async function findCheckable(locator, context) {
2006
- assert(locator, 'locator is required');
2007
- assert(this.t, 'this.t is required');
2008
-
2009
- let contextEl = await this.context;
2010
- if (typeof context === 'string') {
2011
- contextEl = (await findElements.call(this, contextEl, (new Locator(context, 'css')).simplify())).filterVisible();
2012
- contextEl = await contextEl.nth(0);
2013
- }
2014
-
2015
- const matchedLocator = new Locator(locator);
2016
- if (!matchedLocator.isFuzzy()) {
2017
- return (await findElements.call(this, contextEl, matchedLocator.simplify())).filterVisible();
2018
- }
2019
-
2020
- const literal = xpathLocator.literal(locator);
2021
- let els = (await findElements.call(this, contextEl, Locator.checkable.byText(literal))).filterVisible();
2022
- if (await els.count) {
2023
- return els;
2024
- }
2025
-
2026
- els = (await findElements.call(this, contextEl, Locator.checkable.byName(literal))).filterVisible();
2027
- if (await els.count) {
2028
- return els;
2029
- }
2030
-
2031
- return (await findElements.call(this, contextEl, locator)).filterVisible();
2032
- }
2033
-
2034
- async function findFields(locator) {
2035
- const matchedLocator = new Locator(locator);
2036
- if (!matchedLocator.isFuzzy()) {
2037
- return this._locate(matchedLocator);
2038
- }
2039
- const literal = xpathLocator.literal(locator);
2040
-
2041
- let els = await this._locate({ xpath: Locator.field.labelEquals(literal) });
2042
- if (await els.count) {
2043
- return els;
2044
- }
2045
-
2046
- els = await this._locate({ xpath: Locator.field.labelContains(literal) });
2047
- if (await els.count) {
2048
- return els;
2049
- }
2050
- els = await this._locate({ xpath: Locator.field.byName(literal) });
2051
- if (await els.count) {
2052
- return els;
2053
- }
2054
- return this._locate({ css: locator });
2055
- }
2056
-
2057
- module.exports = TestCafe;