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