codeceptjs 4.0.0-beta.4 → 4.0.0-beta.6.esm-aria

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 (188) hide show
  1. package/README.md +89 -119
  2. package/bin/codecept.js +53 -54
  3. package/docs/webapi/clearCookie.mustache +1 -1
  4. package/lib/actor.js +70 -102
  5. package/lib/ai.js +131 -121
  6. package/lib/assert/empty.js +11 -12
  7. package/lib/assert/equal.js +16 -21
  8. package/lib/assert/error.js +2 -2
  9. package/lib/assert/include.js +11 -15
  10. package/lib/assert/throws.js +3 -5
  11. package/lib/assert/truth.js +10 -7
  12. package/lib/assert.js +18 -18
  13. package/lib/codecept.js +112 -101
  14. package/lib/colorUtils.js +48 -50
  15. package/lib/command/check.js +206 -0
  16. package/lib/command/configMigrate.js +13 -14
  17. package/lib/command/definitions.js +24 -36
  18. package/lib/command/dryRun.js +16 -16
  19. package/lib/command/generate.js +38 -39
  20. package/lib/command/gherkin/init.js +36 -38
  21. package/lib/command/gherkin/snippets.js +76 -74
  22. package/lib/command/gherkin/steps.js +21 -18
  23. package/lib/command/info.js +49 -15
  24. package/lib/command/init.js +41 -37
  25. package/lib/command/interactive.js +22 -13
  26. package/lib/command/list.js +11 -10
  27. package/lib/command/run-multiple/chunk.js +50 -47
  28. package/lib/command/run-multiple/collection.js +5 -5
  29. package/lib/command/run-multiple/run.js +3 -3
  30. package/lib/command/run-multiple.js +27 -47
  31. package/lib/command/run-rerun.js +6 -7
  32. package/lib/command/run-workers.js +15 -66
  33. package/lib/command/run.js +8 -8
  34. package/lib/command/utils.js +22 -21
  35. package/lib/command/workers/runTests.js +131 -241
  36. package/lib/config.js +111 -49
  37. package/lib/container.js +589 -244
  38. package/lib/data/context.js +16 -18
  39. package/lib/data/dataScenarioConfig.js +9 -9
  40. package/lib/data/dataTableArgument.js +7 -7
  41. package/lib/data/table.js +6 -12
  42. package/lib/effects.js +307 -0
  43. package/lib/els.js +160 -0
  44. package/lib/event.js +24 -19
  45. package/lib/globals.js +141 -0
  46. package/lib/heal.js +89 -81
  47. package/lib/helper/AI.js +3 -2
  48. package/lib/helper/ApiDataFactory.js +19 -19
  49. package/lib/helper/Appium.js +47 -51
  50. package/lib/helper/FileSystem.js +35 -15
  51. package/lib/helper/GraphQL.js +1 -1
  52. package/lib/helper/GraphQLDataFactory.js +4 -4
  53. package/lib/helper/JSONResponse.js +72 -45
  54. package/lib/helper/Mochawesome.js +14 -11
  55. package/lib/helper/Playwright.js +832 -434
  56. package/lib/helper/Puppeteer.js +393 -292
  57. package/lib/helper/REST.js +32 -27
  58. package/lib/helper/WebDriver.js +320 -219
  59. package/lib/helper/errors/ConnectionRefused.js +6 -6
  60. package/lib/helper/errors/ElementAssertion.js +11 -16
  61. package/lib/helper/errors/ElementNotFound.js +5 -9
  62. package/lib/helper/errors/RemoteBrowserConnectionRefused.js +5 -5
  63. package/lib/helper/extras/Console.js +11 -11
  64. package/lib/helper/extras/PlaywrightLocator.js +110 -0
  65. package/lib/helper/extras/PlaywrightPropEngine.js +18 -18
  66. package/lib/helper/extras/PlaywrightRestartOpts.js +23 -23
  67. package/lib/helper/extras/Popup.js +22 -22
  68. package/lib/helper/extras/React.js +29 -30
  69. package/lib/helper/network/actions.js +33 -48
  70. package/lib/helper/network/utils.js +76 -83
  71. package/lib/helper/scripts/blurElement.js +6 -6
  72. package/lib/helper/scripts/focusElement.js +6 -6
  73. package/lib/helper/scripts/highlightElement.js +9 -9
  74. package/lib/helper/scripts/isElementClickable.js +34 -34
  75. package/lib/helper.js +2 -1
  76. package/lib/history.js +23 -20
  77. package/lib/hooks.js +10 -10
  78. package/lib/html.js +90 -100
  79. package/lib/index.js +48 -21
  80. package/lib/listener/config.js +8 -9
  81. package/lib/listener/emptyRun.js +54 -0
  82. package/lib/listener/exit.js +10 -12
  83. package/lib/listener/{retry.js → globalRetry.js} +10 -10
  84. package/lib/listener/globalTimeout.js +166 -0
  85. package/lib/listener/helpers.js +43 -24
  86. package/lib/listener/mocha.js +4 -5
  87. package/lib/listener/result.js +11 -0
  88. package/lib/listener/steps.js +26 -23
  89. package/lib/listener/store.js +20 -0
  90. package/lib/locator.js +213 -192
  91. package/lib/mocha/asyncWrapper.js +264 -0
  92. package/lib/mocha/bdd.js +167 -0
  93. package/lib/mocha/cli.js +341 -0
  94. package/lib/mocha/factory.js +160 -0
  95. package/lib/{interfaces → mocha}/featureConfig.js +33 -13
  96. package/lib/{interfaces → mocha}/gherkin.js +75 -45
  97. package/lib/mocha/hooks.js +121 -0
  98. package/lib/mocha/index.js +21 -0
  99. package/lib/mocha/inject.js +46 -0
  100. package/lib/{interfaces → mocha}/scenarioConfig.js +32 -8
  101. package/lib/mocha/suite.js +89 -0
  102. package/lib/mocha/test.js +178 -0
  103. package/lib/mocha/types.d.ts +42 -0
  104. package/lib/mocha/ui.js +229 -0
  105. package/lib/output.js +86 -64
  106. package/lib/parser.js +44 -44
  107. package/lib/pause.js +160 -139
  108. package/lib/plugin/analyze.js +403 -0
  109. package/lib/plugin/{autoLogin.js → auth.js} +137 -43
  110. package/lib/plugin/autoDelay.js +19 -15
  111. package/lib/plugin/coverage.js +22 -27
  112. package/lib/plugin/customLocator.js +5 -5
  113. package/lib/plugin/customReporter.js +53 -0
  114. package/lib/plugin/heal.js +49 -17
  115. package/lib/plugin/pageInfo.js +140 -0
  116. package/lib/plugin/pauseOnFail.js +4 -3
  117. package/lib/plugin/retryFailedStep.js +60 -19
  118. package/lib/plugin/screenshotOnFail.js +80 -83
  119. package/lib/plugin/stepByStepReport.js +70 -31
  120. package/lib/plugin/stepTimeout.js +7 -13
  121. package/lib/plugin/subtitles.js +10 -9
  122. package/lib/recorder.js +167 -126
  123. package/lib/rerun.js +94 -50
  124. package/lib/result.js +161 -0
  125. package/lib/secret.js +18 -17
  126. package/lib/session.js +95 -89
  127. package/lib/step/base.js +239 -0
  128. package/lib/step/comment.js +10 -0
  129. package/lib/step/config.js +50 -0
  130. package/lib/step/func.js +46 -0
  131. package/lib/step/helper.js +50 -0
  132. package/lib/step/meta.js +99 -0
  133. package/lib/step/record.js +74 -0
  134. package/lib/step/retry.js +11 -0
  135. package/lib/step/section.js +55 -0
  136. package/lib/step.js +18 -332
  137. package/lib/steps.js +54 -0
  138. package/lib/store.js +37 -5
  139. package/lib/template/heal.js +2 -11
  140. package/lib/timeout.js +60 -0
  141. package/lib/transform.js +8 -8
  142. package/lib/translation.js +32 -18
  143. package/lib/utils.js +354 -250
  144. package/lib/workerStorage.js +16 -16
  145. package/lib/workers.js +366 -282
  146. package/package.json +107 -95
  147. package/translations/de-DE.js +5 -4
  148. package/translations/fr-FR.js +5 -4
  149. package/translations/index.js +23 -9
  150. package/translations/it-IT.js +5 -4
  151. package/translations/ja-JP.js +5 -4
  152. package/translations/nl-NL.js +76 -0
  153. package/translations/pl-PL.js +5 -4
  154. package/translations/pt-BR.js +5 -4
  155. package/translations/ru-RU.js +5 -4
  156. package/translations/utils.js +18 -0
  157. package/translations/zh-CN.js +5 -4
  158. package/translations/zh-TW.js +5 -4
  159. package/typings/index.d.ts +177 -186
  160. package/typings/promiseBasedTypes.d.ts +3573 -5941
  161. package/typings/types.d.ts +4042 -6370
  162. package/lib/cli.js +0 -256
  163. package/lib/helper/ExpectHelper.js +0 -391
  164. package/lib/helper/Nightmare.js +0 -1504
  165. package/lib/helper/Protractor.js +0 -1863
  166. package/lib/helper/SoftExpectHelper.js +0 -381
  167. package/lib/helper/TestCafe.js +0 -1414
  168. package/lib/helper/clientscripts/nightmare.js +0 -213
  169. package/lib/helper/extras/PlaywrightReactVueLocator.js +0 -43
  170. package/lib/helper/testcafe/testControllerHolder.js +0 -42
  171. package/lib/helper/testcafe/testcafe-utils.js +0 -62
  172. package/lib/interfaces/bdd.js +0 -81
  173. package/lib/listener/artifacts.js +0 -19
  174. package/lib/listener/timeout.js +0 -109
  175. package/lib/mochaFactory.js +0 -113
  176. package/lib/plugin/allure.js +0 -15
  177. package/lib/plugin/commentStep.js +0 -136
  178. package/lib/plugin/debugErrors.js +0 -67
  179. package/lib/plugin/eachElement.js +0 -127
  180. package/lib/plugin/fakerTransform.js +0 -49
  181. package/lib/plugin/retryTo.js +0 -127
  182. package/lib/plugin/selenoid.js +0 -384
  183. package/lib/plugin/standardActingHelpers.js +0 -3
  184. package/lib/plugin/tryTo.js +0 -115
  185. package/lib/plugin/wdio.js +0 -249
  186. package/lib/scenario.js +0 -224
  187. package/lib/ui.js +0 -236
  188. package/lib/within.js +0 -70
@@ -1,1414 +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 { mapError, createTestFile, createClientFunction } = require('./testcafe/testcafe-utils')
13
-
14
- const stringIncludes = require('../assert/include').includes
15
- const { urlEquals } = require('../assert/equal')
16
- const { empty } = require('../assert/empty')
17
- const { truth } = require('../assert/truth')
18
- const { xpathLocator, normalizeSpacesInString } = require('../utils')
19
- const Locator = require('../locator')
20
-
21
- /**
22
- * Client Functions
23
- */
24
- const getPageUrl = (t) => ClientFunction(() => document.location.href).with({ boundTestRun: t })
25
- const getHtmlSource = (t) =>
26
- ClientFunction(() => document.getElementsByTagName('html')[0].innerHTML).with({ boundTestRun: t })
27
-
28
- /**
29
- * Uses [TestCafe](https://github.com/DevExpress/testcafe) library to run cross-browser tests.
30
- * The browser version you want to use in tests must be installed on your system.
31
- *
32
- * Requires `testcafe` package to be installed.
33
- *
34
- * ```
35
- * npm i testcafe --save-dev
36
- * ```
37
- *
38
- * ## Configuration
39
- *
40
- * This helper should be configured in codecept.conf.ts or codecept.conf.js
41
- *
42
- * * `url`: base url of website to be tested
43
- * * `show`: (optional, default: false) - show browser window.
44
- * * `windowSize`: (optional) - set browser window width and height
45
- * * `getPageTimeout` (optional, default: '30000') config option to set maximum navigation time in milliseconds.
46
- * * `waitForTimeout`: (optional) default wait* timeout in ms. Default: 5000.
47
- * * `browser`: (optional, default: chrome) - See https://devexpress.github.io/testcafe/documentation/using-testcafe/common-concepts/browsers/browser-support.html
48
- *
49
- *
50
- * #### Example #1: Show chrome browser window
51
- *
52
- * ```js
53
- * {
54
- * helpers: {
55
- * TestCafe : {
56
- * url: "http://localhost",
57
- * waitForTimeout: 15000,
58
- * show: true,
59
- * browser: "chrome"
60
- * }
61
- * }
62
- * }
63
- * ```
64
- *
65
- * To use remote device you can provide 'remote' as browser parameter this will display a link with QR Code
66
- * See https://devexpress.github.io/testcafe/documentation/recipes/test-on-remote-computers-and-mobile-devices.html
67
- * #### Example #2: Remote browser connection
68
- *
69
- * ```js
70
- * {
71
- * helpers: {
72
- * TestCafe : {
73
- * url: "http://localhost",
74
- * waitForTimeout: 15000,
75
- * browser: "remote"
76
- * }
77
- * }
78
- * }
79
- * ```
80
- *
81
- * ## Access From Helpers
82
- *
83
- * Call Testcafe methods directly using the testcafe controller.
84
- *
85
- * ```js
86
- * const testcafeTestController = this.helpers['TestCafe'].t;
87
- * const comboBox = Selector('.combo-box');
88
- * await testcafeTestController
89
- * .hover(comboBox) // hover over combo box
90
- * .click('#i-prefer-both') // click some other element
91
- * ```
92
- *
93
- * ## Methods
94
- */
95
- class TestCafe extends Helper {
96
- constructor(config) {
97
- super(config)
98
-
99
- this.testcafe = undefined // testcafe instance
100
- this.t = undefined // testcafe test controller
101
- this.dummyTestcafeFile // generated testcafe test file
102
-
103
- // context is used for within() function.
104
- // It requires to have _withinBeginand _withinEnd implemented.
105
- // Inside _withinBegin we should define that all next element calls should be started from a specific element (this.context).
106
- this.context = undefined // TODO Not sure if this applies to testcafe
107
-
108
- this.options = {
109
- url: 'http://localhost',
110
- show: false,
111
- browser: 'chrome',
112
- restart: true, // TODO Test if restart false works
113
- manualStart: false,
114
- keepBrowserState: false,
115
- waitForTimeout: 5000,
116
- getPageTimeout: 30000,
117
- fullPageScreenshots: false,
118
- disableScreenshots: false,
119
- windowSize: undefined,
120
- ...config,
121
- }
122
- }
123
-
124
- // TOOD Do a requirements check
125
- static _checkRequirements() {
126
- try {
127
- require('testcafe')
128
- } catch (e) {
129
- return ['testcafe@^1.1.0']
130
- }
131
- }
132
-
133
- static _config() {
134
- return [
135
- { name: 'url', message: 'Base url of site to be tested', default: 'http://localhost' },
136
- { name: 'browser', message: 'Browser to be used', default: 'chrome' },
137
- {
138
- name: 'show',
139
- message: 'Show browser window',
140
- default: true,
141
- type: 'confirm',
142
- },
143
- ]
144
- }
145
-
146
- async _configureAndStartBrowser() {
147
- this.dummyTestcafeFile = createTestFile(global.output_dir) // create a dummy test file to get hold of the test controller
148
-
149
- this.iteration += 2 // Use different ports for each test run
150
- // @ts-ignore
151
- this.testcafe = await createTestCafe('', null, null)
152
-
153
- this.debugSection('_before', 'Starting testcafe browser...')
154
-
155
- this.isRunning = true
156
-
157
- // TODO Do we have to cleanup the runner?
158
- const runner = this.testcafe.createRunner()
159
-
160
- this.options.browser !== 'remote' ? this._startBrowser(runner) : this._startRemoteBrowser(runner)
161
-
162
- this.t = await testControllerHolder.get()
163
- assert(this.t, 'Expected to have the testcafe test controller')
164
-
165
- if (this.options.windowSize && this.options.windowSize.indexOf('x') > 0) {
166
- const dimensions = this.options.windowSize.split('x')
167
- await this.t.resizeWindow(parseInt(dimensions[0], 10), parseInt(dimensions[1], 10))
168
- }
169
- }
170
-
171
- async _startBrowser(runner) {
172
- runner
173
- .src(this.dummyTestcafeFile)
174
- .screenshots(global.output_dir, !this.options.disableScreenshots)
175
- // .video(global.output_dir) // TODO Make this configurable
176
- .browsers(this.options.show ? this.options.browser : `${this.options.browser}:headless`)
177
- .reporter('minimal')
178
- .run({
179
- skipJsErrors: true,
180
- skipUncaughtErrors: true,
181
- quarantineMode: false,
182
- // debugMode: true,
183
- // debugOnFail: true,
184
- // developmentMode: true,
185
- pageLoadTimeout: this.options.getPageTimeout,
186
- selectorTimeout: this.options.waitForTimeout,
187
- assertionTimeout: this.options.waitForTimeout,
188
- takeScreenshotsOnFails: true,
189
- })
190
- .catch((err) => {
191
- this.debugSection('_before', `Error ${err.toString()}`)
192
- this.isRunning = false
193
- this.testcafe.close()
194
- })
195
- }
196
-
197
- async _startRemoteBrowser(runner) {
198
- const remoteConnection = await this.testcafe.createBrowserConnection()
199
- console.log('Connect your device to the following URL or scan QR Code: ', remoteConnection.url)
200
- qrcode.generate(remoteConnection.url)
201
- remoteConnection.once('ready', () => {
202
- runner
203
- .src(this.dummyTestcafeFile)
204
- .browsers(remoteConnection)
205
- .reporter('minimal')
206
- .run({
207
- selectorTimeout: this.options.waitForTimeout,
208
- skipJsErrors: true,
209
- skipUncaughtErrors: true,
210
- })
211
- .catch((err) => {
212
- this.debugSection('_before', `Error ${err.toString()}`)
213
- this.isRunning = false
214
- this.testcafe.close()
215
- })
216
- })
217
- }
218
-
219
- async _stopBrowser() {
220
- this.debugSection('_after', 'Stopping testcafe browser...')
221
-
222
- testControllerHolder.free()
223
- if (this.testcafe) {
224
- this.testcafe.close()
225
- }
226
-
227
- fs.unlinkSync(this.dummyTestcafeFile) // remove the dummy test
228
- this.t = undefined
229
-
230
- this.isRunning = false
231
- }
232
-
233
- _init() {}
234
-
235
- async _beforeSuite() {
236
- if (!this.options.restart && !this.options.manualStart && !this.isRunning) {
237
- this.debugSection('Session', 'Starting singleton browser session')
238
- return this._configureAndStartBrowser()
239
- }
240
- }
241
-
242
- async _before() {
243
- if (this.options.restart && !this.options.manualStart) return this._configureAndStartBrowser()
244
- if (!this.isRunning && !this.options.manualStart) return this._configureAndStartBrowser()
245
- this.context = null
246
- }
247
-
248
- async _after() {
249
- if (!this.isRunning) return
250
-
251
- if (this.options.restart) {
252
- this.isRunning = false
253
- return this._stopBrowser()
254
- }
255
-
256
- if (this.options.keepBrowserState) return
257
-
258
- if (!this.options.keepCookies) {
259
- this.debugSection('Session', 'cleaning cookies and localStorage')
260
- await this.clearCookie()
261
-
262
- // TODO IMHO that should only happen when
263
- await this.executeScript(() => localStorage.clear()).catch((err) => {
264
- if (!(err.message.indexOf("Storage is disabled inside 'data:' URLs.") > -1)) throw err
265
- })
266
- }
267
- }
268
-
269
- _afterSuite() {}
270
-
271
- async _finishTest() {
272
- if (!this.options.restart && this.isRunning) return this._stopBrowser()
273
- }
274
-
275
- /**
276
- * Use [TestCafe](https://devexpress.github.io/testcafe/documentation/test-api/) API inside a test.
277
- *
278
- * First argument is a description of an action.
279
- * Second argument is async function that gets this helper as parameter.
280
- *
281
- * { [`t`](https://devexpress.github.io/testcafe/documentation/test-api/test-code-structure.html#test-controller)) } object from TestCafe API is available.
282
- *
283
- * ```js
284
- * I.useTestCafeTo('handle browser dialog', async ({ t }) {
285
- * await t.setNativeDialogHandler(() => true);
286
- * });
287
- * ```
288
- *
289
- *
290
- *
291
- * @param {string} description used to show in logs.
292
- * @param {function} fn async functuion that executed with TestCafe helper as argument
293
- */
294
- useTestCafeTo(description, fn) {
295
- return this._useTo(...arguments)
296
- }
297
-
298
- /**
299
- * Get elements by different locator types, including strict locator
300
- * Should be used in custom helpers:
301
- *
302
- * ```js
303
- * const elements = await this.helpers['TestCafe']._locate('.item');
304
- * ```
305
- *
306
- */
307
- async _locate(locator) {
308
- return findElements.call(this, this.context, locator).catch(mapError)
309
- }
310
-
311
- async _withinBegin(locator) {
312
- const els = await this._locate(locator)
313
- assertElementExists(els, locator)
314
- this.context = await els.nth(0)
315
- }
316
-
317
- async _withinEnd() {
318
- this.context = null
319
- }
320
-
321
- /**
322
- * {{> amOnPage }}
323
- */
324
- async amOnPage(url) {
325
- if (!/^\w+\:\/\//.test(url)) {
326
- url = this.options.url + url
327
- }
328
-
329
- return this.t.navigateTo(url).catch(mapError)
330
- }
331
-
332
- /**
333
- * {{> resizeWindow }}
334
- */
335
- async resizeWindow(width, height) {
336
- if (width === 'maximize') {
337
- return this.t.maximizeWindow().catch(mapError)
338
- }
339
-
340
- return this.t.resizeWindow(width, height).catch(mapError)
341
- }
342
-
343
- /**
344
- * {{> focus }}
345
- *
346
- */
347
- async focus(locator) {
348
- const els = await this._locate(locator)
349
- await assertElementExists(els, locator, 'Element to focus')
350
- const element = await els.nth(0)
351
-
352
- const focusElement = ClientFunction(() => element().focus(), {
353
- boundTestRun: this.t,
354
- dependencies: { element },
355
- })
356
-
357
- return focusElement()
358
- }
359
-
360
- /**
361
- * {{> blur }}
362
- *
363
- */
364
- async blur(locator) {
365
- const els = await this._locate(locator)
366
- await assertElementExists(els, locator, 'Element to blur')
367
- const element = await els.nth(0)
368
-
369
- const blurElement = ClientFunction(() => element().blur(), { boundTestRun: this.t, dependencies: { element } })
370
-
371
- return blurElement()
372
- }
373
-
374
- /**
375
- * {{> click }}
376
- *
377
- */
378
- async click(locator, context = null) {
379
- return proceedClick.call(this, locator, context)
380
- }
381
-
382
- /**
383
- * {{> refreshPage }}
384
- */
385
- async refreshPage() {
386
- // eslint-disable-next-line no-restricted-globals
387
- return this.t.eval(() => location.reload(true), { boundTestRun: this.t }).catch(mapError)
388
- }
389
-
390
- /**
391
- * {{> waitForVisible }}
392
- *
393
- */
394
- async waitForVisible(locator, sec) {
395
- const timeout = sec ? sec * 1000 : undefined
396
-
397
- return (await findElements.call(this, this.context, locator))
398
- .with({ visibilityCheck: true, timeout })()
399
- .catch(mapError)
400
- }
401
-
402
- /**
403
- * {{> fillField }}
404
- */
405
- async fillField(field, value) {
406
- const els = await findFields.call(this, field)
407
- assertElementExists(els, field, 'Field')
408
- const el = await els.nth(0)
409
- return this.t.typeText(el, value.toString(), { replace: true }).catch(mapError)
410
- }
411
-
412
- /**
413
- * {{> clearField }}
414
- */
415
- async clearField(field) {
416
- const els = await findFields.call(this, field)
417
- assertElementExists(els, field, 'Field')
418
- const el = await els.nth(0)
419
-
420
- const res = await this.t.selectText(el).pressKey('delete')
421
- return res
422
- }
423
-
424
- /**
425
- * {{> appendField }}
426
- *
427
- */
428
- async appendField(field, value) {
429
- const els = await findFields.call(this, field)
430
- assertElementExists(els, field, 'Field')
431
- const el = await els.nth(0)
432
-
433
- return this.t.typeText(el, value.toString(), { replace: false }).catch(mapError)
434
- }
435
-
436
- /**
437
- * {{> attachFile }}
438
- *
439
- */
440
- async attachFile(field, pathToFile) {
441
- const els = await findFields.call(this, field)
442
- assertElementExists(els, field, 'Field')
443
- const el = await els.nth(0)
444
- const file = path.join(global.codecept_dir, pathToFile)
445
-
446
- return this.t.setFilesToUpload(el, [file]).catch(mapError)
447
- }
448
-
449
- /**
450
- * {{> pressKey }}
451
- *
452
- * {{ keys }}
453
- */
454
- async pressKey(key) {
455
- assert(key, 'Expected a sequence of keys or key combinations')
456
-
457
- return this.t
458
- .pressKey(key.toLowerCase()) // testcafe keys are lowercase
459
- .catch(mapError)
460
- }
461
-
462
- /**
463
- * {{> moveCursorTo }}
464
- *
465
- */
466
- async moveCursorTo(locator, offsetX = 0, offsetY = 0) {
467
- const els = (await findElements.call(this, this.context, locator)).filterVisible()
468
- await assertElementExists(els, locator)
469
-
470
- return this.t.hover(els.nth(0), { offsetX, offsetY }).catch(mapError)
471
- }
472
-
473
- /**
474
- * {{> doubleClick }}
475
- *
476
- */
477
- async doubleClick(locator, context = null) {
478
- let matcher
479
- if (context) {
480
- const els = await this._locate(context)
481
- await assertElementExists(els, context)
482
- matcher = await els.nth(0)
483
- }
484
-
485
- const els = (await findClickable.call(this, matcher, locator)).filterVisible()
486
- return this.t.doubleClick(els.nth(0)).catch(mapError)
487
- }
488
-
489
- /**
490
- * {{> rightClick }}
491
- *
492
- */
493
- async rightClick(locator, context = null) {
494
- let matcher
495
- if (context) {
496
- const els = await this._locate(context)
497
- await assertElementExists(els, context)
498
- matcher = await els.nth(0)
499
- }
500
- const els = (await findClickable.call(this, matcher, locator)).filterVisible()
501
- assertElementExists(els, locator)
502
- return this.t.rightClick(els.nth(0)).catch(mapError)
503
- }
504
-
505
- /**
506
- * {{> checkOption }}
507
- */
508
- async checkOption(field, context = null) {
509
- const el = await findCheckable.call(this, field, context)
510
-
511
- return this.t.click(el).catch(mapError)
512
- }
513
-
514
- /**
515
- * {{> uncheckOption }}
516
- */
517
- async uncheckOption(field, context = null) {
518
- const el = await findCheckable.call(this, field, context)
519
-
520
- if (await el.checked) {
521
- return this.t.click(el).catch(mapError)
522
- }
523
- }
524
-
525
- /**
526
- * {{> seeCheckboxIsChecked }}
527
- */
528
- async seeCheckboxIsChecked(field) {
529
- return proceedIsChecked.call(this, 'assert', field)
530
- }
531
-
532
- /**
533
- * {{> dontSeeCheckboxIsChecked }}
534
- */
535
- async dontSeeCheckboxIsChecked(field) {
536
- return proceedIsChecked.call(this, 'negate', field)
537
- }
538
-
539
- /**
540
- * {{> selectOption }}
541
- */
542
- async selectOption(select, option) {
543
- const els = await findFields.call(this, select)
544
- assertElementExists(els, select, 'Selectable field')
545
-
546
- const el = await els.filterVisible().nth(0)
547
-
548
- if ((await el.tagName).toLowerCase() !== 'select') {
549
- throw new Error('Element is not <select>')
550
- }
551
- if (!Array.isArray(option)) option = [option]
552
-
553
- // TODO As far as I understand the testcafe docs this should do a multi-select
554
- // but it does not work
555
- // const clickOpts = { ctrl: option.length > 1 };
556
- await this.t.click(el).catch(mapError)
557
-
558
- for (const key of option) {
559
- const opt = key
560
-
561
- let optEl
562
- try {
563
- optEl = el.child('option').withText(opt)
564
- if (await optEl.count) {
565
- await this.t.click(optEl).catch(mapError)
566
- continue
567
- }
568
- // eslint-disable-next-line no-empty
569
- } catch (err) {}
570
-
571
- try {
572
- const sel = `[value="${opt}"]`
573
- optEl = el.find(sel)
574
- if (await optEl.count) {
575
- await this.t.click(optEl).catch(mapError)
576
- }
577
- // eslint-disable-next-line no-empty
578
- } catch (err) {}
579
- }
580
- }
581
-
582
- /**
583
- * {{> seeInCurrentUrl }}
584
- */
585
- async seeInCurrentUrl(url) {
586
- stringIncludes('url').assert(url, await getPageUrl(this.t)().catch(mapError))
587
- }
588
-
589
- /**
590
- * {{> dontSeeInCurrentUrl }}
591
- */
592
- async dontSeeInCurrentUrl(url) {
593
- stringIncludes('url').negate(url, await getPageUrl(this.t)().catch(mapError))
594
- }
595
-
596
- /**
597
- * {{> seeCurrentUrlEquals }}
598
- */
599
- async seeCurrentUrlEquals(url) {
600
- urlEquals(this.options.url).assert(url, await getPageUrl(this.t)().catch(mapError))
601
- }
602
-
603
- /**
604
- * {{> dontSeeCurrentUrlEquals }}
605
- */
606
- async dontSeeCurrentUrlEquals(url) {
607
- urlEquals(this.options.url).negate(url, await getPageUrl(this.t)().catch(mapError))
608
- }
609
-
610
- /**
611
- * {{> see }}
612
- *
613
- */
614
- async see(text, context = null) {
615
- let els
616
- if (context) {
617
- els = (await findElements.call(this, this.context, context)).withText(normalizeSpacesInString(text))
618
- } else {
619
- els = (await findElements.call(this, this.context, '*')).withText(normalizeSpacesInString(text))
620
- }
621
-
622
- return this.t.expect(els.filterVisible().count).gt(0, `No element with text "${text}" found`).catch(mapError)
623
- }
624
-
625
- /**
626
- * {{> dontSee }}
627
- *
628
- */
629
- async dontSee(text, context = null) {
630
- let els
631
- if (context) {
632
- els = (await findElements.call(this, this.context, context)).withText(text)
633
- } else {
634
- els = (await findElements.call(this, this.context, 'body')).withText(text)
635
- }
636
-
637
- return this.t
638
- .expect(els.filterVisible().count)
639
- .eql(0, `Element with text "${text}" can still be seen`)
640
- .catch(mapError)
641
- }
642
-
643
- /**
644
- * {{> seeElement }}
645
- */
646
- async seeElement(locator) {
647
- const exists = (await findElements.call(this, this.context, locator)).filterVisible().exists
648
- return this.t
649
- .expect(exists)
650
- .ok(`No element "${new Locator(locator)}" found`)
651
- .catch(mapError)
652
- }
653
-
654
- /**
655
- * {{> dontSeeElement }}
656
- */
657
- async dontSeeElement(locator) {
658
- const exists = (await findElements.call(this, this.context, locator)).filterVisible().exists
659
- return this.t
660
- .expect(exists)
661
- .notOk(`Element "${new Locator(locator)}" is still visible`)
662
- .catch(mapError)
663
- }
664
-
665
- /**
666
- * {{> seeElementInDOM }}
667
- */
668
- async seeElementInDOM(locator) {
669
- const exists = (await findElements.call(this, this.context, locator)).exists
670
- return this.t
671
- .expect(exists)
672
- .ok(`No element "${new Locator(locator)}" found in DOM`)
673
- .catch(mapError)
674
- }
675
-
676
- /**
677
- * {{> dontSeeElementInDOM }}
678
- */
679
- async dontSeeElementInDOM(locator) {
680
- const exists = (await findElements.call(this, this.context, locator)).exists
681
- return this.t
682
- .expect(exists)
683
- .notOk(`Element "${new Locator(locator)}" is still in DOM`)
684
- .catch(mapError)
685
- }
686
-
687
- /**
688
- * {{> seeNumberOfVisibleElements }}
689
- *
690
- */
691
- async seeNumberOfVisibleElements(locator, num) {
692
- const count = (await findElements.call(this, this.context, locator)).filterVisible().count
693
- return this.t.expect(count).eql(num).catch(mapError)
694
- }
695
-
696
- /**
697
- * {{> grabNumberOfVisibleElements }}
698
- */
699
- async grabNumberOfVisibleElements(locator) {
700
- const count = (await findElements.call(this, this.context, locator)).filterVisible().count
701
- return count
702
- }
703
-
704
- /**
705
- * {{> seeInField }}
706
- */
707
- async seeInField(field, value) {
708
- const _value = typeof value === 'boolean' ? value : value.toString()
709
- // const expectedValue = findElements.call(this, this.context, field).value;
710
- const els = await findFields.call(this, field)
711
- assertElementExists(els, field, 'Field')
712
- const el = await els.nth(0)
713
-
714
- return this.t
715
- .expect(await el.value)
716
- .eql(_value)
717
- .catch(mapError)
718
- }
719
-
720
- /**
721
- * {{> dontSeeInField }}
722
- */
723
- async dontSeeInField(field, value) {
724
- const _value = typeof value === 'boolean' ? value : value.toString()
725
- // const expectedValue = findElements.call(this, this.context, field).value;
726
- const els = await findFields.call(this, field)
727
- assertElementExists(els, field, 'Field')
728
- const el = await els.nth(0)
729
-
730
- return this.t.expect(el.value).notEql(_value).catch(mapError)
731
- }
732
-
733
- /**
734
- * Checks that text is equal to provided one.
735
- *
736
- * ```js
737
- * I.seeTextEquals('text', 'h1');
738
- * ```
739
- */
740
- async seeTextEquals(text, context = null) {
741
- const expectedText = findElements.call(this, context, undefined).textContent
742
- return this.t.expect(expectedText).eql(text).catch(mapError)
743
- }
744
-
745
- /**
746
- * {{> seeInSource }}
747
- */
748
- async seeInSource(text) {
749
- const source = await getHtmlSource(this.t)()
750
- stringIncludes('HTML source of a page').assert(text, source)
751
- }
752
-
753
- /**
754
- * {{> dontSeeInSource }}
755
- */
756
- async dontSeeInSource(text) {
757
- const source = await getHtmlSource(this.t)()
758
- stringIncludes('HTML source of a page').negate(text, source)
759
- }
760
-
761
- /**
762
- * {{> saveElementScreenshot }}
763
- *
764
- */
765
- async saveElementScreenshot(locator, fileName) {
766
- const outputFile = path.join(global.output_dir, fileName)
767
-
768
- const sel = await findElements.call(this, this.context, locator)
769
- assertElementExists(sel, locator)
770
- const firstElement = await sel.filterVisible().nth(0)
771
-
772
- this.debug(`Screenshot of ${new Locator(locator)} element has been saved to ${outputFile}`)
773
- return this.t.takeElementScreenshot(firstElement, fileName)
774
- }
775
-
776
- /**
777
- * {{> saveScreenshot }}
778
- */
779
- // TODO Implement full page screenshots
780
- async saveScreenshot(fileName) {
781
- const outputFile = path.join(global.output_dir, fileName)
782
- this.debug(`Screenshot is saving to ${outputFile}`)
783
-
784
- // TODO testcafe automatically creates thumbnail images (which cant be turned off)
785
- return this.t.takeScreenshot(fileName)
786
- }
787
-
788
- /**
789
- * {{> wait }}
790
- */
791
- async wait(sec) {
792
- return new Promise((done) => {
793
- setTimeout(done, sec * 1000)
794
- })
795
- }
796
-
797
- /**
798
- * {{> executeScript }}
799
- *
800
- * If a function returns a Promise It will wait for its resolution.
801
- */
802
- async executeScript(fn, ...args) {
803
- const browserFn = createClientFunction(fn, args).with({ boundTestRun: this.t })
804
- return browserFn()
805
- }
806
-
807
- /**
808
- * {{> grabTextFromAll }}
809
- */
810
- async grabTextFromAll(locator) {
811
- const sel = await findElements.call(this, this.context, locator)
812
- const length = await sel.count
813
- const texts = []
814
- for (let i = 0; i < length; i++) {
815
- texts.push(await sel.nth(i).innerText)
816
- }
817
-
818
- return texts
819
- }
820
-
821
- /**
822
- * {{> grabTextFrom }}
823
- */
824
- async grabTextFrom(locator) {
825
- const sel = await findElements.call(this, this.context, locator)
826
- assertElementExists(sel, locator)
827
- const texts = await this.grabTextFromAll(locator)
828
- if (texts.length > 1) {
829
- this.debugSection('GrabText', `Using first element out of ${texts.length}`)
830
- }
831
-
832
- return texts[0]
833
- }
834
-
835
- /**
836
- * {{> grabAttributeFrom }}
837
- */
838
- async grabAttributeFromAll(locator, attr) {
839
- const sel = await findElements.call(this, this.context, locator)
840
- const length = await sel.count
841
- const attrs = []
842
- for (let i = 0; i < length; i++) {
843
- attrs.push(await (await sel.nth(i)).getAttribute(attr))
844
- }
845
-
846
- return attrs
847
- }
848
-
849
- /**
850
- * {{> grabAttributeFrom }}
851
- */
852
- async grabAttributeFrom(locator, attr) {
853
- const sel = await findElements.call(this, this.context, locator)
854
- assertElementExists(sel, locator)
855
- const attrs = await this.grabAttributeFromAll(locator, attr)
856
- if (attrs.length > 1) {
857
- this.debugSection('GrabAttribute', `Using first element out of ${attrs.length}`)
858
- }
859
-
860
- return attrs[0]
861
- }
862
-
863
- /**
864
- * {{> grabValueFromAll }}
865
- */
866
- async grabValueFromAll(locator) {
867
- const sel = await findElements.call(this, this.context, locator)
868
- const length = await sel.count
869
- const values = []
870
- for (let i = 0; i < length; i++) {
871
- values.push(await (await sel.nth(i)).value)
872
- }
873
-
874
- return values
875
- }
876
-
877
- /**
878
- * {{> grabValueFrom }}
879
- */
880
- async grabValueFrom(locator) {
881
- const sel = await findElements.call(this, this.context, locator)
882
- assertElementExists(sel, locator)
883
- const values = await this.grabValueFromAll(locator)
884
- if (values.length > 1) {
885
- this.debugSection('GrabValue', `Using first element out of ${values.length}`)
886
- }
887
-
888
- return values[0]
889
- }
890
-
891
- /**
892
- * {{> grabSource }}
893
- */
894
- async grabSource() {
895
- return ClientFunction(() => document.documentElement.innerHTML).with({ boundTestRun: this.t })()
896
- }
897
-
898
- /**
899
- * Get JS log from browser.
900
- *
901
- * ```js
902
- * let logs = await I.grabBrowserLogs();
903
- * console.log(JSON.stringify(logs))
904
- * ```
905
- */
906
- async grabBrowserLogs() {
907
- // TODO Must map?
908
- return this.t.getBrowserConsoleMessages()
909
- }
910
-
911
- /**
912
- * {{> grabCurrentUrl }}
913
- */
914
- async grabCurrentUrl() {
915
- return ClientFunction(() => document.location.href).with({ boundTestRun: this.t })()
916
- }
917
-
918
- /**
919
- * {{> grabPageScrollPosition }}
920
- */
921
- async grabPageScrollPosition() {
922
- return ClientFunction(() => ({ x: window.pageXOffset, y: window.pageYOffset })).with({ boundTestRun: this.t })()
923
- }
924
-
925
- /**
926
- * {{> scrollPageToTop }}
927
- */
928
- scrollPageToTop() {
929
- return ClientFunction(() => window.scrollTo(0, 0))
930
- .with({ boundTestRun: this.t })()
931
- .catch(mapError)
932
- }
933
-
934
- /**
935
- * {{> scrollPageToBottom }}
936
- */
937
- scrollPageToBottom() {
938
- return ClientFunction(() => {
939
- const body = document.body
940
- const html = document.documentElement
941
- window.scrollTo(
942
- 0,
943
- Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight),
944
- )
945
- })
946
- .with({ boundTestRun: this.t })()
947
- .catch(mapError)
948
- }
949
-
950
- /**
951
- * {{> scrollTo }}
952
- */
953
- async scrollTo(locator, offsetX = 0, offsetY = 0) {
954
- if (typeof locator === 'number' && typeof offsetX === 'number') {
955
- offsetY = offsetX
956
- offsetX = locator
957
- locator = null
958
- }
959
-
960
- const scrollBy = ClientFunction((offset) => {
961
- if (window && window.scrollBy && offset) {
962
- window.scrollBy(offset.x, offset.y)
963
- }
964
- }).with({ boundTestRun: this.t })
965
-
966
- if (locator) {
967
- const els = await this._locate(locator)
968
- assertElementExists(els, locator, 'Element')
969
- const el = await els.nth(0)
970
- const x = (await el.offsetLeft) + offsetX
971
- const y = (await el.offsetTop) + offsetY
972
-
973
- return scrollBy({ x, y }).catch(mapError)
974
- }
975
-
976
- const x = offsetX
977
- const y = offsetY
978
- return scrollBy({ x, y }).catch(mapError)
979
- }
980
-
981
- /**
982
- * {{> switchTo }}
983
- */
984
- async switchTo(locator) {
985
- if (Number.isInteger(locator)) {
986
- throw new Error('Not supported switching to iframe by number')
987
- }
988
-
989
- if (!locator) {
990
- return this.t.switchToMainWindow()
991
- }
992
-
993
- const el = await findElements.call(this, this.context, locator)
994
- return this.t.switchToIframe(el)
995
- }
996
-
997
- // TODO Add url assertions
998
-
999
- /**
1000
- * {{> setCookie }}
1001
- */
1002
- async setCookie(cookie) {
1003
- if (Array.isArray(cookie)) {
1004
- throw new Error('cookie array is not supported')
1005
- }
1006
-
1007
- cookie.path = cookie.path || '/'
1008
- // cookie.expires = cookie.expires || (new Date()).toUTCString();
1009
-
1010
- const setCookie = ClientFunction(
1011
- () => {
1012
- document.cookie = `${cookie.name}=${cookie.value};path=${cookie.path};expires=${cookie.expires};`
1013
- },
1014
- { dependencies: { cookie } },
1015
- ).with({ boundTestRun: this.t })
1016
-
1017
- return setCookie()
1018
- }
1019
-
1020
- /**
1021
- * {{> seeCookie }}
1022
- *
1023
- */
1024
- async seeCookie(name) {
1025
- const cookie = await this.grabCookie(name)
1026
- empty(`cookie ${name} to be set`).negate(cookie)
1027
- }
1028
-
1029
- /**
1030
- * {{> dontSeeCookie }}
1031
- */
1032
- async dontSeeCookie(name) {
1033
- const cookie = await this.grabCookie(name)
1034
- empty(`cookie ${name} not to be set`).assert(cookie)
1035
- }
1036
-
1037
- /**
1038
- * {{> grabCookie }}
1039
- *
1040
- * Returns cookie in JSON format. If name not passed returns all cookies for this domain.
1041
- */
1042
- async grabCookie(name) {
1043
- if (!name) {
1044
- const getCookie = ClientFunction(() => {
1045
- return document.cookie.split(';').map((c) => c.split('='))
1046
- }).with({ boundTestRun: this.t })
1047
- const cookies = await getCookie()
1048
- return cookies.map((cookie) => ({ name: cookie[0].trim(), value: cookie[1] }))
1049
- }
1050
- const getCookie = ClientFunction(
1051
- () => {
1052
- const v = document.cookie.match('(^|;) ?' + name + '=([^;]*)(;|$)')
1053
- return v ? v[2] : null
1054
- },
1055
- { dependencies: { name } },
1056
- ).with({ boundTestRun: this.t })
1057
- const value = await getCookie()
1058
- if (value) return { name, value }
1059
- }
1060
-
1061
- /**
1062
- * {{> clearCookie }}
1063
- */
1064
- async clearCookie(cookieName) {
1065
- const clearCookies = ClientFunction(
1066
- () => {
1067
- const cookies = document.cookie.split(';')
1068
-
1069
- for (let i = 0; i < cookies.length; i++) {
1070
- const cookie = cookies[i]
1071
- const eqPos = cookie.indexOf('=')
1072
- const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie
1073
- if (cookieName === undefined || name === cookieName) {
1074
- document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT`
1075
- }
1076
- }
1077
- },
1078
- { dependencies: { cookieName } },
1079
- ).with({ boundTestRun: this.t })
1080
-
1081
- return clearCookies()
1082
- }
1083
-
1084
- /**
1085
- * {{> waitInUrl }}
1086
- */
1087
- async waitInUrl(urlPart, sec = null) {
1088
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
1089
-
1090
- const clientFn = createClientFunction(
1091
- (urlPart) => {
1092
- const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
1093
- return currUrl.indexOf(urlPart) > -1
1094
- },
1095
- [urlPart],
1096
- ).with({ boundTestRun: this.t })
1097
-
1098
- return waitForFunction(clientFn, waitTimeout).catch(async () => {
1099
- const currUrl = await this.grabCurrentUrl()
1100
- throw new Error(`expected url to include ${urlPart}, but found ${currUrl}`)
1101
- })
1102
- }
1103
-
1104
- /**
1105
- * {{> waitUrlEquals }}
1106
- */
1107
- async waitUrlEquals(urlPart, sec = null) {
1108
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
1109
-
1110
- const baseUrl = this.options.url
1111
- if (urlPart.indexOf('http') < 0) {
1112
- urlPart = baseUrl + urlPart
1113
- }
1114
-
1115
- const clientFn = createClientFunction(
1116
- (urlPart) => {
1117
- const currUrl = decodeURIComponent(decodeURIComponent(decodeURIComponent(window.location.href)))
1118
- return currUrl === urlPart
1119
- },
1120
- [urlPart],
1121
- ).with({ boundTestRun: this.t })
1122
-
1123
- return waitForFunction(clientFn, waitTimeout).catch(async () => {
1124
- const currUrl = await this.grabCurrentUrl()
1125
- throw new Error(`expected url to be ${urlPart}, but found ${currUrl}`)
1126
- })
1127
- }
1128
-
1129
- /**
1130
- * {{> waitForFunction }}
1131
- */
1132
- async waitForFunction(fn, argsOrSec = null, sec = null) {
1133
- let args = []
1134
- if (argsOrSec) {
1135
- if (Array.isArray(argsOrSec)) {
1136
- args = argsOrSec
1137
- } else if (typeof argsOrSec === 'number') {
1138
- sec = argsOrSec
1139
- }
1140
- }
1141
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
1142
-
1143
- const clientFn = createClientFunction(fn, args).with({ boundTestRun: this.t })
1144
-
1145
- return waitForFunction(clientFn, waitTimeout)
1146
- }
1147
-
1148
- /**
1149
- * {{> waitNumberOfVisibleElements }}
1150
- */
1151
- async waitNumberOfVisibleElements(locator, num, sec) {
1152
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
1153
-
1154
- return this.t
1155
- .expect(createSelector(locator).with({ boundTestRun: this.t }).filterVisible().count)
1156
- .eql(num, `The number of elements (${new Locator(locator)}) is not ${num} after ${sec} sec`, {
1157
- timeout: waitTimeout,
1158
- })
1159
- .catch(mapError)
1160
- }
1161
-
1162
- /**
1163
- * {{> waitForElement }}
1164
- */
1165
- async waitForElement(locator, sec) {
1166
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
1167
-
1168
- return this.t.expect(createSelector(locator).with({ boundTestRun: this.t }).exists).ok({ timeout: waitTimeout })
1169
- }
1170
-
1171
- /**
1172
- * {{> waitToHide }}
1173
- */
1174
- async waitToHide(locator, sec) {
1175
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
1176
-
1177
- return this.t
1178
- .expect(createSelector(locator).filterHidden().with({ boundTestRun: this.t }).exists)
1179
- .notOk({ timeout: waitTimeout })
1180
- }
1181
-
1182
- /**
1183
- * {{> waitForInvisible }}
1184
- */
1185
- async waitForInvisible(locator, sec) {
1186
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
1187
-
1188
- return this.t
1189
- .expect(createSelector(locator).filterVisible().with({ boundTestRun: this.t }).exists)
1190
- .ok({ timeout: waitTimeout })
1191
- }
1192
-
1193
- /**
1194
- * {{> waitForText }}
1195
- *
1196
- */
1197
- async waitForText(text, sec = null, context = null) {
1198
- const waitTimeout = sec ? sec * 1000 : this.options.waitForTimeout
1199
-
1200
- let els
1201
- if (context) {
1202
- els = await findElements.call(this, this.context, context)
1203
- await this.t.expect(els.exists).ok(`Context element ${context} not found`, { timeout: waitTimeout })
1204
- } else {
1205
- els = await findElements.call(this, this.context, '*')
1206
- }
1207
-
1208
- return this.t
1209
- .expect(els.withText(text).filterVisible().exists)
1210
- .ok(`No element with text "${text}" found in ${context || 'body'}`, { timeout: waitTimeout })
1211
- .catch(mapError)
1212
- }
1213
- }
1214
-
1215
- async function waitForFunction(browserFn, waitTimeout) {
1216
- const pause = () =>
1217
- new Promise((done) => {
1218
- setTimeout(done, 50)
1219
- })
1220
-
1221
- const start = Date.now()
1222
-
1223
- while (true) {
1224
- let result
1225
- try {
1226
- result = await browserFn()
1227
- } catch (err) {
1228
- throw new Error(`Error running function ${err.toString()}`)
1229
- }
1230
-
1231
- if (result) return result
1232
-
1233
- const duration = Date.now() - start
1234
- if (duration > waitTimeout) {
1235
- throw new Error('waitForFunction timed out')
1236
- }
1237
- await pause() // make polling
1238
- }
1239
- }
1240
-
1241
- const createSelector = (locator) => {
1242
- locator = new Locator(locator, 'css')
1243
- if (locator.isXPath()) return elementByXPath(locator.value)
1244
- return Selector(locator.simplify())
1245
- }
1246
-
1247
- const elementByXPath = (xpath) => {
1248
- assert(xpath, 'xpath is required')
1249
-
1250
- return Selector(
1251
- () => {
1252
- const iterator = document.evaluate(xpath, document, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null)
1253
- const items = []
1254
-
1255
- let item = iterator.iterateNext()
1256
-
1257
- while (item) {
1258
- items.push(item)
1259
- item = iterator.iterateNext()
1260
- }
1261
-
1262
- return items
1263
- },
1264
- { dependencies: { xpath } },
1265
- )
1266
- }
1267
-
1268
- const assertElementExists = async (res, locator, prefix, suffix) => {
1269
- if (!res || !(await res.count) || !(await res.nth(0).tagName)) {
1270
- throw new ElementNotFound(locator, prefix, suffix)
1271
- }
1272
- }
1273
-
1274
- async function findElements(matcher, locator) {
1275
- if (locator && locator.react) throw new Error('react locators are not yet supported')
1276
-
1277
- locator = new Locator(locator, 'css')
1278
-
1279
- if (!locator.isXPath()) {
1280
- return matcher
1281
- ? matcher.find(locator.simplify())
1282
- : Selector(locator.simplify()).with({ timeout: 0, boundTestRun: this.t })
1283
- }
1284
-
1285
- if (!matcher) return elementByXPath(locator.value).with({ timeout: 0, boundTestRun: this.t })
1286
-
1287
- return matcher.find(
1288
- (node, idx, originNode) => {
1289
- const found = document.evaluate(xpath, originNode, null, 5, null)
1290
- let current = null
1291
- while ((current = found.iterateNext())) {
1292
- if (current === node) return true
1293
- }
1294
- return false
1295
- },
1296
- { xpath: locator.value },
1297
- )
1298
- }
1299
-
1300
- async function proceedClick(locator, context = null) {
1301
- let matcher
1302
-
1303
- if (context) {
1304
- const els = await this._locate(context)
1305
- await assertElementExists(els, context)
1306
- matcher = await els.nth(0)
1307
- }
1308
-
1309
- const els = await findClickable.call(this, matcher, locator)
1310
- if (context) {
1311
- await assertElementExists(
1312
- els,
1313
- locator,
1314
- 'Clickable element',
1315
- `was not found inside element ${new Locator(context).toString()}`,
1316
- )
1317
- } else {
1318
- await assertElementExists(els, locator, 'Clickable element')
1319
- }
1320
-
1321
- const firstElement = await els.filterVisible().nth(0)
1322
-
1323
- return this.t.click(firstElement).catch(mapError)
1324
- }
1325
-
1326
- async function findClickable(matcher, locator) {
1327
- if (locator && locator.react) throw new Error('react locators are not yet supported')
1328
-
1329
- locator = new Locator(locator)
1330
- if (!locator.isFuzzy()) return (await findElements.call(this, matcher, locator)).filterVisible()
1331
-
1332
- let els
1333
-
1334
- // try to use native TestCafe locator
1335
- els = matcher ? matcher.find('a,button') : createSelector('a,button')
1336
- els = await els.withExactText(locator.value).with({ timeout: 0, boundTestRun: this.t })
1337
- if (await els.count) return els
1338
-
1339
- const literal = xpathLocator.literal(locator.value)
1340
-
1341
- els = (await findElements.call(this, matcher, Locator.clickable.narrow(literal))).filterVisible()
1342
- if (await els.count) return els
1343
-
1344
- els = (await findElements.call(this, matcher, Locator.clickable.wide(literal))).filterVisible()
1345
- if (await els.count) return els
1346
-
1347
- els = (await findElements.call(this, matcher, Locator.clickable.self(literal))).filterVisible()
1348
- if (await els.count) return els
1349
-
1350
- return findElements.call(this, matcher, locator.value) // by css or xpath
1351
- }
1352
-
1353
- async function proceedIsChecked(assertType, option) {
1354
- const els = await findCheckable.call(this, option)
1355
- assertElementExists(els, option, 'Checkable')
1356
-
1357
- const selected = await els.checked
1358
-
1359
- return truth(`checkable ${option}`, 'to be checked')[assertType](selected)
1360
- }
1361
-
1362
- async function findCheckable(locator, context) {
1363
- assert(locator, 'locator is required')
1364
- assert(this.t, 'this.t is required')
1365
-
1366
- let contextEl = await this.context
1367
- if (typeof context === 'string') {
1368
- contextEl = (await findElements.call(this, contextEl, new Locator(context, 'css').simplify())).filterVisible()
1369
- contextEl = await contextEl.nth(0)
1370
- }
1371
-
1372
- const matchedLocator = new Locator(locator)
1373
- if (!matchedLocator.isFuzzy()) {
1374
- return (await findElements.call(this, contextEl, matchedLocator.simplify())).filterVisible()
1375
- }
1376
-
1377
- const literal = xpathLocator.literal(locator)
1378
- let els = (await findElements.call(this, contextEl, Locator.checkable.byText(literal))).filterVisible()
1379
- if (await els.count) {
1380
- return els
1381
- }
1382
-
1383
- els = (await findElements.call(this, contextEl, Locator.checkable.byName(literal))).filterVisible()
1384
- if (await els.count) {
1385
- return els
1386
- }
1387
-
1388
- return (await findElements.call(this, contextEl, locator)).filterVisible()
1389
- }
1390
-
1391
- async function findFields(locator) {
1392
- const matchedLocator = new Locator(locator)
1393
- if (!matchedLocator.isFuzzy()) {
1394
- return this._locate(matchedLocator)
1395
- }
1396
- const literal = xpathLocator.literal(locator)
1397
-
1398
- let els = await this._locate({ xpath: Locator.field.labelEquals(literal) })
1399
- if (await els.count) {
1400
- return els
1401
- }
1402
-
1403
- els = await this._locate({ xpath: Locator.field.labelContains(literal) })
1404
- if (await els.count) {
1405
- return els
1406
- }
1407
- els = await this._locate({ xpath: Locator.field.byName(literal) })
1408
- if (await els.count) {
1409
- return els
1410
- }
1411
- return this._locate({ css: locator })
1412
- }
1413
-
1414
- module.exports = TestCafe